44import { useAuth } from '@/hooks/useAuth' ;
55import { Card , CardContent , CardDescription , CardHeader , CardTitle , CardFooter } from '@/components/ui/card' ;
66import { Avatar , AvatarFallback , AvatarImage } from '@/components/ui/avatar' ;
7- import { User as UserIcon , Mail , Shield , Edit3 , Image as ImageIcon } from 'lucide-react' ;
7+ import { User as UserIcon , Mail , Shield , Edit3 , Image as ImageIcon , Github , Link2 , PowerOff , ExternalLink , MessageSquare } from 'lucide-react' ;
88import { Skeleton } from '@/components/ui/skeleton' ;
99import { Button } from '@/components/ui/button' ;
1010import { Input } from '@/components/ui/input' ;
1111import { Label } from '@/components/ui/label' ;
1212import { useForm } from 'react-hook-form' ;
1313import { zodResolver } from '@hookform/resolvers/zod' ;
1414import * as z from 'zod' ;
15- import { useEffect , useState } from 'react' ;
15+ import { useEffect , useState , useActionState , startTransition as ReactStartTransition } from 'react' ;
1616import { useToast } from '@/hooks/use-toast' ;
17- import * as authService from '@/lib/authService' ; // Import server actions
17+ import * as authService from '@/lib/authService' ;
18+ import { fetchUserGithubOAuthTokenAction , disconnectGithubAction , fetchGithubUserDetailsAction } from '@/app/(app)/projects/[id]/actions' ; // Re-using from project actions for now
19+ import { useRouter } from 'next/navigation' ;
20+ import Link from 'next/link' ;
21+
1822
1923const profileFormSchema = z . object ( {
2024 name : z . string ( ) . min ( 2 , { message : "Name must be at least 2 characters." } ) ,
@@ -24,12 +28,27 @@ const profileFormSchema = z.object({
2428
2529type ProfileFormValues = z . infer < typeof profileFormSchema > ;
2630
31+ interface GithubUserDetails {
32+ login : string ;
33+ avatar_url : string ;
34+ html_url : string ;
35+ name : string | null ;
36+ }
37+
2738export default function ProfilePage ( ) {
2839 const { user, isLoading : authLoading , refreshUser } = useAuth ( ) ;
40+ const router = useRouter ( ) ;
2941 const [ isEditing , setIsEditing ] = useState ( false ) ;
3042 const [ isSubmitting , setIsSubmitting ] = useState ( false ) ;
3143 const { toast } = useToast ( ) ;
3244
45+ const [ githubToken , setGithubToken ] = useState < string | null > ( null ) ;
46+ const [ githubUserDetails , setGithubUserDetails ] = useState < GithubUserDetails | null > ( null ) ;
47+ const [ isLoadingGithub , setIsLoadingGithub ] = useState ( true ) ;
48+
49+ const [ disconnectState , disconnectFormAction , isDisconnectPending ] = useActionState ( disconnectGithubAction , { success : false } ) ;
50+
51+
3352 const form = useForm < ProfileFormValues > ( {
3453 resolver : zodResolver ( profileFormSchema ) ,
3554 defaultValues : {
@@ -49,6 +68,38 @@ export default function ProfilePage() {
4968 }
5069 } , [ user , form ] ) ;
5170
71+ useEffect ( ( ) => {
72+ async function loadGithubData ( ) {
73+ if ( user ) {
74+ setIsLoadingGithub ( true ) ;
75+ const tokenData = await fetchUserGithubOAuthTokenAction ( ) ;
76+ if ( tokenData ?. accessToken ) {
77+ setGithubToken ( tokenData . accessToken ) ;
78+ const userDetails = await fetchGithubUserDetailsAction ( ) ;
79+ setGithubUserDetails ( userDetails ) ;
80+ } else {
81+ setGithubToken ( null ) ;
82+ setGithubUserDetails ( null ) ;
83+ }
84+ setIsLoadingGithub ( false ) ;
85+ }
86+ }
87+ if ( ! authLoading ) loadGithubData ( ) ;
88+ } , [ user , authLoading ] ) ;
89+
90+ useEffect ( ( ) => {
91+ if ( ! isDisconnectPending && disconnectState ) {
92+ if ( disconnectState . success ) {
93+ toast ( { title : "Success" , description : disconnectState . message } ) ;
94+ setGithubToken ( null ) ;
95+ setGithubUserDetails ( null ) ;
96+ } else if ( disconnectState . error ) {
97+ toast ( { variant : "destructive" , title : "Error" , description : disconnectState . error } ) ;
98+ }
99+ }
100+ } , [ disconnectState , isDisconnectPending , toast ] ) ;
101+
102+
52103 if ( authLoading ) {
53104 return (
54105 < div className = "space-y-6" >
@@ -81,7 +132,9 @@ export default function ProfilePage() {
81132 }
82133
83134 if ( ! user ) {
84- return < p > User not found. Please log in.</ p > ;
135+ // Should be redirected by useAuth hook or AppLayout, but as a fallback:
136+ router . push ( '/login' ) ;
137+ return < p > Redirecting to login...</ p > ;
85138 }
86139
87140 const getInitials = ( name : string ) => {
@@ -112,12 +165,25 @@ export default function ProfilePage() {
112165 }
113166 } ;
114167
168+ const handleConnectGitHub = ( ) => {
169+ // Redirect to the GitHub OAuth login flow
170+ window . location . href = `/api/auth/github/oauth/login?redirectTo=/profile` ;
171+ } ;
172+
173+ const handleDisconnectGitHub = ( ) => {
174+ const dummyFormData = new FormData ( ) ; // useActionState requires FormData
175+ ReactStartTransition ( ( ) => {
176+ disconnectFormAction ( dummyFormData ) ;
177+ } ) ;
178+ } ;
179+
180+
115181 return (
116- < div className = "space-y-6 " >
182+ < div className = "space-y-8 " >
117183 < div className = "flex justify-between items-center" >
118184 < div >
119185 < h1 className = "text-3xl font-headline font-semibold" > My Profile</ h1 >
120- < p className = "text-muted-foreground" > View and manage your personal information.</ p >
186+ < p className = "text-muted-foreground" > View and manage your personal information and connections .</ p >
121187 </ div >
122188 { ! isEditing && (
123189 < Button variant = "outline" onClick = { ( ) => setIsEditing ( true ) } >
@@ -207,6 +273,67 @@ export default function ProfilePage() {
207273 ) }
208274 </ form >
209275 </ Card >
276+
277+ < Card className = "max-w-2xl mx-auto shadow-lg" >
278+ < CardHeader >
279+ < CardTitle className = "text-xl flex items-center" > < Link2 className = "mr-2 h-5 w-5 text-primary" /> External Connections</ CardTitle >
280+ < CardDescription > Manage your connections to third-party services.</ CardDescription >
281+ </ CardHeader >
282+ < CardContent className = "space-y-4" >
283+ { /* GitHub Connection */ }
284+ < Card className = "p-4 bg-muted/30" >
285+ < div className = "flex items-center justify-between" >
286+ < div className = "flex items-center gap-3" >
287+ < Github className = "h-8 w-8" />
288+ < div >
289+ < h4 className = "font-semibold" > GitHub</ h4 >
290+ { isLoadingGithub ? (
291+ < Skeleton className = "h-4 w-32 mt-1" />
292+ ) : githubUserDetails ? (
293+ < div className = "text-sm text-muted-foreground" >
294+ Connected as: < a href = { githubUserDetails . html_url } target = "_blank" rel = "noopener noreferrer" className = "font-medium text-primary hover:underline" > { githubUserDetails . login } </ a > ({ githubUserDetails . name || 'Name not public' } )
295+ < Avatar className = "h-5 w-5 inline-block ml-2 align-middle" >
296+ < AvatarImage src = { githubUserDetails . avatar_url } alt = { githubUserDetails . login } data-ai-hint = "github avatar" />
297+ < AvatarFallback > { getInitials ( githubUserDetails . login ) } </ AvatarFallback >
298+ </ Avatar >
299+ </ div >
300+ ) : (
301+ < p className = "text-sm text-muted-foreground" > Not Connected</ p >
302+ ) }
303+ </ div >
304+ </ div >
305+ { isLoadingGithub ? (
306+ < Skeleton className = "h-9 w-24" />
307+ ) : githubToken ? (
308+ < Button variant = "outline" onClick = { handleDisconnectGitHub } disabled = { isDisconnectPending } >
309+ { isDisconnectPending ? < Loader2 className = "h-4 w-4 animate-spin mr-2" /> : < PowerOff className = "mr-2 h-4 w-4" /> }
310+ Disconnect
311+ </ Button >
312+ ) : (
313+ < Button onClick = { handleConnectGitHub } >
314+ < Github className = "mr-2 h-4 w-4" /> Connect
315+ </ Button >
316+ ) }
317+ </ div >
318+ </ Card >
319+
320+ { /* Discord Connection (Placeholder) */ }
321+ < Card className = "p-4 bg-muted/30" >
322+ < div className = "flex items-center justify-between" >
323+ < div className = "flex items-center gap-3" >
324+ < MessageSquare className = "h-8 w-8 text-indigo-500" />
325+ < div >
326+ < h4 className = "font-semibold" > Discord</ h4 >
327+ < p className = "text-sm text-muted-foreground" > Connect to receive notifications (Coming Soon).</ p >
328+ </ div >
329+ </ div >
330+ < Button disabled >
331+ < MessageSquare className = "mr-2 h-4 w-4" /> Connect (Soon)
332+ </ Button >
333+ </ div >
334+ </ Card >
335+ </ CardContent >
336+ </ Card >
210337 </ div >
211338 ) ;
212339}
0 commit comments