summaryrefslogtreecommitdiff
path: root/makima/frontend/src/routes/directives.tsx
diff options
context:
space:
mode:
Diffstat (limited to 'makima/frontend/src/routes/directives.tsx')
-rw-r--r--makima/frontend/src/routes/directives.tsx403
1 files changed, 0 insertions, 403 deletions
diff --git a/makima/frontend/src/routes/directives.tsx b/makima/frontend/src/routes/directives.tsx
deleted file mode 100644
index fd3808b..0000000
--- a/makima/frontend/src/routes/directives.tsx
+++ /dev/null
@@ -1,403 +0,0 @@
-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 { DirectoryInput } from "../components/mesh/DirectoryInput";
-import { useDirectives } from "../hooks/useDirectives";
-import { useAuth } from "../contexts/AuthContext";
-import {
- getDaemonDirectories,
- getRepositorySuggestions,
-} from "../lib/api";
-import type {
- DirectiveWithChains,
- CreateDirectiveRequest,
- RepositorySourceType,
- DaemonDirectory,
- RepositoryHistoryEntry,
-} 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 (
- <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>
- );
- }
-
- if (isAuthConfigured && !isAuthenticated) {
- return null;
- }
-
- return <DirectivesContent />;
-}
-
-function DirectivesContent() {
- const { id } = useParams<{ id?: string }>();
- const navigate = useNavigate();
- const {
- directives,
- loading,
- error,
- fetchDirective,
- saveDirective,
- removeDirective,
- startDirective,
- } = useDirectives();
-
- const [selectedDirective, setSelectedDirective] =
- useState<DirectiveWithChains | null>(null);
- const [detailLoading, setDetailLoading] = useState(false);
- const [showCreateForm, setShowCreateForm] = useState(false);
- const [createTitle, setCreateTitle] = useState("");
- const [createGoal, setCreateGoal] = useState("");
-
- // Repository state
- const [repoType, setRepoType] = useState<RepositorySourceType>("remote");
- const [repoUrl, setRepoUrl] = useState("");
- const [repoPath, setRepoPath] = useState("");
- const [suggestedDirectories, setSuggestedDirectories] = useState<DaemonDirectory[]>([]);
- const [repoSuggestions, setRepoSuggestions] = useState<RepositoryHistoryEntry[]>([]);
- const [showRepoSuggestions, setShowRepoSuggestions] = useState(false);
-
- // Fetch repository suggestions when modal opens and repo type changes
- useEffect(() => {
- if (showCreateForm && (repoType === "remote" || repoType === "local")) {
- getRepositorySuggestions(repoType, undefined, 10)
- .then((res) => {
- setRepoSuggestions(res.entries);
- setShowRepoSuggestions(res.entries.length > 0);
- })
- .catch(() => {
- setRepoSuggestions([]);
- setShowRepoSuggestions(false);
- });
- } else {
- setRepoSuggestions([]);
- setShowRepoSuggestions(false);
- }
- }, [showCreateForm, repoType]);
-
- // Fetch daemon directories when "local" repo type is selected
- useEffect(() => {
- if (repoType === "local" && showCreateForm) {
- getDaemonDirectories()
- .then((res) => setSuggestedDirectories(res.directories))
- .catch(() => setSuggestedDirectories([]));
- }
- }, [repoType, showCreateForm]);
-
- // Apply a repository suggestion
- const applyRepoSuggestion = useCallback((suggestion: RepositoryHistoryEntry) => {
- if (suggestion.repositoryUrl) {
- setRepoUrl(suggestion.repositoryUrl);
- }
- if (suggestion.localPath) {
- setRepoPath(suggestion.localPath);
- }
- setShowRepoSuggestions(false);
- }, []);
-
- // 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 resetCreateForm = useCallback(() => {
- setShowCreateForm(false);
- setCreateTitle("");
- setCreateGoal("");
- setRepoType("remote");
- setRepoUrl("");
- setRepoPath("");
- }, []);
-
- const handleCreate = useCallback(async () => {
- if (!createTitle.trim() || !createGoal.trim()) return;
-
- const data: CreateDirectiveRequest = {
- title: createTitle.trim(),
- goal: createGoal.trim(),
- };
- if (repoType === "remote" && repoUrl.trim()) {
- data.repositoryUrl = repoUrl.trim();
- } else if (repoType === "local" && repoPath.trim()) {
- data.localPath = repoPath.trim();
- }
-
- const result = await saveDirective(data);
- if (result) {
- resetCreateForm();
- }
- }, [createTitle, createGoal, repoType, repoUrl, repoPath, saveDirective, resetCreateForm]);
-
- const handleDelete = useCallback(
- async (directiveId: string) => {
- const ok = await removeDirective(directiveId);
- if (ok && id === directiveId) {
- navigate("/directives");
- }
- },
- [removeDirective, id, navigate]
- );
-
- const handleStart = useCallback(
- async (directiveId: string) => {
- const ok = await startDirective(directiveId);
- if (ok) {
- const updated = await fetchDirective(directiveId);
- if (updated) {
- setSelectedDirective(updated);
- }
- }
- },
- [startDirective, fetchDirective]
- );
-
- const handleRefresh = useCallback(
- (updated: DirectiveWithChains) => {
- setSelectedDirective(updated);
- },
- []
- );
-
- return (
- <div className="relative z-10 min-h-screen flex flex-col bg-[#0a1628]">
- <Masthead showNav />
- <main className="flex-1 flex flex-col p-4 pt-2 gap-4 overflow-hidden">
- {error && (
- <div className="p-3 bg-red-400/10 border border-red-400/30 text-red-400 font-mono text-sm">
- {error}
- </div>
- )}
-
- {/* Create directive modal */}
- {showCreateForm && (
- <div className="fixed inset-0 z-50 flex items-center justify-center bg-black/50 p-4">
- <div className="w-full max-w-lg max-h-[90vh] overflow-y-auto p-6 bg-[#0a1628] border border-[rgba(117,170,252,0.3)]">
- <h3 className="font-mono text-sm text-[#75aafc] uppercase mb-4">
- New Directive
- </h3>
- <div className="space-y-4">
- {/* Title */}
- <div>
- <label className="block font-mono text-xs text-[#8b949e] uppercase mb-1">
- Title
- </label>
- <input
- type="text"
- placeholder="Directive title"
- value={createTitle}
- onChange={(e) => setCreateTitle(e.target.value)}
- className="w-full px-3 py-2 font-mono text-sm text-[#dbe7ff] bg-[#0d1b2d] border border-[#3f6fb3] focus:border-[#75aafc] outline-none"
- autoFocus
- />
- </div>
-
- {/* Goal */}
- <div>
- <label className="block font-mono text-xs text-[#8b949e] uppercase mb-1">
- Goal
- </label>
- <textarea
- placeholder="What should be accomplished?"
- value={createGoal}
- onChange={(e) => setCreateGoal(e.target.value)}
- rows={3}
- className="w-full px-3 py-2 font-mono text-sm text-[#dbe7ff] bg-[#0d1b2d] border border-[#3f6fb3] focus:border-[#75aafc] outline-none resize-none"
- />
- </div>
-
- {/* Repository Configuration */}
- <div className="border-t border-[rgba(117,170,252,0.2)] pt-4">
- <label className="block font-mono text-xs text-[#75aafc] uppercase mb-3">
- Repository (optional)
- </label>
-
- {/* Repository type selector */}
- <div className="flex gap-2 mb-3">
- <button
- type="button"
- onClick={() => setRepoType("remote")}
- className={`flex-1 px-3 py-2 font-mono text-xs uppercase transition-colors ${
- repoType === "remote"
- ? "bg-[#0f3c78] text-[#dbe7ff] border border-[#75aafc]"
- : "bg-[#0d1b2d] text-[#8b949e] border border-[#3f6fb3] hover:border-[#75aafc]"
- }`}
- >
- Remote
- </button>
- <button
- type="button"
- onClick={() => setRepoType("local")}
- className={`flex-1 px-3 py-2 font-mono text-xs uppercase transition-colors ${
- repoType === "local"
- ? "bg-[#0f3c78] text-[#dbe7ff] border border-[#75aafc]"
- : "bg-[#0d1b2d] text-[#8b949e] border border-[#3f6fb3] hover:border-[#75aafc]"
- }`}
- >
- Local
- </button>
- </div>
-
- {/* Repository suggestions */}
- {showRepoSuggestions && repoSuggestions.length > 0 && (
- <div className="mb-3">
- <label className="block font-mono text-xs text-[#8b949e] uppercase mb-1">
- Recent Repositories
- </label>
- <div className="border border-[rgba(117,170,252,0.2)] bg-[#0a1525] max-h-32 overflow-y-auto">
- {repoSuggestions.map((suggestion) => (
- <button
- key={suggestion.id}
- type="button"
- onClick={() => applyRepoSuggestion(suggestion)}
- className="w-full text-left px-3 py-2 font-mono text-xs hover:bg-[rgba(117,170,252,0.1)] border-b border-[rgba(117,170,252,0.1)] last:border-b-0"
- >
- <div className="flex items-center justify-between">
- <span className="text-[#9bc3ff] truncate">{suggestion.name}</span>
- <span className="text-[10px] text-[#556677] ml-2">
- {suggestion.useCount}&times;
- </span>
- </div>
- <div className="text-[10px] text-[#556677] truncate">
- {repoType === "local" ? suggestion.localPath : suggestion.repositoryUrl}
- </div>
- </button>
- ))}
- </div>
- </div>
- )}
-
- {/* Repository URL (for remote) */}
- {repoType === "remote" && (
- <div>
- <label className="block font-mono text-xs text-[#8b949e] uppercase mb-1">
- Repository URL
- </label>
- <input
- type="text"
- value={repoUrl}
- onChange={(e) => setRepoUrl(e.target.value)}
- placeholder="https://github.com/user/repo.git"
- className="w-full px-3 py-2 bg-[#0d1b2d] border border-[#3f6fb3] text-[#dbe7ff] font-mono text-sm focus:outline-none focus:border-[#75aafc]"
- />
- </div>
- )}
-
- {/* Repository path (for local) */}
- {repoType === "local" && (
- <div>
- <label className="block font-mono text-xs text-[#8b949e] uppercase mb-1">
- Local Path
- </label>
- <DirectoryInput
- value={repoPath}
- onChange={setRepoPath}
- suggestions={suggestedDirectories}
- placeholder="/path/to/repository"
- repoUrl={repoUrl || null}
- />
- </div>
- )}
- </div>
-
- {/* Actions */}
- <div className="flex gap-2 justify-end pt-2">
- <button
- onClick={resetCreateForm}
- className="px-4 py-2 font-mono text-xs text-[#9bc3ff] hover:text-[#dbe7ff] transition-colors"
- >
- Cancel
- </button>
- <button
- onClick={handleCreate}
- disabled={!createTitle.trim() || !createGoal.trim()}
- className="px-4 py-2 font-mono text-xs text-[#dbe7ff] bg-[#0f3c78] border border-[#3f6fb3] hover:bg-[#153667] transition-colors disabled:opacity-50 disabled:cursor-not-allowed"
- >
- Create
- </button>
- </div>
- </div>
- </div>
- </div>
- )}
-
- <div className="flex-1 grid grid-cols-[350px_1fr] gap-4 min-h-0">
- {/* Directive list */}
- <DirectiveList
- directives={directives}
- loading={loading}
- onSelect={handleSelect}
- onCreate={() => setShowCreateForm(true)}
- onDelete={(d) => handleDelete(d.id)}
- selectedId={id}
- />
-
- {/* Directive detail or empty state */}
- {detailLoading ? (
- <div className="panel h-full flex items-center justify-center">
- <p className="text-[#7788aa] font-mono text-sm">Loading directive...</p>
- </div>
- ) : selectedDirective ? (
- <DirectiveDetail
- directive={selectedDirective}
- onBack={handleBack}
- onDelete={handleDelete}
- onStart={handleStart}
- onRefresh={handleRefresh}
- />
- ) : (
- <div className="panel h-full flex items-center justify-center">
- <div className="text-center">
- <p className="font-mono text-sm text-[#555] mb-4">
- Select a directive or create a new one
- </p>
- <button
- onClick={() => setShowCreateForm(true)}
- className="px-4 py-2 font-mono text-xs text-[#dbe7ff] bg-[#0f3c78] border border-[#3f6fb3] hover:bg-[#153667] transition-colors uppercase"
- >
- + New Directive
- </button>
- </div>
- </div>
- )}
- </div>
- </main>
- </div>
- );
-}