@@ -15,7 +15,7 @@ import * as z from 'zod';
1515import { useEffect , useState , useActionState , startTransition } from 'react' ; // Corrected import
1616import { useToast } from '@/hooks/use-toast' ;
1717import * as authService from '@/lib/authService' ;
18- import { fetchUserGithubOAuthTokenAction , disconnectGithubAction , fetchGithubUserDetailsAction } from '@/app/(app)/projects/[id]/actions' ;
18+ import { fetchUserGithubOAuthTokenAction , disconnectGithubAction , fetchGithubUserDetailsAction , fetchDiscordUserDetailsAction , disconnectDiscordAction } from '@/app/(app)/projects/[id]/actions' ;
1919import { useRouter } from 'next/navigation' ;
2020import Link from 'next/link' ;
2121import { RadioGroup , RadioGroupItem } from '@/components/ui/radio-group' ;
@@ -36,6 +36,14 @@ interface GithubUserDetails {
3636 name : string | null ;
3737}
3838
39+ interface DiscordUserDetails {
40+ id : string ;
41+ username : string ;
42+ avatar : string | null ;
43+ discriminator : string ;
44+ }
45+
46+
3947export default function ProfilePage ( ) {
4048 const { user, isLoading : authLoading , refreshUser } = useAuth ( ) ;
4149 const router = useRouter ( ) ;
@@ -47,7 +55,11 @@ export default function ProfilePage() {
4755 const [ githubUserDetails , setGithubUserDetails ] = useState < GithubUserDetails | null > ( null ) ;
4856 const [ isLoadingGithub , setIsLoadingGithub ] = useState ( true ) ;
4957
50- const [ disconnectState , disconnectFormAction , isDisconnectPending ] = useActionState ( disconnectGithubAction , { success : false } ) ;
58+ const [ discordUserDetails , setDiscordUserDetails ] = useState < DiscordUserDetails | null > ( null ) ;
59+ const [ isLoadingDiscord , setIsLoadingDiscord ] = useState ( true ) ;
60+
61+ const [ disconnectGithubState , disconnectGithubFormAction , isDisconnectGithubPending ] = useActionState ( disconnectGithubAction , { success : false } ) ;
62+ const [ disconnectDiscordState , disconnectDiscordFormAction , isDisconnectDiscordPending ] = useActionState ( disconnectDiscordAction , { success : false } ) ;
5163
5264
5365 const form = useForm < ProfileFormValues > ( {
@@ -70,9 +82,11 @@ export default function ProfilePage() {
7082 } , [ user , form ] ) ;
7183
7284 useEffect ( ( ) => {
73- async function loadGithubData ( ) {
85+ async function loadExternalData ( ) {
7486 if ( user ) {
7587 setIsLoadingGithub ( true ) ;
88+ setIsLoadingDiscord ( true ) ;
89+
7690 const tokenData = await fetchUserGithubOAuthTokenAction ( ) ;
7791 if ( tokenData ?. accessToken ) {
7892 setGithubToken ( tokenData . accessToken ) ;
@@ -83,22 +97,37 @@ export default function ProfilePage() {
8397 setGithubUserDetails ( null ) ;
8498 }
8599 setIsLoadingGithub ( false ) ;
100+
101+ const discordDetails = await fetchDiscordUserDetailsAction ( ) ;
102+ setDiscordUserDetails ( discordDetails ) ;
103+ setIsLoadingDiscord ( false ) ;
86104 }
87105 }
88- if ( ! authLoading ) loadGithubData ( ) ;
106+ if ( ! authLoading ) loadExternalData ( ) ;
89107 } , [ user , authLoading ] ) ;
90108
91109 useEffect ( ( ) => {
92- if ( ! isDisconnectPending && disconnectState ) {
93- if ( disconnectState . success && disconnectState . message ) { // Check for message
94- toast ( { title : "Success" , description : disconnectState . message } ) ;
110+ if ( ! isDisconnectGithubPending && disconnectGithubState ) {
111+ if ( disconnectGithubState . success && disconnectGithubState . message ) {
112+ toast ( { title : "Success" , description : disconnectGithubState . message } ) ;
95113 setGithubToken ( null ) ;
96114 setGithubUserDetails ( null ) ;
97- } else if ( disconnectState . error ) {
98- toast ( { variant : "destructive" , title : "Error" , description : disconnectState . error } ) ;
115+ } else if ( disconnectGithubState . error ) {
116+ toast ( { variant : "destructive" , title : "Error" , description : disconnectGithubState . error } ) ;
99117 }
100118 }
101- } , [ disconnectState , isDisconnectPending , toast ] ) ;
119+ } , [ disconnectGithubState , isDisconnectGithubPending , toast ] ) ;
120+
121+ useEffect ( ( ) => {
122+ if ( ! isDisconnectDiscordPending && disconnectDiscordState ) {
123+ if ( disconnectDiscordState . success && disconnectDiscordState . message ) {
124+ toast ( { title : "Success" , description : disconnectDiscordState . message } ) ;
125+ setDiscordUserDetails ( null ) ;
126+ } else if ( disconnectDiscordState . error ) {
127+ toast ( { variant : "destructive" , title : "Error" , description : disconnectDiscordState . error } ) ;
128+ }
129+ }
130+ } , [ disconnectDiscordState , isDisconnectDiscordPending , toast ] ) ;
102131
103132
104133 if ( authLoading ) {
@@ -167,17 +196,27 @@ export default function ProfilePage() {
167196 } ;
168197
169198 const handleConnectGitHub = ( ) => {
170- // Redirect to the GitHub OAuth login flow
171199 window . location . href = `/api/auth/github/oauth/login?redirectTo=/profile` ;
172200 } ;
173201
174202 const handleDisconnectGitHub = ( ) => {
175- const dummyFormData = new FormData ( ) ; // useActionState requires FormData
176- startTransition ( ( ) => { // Corrected usage
177- disconnectFormAction ( dummyFormData ) ;
203+ const dummyFormData = new FormData ( ) ;
204+ startTransition ( ( ) => {
205+ disconnectGithubFormAction ( dummyFormData ) ;
178206 } ) ;
179207 } ;
180208
209+ const handleConnectDiscord = ( ) => {
210+ window . location . href = `/api/auth/discord/oauth/login?redirectTo=/profile` ;
211+ } ;
212+
213+ const handleDisconnectDiscord = ( ) => {
214+ const dummyFormData = new FormData ( ) ;
215+ startTransition ( ( ) => {
216+ disconnectDiscordFormAction ( dummyFormData ) ;
217+ } ) ;
218+ } ;
219+
181220
182221 return (
183222 < div className = "space-y-8" >
@@ -243,8 +282,9 @@ export default function ProfilePage() {
243282 { githubUserDetails && < Avatar className = "h-5 w-5 inline-block" > < AvatarImage src = { githubUserDetails . avatar_url } /> </ Avatar > }
244283 </ div >
245284 < div className = "flex items-center space-x-2" >
246- < RadioGroupItem value = "discord" id = "r-discord" disabled />
247- < Label htmlFor = "r-discord" className = "text-muted-foreground" > Use Discord Profile Picture</ Label >
285+ < RadioGroupItem value = "discord" id = "r-discord" disabled = { ! discordUserDetails } />
286+ < Label htmlFor = "r-discord" className = { ! discordUserDetails ? "text-muted-foreground" : "" } > Use Discord Profile Picture</ Label >
287+ { discordUserDetails ?. avatar && < Avatar className = "h-5 w-5 inline-block" > < AvatarImage src = { `https://cdn.discordapp.com/avatars/${ discordUserDetails . id } /${ discordUserDetails . avatar } .png` } /> </ Avatar > }
248288 </ div >
249289 </ RadioGroup >
250290 </ Card >
@@ -324,8 +364,8 @@ export default function ProfilePage() {
324364 { isLoadingGithub ? (
325365 < Skeleton className = "h-9 w-24" />
326366 ) : githubToken ? (
327- < Button variant = "outline" onClick = { handleDisconnectGitHub } disabled = { isDisconnectPending } >
328- { isDisconnectPending ? < Loader2 className = "h-4 w-4 animate-spin mr-2" /> : < PowerOff className = "mr-2 h-4 w-4" /> }
367+ < Button variant = "outline" onClick = { handleDisconnectGitHub } disabled = { isDisconnectGithubPending } >
368+ { isDisconnectGithubPending ? < Loader2 className = "h-4 w-4 animate-spin mr-2" /> : < PowerOff className = "mr-2 h-4 w-4" /> }
329369 Disconnect
330370 </ Button >
331371 ) : (
@@ -336,19 +376,42 @@ export default function ProfilePage() {
336376 </ div >
337377 </ Card >
338378
339- { /* Discord Connection (Placeholder) */ }
379+ { /* Discord Connection */ }
340380 < Card className = "p-4 bg-muted/30" >
341381 < div className = "flex items-center justify-between" >
342382 < div className = "flex items-center gap-3" >
343383 < MessageSquare className = "h-8 w-8 text-indigo-500" />
344384 < div >
345385 < h4 className = "font-semibold" > Discord</ h4 >
346- < p className = "text-sm text-muted-foreground" > Connect to receive notifications (Coming Soon).</ p >
386+ { isLoadingDiscord ? (
387+ < Skeleton className = "h-4 w-32 mt-1" />
388+ ) : discordUserDetails ? (
389+ < div className = "text-sm text-muted-foreground" >
390+ Connected as: < span className = "font-medium text-primary" > { discordUserDetails . username } #{ discordUserDetails . discriminator } </ span >
391+ { discordUserDetails . avatar && (
392+ < Avatar className = "h-5 w-5 inline-block ml-2 align-middle" >
393+ < AvatarImage src = { `https://cdn.discordapp.com/avatars/${ discordUserDetails . id } /${ discordUserDetails . avatar } .png` } alt = { discordUserDetails . username } data-ai-hint = "discord avatar" />
394+ < AvatarFallback > { getInitials ( discordUserDetails . username ) } </ AvatarFallback >
395+ </ Avatar >
396+ ) }
397+ </ div >
398+ ) : (
399+ < p className = "text-sm text-muted-foreground" > Not Connected. Connect to enable DM notifications.</ p >
400+ ) }
347401 </ div >
348402 </ div >
349- < Button disabled >
350- < MessageSquare className = "mr-2 h-4 w-4" /> Connect (Soon)
351- </ Button >
403+ { isLoadingDiscord ? (
404+ < Skeleton className = "h-9 w-24" />
405+ ) : discordUserDetails ? (
406+ < Button variant = "outline" onClick = { handleDisconnectDiscord } disabled = { isDisconnectDiscordPending } >
407+ { isDisconnectDiscordPending ? < Loader2 className = "h-4 w-4 animate-spin mr-2" /> : < PowerOff className = "mr-2 h-4 w-4" /> }
408+ Disconnect
409+ </ Button >
410+ ) : (
411+ < Button onClick = { handleConnectDiscord } >
412+ < MessageSquare className = "mr-2 h-4 w-4" /> Connect
413+ </ Button >
414+ ) }
352415 </ div >
353416 </ Card >
354417 </ CardContent >
0 commit comments