summaryrefslogblamecommitdiff
path: root/makima/frontend/src/routes/directives.tsx
blob: 89535e2de552547d290a8f1e0651dc34fe0bb854 (plain) (tree)
















































































































































































































                                                                                                                                                                                                            
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 (
      <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 />
      <DirectivesContent />
    </div>
  );
}

function DirectivesContent() {
  const { id } = useParams<{ id?: string }>();
  const navigate = useNavigate();
  const {
    directives,
    loading,
    error,
    fetchDirective,
    saveDirective,
    removeDirective,
  } = 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("");
  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 (
        <main className="flex-1 flex items-center justify-center">
          <p className="text-[#7788aa] font-mono text-sm">Loading directive...</p>
        </main>
      );
    }
    if (!selectedDirective) {
      return (
        <main className="flex-1 flex items-center justify-center">
          <p className="text-[#7788aa] font-mono text-sm">Directive not found</p>
        </main>
      );
    }
    return (
      <main className="flex-1 p-4 max-w-4xl mx-auto w-full">
        <DirectiveDetail
          directive={selectedDirective}
          onBack={handleBack}
          onDelete={handleDelete}
        />
      </main>
    );
  }

  // List view
  return (
    <main className="flex-1 p-4 max-w-4xl mx-auto w-full">
      {error && (
        <div className="mb-4 p-2 border border-red-400/30 bg-red-400/5 font-mono text-xs text-red-400">
          {error}
        </div>
      )}

      {showCreateForm && (
        <div className="mb-4 p-4 border border-dashed border-[rgba(117,170,252,0.35)] bg-[rgba(117,170,252,0.03)]">
          <h3 className="font-mono text-xs text-[#75aafc] uppercase tracking-wider mb-3">
            New Directive
          </h3>
          <div className="space-y-2">
            <input
              type="text"
              placeholder="Title"
              value={createTitle}
              onChange={(e) => 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"
            />
            <textarea
              placeholder="Goal - what should be accomplished?"
              value={createGoal}
              onChange={(e) => setCreateGoal(e.target.value)}
              rows={3}
              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 resize-none"
            />
            <input
              type="text"
              placeholder="Repository URL (optional)"
              value={createRepoUrl}
              onChange={(e) => setCreateRepoUrl(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"
            />
            <div className="flex gap-2">
              <button
                onClick={handleCreate}
                disabled={!createTitle.trim() || !createGoal.trim()}
                className="px-3 py-1.5 font-mono text-xs text-[#dbe7ff] bg-[#0f3c78] border border-[#3f6fb3] hover:bg-[#153667] transition-colors uppercase disabled:opacity-50 disabled:cursor-not-allowed"
              >
                Create
              </button>
              <button
                onClick={() => setShowCreateForm(false)}
                className="px-3 py-1.5 font-mono text-xs text-[#7788aa] hover:text-[#dbe7ff] transition-colors uppercase"
              >
                Cancel
              </button>
            </div>
          </div>
        </div>
      )}

      <DirectiveList
        directives={directives}
        loading={loading}
        onSelect={handleSelect}
        onCreate={() => setShowCreateForm(true)}
        onDelete={(d) => handleDelete(d.id)}
        selectedId={id}
      />
    </main>
  );
}