"use client"; import { useQuery, useMutation, useQueryClient } from "@tanstack/react-query"; import { AppShell } from "@/components/layout/app-shell"; import Link from "next/link"; import { useState } from "react"; import { useUiStore } from "@/stores/ui"; interface Exploration { id: string; title: string; description: string; category: string; status: string; completedNote?: string | null; minutes?: number | null; } export default function CartographerPage() { const qc = useQueryClient(); const showXpToast = useUiStore((s) => s.showXpToast); const showActionToast = useUiStore((s) => s.showActionToast); const [tab, setTab] = useState<"active" | "history">("active"); const [completeId, setCompleteId] = useState(null); const [note, setNote] = useState(""); const { data: desk, isLoading: deskLoading, error: deskError } = useQuery({ queryKey: ["cartographer-desk"], queryFn: async () => { const res = await fetch("/api/cartographer"); if (!res.ok) throw new Error("Failed to load map desk"); return res.json(); }, }); const { data: explorations = [], isLoading, error } = useQuery({ queryKey: ["explorations"], queryFn: async () => { const res = await fetch("/api/explorations"); if (!res.ok) throw new Error("Failed to load explorations"); const data = await res.json(); if (data.error) throw new Error(data.error); return Array.isArray(data) ? data : []; }, }); const { data: history = [] } = useQuery({ queryKey: ["explorations-history"], queryFn: async () => { const res = await fetch("/api/explorations?history=true"); if (!res.ok) throw new Error("Failed"); const data = await res.json(); return Array.isArray(data) ? data : []; }, enabled: tab === "history", }); const [generateSource, setGenerateSource] = useState<"ai" | "fallback" | "existing" | null>( null ); const generate = useMutation({ mutationFn: async () => { const res = await fetch("/api/explorations", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ action: "generate" }), }); if (!res.ok) throw new Error("Generate failed"); const data = await res.json(); if (data.error) throw new Error(data.error); return data as { explorations: Exploration[]; source?: "ai" | "fallback" | "existing"; }; }, onSuccess: (data) => { setGenerateSource(data.source ?? null); qc.invalidateQueries({ queryKey: ["explorations"] }); }, }); const generateNotice = generateSource === "existing" ? "You still have suggested quests to review — finish or deny them before generating more." : generateSource === "fallback" ? "AI unavailable — showing curated offline quest suggestions." : null; const action = useMutation({ mutationFn: async ({ id, action, note }: { id: string; action: string; note?: string }) => { const res = await fetch(`/api/explorations/${id}`, { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ action, note }), }); if (!res.ok) throw new Error("Action failed"); return res.json(); }, onSuccess: (data, vars) => { qc.invalidateQueries({ queryKey: ["explorations"] }); qc.invalidateQueries({ queryKey: ["explorations-history"] }); qc.invalidateQueries({ queryKey: ["dashboard"] }); qc.invalidateQueries({ queryKey: ["cartographer-desk"] }); if (vars.action === "complete") showXpToast(100, "Exploration complete"); if (data.actionEventId) { showActionToast(`Exploration ${vars.action}`, data.actionEventId); } setCompleteId(null); setNote(""); }, }); const suggested = explorations.filter((e: Exploration) => e.status === "suggested"); const active = explorations.filter((e: Exploration) => e.status === "active"); return (

The Cartographer's Desk

{generate.isError && (

Could not generate quests. Check AI Health in Settings.

)} {generateNotice && (

{generateNotice}

)} {generateSource === "fallback" && !generateNotice && (

AI is offline — showing curated offline quest suggestions.

)} {!deskLoading && desk && ( <>
{desk.domains?.map((d: { key: string; label: string; score: number }) => (

{d.label}

{d.score}

))}
{desk.horizon?.intention && (

Horizon: {desk.horizon.intention}

)} )} {deskError && (

Could not load life map scores.

)}

Optional curiosity quests — pick what interests you this week.

{tab === "active" && ( <> {isLoading ? (

Loading map...

) : error ? (

Failed to load explorations.

) : (
{suggested.map((e: Exploration) => ( action.mutate({ id: e.id, action: "accept" })} onDismiss={() => action.mutate({ id: e.id, action: "dismiss" })} /> ))} {active.map((e: Exploration) => ( setCompleteId(e.id)} /> ))} {suggested.length === 0 && active.length === 0 && (

No explorations this week. Generate some quests — dismissed quests won't block new ones.

)}
)} )} {tab === "history" && (
{history.map((e: Exploration) => ( ✓ {e.title} ))}
)} {completeId && (

What did you learn?

setNote(e.target.value)} placeholder="One thing you discovered..." />
)}
); } function QuestCard({ exploration, active, onAccept, onDismiss, onComplete, }: { exploration: Exploration; active?: boolean; onAccept?: () => void; onDismiss?: () => void; onComplete?: () => void; }) { return (
{exploration.category}

{exploration.title}

{exploration.description}

{exploration.minutes && (

~{exploration.minutes} min

)}
{active ? ( <> Study with Teacher ) : ( <> )}
); }