import { Input } from '@/components/ui/input'; import { Label } from '@/components/ui/label'; import { Checkbox } from '@/components/ui/checkbox'; import { cn } from '@/lib/utils'; export type ApplyMode = 'review' | 'auto-write' | 'auto-deploy'; /** * Mirror of the backend's env-path default (see `/api/stacks/from-git` and * the git-source PUT handler): if the user ticks "Sync .env" without * specifying an explicit path, the service reads `/.env` * alongside the compose file. Surfacing this in the form saves the user * a round-trip to figure out which directory the `.env` will come from. */ function computeDefaultEnvPath(composePath: string): string { const normalized = composePath.trim().replace(/\\/g, '/').replace(/^\.\//, ''); const slash = normalized.lastIndexOf('/'); if (slash === -1) return '.env'; return `${normalized.slice(0, slash)}/.env`; } export interface GitSourceFieldsState { repoUrl: string; branch: string; composePath: string; syncEnv: boolean; authType: 'none' | 'token'; token: string; /** When editing an existing source, the server tells us whether a token is already stored. */ hasStoredToken: boolean; applyMode: ApplyMode; } export interface GitSourceFieldsProps extends GitSourceFieldsState { disabled?: boolean; /** 'edit' for the per-stack panel, 'create' for the new-stack dialog. Changes apply-mode copy. */ variant: 'edit' | 'create'; onRepoUrlChange: (value: string) => void; onBranchChange: (value: string) => void; onComposePathChange: (value: string) => void; onSyncEnvChange: (value: boolean) => void; onAuthTypeChange: (value: 'none' | 'token') => void; onTokenChange: (value: string) => void; onApplyModeChange: (value: ApplyMode) => void; } const APPLY_MODE_COPY: Record<'edit' | 'create', Record> = { edit: { 'review': { title: 'Review only', description: 'Webhook fetches and flags a pending diff. You apply manually.' }, 'auto-write': { title: 'Auto-write files', description: 'Webhook writes to disk. You deploy manually.' }, 'auto-deploy': { title: 'Auto-deploy', description: 'Webhook writes and deploys in one step.' }, }, create: { 'review': { title: 'Review only', description: 'Future webhook pulls surface a diff you apply manually.' }, 'auto-write': { title: 'Auto-write files', description: 'Future webhook pulls write to disk. You deploy manually.' }, 'auto-deploy': { title: 'Auto-deploy', description: 'Future webhook pulls write and redeploy automatically.' }, }, }; export function GitSourceFields({ repoUrl, branch, composePath, syncEnv, authType, token, hasStoredToken, applyMode, disabled = false, variant, onRepoUrlChange, onBranchChange, onComposePathChange, onSyncEnvChange, onAuthTypeChange, onTokenChange, onApplyModeChange, }: GitSourceFieldsProps) { const copy = APPLY_MODE_COPY[variant]; const radioOption = (mode: ApplyMode) => ( ); return (
onRepoUrlChange(e.target.value)} disabled={disabled} className="font-mono text-xs" />
onBranchChange(e.target.value)} disabled={disabled} className="font-mono text-xs" />
onComposePathChange(e.target.value)} disabled={disabled} className="font-mono text-xs" />
onSyncEnvChange(c === true)} disabled={disabled} />
{syncEnv && composePath.trim() !== '' && (

Will read{' '} {computeDefaultEnvPath(composePath)} {' '} from the repository.

)}
{authType === 'token' && (
onTokenChange(e.target.value)} disabled={disabled} className="font-mono text-xs" autoComplete="off" />

Token is encrypted at rest and never returned from the API.

)}
{radioOption('review')} {radioOption('auto-write')} {radioOption('auto-deploy')}
); }