diff options
Diffstat (limited to 'makima/frontend/src/routes/contracts.tsx')
| -rw-r--r-- | makima/frontend/src/routes/contracts.tsx | 885 |
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> - ); -} |
