'use client'; import { useEffect, useMemo } from 'react'; import { usePathname } from 'next/navigation'; import { useQuery, useQueries, keepPreviousData } from '@tanstack/react-query'; import { useT } from '@/i18n/I18nProvider'; import api from '@/lib/api'; import type { Application } from '@/types'; import { useAuthStore } from '@/lib/store'; import { useDeployProgressStore } from '@/lib/deploy-progress-store'; import { getAppsInProgress, isActiveBuildProgress } from '@/lib/deployment-progress'; import { filterApplications, filterManagedServices } from '@/lib/product-type'; import { BuildProgressModal, type BuildProgress } from '@/components/build-progress-modal'; import { DeploymentProgressBar } from '@/components/deployment-progress-bar'; function stubAppFromTrack(appId: string, appName: string): Application { return { id: appId, name: appName, runtime: 'nodejs', databaseType: 'none', cpuRequest: '', cpuLimit: '', memoryRequest: '', memoryLimit: '', replicas: 1, port: 3000, userId: '', createdAt: new Date().toISOString(), updatedAt: new Date().toISOString(), deployments: [ { id: `pending-${appId}`, status: 'building', imageTag: '', applicationId: appId, triggeredBy: 'user', createdAt: new Date().toISOString(), }, ], } as Application; } export function DeploymentProgressManager() { const t = useT(); const isAuthenticated = useAuthStore((s) => s.isAuthenticated); const pathname = usePathname(); const { minimized, focusedAppId, tracked, minimize, expand, stopTracking } = useDeployProgressStore(); const pollAppsList = (list: Application[] | undefined) => { const pending = useDeployProgressStore.getState().tracked.length; return getAppsInProgress(list ?? []).length > 0 || pending > 0 ? 3000 : false; }; const { data: appsList = [] } = useQuery({ queryKey: ['applications', 'application'], queryFn: () => api.get('/applications', { params: { productType: 'application' } }).then((r) => r.data), enabled: isAuthenticated, refetchInterval: (query) => pollAppsList(query.state.data), }); const { data: servicesList = [] } = useQuery({ queryKey: ['applications', 'managed'], queryFn: () => api.get('/applications', { params: { productType: 'managed' } }).then((r) => r.data), enabled: isAuthenticated, refetchInterval: (query) => pollAppsList(query.state.data), }); const allResources = useMemo( () => [...filterApplications(appsList), ...filterManagedServices(servicesList)], [appsList, servicesList], ); const deployingFromList = useMemo(() => getAppsInProgress(allResources), [allResources]); const deployingApps = useMemo(() => { const byId = new Map(); for (const app of deployingFromList) { byId.set(app.id, app); } for (const t of tracked) { if (!byId.has(t.appId)) { const fromList = allResources.find((a) => a.id === t.appId); byId.set(t.appId, fromList ?? stubAppFromTrack(t.appId, t.appName)); } } return Array.from(byId.values()); }, [deployingFromList, tracked, allResources]); const progressQueries = useQueries({ queries: deployingApps.map((app) => ({ queryKey: ['build-progress', app.id], queryFn: () => api .get<{ progress: BuildProgress | null }>(`/deployments/applications/${app.id}/build-progress`) .then((r) => r.data.progress), refetchInterval: 1500, placeholderData: keepPreviousData, enabled: isAuthenticated, })), }); const activeItems = useMemo(() => { return deployingApps.map((app, i) => ({ app, progress: progressQueries[i]?.data ?? null, })); }, [deployingApps, progressQueries]); const routeAppId = pathname.match(/\/dashboard\/apps\/([^/]+)/)?.[1] ?? pathname.match(/\/dashboard\/services\/([^/]+)/)?.[1]; useEffect(() => { if (deployingApps.length === 0) { useDeployProgressStore.setState({ minimized: false, focusedAppId: null, tracked: [] }); } }, [deployingApps.length]); useEffect(() => { if (activeItems.length === 0) return; const { focusedAppId: current } = useDeployProgressStore.getState(); const ids = new Set(activeItems.map((item) => item.app.id)); if (current && ids.has(current)) return; const preferred = (routeAppId && ids.has(routeAppId) ? routeAppId : null) ?? activeItems[0].app.id; useDeployProgressStore.setState({ focusedAppId: preferred }); }, [activeItems, routeAppId]); useEffect(() => { for (const { app, progress } of activeItems) { if (!progress) continue; if (progress.phase === 'done' || progress.phase === 'failed' || progress.phase === 'cancelled') { const stillBuilding = deployingFromList.some((a) => a.id === app.id); if (!stillBuilding) { stopTracking(app.id); } } } }, [activeItems, deployingFromList, stopTracking]); const focusedItem = activeItems.find((item) => item.app.id === focusedAppId) ?? activeItems[0]; const focusedProgress = focusedItem?.progress ?? ({ phase: 'building', percent: 0, message: t.components.startingDeployment } satisfies BuildProgress); const showModal = !minimized && !!focusedItem && (!focusedItem.progress || isActiveBuildProgress(focusedItem.progress) || focusedItem.progress.phase === 'done'); const handleMinimize = () => { if (focusedItem) minimize(focusedItem.app.id); }; const handleExpand = (appId: string) => { expand(appId); }; if (deployingApps.length === 0) return null; return ( <> {minimized && (
)} {showModal && ( )} ); }