@@ -7,7 +7,7 @@ import { Card, CardContent, CardDescription, CardHeader, CardTitle, CardFooter }
77import { Tabs , TabsContent , TabsList , TabsTrigger } from "@/components/ui/tabs" ;
88import { Avatar , AvatarFallback , AvatarImage } from '@/components/ui/avatar' ;
99import { AlertDialog , AlertDialogAction , AlertDialogCancel , AlertDialogContent , AlertDialogDescription , AlertDialogFooter , AlertDialogHeader , AlertDialogTitle , AlertDialogTrigger } from "@/components/ui/alert-dialog" ;
10- import { ArrowLeft , Edit3 , PlusCircle , Trash2 , CheckSquare , FileText , Megaphone , Users , FolderGit2 , Loader2 , Mail , UserX , Tag as TagIcon , BookOpen , Pin , PinOff , ShieldAlert , Eye as EyeIcon , Flame , AlertCircle , ListChecks , Palette , CheckCircle , ExternalLink , Info , Code2 , Github , Link2 , Unlink , Copy as CopyIcon , Terminal , InfoIcon , GitBranch , DownloadCloud , MessageSquare , FileCode , Edit , XCircle } from 'lucide-react' ;
10+ import { ArrowLeft , Edit3 , PlusCircle , Trash2 , CheckSquare , FileText , Megaphone , Users , FolderGit2 , Loader2 , Mail , UserX , Tag as TagIcon , BookOpen , Pin , PinOff , ShieldAlert , Eye as EyeIcon , Flame , AlertCircle , ListChecks , Palette , CheckCircle , ExternalLink , Info , Code2 , Github , Link2 , Unlink , Copy as CopyIcon , Terminal , InfoIcon , GitBranch , DownloadCloud , MessageSquare , FileCode , Edit , XCircle , Settings2 } from 'lucide-react' ;
1111import Link from 'next/link' ;
1212import type { Project , Task , Document as ProjectDocumentType , Tag as TagType , ProjectMember , ProjectMemberRole , TaskStatus , Announcement as ProjectAnnouncementType , UserGithubOAuthToken } from '@/types' ;
1313import { Badge } from '@/components/ui/badge' ;
@@ -118,10 +118,10 @@ const linkGithubFormSchema = z.object({
118118 githubRepoName : z . string ( ) . optional ( ) ,
119119 useDefaultRepoName : z . boolean ( ) . default ( true ) ,
120120} ) . refine ( data => {
121- if ( ! data . useDefaultRepoName ) {
122- return data . githubRepoName && data . githubRepoName . trim ( ) !== '' ;
121+ if ( ! data . useDefaultRepoName ) {
122+ return data . githubRepoName && data . githubRepoName . trim ( ) !== '' ;
123123 }
124- return true ;
124+ return true ;
125125} , {
126126 message : "Custom repository name cannot be empty if not using the default." ,
127127 path : [ "githubRepoName" ] ,
@@ -238,11 +238,11 @@ function ProjectDetailPageContent() {
238238 const oauthStatus = searchParams . get ( 'oauth_status' ) ;
239239 if ( oauthStatus === 'success' ) {
240240 toast ( { title : "GitHub Connected!" , description : "Your GitHub account has been successfully linked." } ) ;
241- loadUserGithubOAuth ( ) ; // Reload OAuth token state
241+ loadUserGithubOAuth ( ) ;
242242 const newUrl = new URL ( window . location . href ) ;
243243 newUrl . searchParams . delete ( 'oauth_status' ) ;
244- newUrl . searchParams . delete ( 'code' ) ; // if GitHub adds it
245- newUrl . searchParams . delete ( 'state' ) ; // if GitHub adds it
244+ newUrl . searchParams . delete ( 'code' ) ;
245+ newUrl . searchParams . delete ( 'state' ) ;
246246 router . replace ( newUrl . toString ( ) , { scroll : false } ) ;
247247 } else if ( oauthStatus === 'error' || searchParams . get ( 'error' ) ) {
248248 toast ( { variant : "destructive" , title : "GitHub Connection Error" , description : searchParams . get ( 'message' ) || searchParams . get ( 'error_description' ) || "Failed to connect GitHub account." } ) ;
@@ -803,9 +803,6 @@ function ProjectDetailPageContent() {
803803 formData . append ( 'projectUuid' , project . uuid ) ;
804804 formData . append ( 'todoListMarkdown' , newTodoListMarkdown ) ;
805805
806- // Pass other existing fields to satisfy schema, but they won't be "updated" by this action
807- // if the updateTaskAction is smart enough to only update provided fields.
808- // Assuming updateTaskAction only updates fields present in formData or changed.
809806 formData . append ( 'title' , taskToManageSubtasks . title ) ;
810807 formData . append ( 'status' , taskToManageSubtasks . status ) ;
811808 if ( taskToManageSubtasks . description ) formData . append ( 'description' , taskToManageSubtasks . description ) ;
@@ -829,13 +826,12 @@ function ProjectDetailPageContent() {
829826 }
830827
831828 debounceTimers . current [ taskUuid ] = setTimeout ( ( ) => {
832- lastSubmitSourceRef . current = null ; // Not from main edit dialog or subtask dialog
829+ lastSubmitSourceRef . current = null ;
833830 const formData = new FormData ( ) ;
834831 formData . append ( 'taskUuid' , taskUuid ) ;
835832 formData . append ( 'projectUuid' , project . uuid ) ;
836833 formData . append ( 'todoListMarkdown' , newTodoListMarkdown ) ;
837834
838- // Pass other existing fields
839835 formData . append ( 'title' , taskToUpdate . title ) ;
840836 formData . append ( 'status' , taskToUpdate . status ) ;
841837 if ( taskToUpdate . description ) formData . append ( 'description' , taskToUpdate . description ) ;
@@ -881,10 +877,9 @@ function ProjectDetailPageContent() {
881877 formData . append ( 'projectUuid' , project . uuid ) ;
882878 formData . append ( 'status' , newStatus as string ) ;
883879
884- // Pass other fields to satisfy schema for updateTaskAction
885880 const taskToUpdate = tasks . find ( t => t . uuid === taskUuid ) ;
886881 if ( taskToUpdate ) {
887- formData . append ( 'title' , taskToUpdate . title ) ; // Required by schema if not optional
882+ formData . append ( 'title' , taskToUpdate . title ) ;
888883 }
889884
890885
@@ -979,11 +974,10 @@ function ProjectDetailPageContent() {
979974 if ( grouped [ task . status ] ) {
980975 grouped [ task . status ] . push ( task ) ;
981976 } else {
982- grouped [ 'Archived' ] . push ( task ) ; // Default to Archived if status is unexpected
977+ grouped [ 'Archived' ] . push ( task ) ;
983978 }
984979 } ) ;
985980
986- // Sort tasks within each status group: pinned first, then by updatedAt descending
987981 for ( const status in grouped ) {
988982 grouped [ status as TaskStatus ] . sort ( ( a , b ) => {
989983 if ( a . isPinned && ! b . isPinned ) return - 1 ;
@@ -1003,29 +997,27 @@ function ProjectDetailPageContent() {
1003997
1004998 const handleTagsStringInputChange = (
1005999 event : React . ChangeEvent < HTMLInputElement > ,
1006- fieldApi : any , // ControllerRenderProps from react-hook-form
1007- formApi : any // UseFormReturn
1000+ fieldApi : any ,
1001+ formApi : any
10081002 ) => {
10091003 const inputValue = event . currentTarget . value ;
1010- fieldApi . onChange ( inputValue ) ; // Update form state
1004+ fieldApi . onChange ( inputValue ) ;
10111005
10121006 const fragment = getCurrentTagFragment ( inputValue ) ;
1013- setActiveTagInputName ( fieldApi . name as "tagsString" ) ; // Ensure type safety
1007+ setActiveTagInputName ( fieldApi . name as "tagsString" ) ;
10141008
10151009 if ( fragment ) {
10161010 const lowerFragment = fragment . toLowerCase ( ) ;
1017- // Filter out tags already fully entered in the input
10181011 const currentTagsInInput = inputValue . split ( ',' ) . map ( t => t . trim ( ) . toLowerCase ( ) ) . filter ( t => t . length > 0 ) ;
10191012 const filtered = projectTags
10201013 . filter ( tag =>
10211014 tag . name . toLowerCase ( ) . startsWith ( lowerFragment ) &&
1022- ! currentTagsInInput . slice ( 0 , - 1 ) . includes ( tag . name . toLowerCase ( ) ) // Exclude already "committed" tags
1015+ ! currentTagsInInput . slice ( 0 , - 1 ) . includes ( tag . name . toLowerCase ( ) )
10231016 )
1024- . slice ( 0 , 5 ) ; // Limit suggestions
1017+ . slice ( 0 , 5 ) ;
10251018 setTagSuggestions ( filtered ) ;
10261019 setShowTagSuggestions ( filtered . length > 0 ) ;
10271020
1028- // Reset active suggestion if the typed fragment changes significantly (not just adding a character)
10291021 if ( fragment !== lastTypedFragmentRef . current ) {
10301022 setActiveSuggestionIndex ( - 1 ) ;
10311023 }
@@ -1042,30 +1034,30 @@ function ProjectDetailPageContent() {
10421034
10431035 const handleTagSuggestionClick = (
10441036 suggestion : TagType ,
1045- fieldApi : any , // ControllerRenderProps
1046- formApi : any // UseFormReturn
1037+ fieldApi : any ,
1038+ formApi : any
10471039 ) => {
10481040 const currentFieldValue = fieldApi . value || "" ;
10491041 const parts = currentFieldValue . split ( ',' ) ;
1050- parts [ parts . length - 1 ] = suggestion . name ; // Replace current fragment with full tag name
1042+ parts [ parts . length - 1 ] = suggestion . name ;
10511043
10521044 let newValue = parts . join ( ',' ) ;
1053- if ( ! newValue . endsWith ( ', ' ) ) { // Add a comma and space for the next tag
1045+ if ( ! newValue . endsWith ( ', ' ) ) {
10541046 newValue += ', ' ;
10551047 }
10561048
10571049 fieldApi . onChange ( newValue ) ;
10581050 setTagSuggestions ( [ ] ) ;
10591051 setShowTagSuggestions ( false ) ;
10601052 setActiveSuggestionIndex ( - 1 ) ;
1061- lastTypedFragmentRef . current = "" ; // Clear last fragment
1062- setTimeout ( ( ) => tagInputRef . current ?. focus ( ) , 0 ) ; // Refocus input
1053+ lastTypedFragmentRef . current = "" ;
1054+ setTimeout ( ( ) => tagInputRef . current ?. focus ( ) , 0 ) ;
10631055 } ;
10641056
10651057 const handleTagInputKeyDown = (
10661058 event : React . KeyboardEvent < HTMLInputElement > ,
1067- fieldApi : any , // ControllerRenderProps
1068- formApi : any // UseFormReturn
1059+ fieldApi : any ,
1060+ formApi : any
10691061 ) => {
10701062 if ( showTagSuggestions && tagSuggestions . length > 0 ) {
10711063 if ( event . key === 'ArrowDown' ) {
@@ -1075,10 +1067,10 @@ function ProjectDetailPageContent() {
10751067 event . preventDefault ( ) ;
10761068 setActiveSuggestionIndex ( prev => Math . max ( prev - 1 , 0 ) ) ;
10771069 } else if ( ( event . key === 'Enter' || event . key === 'Tab' ) && activeSuggestionIndex >= 0 && activeSuggestionIndex < tagSuggestions . length ) {
1078- event . preventDefault ( ) ; // Prevent form submission or tabbing away
1079- event . stopPropagation ( ) ; // Stop event from bubbling
1070+ event . preventDefault ( ) ;
1071+ event . stopPropagation ( ) ;
10801072 handleTagSuggestionClick ( tagSuggestions [ activeSuggestionIndex ] , fieldApi , formApi ) ;
1081- return ; // Important to return to prevent further processing
1073+ return ;
10821074 } else if ( event . key === 'Escape' ) {
10831075 event . preventDefault ( ) ;
10841076 event . stopPropagation ( ) ;
@@ -1088,7 +1080,6 @@ function ProjectDetailPageContent() {
10881080 return ;
10891081 }
10901082 } else {
1091- // If suggestions are not shown, still allow escape to clear things if needed
10921083 if ( event . key === 'Escape' ) {
10931084 setShowTagSuggestions ( false ) ;
10941085 setActiveSuggestionIndex ( - 1 ) ;
@@ -1160,7 +1151,7 @@ function ProjectDetailPageContent() {
11601151 if ( ! project || ! user ) return ;
11611152 const formData = new FormData ( ) ;
11621153 formData . append ( 'projectUuid' , project . uuid ) ;
1163- formData . append ( 'flowUpProjectName' , project . name ) ; // Pass FlowUp project name
1154+ formData . append ( 'flowUpProjectName' , project . name ) ;
11641155 formData . append ( 'useDefaultRepoName' , values . useDefaultRepoName . toString ( ) ) ;
11651156 if ( ! values . useDefaultRepoName && values . githubRepoName ) {
11661157 formData . append ( 'githubRepoName' , values . githubRepoName ) ;
@@ -1173,9 +1164,8 @@ function ProjectDetailPageContent() {
11731164
11741165 const handleInitiateGithubOAuth = ( ) => {
11751166 if ( ! project ) return ;
1176- // Construct state with redirectTo and projectUuid
11771167 const statePayload = new URLSearchParams ( {
1178- redirectTo : `/projects/${ project . uuid } ?tab=codespace` , // Ensure redirect back to codespace
1168+ redirectTo : `/projects/${ project . uuid } ?tab=codespace` ,
11791169 projectUuid : project . uuid ,
11801170 } ) . toString ( ) ;
11811171 window . location . href = `/api/auth/github/oauth/login?state=${ encodeURIComponent ( statePayload ) } ` ;
@@ -1190,11 +1180,11 @@ function ProjectDetailPageContent() {
11901180 if ( authLoading || isLoadingData || isLoadingGithubAuth ) {
11911181 return (
11921182 < div className = "space-y-6" >
1193- < Skeleton className = "h-9 w-36 mb-4" /> { /* Back button */ }
1183+ < Skeleton className = "h-9 w-36 mb-4" />
11941184 < Card className = "shadow-lg" >
11951185 < CardHeader >
1196- < Skeleton className = "h-8 w-1/2" /> { /* Project Name */ }
1197- < Skeleton className = "h-5 w-3/4 mt-2" /> { /* Description */ }
1186+ < Skeleton className = "h-8 w-1/2" />
1187+ < Skeleton className = "h-5 w-3/4 mt-2" />
11981188 < div className = "mt-3 flex flex-wrap gap-2" >
11991189 < Skeleton className = "h-6 w-20 rounded-full" />
12001190 < Skeleton className = "h-6 w-24 rounded-full" />
@@ -1209,14 +1199,13 @@ function ProjectDetailPageContent() {
12091199 </ div >
12101200 </ CardContent >
12111201 </ Card >
1212- < Skeleton className = "h-10 w-full" /> { /* Tabs List */ }
1213- < Card > < CardContent className = "p-6" > < Skeleton className = "h-40 w-full" /> </ CardContent > </ Card > { /* Tab Content */ }
1202+ < Skeleton className = "h-10 w-full" />
1203+ < Card > < CardContent className = "p-6" > < Skeleton className = "h-40 w-full" /> </ CardContent > </ Card >
12141204 </ div >
12151205 ) ;
12161206 }
12171207
12181208 if ( accessDenied || ! project || ! user ) {
1219- // This should ideally not be reached if routing logic in useEffect is correct, but good fallback.
12201209 return (
12211210 < div className = "space-y-6 text-center flex flex-col items-center justify-center min-h-[calc(100vh-12rem)]" >
12221211 < Button variant = "outline" onClick = { ( ) => router . push ( '/projects' ) } className = "mb-4 self-start" >
@@ -1234,7 +1223,7 @@ function ProjectDetailPageContent() {
12341223
12351224 return (
12361225 < div className = "space-y-6" >
1237- < Button variant = "outline" onClick = { ( ) => router . back ( ) } className = "mb-0" > { /* Changed from router.push('/projects') to router.back() */ }
1226+ < Button variant = "outline" onClick = { ( ) => router . back ( ) } className = "mb-0" >
12381227 < ArrowLeft className = "mr-2 h-4 w-4" /> Back
12391228 </ Button >
12401229
@@ -1257,14 +1246,13 @@ function ProjectDetailPageContent() {
12571246 < CardDescription className = "mt-1" > No description provided.</ CardDescription >
12581247 ) }
12591248 < div className = "mt-2 flex flex-wrap gap-2" >
1260- { projectTags . slice ( 0 , 5 ) . map ( tag => ( // Only show a few tags initially
1249+ { projectTags . slice ( 0 , 5 ) . map ( tag => (
12611250 < Badge key = { tag . uuid } style = { { backgroundColor : tag . color } } className = "text-white text-xs" > { tag . name } </ Badge >
12621251 ) ) }
12631252 { projectTags . length > 5 && < Badge variant = "outline" > +{ projectTags . length - 5 } more</ Badge > }
12641253 </ div >
12651254 </ div >
12661255 < div className = "flex gap-2 flex-shrink-0" >
1267- { /* Edit Project Dialog */ }
12681256 < Dialog open = { isEditDialogOpen } onOpenChange = { setIsEditDialogOpen } >
12691257 < DialogTrigger asChild >
12701258 < Button variant = "outline" size = "sm" disabled = { ! canManageProjectSettings } >
@@ -1320,7 +1308,6 @@ function ProjectDetailPageContent() {
13201308 </ Form >
13211309 </ DialogContent >
13221310 </ Dialog >
1323- { /* Delete Project Alert Dialog (Placeholder) */ }
13241311 < AlertDialog >
13251312 < AlertDialogTrigger asChild >
13261313 < Button variant = "destructive" size = "sm" disabled = { currentUserRole !== 'owner' } >
@@ -1365,7 +1352,6 @@ function ProjectDetailPageContent() {
13651352 < TabsTrigger value = "settings" > < Settings2 className = "mr-2 h-4 w-4" /> Team & Settings </ TabsTrigger >
13661353 </ TabsList >
13671354
1368- { /* TASKS TAB */ }
13691355 < TabsContent value = "tasks" className = "mt-4" >
13701356 < Card >
13711357 < CardHeader className = "flex flex-row justify-between items-center" >
@@ -1698,7 +1684,6 @@ function ProjectDetailPageContent() {
16981684 </ Dialog >
16991685 </ TabsContent >
17001686
1701- { /* README TAB */ }
17021687 < TabsContent value = "readme" className = "mt-4" >
17031688 < Card >
17041689 < CardHeader className = "flex flex-row justify-between items-center" >
@@ -1734,7 +1719,6 @@ function ProjectDetailPageContent() {
17341719 </ Card >
17351720 </ TabsContent >
17361721
1737- { /* DOCUMENTS TAB */ }
17381722 < TabsContent value = "documents" className = "mt-4" >
17391723 < Card >
17401724 < CardHeader className = "flex flex-row justify-between items-center" >
@@ -1881,7 +1865,6 @@ function ProjectDetailPageContent() {
18811865 </ DialogContent >
18821866 </ Dialog >
18831867
1884- { /* ANNOUNCEMENTS TAB */ }
18851868 < TabsContent value = "announcements" className = "mt-4" >
18861869 < Card >
18871870 < CardHeader className = "flex flex-row justify-between items-center" >
@@ -1989,7 +1972,6 @@ function ProjectDetailPageContent() {
19891972 </ Card >
19901973 </ TabsContent >
19911974
1992- { /* CODESPACE TAB */ }
19931975 < TabsContent value = "codespace" className = "mt-4" >
19941976 < Card >
19951977 < CardHeader >
@@ -2044,12 +2026,12 @@ function ProjectDetailPageContent() {
20442026 control = { linkGithubForm . control }
20452027 name = "useDefaultRepoName"
20462028 render = { ( { field } ) => (
2047- < FormItem className = "flex flex-row items-start space-x-3 space-y-0 rounded-md border p-4 shadow-sm bg-background/50" >
2029+ < FormItem className = "flex flex-row items-center space-x-3 space-y-0 rounded-md border p-4 shadow-sm bg-background/50" >
20482030 < FormControl >
20492031 < Checkbox
20502032 checked = { field . value }
20512033 onCheckedChange = { ( checked ) => {
2052- field . onChange ( Boolean ( checked ) ) ; // Ensure it's a boolean
2034+ field . onChange ( Boolean ( checked ) ) ;
20532035 if ( Boolean ( checked ) ) {
20542036 linkGithubForm . setValue ( 'githubRepoName' , '' ) ;
20552037 linkGithubForm . clearErrors ( 'githubRepoName' ) ;
@@ -2111,28 +2093,24 @@ function ProjectDetailPageContent() {
21112093 </ div >
21122094 </ CardHeader >
21132095 < CardContent className = "space-y-4" >
2114- < p className = "text-sm" >
2115- This project is linked to the GitHub repository:
2116- </ p >
2117- < div className = "flex items-center gap-2 p-3 bg-background/50 rounded-md border" >
2118- < Github className = "h-5 w-5 text-foreground" />
2096+ < div className = "flex flex-col sm:flex-row sm:items-center sm:justify-between gap-3" >
2097+ < p className = "text-sm" >
2098+ This project is linked to:
21192099 < a
2120- href = { project . githubRepoUrl }
2121- target = "_blank"
2122- rel = "noopener noreferrer"
2123- className = "font-mono text-sm text-primary hover:underline break-all"
2100+ href = { project . githubRepoUrl }
2101+ target = "_blank"
2102+ rel = "noopener noreferrer"
2103+ className = "font-mono text-sm text-primary hover:underline break-all block sm:inline ml-1 "
21242104 >
2125- { project . githubRepoName || project . githubRepoUrl }
2105+ { project . githubRepoName || project . githubRepoUrl }
21262106 </ a >
2127- < Button variant = "ghost" size = "icon" className = "h-7 w-7 ml-auto" onClick = { ( ) => copyToClipboard ( project . githubRepoUrl ! , 'Repository URL' ) } > < Link2 className = "h-4 w-4" /> </ Button >
2128- </ div >
2129- { canManageCodeSpace && (
2130- < Button size = "lg" className = "w-full mt-4 shadow-sm" asChild >
2107+ </ p >
2108+ < Button size = "lg" className = "w-full sm:w-auto shadow-sm" asChild >
21312109 < Link href = { `/projects/${ projectUuid } /codespace/files` } >
21322110 < FileCode className = "mr-2 h-5 w-5" /> Browse & Edit Repository Files
21332111 </ Link >
21342112 </ Button >
2135- ) }
2113+ </ div >
21362114 < div className = "grid grid-cols-1 sm:grid-cols-2 gap-3 text-sm pt-4" >
21372115 < div >
21382116 < Label htmlFor = "git-https-url" className = "text-xs text-muted-foreground" > HTTPS Clone URL</ Label >
@@ -2183,7 +2161,6 @@ function ProjectDetailPageContent() {
21832161 </ Card >
21842162 </ TabsContent >
21852163
2186- { /* TEAM & SETTINGS TAB */ }
21872164 < TabsContent value = "settings" className = "mt-4" >
21882165 < Card >
21892166 < CardHeader className = "flex flex-row justify-between items-center" >
0 commit comments