diff options
Diffstat (limited to 'makima/frontend/src/routes/tmp.tsx')
| -rw-r--r-- | makima/frontend/src/routes/tmp.tsx | 93 |
1 files changed, 93 insertions, 0 deletions
diff --git a/makima/frontend/src/routes/tmp.tsx b/makima/frontend/src/routes/tmp.tsx new file mode 100644 index 0000000..69f13a2 --- /dev/null +++ b/makima/frontend/src/routes/tmp.tsx @@ -0,0 +1,93 @@ +/** + * Standalone task page for orphan tasks (`/tmp/:taskId`). These are tasks + * with no directive attachment — the document-mode sidebar surfaces them + * under the `tmp/` pseudo-folder. We render `DocumentTaskStream` directly + * without the directive sidebar selection, framed by the masthead so users + * still have global navigation. + */ +import { useEffect, useState } from "react"; +import { useNavigate, useParams } from "react-router"; +import { Masthead } from "../components/Masthead"; +import { DocumentTaskStream } from "../components/directives/DocumentTaskStream"; +import { useAuth } from "../contexts/AuthContext"; +import { getTask, type Task } from "../lib/api"; + +export default function TmpTaskPage() { + const { isAuthenticated, isAuthConfigured, isLoading: authLoading } = useAuth(); + const navigate = useNavigate(); + const { taskId } = useParams<{ taskId: string }>(); + const [task, setTask] = useState<Task | null>(null); + const [error, setError] = useState<string | null>(null); + + useEffect(() => { + if (!authLoading && isAuthConfigured && !isAuthenticated) { + navigate("/login"); + } + }, [authLoading, isAuthConfigured, isAuthenticated, navigate]); + + useEffect(() => { + if (!taskId) return; + let cancelled = false; + getTask(taskId) + .then((t) => { + if (cancelled) return; + const directiveId = (t as Task & { directiveId?: string | null }) + .directiveId ?? null; + // If this task actually IS attached to a directive, redirect + // there — /tmp/ is reserved for genuine orphans. + if (directiveId) { + navigate(`/directives/${directiveId}?task=${taskId}`, { + replace: true, + }); + return; + } + setTask(t); + }) + .catch((e) => { + if (!cancelled) setError(e instanceof Error ? e.message : String(e)); + }); + return () => { + cancelled = true; + }; + }, [taskId, navigate]); + + if (authLoading) { + return ( + <div className="relative z-10 min-h-screen flex flex-col bg-[#0a1628]"> + <Masthead showNav /> + <main className="flex-1 flex items-center justify-center"> + <p className="text-[#7788aa] font-mono text-sm">Loading...</p> + </main> + </div> + ); + } + + return ( + <div className="relative z-10 min-h-screen flex flex-col bg-[#0a1628]"> + <Masthead showNav /> + <main + className="flex-1 flex flex-col overflow-hidden" + style={{ height: "calc(100vh - 80px)" }} + > + {/* Breadcrumb echoing the document-mode header style. */} + <div className="px-6 py-3 border-b border-dashed border-[rgba(117,170,252,0.2)]"> + <div className="flex items-center gap-2 text-[10px] font-mono uppercase tracking-wide text-[#7788aa]"> + <span>tmp /</span> + <span className="text-[#9bc3ff]"> + {taskId ? taskId.slice(0, 8) : ""} + </span> + {task && <span className="normal-case text-[#dbe7ff]">— {task.name}</span>} + </div> + </div> + + {error ? ( + <div className="flex-1 flex items-center justify-center"> + <p className="text-red-400 font-mono text-xs">{error}</p> + </div> + ) : taskId ? ( + <DocumentTaskStream taskId={taskId} label={task?.name ?? taskId.slice(0, 8)} /> + ) : null} + </main> + </div> + ); +} |
