diff options
Diffstat (limited to 'makima/frontend/src/components/contracts/ContractDetail.tsx')
| -rw-r--r-- | makima/frontend/src/components/contracts/ContractDetail.tsx | 810 |
1 files changed, 0 insertions, 810 deletions
diff --git a/makima/frontend/src/components/contracts/ContractDetail.tsx b/makima/frontend/src/components/contracts/ContractDetail.tsx deleted file mode 100644 index 02c129e..0000000 --- a/makima/frontend/src/components/contracts/ContractDetail.tsx +++ /dev/null @@ -1,810 +0,0 @@ -import { useState, useEffect, useCallback } from "react"; -import type { - ContractWithRelations, - ContractPhase, - ContractStatus, - ContractRepository, - FileSummary, - TaskSummary, - TemplateSummary, -} from "../../lib/api"; -import { - listTemplates, - getTemplate, - createFile, -} from "../../lib/api"; -import { PhaseProgressBar } from "./PhaseProgressBar"; -import { PhaseHint } from "./PhaseHint"; -import { RepositoryPanel } from "./RepositoryPanel"; -import { ContractCliInput } from "./ContractCliInput"; -import { PhaseDeliverablesPanel } from "./PhaseDeliverablesPanel"; -import { CommandModePanel } from "./CommandModePanel"; -import { TaskTree } from "../mesh/TaskTree"; - -type Tab = "overview" | "repos" | "files" | "tasks"; - -interface ContractDetailProps { - contract: ContractWithRelations; - loading: boolean; - onBack: () => void; - onUpdate: (name: string, description: string) => void; - onDelete: () => void; - onPhaseChange: (phase: ContractPhase) => void; - onStatusChange: (status: ContractStatus) => void; - onFileSelect: (id: string) => void; - onTaskSelect: (id: string) => void; - onTaskCreate: (name: string, plan: string, repositoryUrl?: string) => void; - onRefresh: () => void; - // Repository callbacks - onAddRemoteRepo: (name: string, url: string, isPrimary: boolean) => void; - onAddLocalRepo: (name: string, path: string, isPrimary: boolean) => void; - onCreateManagedRepo: (name: string, isPrimary: boolean) => void; - onDeleteRepo: (repoId: string) => void; - onSetRepoPrimary: (repoId: string) => void; - // File creation callback for phase deliverables - onCreateFileFromTemplate?: (templateId: string, suggestedName: string) => void; -} - -const statusConfig: Record<ContractStatus, { label: string; color: string }> = { - active: { label: "Active", color: "text-green-400" }, - completed: { label: "Completed", color: "text-blue-400" }, - archived: { label: "Archived", color: "text-[#555]" }, -}; - -export function ContractDetail({ - contract, - loading, - onBack, - onUpdate, - onDelete, - onPhaseChange, - onStatusChange, - onFileSelect, - onTaskSelect, - onTaskCreate, - onRefresh, - onAddRemoteRepo, - onAddLocalRepo, - onCreateManagedRepo, - onDeleteRepo, - onSetRepoPrimary, - onCreateFileFromTemplate, -}: ContractDetailProps) { - const [activeTab, setActiveTab] = useState<Tab>("overview"); - const [isEditing, setIsEditing] = useState(false); - const [name, setName] = useState(contract.name); - const [description, setDescription] = useState(contract.description || ""); - - const handleSave = () => { - onUpdate(name, description); - setIsEditing(false); - }; - - const handleCancel = () => { - setName(contract.name); - setDescription(contract.description || ""); - setIsEditing(false); - }; - - if (loading) { - return ( - <div className="panel h-full flex items-center justify-center"> - <div className="font-mono text-[#9bc3ff] text-sm">Loading...</div> - </div> - ); - } - - const tabs: { key: Tab; label: string; count?: number }[] = [ - { key: "overview", label: "Overview" }, - { key: "repos", label: "Repositories", count: contract.repositories.length }, - { key: "files", label: "Files", count: contract.files.length }, - { key: "tasks", label: "Tasks", count: contract.tasks.length }, - ]; - - return ( - <div className="panel h-full flex flex-col min-h-0"> - {/* Header */} - <div className="p-4 border-b border-dashed border-[rgba(117,170,252,0.35)]"> - <div className="flex items-center justify-between mb-3"> - <button - onClick={onBack} - className="font-mono text-xs text-[#75aafc] hover:text-[#9bc3ff] transition-colors" - > - ← Back to list - </button> - <div className="flex items-center gap-2"> - {isEditing ? ( - <> - <button - onClick={handleCancel} - className="px-3 py-1.5 font-mono text-xs text-[#9bc3ff] border border-[rgba(117,170,252,0.25)] hover:border-[#3f6fb3] transition-colors uppercase" - > - Cancel - </button> - <button - onClick={handleSave} - className="px-3 py-1.5 font-mono text-xs text-[#dbe7ff] bg-[#0f3c78] border border-[#3f6fb3] hover:bg-[#153667] transition-colors uppercase" - > - Save - </button> - </> - ) : ( - <> - <button - onClick={() => setIsEditing(true)} - className="px-3 py-1.5 font-mono text-xs text-[#dbe7ff] border border-[#0f3c78] hover:border-[#3f6fb3] transition-colors uppercase" - > - Edit - </button> - <button - onClick={onDelete} - className="px-3 py-1.5 font-mono text-xs text-red-400 border border-red-400/30 hover:border-red-400/50 transition-colors uppercase" - > - Delete - </button> - </> - )} - </div> - </div> - - {isEditing ? ( - <div className="space-y-3"> - <input - type="text" - value={name} - onChange={(e) => setName(e.target.value)} - className="w-full px-3 py-2 bg-[#0d1b2d] border border-[#3f6fb3] text-[#dbe7ff] font-mono text-sm focus:outline-none focus:border-[#75aafc]" - placeholder="Contract name" - /> - <textarea - value={description} - onChange={(e) => setDescription(e.target.value)} - className="w-full px-3 py-2 bg-[#0d1b2d] border border-[#3f6fb3] text-[#dbe7ff] font-mono text-sm focus:outline-none focus:border-[#75aafc] resize-none" - rows={2} - placeholder="Description (optional)" - /> - </div> - ) : ( - <> - <div className="flex items-center gap-3 mb-2"> - <h2 className="font-mono text-lg text-[#dbe7ff]"> - {contract.name} - </h2> - <span - className={`font-mono text-xs uppercase ${ - statusConfig[contract.status].color - }`} - > - {statusConfig[contract.status].label} - </span> - {contract.localOnly && ( - <span className="px-2 py-0.5 font-mono text-[10px] uppercase text-amber-400 border border-amber-400/30 bg-amber-400/10"> - Local-Only - </span> - )} - </div> - {contract.description && ( - <p className="font-mono text-sm text-[#9bc3ff] mb-3"> - {contract.description} - </p> - )} - </> - )} - - {/* Phase progress */} - <div className="mt-4 pt-4 border-t border-dashed border-[rgba(117,170,252,0.2)]"> - <PhaseProgressBar - currentPhase={contract.phase} - contractType={contract.contractType} - onPhaseClick={onPhaseChange} - /> - </div> - </div> - - {/* Tabs */} - <div className="flex border-b border-[rgba(117,170,252,0.2)]"> - {tabs.map((tab) => ( - <button - key={tab.key} - onClick={() => setActiveTab(tab.key)} - className={` - px-4 py-2 font-mono text-xs uppercase tracking-wider transition-colors - ${ - activeTab === tab.key - ? "text-[#dbe7ff] border-b-2 border-[#75aafc]" - : "text-[#555] hover:text-[#9bc3ff]" - } - `} - > - {tab.label} - {tab.count !== undefined && tab.count > 0 && ( - <span className="ml-1 text-[10px]">({tab.count})</span> - )} - </button> - ))} - </div> - - {/* Tab content */} - <div className="flex-1 overflow-y-auto p-4 min-h-0"> - {activeTab === "overview" && ( - <OverviewTab - contract={contract} - onStatusChange={onStatusChange} - onPhaseChange={onPhaseChange} - onCreateFile={onCreateFileFromTemplate} - onRefresh={onRefresh} - /> - )} - - {activeTab === "repos" && ( - <RepositoryPanel - repositories={contract.repositories} - onAddRemote={onAddRemoteRepo} - onAddLocal={onAddLocalRepo} - onCreateManaged={onCreateManagedRepo} - onDelete={onDeleteRepo} - onSetPrimary={onSetRepoPrimary} - /> - )} - - {activeTab === "files" && ( - <FilesTab - files={contract.files} - contractId={contract.id} - contractPhase={contract.phase} - onSelect={onFileSelect} - onRefresh={onRefresh} - /> - )} - - {activeTab === "tasks" && ( - <TasksTab - tasks={contract.tasks} - repositories={contract.repositories} - supervisorTaskId={contract.supervisorTaskId} - contractType={contract.contractType} - onSelect={onTaskSelect} - onCreate={onTaskCreate} - /> - )} - </div> - - {/* Chat Input */} - <ContractCliInput - contractId={contract.id} - contract={contract} - onUpdate={onRefresh} - /> - </div> - ); -} - -// Overview tab -function OverviewTab({ - contract, - onStatusChange, - onPhaseChange, - onCreateFile, - onRefresh, -}: { - contract: ContractWithRelations; - onStatusChange: (status: ContractStatus) => void; - onPhaseChange: (phase: ContractPhase) => void; - onCreateFile?: (templateId: string, suggestedName: string) => void; - onRefresh: () => void; -}) { - return ( - <div className="space-y-6"> - {/* Command Mode controls */} - <CommandModePanel contract={contract} onUpdate={onRefresh} /> - - {/* Phase deliverables checklist */} - <PhaseDeliverablesPanel - contract={contract} - onCreateFile={onCreateFile} - /> - - {/* Phase hint */} - <PhaseHint contract={contract} onAdvancePhase={onPhaseChange} /> - - {/* Task progress summary */} - <TaskStatusSummary tasks={contract.tasks} /> - - {/* Stats */} - <div className="grid grid-cols-3 gap-4"> - <StatCard label="Repositories" value={contract.repositories.length} /> - <StatCard label="Files" value={contract.files.length} /> - <StatCard label="Tasks" value={contract.tasks.length} /> - </div> - - {/* Status change */} - <div> - <h3 className="font-mono text-xs text-[#75aafc] uppercase mb-2"> - Status - </h3> - <div className="flex gap-2"> - {(["active", "completed", "archived"] as ContractStatus[]).map( - (status) => ( - <button - key={status} - onClick={() => onStatusChange(status)} - className={` - px-3 py-1.5 font-mono text-xs uppercase transition-colors - ${ - contract.status === status - ? "bg-[rgba(117,170,252,0.1)] text-[#9bc3ff] border border-[rgba(117,170,252,0.3)]" - : "text-[#555] border border-transparent hover:text-[#75aafc]" - } - `} - > - {status} - </button> - ) - )} - </div> - </div> - - {/* Metadata */} - <div> - <h3 className="font-mono text-xs text-[#75aafc] uppercase mb-2"> - Details - </h3> - <div className="space-y-1 font-mono text-xs text-[#555]"> - <p>Created: {new Date(contract.createdAt).toLocaleString()}</p> - <p>Updated: {new Date(contract.updatedAt).toLocaleString()}</p> - <p>Version: {contract.version}</p> - </div> - </div> - </div> - ); -} - -function StatCard({ label, value }: { label: string; value: number }) { - return ( - <div className="p-3 border border-[rgba(117,170,252,0.2)]"> - <div className="font-mono text-2xl text-[#dbe7ff]">{value}</div> - <div className="font-mono text-[10px] text-[#555] uppercase">{label}</div> - </div> - ); -} - -// Task status summary with progress bar -function TaskStatusSummary({ tasks }: { tasks: TaskSummary[] }) { - if (tasks.length === 0) return null; - - // Count tasks by status - const statusCounts = { - done: 0, - merged: 0, - running: 0, - pending: 0, - failed: 0, - other: 0, - }; - - for (const task of tasks) { - switch (task.status) { - case "done": - statusCounts.done++; - break; - case "merged": - statusCounts.merged++; - break; - case "running": - case "initializing": - case "starting": - statusCounts.running++; - break; - case "pending": - statusCounts.pending++; - break; - case "failed": - statusCounts.failed++; - break; - default: - statusCounts.other++; - } - } - - const completedCount = statusCounts.done + statusCounts.merged; - const progressPercent = (completedCount / tasks.length) * 100; - - // Build summary parts - const parts: string[] = []; - if (completedCount > 0) parts.push(`${completedCount} done`); - if (statusCounts.running > 0) parts.push(`${statusCounts.running} running`); - if (statusCounts.pending > 0) parts.push(`${statusCounts.pending} pending`); - if (statusCounts.failed > 0) parts.push(`${statusCounts.failed} failed`); - - return ( - <div className="space-y-2"> - <h3 className="font-mono text-xs text-[#75aafc] uppercase"> - Task Progress - </h3> - - {/* Progress bar */} - <div className="h-2 bg-[rgba(117,170,252,0.1)] rounded overflow-hidden"> - <div - className="h-full bg-green-400 transition-all duration-300" - style={{ width: `${progressPercent}%` }} - /> - </div> - - {/* Summary text */} - <div className="flex items-center justify-between"> - <span className="font-mono text-xs text-[#9bc3ff]"> - {parts.join(", ")} - </span> - <span className="font-mono text-xs text-[#555]"> - {completedCount}/{tasks.length} completed - </span> - </div> - </div> - ); -} - -// Phase color mapping for badges -const phaseColors: Record<ContractPhase, string> = { - research: "bg-purple-500/20 text-purple-400 border-purple-400/30", - specify: "bg-blue-500/20 text-blue-400 border-blue-400/30", - plan: "bg-cyan-500/20 text-cyan-400 border-cyan-400/30", - execute: "bg-green-500/20 text-green-400 border-green-400/30", - review: "bg-yellow-500/20 text-yellow-400 border-yellow-400/30", -}; - -// Files tab with template creation -function FilesTab({ - files, - contractId, - contractPhase, - onSelect, - onRefresh, -}: { - files: FileSummary[]; - contractId: string; - contractPhase: ContractPhase; - onSelect: (id: string) => void; - onRefresh: () => void; -}) { - const [showTemplateModal, setShowTemplateModal] = useState(false); - const [templates, setTemplates] = useState<TemplateSummary[]>([]); - const [loadingTemplates, setLoadingTemplates] = useState(false); - const [creating, setCreating] = useState(false); - const [fileName, setFileName] = useState(""); - const [selectedTemplateId, setSelectedTemplateId] = useState<string | null>(null); - - // Load templates when modal opens - useEffect(() => { - if (showTemplateModal) { - setLoadingTemplates(true); - listTemplates(contractPhase) - .then((res) => setTemplates(res.templates)) - .catch((err) => console.error("Failed to load templates:", err)) - .finally(() => setLoadingTemplates(false)); - } - }, [showTemplateModal, contractPhase]); - - const handleCreateFromTemplate = useCallback(async () => { - if (!fileName.trim() || !selectedTemplateId) return; - - setCreating(true); - try { - // Get the full template with body - const template = await getTemplate(selectedTemplateId); - - // Create the file with contract (files must belong to contracts) - await createFile({ - contractId, - name: fileName.trim(), - description: template.description, - body: template.suggestedBody, - }); - - // Reset and close - setShowTemplateModal(false); - setFileName(""); - setSelectedTemplateId(null); - onRefresh(); - } catch (err) { - console.error("Failed to create file from template:", err); - } finally { - setCreating(false); - } - }, [fileName, selectedTemplateId, contractId, onRefresh]); - - const handleCloseModal = () => { - setShowTemplateModal(false); - setFileName(""); - setSelectedTemplateId(null); - }; - - return ( - <div className="space-y-4"> - {/* Create from template button */} - <button - onClick={() => setShowTemplateModal(true)} - className="px-3 py-1.5 font-mono text-xs text-[#9bc3ff] border border-[rgba(117,170,252,0.25)] hover:border-[#3f6fb3] transition-colors" - > - + Create from Template - </button> - - {/* Template Selection Modal */} - {showTemplateModal && ( - <div className="fixed inset-0 z-50 flex items-center justify-center bg-black/50"> - <div className="w-full max-w-lg p-6 bg-[#0a1628] border border-[rgba(117,170,252,0.3)] max-h-[80vh] flex flex-col"> - <div className="flex items-center justify-between mb-4"> - <h3 className="font-mono text-sm text-[#75aafc] uppercase"> - Create File from Template - </h3> - <span className={`px-2 py-0.5 text-[10px] font-mono uppercase border rounded ${phaseColors[contractPhase]}`}> - {contractPhase} phase - </span> - </div> - - <div className="space-y-4 flex-1 overflow-y-auto"> - {/* File name input */} - <div> - <label className="block font-mono text-xs text-[#555] uppercase mb-1"> - File Name - </label> - <input - type="text" - value={fileName} - onChange={(e) => setFileName(e.target.value)} - placeholder="e.g., Project Requirements" - className="w-full px-3 py-2 bg-[#0d1b2d] border border-[#3f6fb3] text-[#dbe7ff] font-mono text-sm focus:outline-none focus:border-[#75aafc]" - autoFocus - /> - </div> - - {/* Template selection */} - <div> - <label className="block font-mono text-xs text-[#555] uppercase mb-2"> - Select Template - </label> - {loadingTemplates ? ( - <p className="font-mono text-xs text-[#555]">Loading templates...</p> - ) : templates.length === 0 ? ( - <p className="font-mono text-xs text-[#555]">No templates available for {contractPhase} phase</p> - ) : ( - <div className="space-y-2 max-h-60 overflow-y-auto"> - {templates.map((template) => ( - <button - key={template.id} - onClick={() => setSelectedTemplateId(template.id)} - className={`w-full text-left p-3 border transition-colors ${ - selectedTemplateId === template.id - ? "border-[#75aafc] bg-[rgba(117,170,252,0.1)]" - : "border-[rgba(117,170,252,0.2)] hover:border-[rgba(117,170,252,0.4)]" - }`} - > - <div className="flex items-center justify-between mb-1"> - <span className="font-mono text-sm text-[#dbe7ff]"> - {template.name} - </span> - <span className="font-mono text-[10px] text-[#555]"> - {template.elementCount} elements - </span> - </div> - <p className="font-mono text-xs text-[#555]"> - {template.description} - </p> - </button> - ))} - </div> - )} - </div> - </div> - - <div className="flex gap-2 justify-end mt-4 pt-4 border-t border-[rgba(117,170,252,0.2)]"> - <button - onClick={handleCloseModal} - className="px-4 py-2 font-mono text-xs text-[#9bc3ff] hover:text-[#dbe7ff] transition-colors" - > - Cancel - </button> - <button - onClick={handleCreateFromTemplate} - disabled={!fileName.trim() || !selectedTemplateId || creating} - 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" - > - {creating ? "Creating..." : "Create File"} - </button> - </div> - </div> - </div> - )} - - {/* File list */} - {files.length === 0 ? ( - <p className="font-mono text-xs text-[#555]"> - No files in this contract. Create one from a template above. - </p> - ) : ( - <div className="space-y-2"> - {files.map((file) => ( - <button - key={file.id} - onClick={() => onSelect(file.id)} - className="w-full text-left p-3 border border-[rgba(117,170,252,0.2)] hover:border-[rgba(117,170,252,0.4)] transition-colors" - > - <div className="flex items-center justify-between"> - <div className="flex items-center gap-2"> - <span className="font-mono text-sm text-[#dbe7ff]"> - {file.name} - </span> - {file.contractPhase && ( - <span - className={`px-1.5 py-0.5 text-[9px] font-mono uppercase border rounded ${ - phaseColors[file.contractPhase] - }`} - title={`Added during ${file.contractPhase} phase`} - > - {file.contractPhase} - </span> - )} - </div> - <span className="font-mono text-[10px] text-[#555]"> - v{file.version} - </span> - </div> - {file.description && ( - <p className="font-mono text-xs text-[#555] mt-1 truncate"> - {file.description} - </p> - )} - </button> - ))} - </div> - )} - </div> - ); -} - -// Tasks tab - now using TaskTree for supervisor view -function TasksTab({ - tasks, - repositories, - supervisorTaskId, - contractType, - onSelect, - onCreate, -}: { - tasks: TaskSummary[]; - repositories: ContractRepository[]; - supervisorTaskId: string | null; - contractType: string; - onSelect: (id: string) => void; - onCreate: (name: string, plan: string, repositoryUrl?: string) => void; -}) { - const [isCreating, setIsCreating] = useState(false); - const [taskName, setTaskName] = useState(""); - const [taskPlan, setTaskPlan] = useState("# Plan\n\nDescribe what this task should accomplish..."); - - // Find primary repository or first ready one - const readyRepos = repositories.filter((r) => r.status === "ready"); - const primaryRepo = readyRepos.find((r) => r.isPrimary) || readyRepos[0]; - const [selectedRepoId, setSelectedRepoId] = useState<string>(primaryRepo?.id || ""); - - const handleCreate = () => { - if (!taskName.trim()) return; - const selectedRepo = repositories.find((r) => r.id === selectedRepoId); - // Get the URL - for remote repos it's repositoryUrl, for local it's the local path - const repoUrl = selectedRepo?.repositoryUrl || selectedRepo?.localPath; - onCreate(taskName.trim(), taskPlan, repoUrl || undefined); - setIsCreating(false); - setTaskName(""); - setTaskPlan("# Plan\n\nDescribe what this task should accomplish..."); - setSelectedRepoId(primaryRepo?.id || ""); - }; - - const handleCancel = () => { - setIsCreating(false); - setTaskName(""); - setTaskPlan("# Plan\n\nDescribe what this task should accomplish..."); - setSelectedRepoId(primaryRepo?.id || ""); - }; - - return ( - <div className="space-y-4"> - {/* TaskTree with supervisor view */} - <TaskTree - tasks={tasks} - supervisorTaskId={supervisorTaskId} - onSelect={onSelect} - /> - - {/* Manual task creation - show for task-type contracts or contracts without supervisors */} - {(contractType === "task" || !supervisorTaskId) && ( - <> - <div className="border-t border-[rgba(117,170,252,0.2)] pt-4"> - <button - onClick={() => setIsCreating(true)} - className="px-3 py-1.5 font-mono text-xs text-[#9bc3ff] border border-[rgba(117,170,252,0.25)] hover:border-[#3f6fb3] transition-colors" - > - + Create Task Manually - </button> - </div> - - {/* Create Task Modal */} - {isCreating && ( - <div className="fixed inset-0 z-50 flex items-center justify-center bg-black/50"> - <div className="w-full max-w-lg p-6 bg-[#0a1628] border border-[rgba(117,170,252,0.3)]"> - <h3 className="font-mono text-sm text-[#75aafc] uppercase mb-4"> - Create Task - </h3> - <div className="space-y-4"> - <div> - <label className="block font-mono text-xs text-[#555] uppercase mb-1"> - Name - </label> - <input - type="text" - value={taskName} - onChange={(e) => setTaskName(e.target.value)} - placeholder="Task name" - className="w-full px-3 py-2 bg-[#0d1b2d] border border-[#3f6fb3] text-[#dbe7ff] font-mono text-sm focus:outline-none focus:border-[#75aafc]" - autoFocus - /> - </div> - - {/* Repository selection */} - {readyRepos.length > 0 && ( - <div> - <label className="block font-mono text-xs text-[#555] uppercase mb-1"> - Repository - </label> - <select - value={selectedRepoId} - onChange={(e) => setSelectedRepoId(e.target.value)} - className="w-full px-3 py-2 bg-[#0d1b2d] border border-[#3f6fb3] text-[#dbe7ff] font-mono text-sm focus:outline-none focus:border-[#75aafc]" - > - <option value="">No repository</option> - {readyRepos.map((repo) => ( - <option key={repo.id} value={repo.id}> - {repo.name} - {repo.isPrimary ? " (Primary)" : ""} - {" - "} - {repo.sourceType} - </option> - ))} - </select> - </div> - )} - - <div> - <label className="block font-mono text-xs text-[#555] uppercase mb-1"> - Plan - </label> - <textarea - value={taskPlan} - onChange={(e) => setTaskPlan(e.target.value)} - rows={6} - className="w-full px-3 py-2 bg-[#0d1b2d] border border-[#3f6fb3] text-[#dbe7ff] font-mono text-sm focus:outline-none focus:border-[#75aafc] resize-none" - /> - </div> - - <div className="flex gap-2 justify-end"> - <button - onClick={handleCancel} - className="px-4 py-2 font-mono text-xs text-[#9bc3ff] hover:text-[#dbe7ff] transition-colors" - > - Cancel - </button> - <button - onClick={handleCreate} - disabled={!taskName.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> - ); -} |
