'use client' import { useCallback, useEffect, useState } from 'react' import { Badge } from '@/components/ui/badge' import { Button } from '@/components/ui/button' import { Card, CardContent, CardFooter, CardHeader, CardTitle } from '@/components/ui/card' import type { MissionDto } from '@/lib/game/missions' // MISSIONS tab — live data. Fetches /api/missions on mount (Radix Tabs unmounts inactive tabs, so it // refetches whenever the tab is opened); accept responses are applied directly (no polling). ACCEPT is // enabled only when the contract is AVAILABLE and no other mission is ACTIVE — mirroring what // POST /api/missions/{id}/accept enforces. export function MissionPanel() { const [missions, setMissions] = useState(null) const [error, setError] = useState(null) const [busyId, setBusyId] = useState(null) // one action at a time — turn-based game useEffect(() => { let cancelled = false fetch('/api/missions') .then(async (res) => { if (!res.ok) throw new Error(`HTTP ${res.status}`) return (await res.json()) as MissionDto[] }) .then((m) => { if (!cancelled) setMissions(m) }) .catch((err: unknown) => { if (!cancelled) setError(err instanceof Error ? err.message : String(err)) }) return () => { cancelled = true } }, []) const accept = useCallback(async (mission: MissionDto) => { setBusyId(mission.id) setError(null) try { const res = await fetch(`/api/missions/${mission.id}/accept`, { method: 'POST' }) const data = (await res.json()) as MissionDto[] & { error?: string } if (!res.ok) throw new Error(data.error ?? `HTTP ${res.status}`) setMissions(data) // response is the fresh board — apply directly } catch (err: unknown) { setError(err instanceof Error ? err.message : String(err)) } finally { setBusyId(null) } }, []) if (!missions && !error) { return (
LOADING MISSIONS…
) } const active = (missions ?? []).find((m) => m.state === 'ACTIVE') return (
{error && (
{error.toUpperCase()}
)}
{(missions ?? []).map((mission) => ( ))}
) } function MissionCard({ mission, busy, blockedByActive, onAccept, }: { mission: MissionDto busy: boolean blockedByActive: boolean onAccept: (mission: MissionDto) => void }) { const acceptDisabled = busy || mission.state !== 'AVAILABLE' || blockedByActive return (
{mission.type} {mission.state}

CONTRACT // #{mission.id}

{mission.description.toUpperCase()}

REWARD {mission.rewardQty}× {mission.rewardKind === 'resource' ? 'RESOURCES' : 'PARTS'}

{mission.type === 'EXPLORE' && (

PROGRESS // {mission.progress}/{mission.progressTarget}

)}
) }