@@ -12,30 +12,31 @@ import { Skeleton } from '@/components/ui/skeleton';
1212import { Alert , AlertDescription , AlertTitle } from '@/components/ui/alert' ;
1313import { Table , TableBody , TableCell , TableHead , TableHeader , TableRow } from '@/components/ui/table' ;
1414import { Dialog , DialogContent , DialogHeader , DialogTitle , DialogDescription , DialogFooter , DialogTrigger , DialogClose } from "@/components/ui/dialog" ;
15- import { AlertDialog , AlertDialogAction , AlertDialogCancel , AlertDialogContent , AlertDialogDescription as UIAlertDialogDescription , AlertDialogFooter , AlertDialogHeader , AlertDialogTitle , AlertDialogTrigger } from "@/components/ui/alert-dialog" ;
15+ import { AlertDialog , AlertDialogAction , AlertDialogCancel , AlertDialogContent , AlertDialogDescription as UIAlertDialogDescription , AlertDialogFooter , AlertDialogHeader , AlertDialogTitle , AlertDialogTrigger } from "@/components/ui/alert-dialog" ; // Renamed to avoid conflict
1616import { Textarea } from '@/components/ui/textarea' ;
1717import { Input } from '@/components/ui/input' ;
1818import { Label } from '@/components/ui/label' ;
1919import { ArrowLeft , Folder , FileText , FileCode , Loader2 , AlertTriangle , Home , ChevronRight , ExternalLink , Image as ImageIcon , Download , Edit , Save , UploadCloud , FolderPlus , FilePlus , Trash2 , RefreshCw , FileEdit , Sparkles } from 'lucide-react' ;
20- import {
21- getRepoContentsAction ,
22- getFileContentAction ,
23- fetchProjectAction ,
20+ import {
21+ getRepoContentsAction ,
22+ getFileContentAction ,
23+ fetchProjectAction ,
2424 saveFileContentAction ,
2525 createGithubFileAction ,
2626 createGithubFolderAction ,
2727 deleteGithubFileAction ,
2828 generateProjectFilesWithAIAction ,
2929 type GenerateProjectFilesAIFormState ,
30- editFileWithAIAction , // Added new action
30+ editFileWithAIAction ,
31+ type EditFileContentAIOutput ,
3132} from '@/app/(app)/projects/[id]/actions' ;
3233import type { GithubRepoContentItem , Project } from '@/types' ;
3334import ReactMarkdown from 'react-markdown' ;
3435import remarkGfm from 'remark-gfm' ;
3536import { Badge } from '@/components/ui/badge' ;
3637import NextImage from 'next/image' ;
37- import { useForm } from 'react-hook-form' ;
38- import { useActionState , startTransition } from 'react' ; // Corrected import
38+ import { useForm } from 'react-hook-form' ;
39+ import { useActionState , startTransition } from 'react' ;
3940import { zodResolver } from '@hookform/resolvers/zod' ;
4041import * as z from 'zod' ;
4142
@@ -78,14 +79,14 @@ function FileExplorerContent() {
7879 const { toast } = useToast ( ) ;
7980
8081 const projectUuid = params . id as string ;
81-
82+
8283 const filePathArray = useMemo ( ( ) => ( params . path || [ ] ) as string [ ] , [ params . path ] ) ;
8384 const currentPath = useMemo ( ( ) => filePathArray . join ( '/' ) , [ filePathArray ] ) ;
8485
8586 const [ project , setProject ] = useState < Project | null > ( null ) ;
8687 const [ contents , setContents ] = useState < GithubRepoContentItem [ ] > ( [ ] ) ;
8788 const [ fileData , setFileData ] = useState < { name : string ; path : string ; content : string ; type : 'md' | 'image' | 'html' | 'text' | 'other' ; downloadUrl ?: string | null , encoding ?: string , sha : string } | null > ( null ) ;
88-
89+
8990 const [ isLoadingProject , setIsLoadingProject ] = useState ( true ) ;
9091 const [ isLoadingPathContent , setIsLoadingPathContent ] = useState ( true ) ;
9192 const [ error , setError ] = useState < string | null > ( null ) ;
@@ -114,19 +115,20 @@ function FileExplorerContent() {
114115 const newFileForm = useForm < NewFileFormValues > ( { resolver : zodResolver ( newFileFormSchema ) , defaultValues : { fileName : '' , initialContent : '' } } ) ;
115116 const newFolderForm = useForm < NewFolderFormValues > ( { resolver : zodResolver ( newFolderFormSchema ) , defaultValues : { folderName : '' } } ) ;
116117
118+
117119 const loadContent = useCallback ( async ( pathToLoad : string ) => {
118120 if ( ! project || ! project . githubRepoName || authLoading || isLoadingProject ) {
119121 return ;
120122 }
121123 setIsLoadingPathContent ( true ) ;
122124 setError ( null ) ;
123- setFileData ( null ) ;
124- setIsViewingFile ( false ) ;
125+ setFileData ( null ) ;
126+ setIsViewingFile ( false ) ;
125127
126128 try {
127129 const extension = pathToLoad . includes ( '.' ) ? getFileExtension ( pathToLoad . split ( '/' ) . pop ( ) ! ) : null ;
128- const isFileCandidate = ! ! extension && ! pathToLoad . endsWith ( '/' ) ;
129-
130+ const isFileCandidate = ! ! extension && ! pathToLoad . endsWith ( '/' ) ;
131+
130132 if ( isFileCandidate ) {
131133 const fetchedFileData = await getFileContentAction ( projectUuid , pathToLoad ) ;
132134 if ( 'content' in fetchedFileData && fetchedFileData . sha ) {
@@ -135,7 +137,7 @@ function FileExplorerContent() {
135137 else if ( IMAGE_EXTENSIONS . includes ( `.${ extension } ` ) ) fileDisplayType = 'image' ;
136138 else if ( extension === 'html' ) fileDisplayType = 'html' ;
137139 else if ( TEXT_EXTENSIONS . includes ( `.${ extension } ` ) || fetchedFileData . encoding === 'utf-8' || ! fetchedFileData . encoding ) fileDisplayType = 'text' ;
138-
140+
139141 setFileData ( {
140142 name : pathToLoad . split ( '/' ) . pop ( ) ! ,
141143 path : pathToLoad ,
@@ -151,7 +153,7 @@ function FileExplorerContent() {
151153 setError ( fetchedFileData . error || "Failed to load file content." ) ;
152154 setIsViewingFile ( false ) ;
153155 }
154- } else {
156+ } else {
155157 const dirData = await getRepoContentsAction ( projectUuid , pathToLoad ) ;
156158 if ( Array . isArray ( dirData ) ) {
157159 setContents ( dirData . sort ( ( a , b ) => {
@@ -188,7 +190,7 @@ function FileExplorerContent() {
188190 setProject ( fetchedProject ) ;
189191 if ( ! fetchedProject . githubRepoName ) {
190192 setError ( "Project is not linked to a GitHub repository." ) ;
191- setIsLoadingPathContent ( false ) ;
193+ setIsLoadingPathContent ( false ) ;
192194 }
193195 }
194196 } )
@@ -235,7 +237,7 @@ function FileExplorerContent() {
235237 toast ( { title : "File Created" , description : `${ values . fileName } created successfully.` } ) ;
236238 setIsCreateFileModalOpen ( false ) ;
237239 newFileForm . reset ( ) ;
238- loadContent ( currentPath ) ;
240+ loadContent ( currentPath ) ;
239241 } else {
240242 toast ( { variant : "destructive" , title : "Creation Error" , description : result . error || "Failed to create file." } ) ;
241243 }
@@ -251,7 +253,7 @@ function FileExplorerContent() {
251253 toast ( { title : "Folder Created" , description : `${ values . folderName } created successfully.` } ) ;
252254 setIsCreateFolderModalOpen ( false ) ;
253255 newFolderForm . reset ( ) ;
254- loadContent ( currentPath ) ;
256+ loadContent ( currentPath ) ;
255257 } else {
256258 toast ( { variant : "destructive" , title : "Creation Error" , description : result . error || "Failed to create folder." } ) ;
257259 }
@@ -265,11 +267,11 @@ function FileExplorerContent() {
265267 if ( result . success ) {
266268 toast ( { title : "File Deleted" , description : `${ contentToDelete . name } deleted successfully.` } ) ;
267269 setContentToDelete ( null ) ;
268- if ( isViewingFile && fileData ?. path === contentToDelete . path ) {
270+ if ( isViewingFile && fileData ?. path === contentToDelete . path ) {
269271 const parentPath = currentPath . substring ( 0 , currentPath . lastIndexOf ( '/' ) ) ;
270272 router . push ( `/projects/${ projectUuid } /codespace/files${ parentPath ? '/' + parentPath : '' } ` ) ;
271273 } else {
272- loadContent ( currentPath ) ;
274+ loadContent ( currentPath ) ;
273275 }
274276 } else {
275277 toast ( { variant : "destructive" , title : "Deletion Error" , description : result . error || "Failed to delete file." } ) ;
@@ -293,7 +295,7 @@ function FileExplorerContent() {
293295 toast ( { title : "AI Scaffold Success" , description : aiScaffoldState . message } ) ;
294296 setIsAiScaffoldModalOpen ( false ) ;
295297 aiScaffoldForm . reset ( ) ;
296- loadContent ( currentPath ) ;
298+ loadContent ( currentPath ) ;
297299 }
298300 if ( aiScaffoldState . error ) {
299301 toast ( { variant : "destructive" , title : "AI Scaffold Error" , description : aiScaffoldState . error } ) ;
@@ -305,8 +307,8 @@ function FileExplorerContent() {
305307 if ( ! project || ! fileData || ! editingContent ) return ;
306308 setIsAiEditingFile ( true ) ;
307309 try {
308- const result = await editFileWithAIAction ( project . uuid , editingContent , values . aiEditPrompt ) ;
309- if ( result . newContent ) {
310+ const result : EditFileContentAIOutput | { error : string } = await editFileWithAIAction ( project . uuid , editingContent , values . aiEditPrompt ) ;
311+ if ( ' newContent' in result ) {
310312 setEditingContent ( result . newContent ) ;
311313 toast ( { title : "AI Edit Success" , description : "Content updated by AI." } ) ;
312314 setIsAiEditFileModalOpen ( false ) ;
@@ -336,17 +338,17 @@ function FileExplorerContent() {
336338 const isLoadingPage = authLoading || isLoadingProject || isLoadingPathContent ;
337339
338340
339- if ( isLoadingPage && ! error && ! project ?. githubRepoName ) {
341+ if ( isLoadingPage && ! error && ! project ?. githubRepoName ) {
340342 return (
341343 < div className = "space-y-6" >
342344 < div className = "flex justify-between items-center" >
343- < Skeleton className = "h-9 w-48" />
345+ < Skeleton className = "h-9 w-48" />
344346 </ div >
345347 < Card >
346348 < CardHeader >
347- < Skeleton className = "h-8 w-1/2 mb-2" />
348- < Skeleton className = "h-5 w-3/4" />
349- < div className = "mt-2 flex space-x-1 pb-1 border-t pt-2" >
349+ < Skeleton className = "h-8 w-1/2 mb-2" />
350+ < Skeleton className = "h-5 w-3/4" />
351+ < div className = "mt-2 flex space-x-1 pb-1 border-t pt-2" >
350352 < Skeleton className = "h-5 w-16" /> < Skeleton className = "h-5 w-4" /> < Skeleton className = "h-5 w-20" />
351353 </ div >
352354 </ CardHeader >
@@ -359,7 +361,7 @@ function FileExplorerContent() {
359361 </ div >
360362 ) ;
361363 }
362-
364+
363365 return (
364366 < div className = "space-y-6" >
365367 < div className = "flex flex-col sm:flex-row justify-between items-center gap-4" >
@@ -506,8 +508,8 @@ function FileExplorerContent() {
506508 </ AlertDescription >
507509 </ Alert >
508510 ) : isViewingFile && fileData ? (
509- < div className = "flex flex-col h-full" > { /* Main container for file view */ }
510- < div className = "flex justify-between items-center mb-4 flex-shrink-0" > { /* Header: Title and Buttons */ }
511+ < div className = "flex flex-col h-full" >
512+ < div className = "flex justify-between items-center mb-4 flex-shrink-0" >
511513 < h3 className = "text-xl font-semibold truncate" > { fileData . name } </ h3 >
512514 < div className = "flex items-center gap-2" >
513515 { canEditCurrentFile && (
@@ -531,9 +533,7 @@ function FileExplorerContent() {
531533 ) }
532534 </ div >
533535 </ div >
534-
535- { /* Content area that will scroll if needed */ }
536- < div className = "overflow-auto flex-grow" > { /* This div handles scrolling for its children */ }
536+ < div className = "overflow-auto flex-grow" >
537537 { fileData . type === 'md' && (
538538 < div className = "prose dark:prose-invert max-w-none p-4 border rounded-md bg-muted/30 min-w-max" >
539539 < ReactMarkdown remarkPlugins = { [ remarkGfm ] } > { fileData . content } </ ReactMarkdown >
@@ -556,11 +556,11 @@ function FileExplorerContent() {
556556 { fileData . type === 'html' && (
557557 < div className = "p-4 border rounded-md bg-muted/30" >
558558 < h4 className = "text-sm font-semibold mb-2" > HTML Preview:</ h4 >
559- < iframe
560- srcDoc = { fileData . content }
559+ < iframe
560+ srcDoc = { fileData . content }
561561 title = { `Preview of ${ fileData . name } ` }
562562 className = "w-full h-[50vh] border rounded-md bg-white min-w-[600px]"
563- sandbox = "allow-scripts"
563+ sandbox = "allow-scripts"
564564 />
565565 </ div >
566566 ) }
@@ -598,7 +598,7 @@ function FileExplorerContent() {
598598 </ TableRow >
599599 </ TableHeader >
600600 < TableBody >
601- { filePathArray . length > 0 && (
601+ { filePathArray . length > 0 && (
602602 < TableRow className = "hover:bg-muted/30 cursor-pointer" >
603603 < TableCell colSpan = { 4 } >
604604 < Link href = {
@@ -612,19 +612,15 @@ function FileExplorerContent() {
612612 </ TableRow >
613613 ) }
614614 { contents . map ( ( item ) => (
615- < TableRow key = { item . path } > { /* Changed key to item.path */ }
616- < TableCell >
615+ < TableRow key = { item . path } > < TableCell >
617616 < Link
618617 href = { `/projects/${ projectUuid } /codespace/files/${ item . path } ` }
619618 className = "flex items-center hover:underline group"
620619 >
621620 { item . type === 'dir' ? < Folder className = "mr-2 h-5 w-5 text-sky-500 group-hover:text-sky-600 flex-shrink-0" /> : < FileText className = "mr-2 h-5 w-5 text-gray-500 group-hover:text-gray-700 flex-shrink-0" /> }
622621 < span className = "truncate" > { item . name } </ span >
623622 </ Link >
624- </ TableCell >
625- < TableCell className = "hidden sm:table-cell capitalize" > { item . type === 'dir' ? 'Folder' : 'File' } </ TableCell >
626- < TableCell className = "hidden md:table-cell" > { item . type === 'file' && item . size > 0 ? `${ ( item . size / 1024 ) . toFixed ( 2 ) } KB` : item . type === 'file' ? '0 KB' : '-' } </ TableCell >
627- < TableCell className = "text-right" >
623+ </ TableCell > < TableCell className = "hidden sm:table-cell capitalize" > { item . type === 'dir' ? 'Folder' : 'File' } </ TableCell > < TableCell className = "hidden md:table-cell" > { item . type === 'file' && item . size > 0 ? `${ ( item . size / 1024 ) . toFixed ( 2 ) } KB` : item . type === 'file' ? '0 KB' : '-' } </ TableCell > < TableCell className = "text-right" >
628624 < div className = "flex items-center justify-end gap-1" >
629625 < Button variant = "outline" size = "sm" asChild >
630626 < Link href = { `/projects/${ projectUuid } /codespace/files/${ item . path } ` } >
@@ -638,7 +634,7 @@ function FileExplorerContent() {
638634 < Trash2 className = "h-4 w-4" />
639635 </ Button >
640636 </ AlertDialogTrigger >
641- { contentToDelete ?. path === item . path && (
637+ { contentToDelete ?. path === item . path && (
642638 < AlertDialogContent >
643639 < AlertDialogHeader >
644640 < AlertDialogTitle > Delete File: "{ contentToDelete . name } "?</ AlertDialogTitle >
@@ -654,14 +650,13 @@ function FileExplorerContent() {
654650 ) }
655651 </ AlertDialog >
656652 ) }
657- { item . type === 'dir' && (
653+ { item . type === 'dir' && (
658654 < Button variant = "ghost" size = "icon" className = "h-8 w-8" title = "Delete Folder (Not Implemented)" disabled >
659655 < Trash2 className = "h-4 w-4 opacity-50" />
660656 </ Button >
661657 ) }
662658 </ div >
663- </ TableCell >
664- </ TableRow >
659+ </ TableCell > </ TableRow >
665660 ) ) }
666661 </ TableBody >
667662 </ Table >
@@ -689,7 +684,7 @@ function FileExplorerContent() {
689684 Modify the content of the file. Your changes will be committed to GitHub.
690685 < Dialog open = { isAiEditFileModalOpen } onOpenChange = { setIsAiEditFileModalOpen } >
691686 < DialogTrigger asChild >
692- < Button variant = "outline" size = "sm" disabled = { ! canEditCurrentFile || isSavingFile } >
687+ < Button variant = "outline" size = "sm" disabled = { ! canEditCurrentFile || isSavingFile || isAiEditingFile } >
693688 < Sparkles className = "mr-2 h-4 w-4 text-primary" /> Assist with AI
694689 </ Button >
695690 </ DialogTrigger >
0 commit comments