import { useEffect, useRef, useState } from 'react'; import { Button } from '@renderer/components/ui/button'; import { useStore } from '@renderer/store'; import { CheckCircle2, X } from 'lucide-react'; import { useShallow } from 'zustand/react/shallow'; import { ProvisioningProgressBlock } from './ProvisioningProgressBlock'; import { STEP_ORDER } from './provisioningSteps'; import type { ProvisioningStep } from './provisioningSteps'; import type { TeamProvisioningProgress } from '@shared/types'; interface TeamProvisioningBannerProps { teamName: string; } function findProgressForTeam( runs: Record, teamName: string ): TeamProvisioningProgress | null { const entries = Object.values(runs); const matching = entries .filter((r) => r.teamName === teamName) .sort((a, b) => b.updatedAt.localeCompare(a.updatedAt)); return matching[0] ?? null; } export const TeamProvisioningBanner = ({ teamName, }: TeamProvisioningBannerProps): React.JSX.Element | null => { const { provisioningRuns, cancelProvisioning } = useStore( useShallow((s) => ({ provisioningRuns: s.provisioningRuns, cancelProvisioning: s.cancelProvisioning, })) ); const progress = findProgressForTeam(provisioningRuns, teamName); const [dismissed, setDismissed] = useState(false); const prevRunIdRef = useRef(progress?.runId); if (prevRunIdRef.current !== progress?.runId) { prevRunIdRef.current = progress?.runId; if (dismissed) { setDismissed(false); } } // NOTE: we intentionally do NOT auto-dismiss "ready" banners. // Users frequently need to inspect launch output after fast stop→start cycles, // and auto-dismiss can make it look like no progress/logs were produced. if (!progress || dismissed) { return null; } if (progress.state === 'cancelled') { return null; } const isReady = progress.state === 'ready'; const isFailed = progress.state === 'failed'; const isDisconnected = progress.state === 'disconnected'; const isActive = progress.state === 'validating' || progress.state === 'spawning' || progress.state === 'monitoring' || progress.state === 'verifying'; const canCancel = progress.state === 'spawning' || progress.state === 'monitoring' || progress.state === 'verifying'; const progressStepIndex = STEP_ORDER.indexOf(progress.state as ProvisioningStep); if (isFailed) { return (

{progress.message}

= 0 ? progressStepIndex : -1} startedAt={progress.startedAt} pid={progress.pid} cliLogsTail={progress.cliLogsTail} assistantOutput={progress.assistantOutput} defaultLiveOutputOpen onCancel={null} />
); } if (isDisconnected) { return (

Team offline

); } if (isReady) { return (

Team launched — process alive

= 0 ? progressStepIndex : -1} startedAt={progress.startedAt} pid={progress.pid} cliLogsTail={progress.cliLogsTail} assistantOutput={progress.assistantOutput} defaultLiveOutputOpen={false} onCancel={null} />
); } if (isActive) { return (
= 0 ? progressStepIndex : -1} loading startedAt={progress.startedAt} pid={progress.pid} cliLogsTail={progress.cliLogsTail} assistantOutput={progress.assistantOutput} defaultLiveOutputOpen onCancel={ canCancel ? () => { void cancelProvisioning(progress.runId); } : null } />
); } return null; };