From 08a15a09cf2821059ec8fd5b716104ee299b25f4 Mon Sep 17 00:00:00 2001 From: Kamil Krzywda Date: Mon, 28 Sep 2026 22:50:01 +0000 Subject: [PATCH] ui: CREW tab renders real roster cards with traits + origin (real data from /api/crew) --- components/crew-panel.tsx | 101 +++++++++++++++++++++++++++++++++ components/mission-control.tsx | 55 ++---------------- 2 files changed, 106 insertions(+), 50 deletions(-) create mode 100644 components/crew-panel.tsx diff --git a/components/crew-panel.tsx b/components/crew-panel.tsx new file mode 100644 index 0000000..19580a0 --- /dev/null +++ b/components/crew-panel.tsx @@ -0,0 +1,101 @@ +'use client' + +import { useEffect, useState } from 'react' +import { Badge } from '@/components/ui/badge' +import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card' +import type { CrewDto } from '@/lib/game/save' + +// CREW tab — the ship's roster: starting crew plus members found while exploring. Fetches +// /api/crew on mount; Radix unmounts inactive tabs, so opening this tab always refetches +// (no polling needed — turn-based game). Display-only for now: no dismiss/assign actions yet. + +interface CrewPanelProps { + narrow?: boolean +} + +export function CrewPanel({ narrow }: CrewPanelProps) { + const [roster, setRoster] = useState(null) + const [error, setError] = useState(null) + + useEffect(() => { + let cancelled = false + fetch('/api/crew') + .then(async (res) => { + if (!res.ok) throw new Error(`HTTP ${res.status}`) + return (await res.json()) as CrewDto[] + }) + .then((r) => { + if (!cancelled) setRoster(r) + }) + .catch((err: unknown) => { + if (!cancelled) setError(err instanceof Error ? err.message : String(err)) + }) + return () => { + cancelled = true + } + }, []) + + if (error && !roster) { + return ( +
+ + CREW MANIFEST OFFLINE // {error.toUpperCase()} + +
+ ) + } + + if (!roster) { + return ( +
+ LOADING CREW MANIFEST… +
+ ) + } + + return ( +
+ {roster.map((member) => ( + + +
+ {member.name} + + ACTIVE + +
+

+ {member.role} +

+
+ +
+ + + {member.traitGood} +
+
+ - + {member.traitBad} +
+

+ ORIGIN // {member.origin.toUpperCase()} +

+
+
+ ))} +
+ ) +} diff --git a/components/mission-control.tsx b/components/mission-control.tsx index 55d8f34..f67cb28 100644 --- a/components/mission-control.tsx +++ b/components/mission-control.tsx @@ -11,13 +11,14 @@ import { StatCard } from '@/components/ui/stat-card' import { StatusGrid, type SystemEntry } from '@/components/ui/status-grid' import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs' import { Terminal, type TerminalLine } from '@/components/ui/terminal' +import { CrewPanel } from '@/components/crew-panel' import { LocalSystemPanel } from '@/components/local-system' import { WorldMapPanel } from '@/components/world-map' // Interface base: layout + visual treatment rebuilt from the scificn-ui Mission Control // showcase (src/app/_pages/showcase/mission-control-content.tsx in baxy5/scificn-ui). The -// WORLD MAP and LOCAL SYSTEM tabs render live data (/api/world, /api/system); the other -// tabs keep placeholder content until their own issues land. Single player, no auth, by design. +// WORLD MAP, LOCAL SYSTEM and CREW tabs render live data (/api/world, /api/system, /api/crew); +// the other tabs keep placeholder content until their own issues land. Single player, no auth, by design. function useNarrow(threshold = 900): boolean { const [narrow, setNarrow] = useState( @@ -45,13 +46,6 @@ const shipSystems: SystemEntry[] = [ { name: 'COMMS', status: 'ACTIVE' }, ] -const crew = [ - { name: 'CMDR. E. VANCE', role: 'COMMANDER', good: 'STEADY UNDER PRESSURE', bad: 'RIGID — SLOW TO ADAPT' }, - { name: 'LT. M. OKONKWO', role: 'PILOT', good: 'RECKLESS COURAGE', bad: 'SHORT FUSE' }, - { name: 'DR. I. SORENSEN', role: 'MEDIC', good: 'CALM IN A CRISIS', bad: 'OVERCAUTIOUS' }, - { name: 'R. "SPARK" DELACRUZ', role: 'ENGINEER', good: 'FIXES ANYTHING', bad: 'CARELESS WITH FUSES' }, -] - const shipSystemCards = [ { name: 'REACTOR', level: 'LV 2', status: 'ACTIVE' as const, actions: ['EXPAND', 'UPGRADE'] }, { name: 'LIFE SUPPORT', level: 'LV 1', status: 'ACTIVE' as const, actions: ['EXPAND', 'UPGRADE'] }, @@ -331,48 +325,9 @@ export function MissionControl() { - {/* CREW TAB */} + {/* CREW TAB — live data from /api/crew */} -
- {crew.map((member) => ( - - -
- {member.name} - - ACTIVE - -
-

- {member.role} -

-
- -
- + - {member.good} -
-
- - - {member.bad} -
-
-
- ))} -
+
{/* SHIP SYSTEMS TAB */}