1+ import React , { useState } from "react" ;
2+ import { AlertTriangle , CheckCircle , XCircle , Loader2 } from "lucide-react" ;
3+ import { Button } from "@/components/ui/button" ;
4+ import { Card , CardContent , CardDescription , CardHeader , CardTitle } from "@/components/ui/card" ;
5+ import { LoadingBar } from "@/components/ui/LoadingBar" ;
6+ import { InstallationProgress } from "@/services/installationCoordinator" ;
7+
8+ interface DependencyPermissionDialogProps {
9+ isOpen : boolean ;
10+ missingDependencies : string [ ] ;
11+ onInstall : ( onProgress : ( progress : InstallationProgress ) => void ) => Promise < void > ;
12+ onSkip : ( ) => void ;
13+ onCancel : ( ) => void ;
14+ }
15+
16+ export function DependencyPermissionDialog ( {
17+ isOpen,
18+ missingDependencies,
19+ onInstall,
20+ onSkip,
21+ onCancel,
22+ } : DependencyPermissionDialogProps ) {
23+ const [ isInstalling , setIsInstalling ] = useState ( false ) ;
24+ const [ installationProgress , setInstallationProgress ] = useState < InstallationProgress [ ] > ( [ ] ) ;
25+ const [ installationComplete , setInstallationComplete ] = useState ( false ) ;
26+
27+ if ( ! isOpen ) return null ;
28+
29+ const handleInstall = async ( ) => {
30+ setIsInstalling ( true ) ;
31+ setInstallationProgress ( [ ] ) ;
32+
33+ const progressCallback = ( progress : InstallationProgress ) => {
34+ setInstallationProgress ( prev => {
35+ const existingIndex = prev . findIndex ( p => p . dependency === progress . dependency ) ;
36+ if ( existingIndex >= 0 ) {
37+ // Update existing progress
38+ const updated = [ ...prev ] ;
39+ updated [ existingIndex ] = progress ;
40+ return updated ;
41+ } else {
42+ // Add new progress
43+ return [ ...prev , progress ] ;
44+ }
45+ } ) ;
46+ } ;
47+
48+ try {
49+ await onInstall ( progressCallback ) ;
50+ setInstallationComplete ( true ) ;
51+ } catch ( error ) {
52+ console . error ( "Installation failed:" , error ) ;
53+ } finally {
54+ setIsInstalling ( false ) ;
55+ }
56+ } ;
57+
58+ const getProgressIcon = ( status : InstallationProgress [ "status" ] ) => {
59+ switch ( status ) {
60+ case "installing" :
61+ return < Loader2 className = "h-4 w-4 animate-spin text-blue-500" /> ;
62+ case "success" :
63+ return < CheckCircle className = "h-4 w-4 text-green-500" /> ;
64+ case "error" :
65+ return < XCircle className = "h-4 w-4 text-red-500" /> ;
66+ default :
67+ return null ;
68+ }
69+ } ;
70+
71+ const getProgressColor = ( status : InstallationProgress [ "status" ] ) => {
72+ switch ( status ) {
73+ case "installing" :
74+ return "text-blue-600" ;
75+ case "success" :
76+ return "text-green-600" ;
77+ case "error" :
78+ return "text-red-600" ;
79+ default :
80+ return "text-gray-600" ;
81+ }
82+ } ;
83+
84+ const completedCount = installationProgress . filter ( p => p . status !== "installing" ) . length ;
85+ const totalCount = missingDependencies . length ;
86+ const progressPercentage = totalCount > 0 ? ( completedCount / totalCount ) * 100 : 0 ;
87+
88+ return (
89+ < div className = "fixed inset-0 z-50 overflow-y-auto" >
90+ < div className = "flex min-h-screen items-center justify-center p-4 text-center sm:p-0" >
91+ < div
92+ className = "fixed inset-0 bg-gray-500 bg-opacity-75 transition-opacity"
93+ onClick = { onCancel }
94+ />
95+
96+ < div className = "relative transform overflow-hidden rounded-lg bg-white dark:bg-gray-800 text-left shadow-xl transition-all sm:my-8 sm:w-full sm:max-w-2xl" >
97+ < div className = "bg-white dark:bg-gray-800 px-4 pb-4 pt-5 sm:p-6" >
98+ < div className = "flex items-start" >
99+ < div className = "mx-auto flex h-12 w-12 flex-shrink-0 items-center justify-center rounded-full bg-yellow-100 sm:mx-0 sm:h-10 sm:w-10" >
100+ < AlertTriangle className = "h-6 w-6 text-yellow-600" />
101+ </ div >
102+ < div className = "mt-3 text-center sm:ml-4 sm:mt-0 sm:text-left flex-1" >
103+ < h3 className = "text-lg font-medium leading-6 text-gray-900 dark:text-white" >
104+ Missing System Dependencies
105+ </ h3 >
106+ < div className = "mt-2" >
107+ < p className = "text-sm text-gray-500 dark:text-gray-400" >
108+ AliFullStack requires the following dependencies to run backend servers and manage Node.js packages:
109+ </ p >
110+ < ul className = "mt-2 list-disc list-inside text-sm text-gray-600 dark:text-gray-300" >
111+ { missingDependencies . map ( dep => (
112+ < li key = { dep } >
113+ < strong > { dep } </ strong > - { dep === "uvicorn" ? "FastAPI/ASGI server for Python backends" : "Node.js package runner for frontend tools" }
114+ </ li >
115+ ) ) }
116+ </ ul >
117+ < p className = "mt-2 text-sm text-gray-500 dark:text-gray-400" >
118+ Would you like AliFullStack to install these dependencies automatically?
119+ </ p >
120+ </ div >
121+ </ div >
122+ </ div >
123+
124+ { /* Installation Progress */ }
125+ { isInstalling && (
126+ < div className = "mt-6" >
127+ < div className = "mb-4" >
128+ < div className = "flex justify-between text-sm text-gray-600 dark:text-gray-400 mb-2" >
129+ < span > Installing dependencies...</ span >
130+ < span > { completedCount } /{ totalCount } </ span >
131+ </ div >
132+ < LoadingBar isVisible = { true } />
133+ </ div >
134+
135+ < div className = "space-y-2" >
136+ { installationProgress . map ( progress => (
137+ < div key = { progress . dependency } className = "flex items-center space-x-2 text-sm" >
138+ { getProgressIcon ( progress . status ) }
139+ < span className = "flex-1" > { progress . message } </ span >
140+ { progress . error && (
141+ < span className = "text-xs text-red-500" > { progress . error } </ span >
142+ ) }
143+ </ div >
144+ ) ) }
145+ </ div >
146+ </ div >
147+ ) }
148+
149+ { /* Installation Complete */ }
150+ { installationComplete && (
151+ < div className = "mt-6" >
152+ < Card >
153+ < CardHeader className = "pb-3" >
154+ < CardTitle className = "text-sm flex items-center space-x-2" >
155+ < CheckCircle className = "h-4 w-4 text-green-500" />
156+ < span > Installation Complete</ span >
157+ </ CardTitle >
158+ </ CardHeader >
159+ < CardContent >
160+ < p className = "text-sm text-gray-600 dark:text-gray-400" >
161+ All dependencies have been installed successfully. Your app will now restart automatically.
162+ </ p >
163+ </ CardContent >
164+ </ Card >
165+ </ div >
166+ ) }
167+ </ div >
168+
169+ < div className = "bg-gray-50 dark:bg-gray-700 px-4 py-3 sm:flex sm:flex-row-reverse sm:px-6" >
170+ { ! installationComplete ? (
171+ < >
172+ < Button
173+ type = "button"
174+ className = "inline-flex w-full justify-center rounded-md border border-transparent px-4 py-2 text-base font-medium text-white shadow-sm bg-blue-600 hover:bg-blue-700 focus:ring-blue-500 sm:ml-3 sm:w-auto sm:text-sm"
175+ onClick = { handleInstall }
176+ disabled = { isInstalling }
177+ >
178+ { isInstalling ? (
179+ < >
180+ < Loader2 className = "mr-2 h-4 w-4 animate-spin" />
181+ Installing...
182+ </ >
183+ ) : (
184+ "Install Dependencies"
185+ ) }
186+ </ Button >
187+ < Button
188+ type = "button"
189+ variant = "outline"
190+ className = "mt-3 inline-flex w-full justify-center rounded-md px-4 py-2 text-base font-medium shadow-sm sm:mt-0 sm:w-auto sm:text-sm"
191+ onClick = { onSkip }
192+ disabled = { isInstalling }
193+ >
194+ Skip for Now
195+ </ Button >
196+ </ >
197+ ) : (
198+ < Button
199+ type = "button"
200+ className = "inline-flex w-full justify-center rounded-md border border-transparent px-4 py-2 text-base font-medium text-white shadow-sm bg-green-600 hover:bg-green-700 focus:ring-green-500 sm:ml-3 sm:w-auto sm:text-sm"
201+ onClick = { onSkip }
202+ >
203+ Continue
204+ </ Button >
205+ ) }
206+ < Button
207+ type = "button"
208+ variant = "ghost"
209+ className = "mt-3 inline-flex w-full justify-center rounded-md px-4 py-2 text-base font-medium shadow-sm sm:mt-0 sm:w-auto sm:text-sm"
210+ onClick = { onCancel }
211+ disabled = { isInstalling }
212+ >
213+ Cancel
214+ </ Button >
215+ </ div >
216+ </ div >
217+ </ div >
218+ </ div >
219+ ) ;
220+ }
0 commit comments