summaryrefslogtreecommitdiff
path: root/makima/frontend/src/routes/contracts.tsx
diff options
context:
space:
mode:
Diffstat (limited to 'makima/frontend/src/routes/contracts.tsx')
-rw-r--r--makima/frontend/src/routes/contracts.tsx885
1 files changed, 0 insertions, 885 deletions
diff --git a/makima/frontend/src/routes/contracts.tsx b/makima/frontend/src/routes/contracts.tsx
deleted file mode 100644
index ce9ceca..0000000
--- a/makima/frontend/src/routes/contracts.tsx
+++ /dev/null
@@ -1,885 +0,0 @@
-import { useState, useCallback, useEffect } from "react";
-import { useParams, useNavigate } from "react-router";
-import { Masthead } from "../components/Masthead";
-import { ContractList } from "../components/contracts/ContractList";
-import { ContractDetail } from "../components/contracts/ContractDetail";
-import { DirectoryInput } from "../components/mesh/DirectoryInput";
-import { useContracts } from "../hooks/useContracts";
-import { useAuth } from "../contexts/AuthContext";
-import {
- createTask,
- getDaemonDirectories,
- getRepositorySuggestions,
- listContractTypes,
-} from "../lib/api";
-import type {
- ContractWithRelations,
- ContractSummary,
- ContractPhase,
- ContractStatus,
- ContractType,
- CreateContractRequest,
- RepositorySourceType,
- DaemonDirectory,
- RepositoryHistoryEntry,
- ContractTypeTemplate,
-} from "../lib/api";
-
-export default function ContractsPage() {
- 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]);
-
- // Show loading while checking auth
- 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>
- );
- }
-
- // Don't render if not authenticated (will redirect)
- if (isAuthConfigured && !isAuthenticated) {
- return null;
- }
-
- return <ContractsPageContent />;
-}
-
-function ContractsPageContent() {
- const { id } = useParams<{ id: string }>();
- const navigate = useNavigate();
- const {
- contracts,
- loading,
- error,
- fetchContract,
- saveContract,
- editContract,
- removeContract,
- changePhase,
- addRemoteRepo,
- addLocalRepo,
- createManagedRepo,
- removeRepo,
- setRepoPrimary,
- } = useContracts();
-
- const [contractDetail, setContractDetail] = useState<ContractWithRelations | null>(null);
- const [detailLoading, setDetailLoading] = useState(false);
- const [isCreating, setIsCreating] = useState(false);
- const [newContractName, setNewContractName] = useState("");
- const [newContractDescription, setNewContractDescription] = useState("");
- const [contractType, setContractType] = useState<ContractType>("simple");
- const [initialPhase, setInitialPhase] = useState<ContractPhase>("plan");
- const [repoType, setRepoType] = useState<RepositorySourceType>("remote");
- const [repoName, setRepoName] = useState("");
- const [repoUrl, setRepoUrl] = useState("");
- const [repoPath, setRepoPath] = useState("");
- const [createError, setCreateError] = useState<string | null>(null);
- const [suggestedDirectories, setSuggestedDirectories] = useState<DaemonDirectory[]>([]);
- const [repoSuggestions, setRepoSuggestions] = useState<RepositoryHistoryEntry[]>([]);
- const [showRepoSuggestions, setShowRepoSuggestions] = useState(false);
- const [contractTypes, setContractTypes] = useState<ContractTypeTemplate[]>([]);
- const [contractTypesLoading, setContractTypesLoading] = useState(false);
- const [localOnly, setLocalOnly] = useState(false);
-
- // Fetch contract types when modal opens - API returns both built-in and custom templates
- useEffect(() => {
- if (isCreating) {
- setContractTypesLoading(true);
-
- listContractTypes()
- .then((res) => {
- setContractTypes(res.contractTypes);
- setContractTypesLoading(false);
- })
- .catch((err) => {
- console.error("Failed to fetch contract types:", err);
- // Fall back to built-in types
- const builtinTypes: ContractTypeTemplate[] = [
- {
- id: "simple",
- name: "Simple",
- description: "Plan \u2192 Execute: Simple workflow with a plan document",
- phases: ["plan", "execute"],
- defaultPhase: "plan",
- isBuiltin: true,
- },
- {
- id: "specification",
- name: "Specification",
- description: "Research \u2192 Specify \u2192 Plan \u2192 Execute \u2192 Review: Full specification-driven development with TDD",
- phases: ["research", "specify", "plan", "execute", "review"],
- defaultPhase: "research",
- isBuiltin: true,
- },
- {
- id: "execute",
- name: "Execute",
- description: "Execute only: Minimal workflow for immediate task execution",
- phases: ["execute"],
- defaultPhase: "execute",
- isBuiltin: true,
- },
- ];
- setContractTypes(builtinTypes);
- setContractTypesLoading(false);
- });
- }
- }, [isCreating]);
-
- // Fetch repository suggestions when modal opens and repo type changes
- useEffect(() => {
- if (isCreating && (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);
- }
- }, [isCreating, repoType]);
-
- // Apply a repository suggestion
- const applyRepoSuggestion = useCallback((suggestion: RepositoryHistoryEntry) => {
- setRepoName(suggestion.name);
- if (suggestion.repositoryUrl) {
- setRepoUrl(suggestion.repositoryUrl);
- }
- if (suggestion.localPath) {
- setRepoPath(suggestion.localPath);
- }
- setShowRepoSuggestions(false);
- }, []);
-
- // Fetch daemon directories when "local" repo type is selected
- useEffect(() => {
- if (repoType === "local" && isCreating) {
- getDaemonDirectories()
- .then((res) => setSuggestedDirectories(res.directories))
- .catch(() => setSuggestedDirectories([]));
- }
- }, [repoType, isCreating]);
-
- // Load contract detail when ID changes
- useEffect(() => {
- if (id) {
- setDetailLoading(true);
- fetchContract(id).then((contract) => {
- setContractDetail(contract);
- setDetailLoading(false);
- });
- } else {
- setContractDetail(null);
- }
- }, [id, fetchContract]);
-
- const handleSelect = useCallback(
- (contractId: string) => {
- navigate(`/contracts/${contractId}`);
- },
- [navigate]
- );
-
- const handleBack = useCallback(() => {
- navigate("/contracts");
- }, [navigate]);
-
- const handleCreate = useCallback(() => {
- setIsCreating(true);
- }, []);
-
- // Validate repository configuration
- const isRepoValid = useCallback(() => {
- if (!repoName.trim()) return false;
- if (repoType === "remote" && !repoUrl.trim()) return false;
- if (repoType === "local" && !repoPath.trim()) return false;
- return true;
- }, [repoType, repoName, repoUrl, repoPath]);
-
- const handleCreateSubmit = useCallback(async () => {
- if (!newContractName.trim()) return;
- if (!isRepoValid()) {
- setCreateError("Repository configuration is required");
- return;
- }
-
- setCreateError(null);
-
- // Get default phase from contract types or fall back to static function
- const selectedType = contractTypes.find((t) => t.id === contractType);
- const defaultPhaseForType = selectedType?.defaultPhase || (contractType === "simple" ? "plan" : "research");
- const isCustomTemplate = selectedType && !selectedType.isBuiltin;
-
- const data: CreateContractRequest = {
- name: newContractName.trim(),
- description: newContractDescription.trim() || undefined,
- // For custom templates, send templateId instead of contractType
- contractType: isCustomTemplate ? undefined : contractType,
- templateId: isCustomTemplate ? contractType : undefined,
- initialPhase: initialPhase !== defaultPhaseForType ? initialPhase : undefined,
- localOnly: localOnly || undefined,
- };
-
- try {
- const contract = await saveContract(data);
- if (contract) {
- // Add the repository after contract creation
- try {
- if (repoType === "remote") {
- await addRemoteRepo(contract.id, {
- name: repoName.trim(),
- repositoryUrl: repoUrl.trim(),
- isPrimary: true,
- });
- } else if (repoType === "local") {
- await addLocalRepo(contract.id, {
- name: repoName.trim(),
- localPath: repoPath.trim(),
- isPrimary: true,
- });
- } else if (repoType === "managed") {
- await createManagedRepo(contract.id, {
- name: repoName.trim(),
- isPrimary: true,
- });
- }
- } catch (repoError) {
- console.error("Failed to add repository:", repoError);
- // Still navigate to the contract - repo can be added later
- }
-
- // Clear form state
- setIsCreating(false);
- setNewContractName("");
- setNewContractDescription("");
- setContractType("simple");
- setInitialPhase("plan");
- setRepoType("remote");
- setRepoName("");
- setRepoUrl("");
- setRepoPath("");
- setLocalOnly(false);
- navigate(`/contracts/${contract.id}`);
- }
- } catch (err) {
- setCreateError(err instanceof Error ? err.message : "Failed to create contract");
- }
- }, [
- newContractName,
- newContractDescription,
- contractType,
- contractTypes,
- initialPhase,
- repoType,
- repoName,
- repoUrl,
- repoPath,
- isRepoValid,
- saveContract,
- addRemoteRepo,
- addLocalRepo,
- createManagedRepo,
- navigate,
- ]);
-
- const handleCreateCancel = useCallback(() => {
- setIsCreating(false);
- setNewContractName("");
- setNewContractDescription("");
- setContractType("simple");
- setInitialPhase("plan");
- setRepoType("remote");
- setRepoName("");
- setRepoUrl("");
- setRepoPath("");
- setLocalOnly(false);
- setCreateError(null);
- }, []);
-
- const handleUpdate = useCallback(
- async (name: string, description: string) => {
- if (contractDetail) {
- const updated = await editContract(contractDetail.id, {
- name,
- description: description || undefined,
- version: contractDetail.version,
- });
- if (updated) {
- // Refresh detail
- const refreshed = await fetchContract(contractDetail.id);
- setContractDetail(refreshed);
- }
- }
- },
- [contractDetail, editContract, fetchContract]
- );
-
- const handleDelete = useCallback(async () => {
- if (contractDetail && confirm("Are you sure you want to delete this contract?")) {
- const success = await removeContract(contractDetail.id);
- if (success) {
- navigate("/contracts");
- }
- }
- }, [contractDetail, removeContract, navigate]);
-
- const handlePhaseChange = useCallback(
- async (phase: ContractPhase) => {
- if (contractDetail) {
- const updated = await changePhase(contractDetail.id, phase);
- if (updated) {
- // Refresh detail
- const refreshed = await fetchContract(contractDetail.id);
- setContractDetail(refreshed);
- }
- }
- },
- [contractDetail, changePhase, fetchContract]
- );
-
- const handleStatusChange = useCallback(
- async (status: ContractStatus) => {
- if (contractDetail) {
- const updated = await editContract(contractDetail.id, {
- status,
- version: contractDetail.version,
- });
- if (updated) {
- // Refresh detail
- const refreshed = await fetchContract(contractDetail.id);
- setContractDetail(refreshed);
- }
- }
- },
- [contractDetail, editContract, fetchContract]
- );
-
- // Repository handlers
- const handleAddRemoteRepo = useCallback(
- async (name: string, url: string, isPrimary: boolean) => {
- if (contractDetail) {
- await addRemoteRepo(contractDetail.id, { name, repositoryUrl: url, isPrimary });
- const refreshed = await fetchContract(contractDetail.id);
- setContractDetail(refreshed);
- }
- },
- [contractDetail, addRemoteRepo, fetchContract]
- );
-
- const handleAddLocalRepo = useCallback(
- async (name: string, path: string, isPrimary: boolean) => {
- if (contractDetail) {
- await addLocalRepo(contractDetail.id, { name, localPath: path, isPrimary });
- const refreshed = await fetchContract(contractDetail.id);
- setContractDetail(refreshed);
- }
- },
- [contractDetail, addLocalRepo, fetchContract]
- );
-
- const handleCreateManagedRepo = useCallback(
- async (name: string, isPrimary: boolean) => {
- if (contractDetail) {
- await createManagedRepo(contractDetail.id, { name, isPrimary });
- const refreshed = await fetchContract(contractDetail.id);
- setContractDetail(refreshed);
- }
- },
- [contractDetail, createManagedRepo, fetchContract]
- );
-
- const handleDeleteRepo = useCallback(
- async (repoId: string) => {
- if (contractDetail) {
- await removeRepo(contractDetail.id, repoId);
- const refreshed = await fetchContract(contractDetail.id);
- setContractDetail(refreshed);
- }
- },
- [contractDetail, removeRepo, fetchContract]
- );
-
- const handleSetRepoPrimary = useCallback(
- async (repoId: string) => {
- if (contractDetail) {
- await setRepoPrimary(contractDetail.id, repoId);
- const refreshed = await fetchContract(contractDetail.id);
- setContractDetail(refreshed);
- }
- },
- [contractDetail, setRepoPrimary, fetchContract]
- );
-
- // Refresh contract detail (used after file/task operations)
- const handleRefresh = useCallback(async () => {
- if (contractDetail) {
- const refreshed = await fetchContract(contractDetail.id);
- setContractDetail(refreshed);
- }
- }, [contractDetail, fetchContract]);
-
- // File/task navigation handlers
- const handleFileSelect = useCallback(
- (fileId: string) => {
- if (contractDetail) {
- navigate(`/contracts/${contractDetail.id}/files/${fileId}`);
- }
- },
- [navigate, contractDetail]
- );
-
- const handleTaskSelect = useCallback(
- (taskId: string) => {
- navigate(`/exec/${taskId}`);
- },
- [navigate]
- );
-
- // Create task within contract context
- const handleTaskCreate = useCallback(
- async (name: string, plan: string, repositoryUrl?: string) => {
- if (!contractDetail) return;
- try {
- // Create the task with contract_id (task is automatically associated)
- const task = await createTask({
- contractId: contractDetail.id,
- name,
- plan,
- repositoryUrl,
- });
- // Refresh contract detail to show new task
- const refreshed = await fetchContract(contractDetail.id);
- setContractDetail(refreshed);
- // Navigate to the new task
- navigate(`/exec/${task.id}`);
- } catch (e) {
- console.error("Failed to create task:", e);
- alert(e instanceof Error ? e.message : "Failed to create task");
- }
- },
- [contractDetail, fetchContract, navigate]
- );
-
- // Context menu handlers for ContractList
- const handleContextMarkComplete = useCallback(
- async (contract: ContractSummary) => {
- await editContract(contract.id, { status: "completed", version: contract.version });
- },
- [editContract]
- );
-
- const handleContextMarkActive = useCallback(
- async (contract: ContractSummary) => {
- await editContract(contract.id, { status: "active", version: contract.version });
- },
- [editContract]
- );
-
- const handleContextArchive = useCallback(
- async (contract: ContractSummary) => {
- await editContract(contract.id, { status: "archived", version: contract.version });
- },
- [editContract]
- );
-
- const handleContextDelete = useCallback(
- async (contract: ContractSummary) => {
- if (confirm(`Are you sure you want to delete "${contract.name}"?`)) {
- const success = await removeContract(contract.id);
- if (success && contract.id === id) {
- navigate("/contracts");
- }
- }
- },
- [removeContract, id, navigate]
- );
-
- const handleContextGoToSupervisor = useCallback(
- (contract: ContractSummary) => {
- if (contract.supervisorTaskId) {
- navigate(`/exec/${contract.supervisorTaskId}`);
- }
- },
- [navigate]
- );
-
- return (
- <div className="relative z-10 h-screen flex flex-col overflow-hidden bg-[#0a1628]">
- <Masthead showNav />
- <main className="flex-1 flex overflow-hidden" style={{ height: "calc(100vh - 80px)" }}>
- {/* Left: Contract list */}
- <div className="w-[350px] shrink-0 border-r border-dashed border-[rgba(117,170,252,0.2)] overflow-hidden flex flex-col">
- <ContractList
- contracts={contracts}
- loading={loading}
- onSelect={handleSelect}
- onCreate={handleCreate}
- selectedId={id}
- onMarkComplete={handleContextMarkComplete}
- onMarkActive={handleContextMarkActive}
- onArchive={handleContextArchive}
- onDelete={handleContextDelete}
- onGoToSupervisor={handleContextGoToSupervisor}
- />
- </div>
-
- {/* Right: Detail or Create */}
- <div className="flex-1 overflow-hidden flex flex-col min-h-0">
- {error && (
- <div className="p-3 bg-red-400/10 border border-red-400/30 text-red-400 font-mono text-sm">
- {error}
- </div>
- )}
-
- {/* Contract detail, creation form, or empty state */}
- <div className="flex-1 min-h-0 overflow-hidden">
- {isCreating ? (
- <div className="p-4 max-w-lg overflow-y-auto h-full bg-[#0a1628]">
- <h3 className="font-mono text-[10px] text-[#9bc3ff] uppercase tracking-wide mb-4">
- Create Contract
- </h3>
-
- {createError && (
- <div className="mb-4 p-3 bg-red-400/10 border border-red-400/30 text-red-400 font-mono text-xs">
- {createError}
- </div>
- )}
-
- <div className="space-y-4">
- {/* Contract name */}
- <div>
- <label className="block text-[10px] font-mono text-[#9bc3ff] uppercase tracking-wide mb-1">
- Contract Name
- </label>
- <input
- type="text"
- value={newContractName}
- onChange={(e) => setNewContractName(e.target.value)}
- placeholder="Contract name"
- className="w-full px-3 py-2 bg-[#0d1b2d] border border-[rgba(117,170,252,0.2)] text-[12px] font-mono text-white focus:outline-none focus:border-[#75aafc]"
- autoFocus
- />
- </div>
-
- {/* Description */}
- <div>
- <label className="block text-[10px] font-mono text-[#9bc3ff] uppercase tracking-wide mb-1">
- Description (optional)
- </label>
- <textarea
- value={newContractDescription}
- onChange={(e) => setNewContractDescription(e.target.value)}
- placeholder="Describe what this contract is for..."
- rows={2}
- className="w-full px-3 py-2 bg-[#0d1b2d] border border-[rgba(117,170,252,0.2)] text-[12px] font-mono text-white focus:outline-none focus:border-[#75aafc] resize-none"
- />
- </div>
-
- {/* Contract Type */}
- <div>
- <label className="block text-[10px] font-mono text-[#9bc3ff] uppercase tracking-wide mb-1">
- Contract Type
- </label>
- {contractTypesLoading ? (
- <div className="flex items-center justify-center py-4">
- <span className="font-mono text-xs text-[#8b949e]">Loading contract types...</span>
- </div>
- ) : (
- <>
- <div className="flex gap-2">
- {contractTypes.map((type) => (
- <button
- key={type.id}
- type="button"
- onClick={() => {
- setContractType(type.id as ContractType);
- setInitialPhase(type.defaultPhase as ContractPhase);
- }}
- className={`flex-1 px-3 py-2 font-mono text-xs uppercase transition-colors ${
- contractType === type.id
- ? "bg-[#0f3c78] text-[#dbe7ff] border border-[#75aafc]"
- : "bg-[#0d1b2d] text-[#8b949e] border border-[rgba(117,170,252,0.2)] hover:border-[#75aafc]"
- }`}
- >
- {type.name}
- </button>
- ))}
- </div>
- <p className="mt-1 font-mono text-xs text-[#8b949e]">
- {contractTypes.find((t) => t.id === contractType)?.description ||
- "Select a contract type"}
- </p>
- </>
- )}
- </div>
-
- {/* Starting Phase */}
- <div>
- <label className="block text-[10px] font-mono text-[#9bc3ff] uppercase tracking-wide mb-1">
- Starting Phase
- </label>
- <select
- value={initialPhase}
- onChange={(e) => setInitialPhase(e.target.value as ContractPhase)}
- className="w-full px-3 py-2 bg-[#0d1b2d] border border-[rgba(117,170,252,0.2)] text-[12px] font-mono text-white focus:outline-none focus:border-[#75aafc]"
- >
- {(() => {
- const template = contractTypes.find((t) => t.id === contractType);
- return (template?.phases || []).map((phase) => {
- const displayName = template?.phaseNames?.[phase] || (phase.charAt(0).toUpperCase() + phase.slice(1));
- return (
- <option key={phase} value={phase}>
- {displayName}
- </option>
- );
- });
- })()}
- </select>
- <p className="mt-1 font-mono text-xs text-[#8b949e]">
- {contractType === "simple"
- ? "Start in Plan to define what to build, or Execute if already planned"
- : "Skip earlier phases if you already have requirements defined"}
- </p>
- </div>
-
- {/* Local-Only Mode */}
- <div className="space-y-2">
- <div className="flex items-center space-x-3">
- <button
- type="button"
- onClick={() => setLocalOnly(!localOnly)}
- className={`w-5 h-5 flex items-center justify-center border transition-colors ${
- localOnly
- ? "bg-[#0f3c78] border-[#75aafc] text-[#dbe7ff]"
- : "bg-[#0d1b2d] border-[rgba(117,170,252,0.2)] text-transparent"
- }`}
- >
- {localOnly && (
- <svg
- xmlns="http://www.w3.org/2000/svg"
- viewBox="0 0 24 24"
- fill="none"
- stroke="currentColor"
- strokeWidth="3"
- strokeLinecap="round"
- strokeLinejoin="round"
- className="w-3 h-3"
- >
- <polyline points="20 6 9 17 4 12" />
- </svg>
- )}
- </button>
- <label
- className="font-mono text-sm text-[#dbe7ff] cursor-pointer select-none"
- onClick={() => setLocalOnly(!localOnly)}
- >
- Local-Only Mode
- </label>
- </div>
- <p className="font-mono text-xs text-[#8b949e] pl-8">
- When enabled, tasks won't automatically push to remote or create PRs.
- Use patch files to export changes.
- </p>
- </div>
-
- {/* Repository Configuration */}
- <div className="border-t border-[rgba(117,170,252,0.2)] pt-4">
- <label className="block text-[10px] font-mono text-[#9bc3ff] uppercase tracking-wide mb-3">
- Repository Configuration (Required)
- </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-[rgba(117,170,252,0.2)] 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-[rgba(117,170,252,0.2)] hover:border-[#75aafc]"
- }`}
- >
- Local
- </button>
- <button
- type="button"
- onClick={() => setRepoType("managed")}
- className={`flex-1 px-3 py-2 font-mono text-xs uppercase transition-colors ${
- repoType === "managed"
- ? "bg-[#0f3c78] text-[#dbe7ff] border border-[#75aafc]"
- : "bg-[#0d1b2d] text-[#8b949e] border border-[rgba(117,170,252,0.2)] hover:border-[#75aafc]"
- }`}
- >
- Managed
- </button>
- </div>
-
- {/* Repository suggestions */}
- {showRepoSuggestions && repoSuggestions.length > 0 && (
- <div className="mb-3">
- <label className="block text-[10px] font-mono text-[#9bc3ff] uppercase tracking-wide 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}×
- </span>
- </div>
- <div className="text-[10px] text-[#556677] truncate">
- {repoType === "local" ? suggestion.localPath : suggestion.repositoryUrl}
- </div>
- </button>
- ))}
- </div>
- </div>
- )}
-
- {/* Repository name */}
- <div className="mb-3">
- <label className="block text-[10px] font-mono text-[#9bc3ff] uppercase tracking-wide mb-1">
- Repository Name
- </label>
- <input
- type="text"
- value={repoName}
- onChange={(e) => setRepoName(e.target.value)}
- placeholder="e.g., my-project"
- className="w-full px-3 py-2 bg-[#0d1b2d] border border-[rgba(117,170,252,0.2)] text-[12px] font-mono text-white focus:outline-none focus:border-[#75aafc]"
- />
- </div>
-
- {/* Repository URL (for remote) */}
- {repoType === "remote" && (
- <div>
- <label className="block text-[10px] font-mono text-[#9bc3ff] uppercase tracking-wide 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-[rgba(117,170,252,0.2)] text-[12px] font-mono text-white focus:outline-none focus:border-[#75aafc]"
- />
- </div>
- )}
-
- {/* Repository path (for local) */}
- {repoType === "local" && (
- <div>
- <label className="block text-[10px] font-mono text-[#9bc3ff] uppercase tracking-wide mb-1">
- Local Path
- </label>
- <DirectoryInput
- value={repoPath}
- onChange={setRepoPath}
- suggestions={suggestedDirectories}
- placeholder="/path/to/repository"
- />
- </div>
- )}
-
- {/* Managed description */}
- {repoType === "managed" && (
- <p className="font-mono text-xs text-[#8b949e]">
- A managed repository will be created automatically by the daemon.
- </p>
- )}
- </div>
-
- {/* Actions */}
- <div className="flex gap-2 justify-end pt-2">
- <button
- onClick={handleCreateCancel}
- className="px-4 py-2 font-mono text-xs text-[#9bc3ff] hover:text-[#dbe7ff] transition-colors"
- >
- Cancel
- </button>
- <button
- onClick={handleCreateSubmit}
- disabled={!newContractName.trim() || !isRepoValid()}
- className="px-4 py-2 font-mono text-xs text-[#dbe7ff] bg-[#0f3c78] border border-[rgba(117,170,252,0.2)] hover:bg-[#153667] transition-colors disabled:opacity-50 disabled:cursor-not-allowed"
- >
- Create
- </button>
- </div>
- </div>
- </div>
- ) : contractDetail ? (
- <ContractDetail
- contract={contractDetail}
- loading={detailLoading}
- onBack={handleBack}
- onUpdate={handleUpdate}
- onDelete={handleDelete}
- onPhaseChange={handlePhaseChange}
- onStatusChange={handleStatusChange}
- onFileSelect={handleFileSelect}
- onTaskSelect={handleTaskSelect}
- onTaskCreate={handleTaskCreate}
- onRefresh={handleRefresh}
- onAddRemoteRepo={handleAddRemoteRepo}
- onAddLocalRepo={handleAddLocalRepo}
- onCreateManagedRepo={handleCreateManagedRepo}
- onDeleteRepo={handleDeleteRepo}
- onSetRepoPrimary={handleSetRepoPrimary}
- />
- ) : (
- <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 contract or create a new one
- </p>
- <button
- onClick={handleCreate}
- className="px-4 py-2 font-mono text-xs text-[#dbe7ff] bg-[#0f3c78] border border-[#3f6fb3] hover:bg-[#153667] transition-colors uppercase"
- >
- + New Contract
- </button>
- </div>
- </div>
- )}
- </div>
- </div>
- </main>
- </div>
- );
-}