import { useState } from "react"; import { open } from "@tauri-apps/plugin-dialog"; import type { Project, AuthMode, BedrockConfig, BedrockAuthMethod } from "../../lib/types"; import { useProjects } from "../../hooks/useProjects"; import { useTerminal } from "../../hooks/useTerminal"; import { useAppState } from "../../store/appState"; interface Props { project: Project; } export default function ProjectCard({ project }: Props) { const { selectedProjectId, setSelectedProject } = useAppState(); const { start, stop, rebuild, remove, update } = useProjects(); const { open: openTerminal } = useTerminal(); const [loading, setLoading] = useState(false); const [error, setError] = useState(null); const [showConfig, setShowConfig] = useState(false); const isSelected = selectedProjectId === project.id; const isStopped = project.status === "stopped" || project.status === "error"; const handleStart = async () => { setLoading(true); setError(null); try { await start(project.id); } catch (e) { setError(String(e)); } finally { setLoading(false); } }; const handleStop = async () => { setLoading(true); setError(null); try { await stop(project.id); } catch (e) { setError(String(e)); } finally { setLoading(false); } }; const handleOpenTerminal = async () => { try { await openTerminal(project.id, project.name); } catch (e) { setError(String(e)); } }; const defaultBedrockConfig: BedrockConfig = { auth_method: "static_credentials", aws_region: "us-east-1", aws_access_key_id: null, aws_secret_access_key: null, aws_session_token: null, aws_profile: null, aws_bearer_token: null, model_id: null, disable_prompt_caching: false, }; const handleAuthModeChange = async (mode: AuthMode) => { try { const updates: Partial = { auth_mode: mode }; if (mode === "bedrock" && !project.bedrock_config) { updates.bedrock_config = defaultBedrockConfig; } await update({ ...project, ...updates }); } catch (e) { setError(String(e)); } }; const updateBedrockConfig = async (patch: Partial) => { try { const current = project.bedrock_config ?? defaultBedrockConfig; await update({ ...project, bedrock_config: { ...current, ...patch } }); } catch {} }; const handleBrowseSSH = async () => { const selected = await open({ directory: true, multiple: false }); if (selected) { try { await update({ ...project, ssh_key_path: selected as string }); } catch (e) { setError(String(e)); } } }; const statusColor = { stopped: "bg-[var(--text-secondary)]", starting: "bg-[var(--warning)]", running: "bg-[var(--success)]", stopping: "bg-[var(--warning)]", error: "bg-[var(--error)]", }[project.status]; return (
setSelectedProject(project.id)} className={`px-3 py-2 rounded cursor-pointer transition-colors ${ isSelected ? "bg-[var(--bg-tertiary)]" : "hover:bg-[var(--bg-tertiary)]" }`} >
{project.name}
{project.path}
{isSelected && (
{/* Auth mode selector */}
Auth:
{/* Action buttons */}
{isStopped ? ( <> { setLoading(true); try { await rebuild(project.id); } catch (e) { setError(String(e)); } setLoading(false); }} disabled={loading} label="Reset" /> ) : project.status === "running" ? ( <> ) : ( {project.status}... )} { e?.stopPropagation?.(); setShowConfig(!showConfig); }} disabled={false} label={showConfig ? "Hide" : "Config"} /> { if (confirm(`Remove project "${project.name}"?`)) { await remove(project.id); } }} disabled={loading} label="Remove" danger />
{/* Config panel */} {showConfig && (
e.stopPropagation()}> {/* SSH Key */}
{ try { await update({ ...project, ssh_key_path: e.target.value || null }); } catch {} }} placeholder="~/.ssh" disabled={!isStopped} className="flex-1 px-2 py-1 bg-[var(--bg-primary)] border border-[var(--border-color)] rounded text-xs text-[var(--text-primary)] focus:outline-none focus:border-[var(--accent)] disabled:opacity-50" />
{/* Git Name */}
{ try { await update({ ...project, git_user_name: e.target.value || null }); } catch {} }} placeholder="Your Name" disabled={!isStopped} className="w-full px-2 py-1 bg-[var(--bg-primary)] border border-[var(--border-color)] rounded text-xs text-[var(--text-primary)] focus:outline-none focus:border-[var(--accent)] disabled:opacity-50" />
{/* Git Email */}
{ try { await update({ ...project, git_user_email: e.target.value || null }); } catch {} }} placeholder="you@example.com" disabled={!isStopped} className="w-full px-2 py-1 bg-[var(--bg-primary)] border border-[var(--border-color)] rounded text-xs text-[var(--text-primary)] focus:outline-none focus:border-[var(--accent)] disabled:opacity-50" />
{/* Git Token (HTTPS) */}
{ try { await update({ ...project, git_token: e.target.value || null }); } catch {} }} placeholder="ghp_..." disabled={!isStopped} className="w-full px-2 py-1 bg-[var(--bg-primary)] border border-[var(--border-color)] rounded text-xs text-[var(--text-primary)] focus:outline-none focus:border-[var(--accent)] disabled:opacity-50" />
{/* Docker access toggle */}
{/* Bedrock config */} {project.auth_mode === "bedrock" && (() => { const bc = project.bedrock_config ?? defaultBedrockConfig; const inputCls = "w-full px-2 py-1 bg-[var(--bg-primary)] border border-[var(--border-color)] rounded text-xs text-[var(--text-primary)] focus:outline-none focus:border-[var(--accent)] disabled:opacity-50"; return (
{/* Sub-method selector */}
Method: {(["static_credentials", "profile", "bearer_token"] as BedrockAuthMethod[]).map((m) => ( ))}
{/* AWS Region (always shown) */}
updateBedrockConfig({ aws_region: e.target.value })} placeholder="us-east-1" disabled={!isStopped} className={inputCls} />
{/* Static credentials fields */} {bc.auth_method === "static_credentials" && ( <>
updateBedrockConfig({ aws_access_key_id: e.target.value || null })} placeholder="AKIA..." disabled={!isStopped} className={inputCls} />
updateBedrockConfig({ aws_secret_access_key: e.target.value || null })} disabled={!isStopped} className={inputCls} />
updateBedrockConfig({ aws_session_token: e.target.value || null })} disabled={!isStopped} className={inputCls} />
)} {/* Profile field */} {bc.auth_method === "profile" && (
updateBedrockConfig({ aws_profile: e.target.value || null })} placeholder="default" disabled={!isStopped} className={inputCls} />
)} {/* Bearer token field */} {bc.auth_method === "bearer_token" && (
updateBedrockConfig({ aws_bearer_token: e.target.value || null })} disabled={!isStopped} className={inputCls} />
)} {/* Model override */}
updateBedrockConfig({ model_id: e.target.value || null })} placeholder="anthropic.claude-sonnet-4-20250514-v1:0" disabled={!isStopped} className={inputCls} />
); })()}
)}
)} {error && (
{error}
)}
); } function ActionButton({ onClick, disabled, label, accent, danger, }: { onClick: (e?: React.MouseEvent) => void; disabled: boolean; label: string; accent?: boolean; danger?: boolean; }) { let color = "text-[var(--text-secondary)] hover:text-[var(--text-primary)]"; if (accent) color = "text-[var(--accent)] hover:text-[var(--accent-hover)]"; if (danger) color = "text-[var(--error)] hover:text-[var(--error)]"; return ( ); }