forked from
atpota.to/flushes.app
The 1st decentralized social network for sharing when you're on the toilet. Post a "flush" today! Powered by the AT Protocol.
1'use client';
2
3import { useEffect, useState, Suspense } from 'react';
4import { useRouter } from 'next/navigation';
5import { useAuth } from '@/lib/auth-context';
6import styles from './callback.module.css';
7
8// Loading component to show while waiting
9function CallbackLoader() {
10 return (
11 <div className={styles.container}>
12 <div className={styles.loaderContainer}>
13 <div className={styles.loader}></div>
14 <p>Processing login...</p>
15 </div>
16 </div>
17 );
18}
19
20// Main callback handler component
21function CallbackHandler() {
22 const router = useRouter();
23 const { session, isLoading } = useAuth();
24 const [error, setError] = useState<string | null>(null);
25 const [processed, setProcessed] = useState(false);
26
27 useEffect(() => {
28 // Prevent double processing
29 if (processed) return;
30
31 // The OAuth client handles the callback automatically during initialization
32 // We just need to wait for the session to be available or an error to occur
33
34 // Set a timeout to handle potential issues
35 const timeout = setTimeout(() => {
36 if (!session && !isLoading) {
37 console.error('OAuth callback processing timed out');
38 setError('Login process timed out. Please try again.');
39 }
40 }, 30000); // 30 second timeout
41
42 // Clean up timeout if component unmounts
43 return () => {
44 clearTimeout(timeout);
45 setProcessed(true);
46 };
47 }, [session, isLoading, processed]);
48
49 // Once we have a session, redirect to home
50 useEffect(() => {
51 if (session && !isLoading) {
52 console.log(`Successfully authenticated user: ${session.sub}`);
53
54 // Small delay to show success state
55 setTimeout(() => {
56 router.push('/');
57 }, 1000);
58 }
59 }, [session, isLoading, router]);
60
61 // Handle cases where the OAuth flow fails
62 useEffect(() => {
63 // Check for error parameters in the URL
64 if (typeof window !== 'undefined') {
65 const urlParams = new URLSearchParams(window.location.search);
66 const urlError = urlParams.get('error');
67 const errorDescription = urlParams.get('error_description');
68
69 if (urlError) {
70 console.error(`OAuth error in URL: ${urlError} - ${errorDescription}`);
71 setError(`Authentication error: ${errorDescription || urlError}`);
72 return;
73 }
74
75 // Also check hash params (since we use fragment mode)
76 const hashParams = new URLSearchParams(window.location.hash.substring(1));
77 const hashError = hashParams.get('error');
78 const hashErrorDescription = hashParams.get('error_description');
79
80 if (hashError) {
81 console.error(`OAuth error in hash: ${hashError} - ${hashErrorDescription}`);
82 setError(`Authentication error: ${hashErrorDescription || hashError}`);
83 return;
84 }
85 }
86
87 // If we're not loading and don't have a session after a reasonable time,
88 // something went wrong
89 if (!isLoading && !session) {
90 const timer = setTimeout(() => {
91 if (!session) {
92 console.error('No session available after callback processing');
93 setError('Failed to complete authentication. Please try again.');
94 }
95 }, 5000); // Wait 5 seconds for session to appear
96
97 return () => clearTimeout(timer);
98 }
99 }, [isLoading, session]);
100
101 if (error) {
102 return (
103 <div className={styles.container}>
104 <div className={styles.errorContainer}>
105 <h1>Authentication Error</h1>
106 <p className={styles.error}>{error}</p>
107 <button onClick={() => router.push('/auth/login')} className={styles.button}>
108 Try Again
109 </button>
110 <button onClick={() => router.push('/')} className={styles.button}>
111 Back to Home
112 </button>
113 </div>
114 </div>
115 );
116 }
117
118 if (session) {
119 return (
120 <div className={styles.container}>
121 <div className={styles.successContainer}>
122 <div className={styles.checkmark}>✓</div>
123 <h1>Welcome back!</h1>
124 <p>Successfully signed in! Redirecting...</p>
125 <p>Redirecting to home page...</p>
126 </div>
127 </div>
128 );
129 }
130
131 return (
132 <div className={styles.container}>
133 <div className={styles.loaderContainer}>
134 <div className={styles.loader}></div>
135 <p>Completing authentication...</p>
136 </div>
137 </div>
138 );
139}
140
141// Main export with Suspense boundary
142export default function CallbackPage() {
143 return (
144 <Suspense fallback={<CallbackLoader />}>
145 <CallbackHandler />
146 </Suspense>
147 );
148}