From 9e9f18884c78c21f5785908fb7ccd00e2fa5436b Mon Sep 17 00:00:00 2001 From: soryu Date: Sat, 7 Feb 2026 01:11:26 +0000 Subject: Add new directive initial implementation --- makima/frontend/src/routes/directives.tsx | 209 ++++++++++++++++++++++++++++++ 1 file changed, 209 insertions(+) create mode 100644 makima/frontend/src/routes/directives.tsx (limited to 'makima/frontend/src/routes/directives.tsx') diff --git a/makima/frontend/src/routes/directives.tsx b/makima/frontend/src/routes/directives.tsx new file mode 100644 index 0000000..89535e2 --- /dev/null +++ b/makima/frontend/src/routes/directives.tsx @@ -0,0 +1,209 @@ +import { useState, useCallback, useEffect } from "react"; +import { useParams, useNavigate } from "react-router"; +import { Masthead } from "../components/Masthead"; +import { DirectiveList } from "../components/directives/DirectiveList"; +import { DirectiveDetail } from "../components/directives/DirectiveDetail"; +import { useDirectives } from "../hooks/useDirectives"; +import { useAuth } from "../contexts/AuthContext"; +import type { + DirectiveWithChains, + CreateDirectiveRequest, +} from "../lib/api"; + +export default function DirectivesPage() { + const { isAuthenticated, isAuthConfigured, isLoading: authLoading } = useAuth(); + const navigate = useNavigate(); + + // Redirect to login if not authenticated (when auth is configured) + useEffect(() => { + if (!authLoading && isAuthConfigured && !isAuthenticated) { + navigate("/login"); + } + }, [authLoading, isAuthConfigured, isAuthenticated, navigate]); + + if (authLoading) { + return ( +
+ +
+

Loading...

+
+
+ ); + } + + return ( +
+ + +
+ ); +} + +function DirectivesContent() { + const { id } = useParams<{ id?: string }>(); + const navigate = useNavigate(); + const { + directives, + loading, + error, + fetchDirective, + saveDirective, + removeDirective, + } = useDirectives(); + + const [selectedDirective, setSelectedDirective] = + useState(null); + const [detailLoading, setDetailLoading] = useState(false); + const [showCreateForm, setShowCreateForm] = useState(false); + const [createTitle, setCreateTitle] = useState(""); + const [createGoal, setCreateGoal] = useState(""); + const [createRepoUrl, setCreateRepoUrl] = useState(""); + + // Load directive when ID changes + useEffect(() => { + if (id) { + setDetailLoading(true); + fetchDirective(id).then((d) => { + setSelectedDirective(d); + setDetailLoading(false); + }); + } else { + setSelectedDirective(null); + } + }, [id, fetchDirective]); + + const handleSelect = useCallback( + (directiveId: string) => { + navigate(`/directives/${directiveId}`); + }, + [navigate] + ); + + const handleBack = useCallback(() => { + navigate("/directives"); + }, [navigate]); + + const handleCreate = useCallback(async () => { + if (!createTitle.trim() || !createGoal.trim()) return; + + const data: CreateDirectiveRequest = { + title: createTitle.trim(), + goal: createGoal.trim(), + }; + if (createRepoUrl.trim()) { + data.repositoryUrl = createRepoUrl.trim(); + } + + const result = await saveDirective(data); + if (result) { + setShowCreateForm(false); + setCreateTitle(""); + setCreateGoal(""); + setCreateRepoUrl(""); + } + }, [createTitle, createGoal, createRepoUrl, saveDirective]); + + const handleDelete = useCallback( + async (directiveId: string) => { + const ok = await removeDirective(directiveId); + if (ok && id === directiveId) { + navigate("/directives"); + } + }, + [removeDirective, id, navigate] + ); + + // Detail view + if (id) { + if (detailLoading) { + return ( +
+

Loading directive...

+
+ ); + } + if (!selectedDirective) { + return ( +
+

Directive not found

+
+ ); + } + return ( +
+ +
+ ); + } + + // List view + return ( +
+ {error && ( +
+ {error} +
+ )} + + {showCreateForm && ( +
+

+ New Directive +

+
+ setCreateTitle(e.target.value)} + className="w-full px-2 py-1.5 font-mono text-xs text-[#dbe7ff] bg-[#0a1628] border border-[rgba(117,170,252,0.3)] focus:border-[#75aafc] outline-none" + /> +