diff options
| author | soryu <soryu@soryu.co> | 2026-02-09 00:11:51 +0000 |
|---|---|---|
| committer | soryu <soryu@soryu.co> | 2026-02-09 00:11:51 +0000 |
| commit | 8c23b3ab6f7fabca01b0468911bae073aa5ced32 (patch) | |
| tree | f50159aee13b13f0b55618ac09e9be1f89a41bb2 /makima/frontend/src/routes | |
| parent | 3662b334dfd68cfdf00ed44ae88927c2e1b2aabe (diff) | |
| download | soryu-8c23b3ab6f7fabca01b0468911bae073aa5ced32.tar.gz soryu-8c23b3ab6f7fabca01b0468911bae073aa5ced32.zip | |
Add new directive mechanism v3
Diffstat (limited to 'makima/frontend/src/routes')
| -rw-r--r-- | makima/frontend/src/routes/directives.tsx | 168 |
1 files changed, 168 insertions, 0 deletions
diff --git a/makima/frontend/src/routes/directives.tsx b/makima/frontend/src/routes/directives.tsx new file mode 100644 index 0000000..82e5d48 --- /dev/null +++ b/makima/frontend/src/routes/directives.tsx @@ -0,0 +1,168 @@ +import { useState, 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, useDirective } from "../hooks/useDirectives"; +import { useAuth } from "../contexts/AuthContext"; + +export default function DirectivesPage() { + const { isAuthenticated, isAuthConfigured, isLoading: authLoading } = useAuth(); + const navigate = useNavigate(); + const { id: selectedId } = useParams<{ id: string }>(); + const { directives, loading: listLoading, create, remove } = useDirectives(); + const { directive, refresh: refreshDetail, start, pause, advance, completeStep, failStep, skipStep, updateGoal } = useDirective(selectedId); + + const [showCreate, setShowCreate] = useState(false); + const [newTitle, setNewTitle] = useState(""); + const [newGoal, setNewGoal] = useState(""); + const [newRepoUrl, setNewRepoUrl] = useState(""); + + useEffect(() => { + if (!authLoading && isAuthConfigured && !isAuthenticated) { + navigate("/login"); + } + }, [authLoading, isAuthConfigured, isAuthenticated, 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> + ); + } + + const handleCreate = async () => { + if (!newTitle.trim() || !newGoal.trim()) return; + try { + const d = await create({ + title: newTitle.trim(), + goal: newGoal.trim(), + repositoryUrl: newRepoUrl.trim() || undefined, + }); + setShowCreate(false); + setNewTitle(""); + setNewGoal(""); + setNewRepoUrl(""); + navigate(`/directives/${d.id}`); + } catch (e) { + console.error("Failed to create directive:", e); + } + }; + + const handleDelete = async () => { + if (!selectedId) return; + if (!window.confirm("Delete this directive?")) return; + try { + await remove(selectedId); + navigate("/directives"); + } catch (e) { + console.error("Failed to delete:", e); + } + }; + + return ( + <div className="relative z-10 min-h-screen flex flex-col bg-[#0a1628]"> + <Masthead showNav /> + <main className="flex-1 flex overflow-hidden" style={{ height: "calc(100vh - 80px)" }}> + {/* Left: List */} + <div className="w-[280px] shrink-0 border-r border-dashed border-[rgba(117,170,252,0.2)] overflow-hidden flex flex-col"> + <DirectiveList + directives={directives} + selectedId={selectedId ?? null} + onSelect={(id) => navigate(`/directives/${id}`)} + onCreate={() => setShowCreate(true)} + /> + </div> + + {/* Right: Detail or Create */} + <div className="flex-1 overflow-hidden"> + {showCreate ? ( + <div className="p-4 max-w-lg"> + <h2 className="text-[14px] font-mono text-white font-medium mb-4"> + New Directive + </h2> + <div className="flex flex-col gap-3"> + <div> + <label className="text-[10px] font-mono text-[#9bc3ff] uppercase tracking-wide block mb-1"> + Title + </label> + <input + value={newTitle} + onChange={(e) => setNewTitle(e.target.value)} + placeholder="Project title..." + className="w-full bg-[#0a1628] border border-[rgba(117,170,252,0.2)] rounded px-2 py-1.5 text-[12px] font-mono text-white" + /> + </div> + <div> + <label className="text-[10px] font-mono text-[#9bc3ff] uppercase tracking-wide block mb-1"> + Goal + </label> + <textarea + value={newGoal} + onChange={(e) => setNewGoal(e.target.value)} + placeholder="What should this directive accomplish?" + rows={4} + className="w-full bg-[#0a1628] border border-[rgba(117,170,252,0.2)] rounded px-2 py-1.5 text-[12px] font-mono text-white resize-y" + /> + </div> + <div> + <label className="text-[10px] font-mono text-[#9bc3ff] uppercase tracking-wide block mb-1"> + Repository URL (optional) + </label> + <input + value={newRepoUrl} + onChange={(e) => setNewRepoUrl(e.target.value)} + placeholder="https://github.com/..." + className="w-full bg-[#0a1628] border border-[rgba(117,170,252,0.2)] rounded px-2 py-1.5 text-[12px] font-mono text-white" + /> + </div> + <div className="flex gap-2"> + <button + type="button" + onClick={handleCreate} + disabled={!newTitle.trim() || !newGoal.trim()} + className="text-[11px] font-mono text-emerald-400 hover:text-emerald-300 border border-emerald-800 rounded px-3 py-1 disabled:opacity-50" + > + Create + </button> + <button + type="button" + onClick={() => setShowCreate(false)} + className="text-[11px] font-mono text-[#7788aa] hover:text-white border border-[#2a3a5a] rounded px-3 py-1" + > + Cancel + </button> + </div> + </div> + </div> + ) : selectedId && directive ? ( + <DirectiveDetail + directive={directive} + onStart={start} + onPause={pause} + onAdvance={advance} + onCompleteStep={completeStep} + onFailStep={failStep} + onSkipStep={skipStep} + onUpdateGoal={updateGoal} + onDelete={handleDelete} + onRefresh={refreshDetail} + /> + ) : ( + <div className="flex-1 flex items-center justify-center h-full"> + <p className="text-[#556677] font-mono text-[12px]"> + {listLoading + ? "Loading..." + : "Select a directive or create a new one"} + </p> + </div> + )} + </div> + </main> + </div> + ); +} |
