Files
vibn-frontend/app/api/github/oauth/callback/page.tsx

141 lines
4.4 KiB
TypeScript

'use client';
import { useEffect, useState, Suspense } from 'react';
import { useSearchParams, useRouter } from 'next/navigation';
import { auth } from '@/lib/firebase/config';
import { exchangeCodeForToken, getGitHubUser } from '@/lib/github/oauth';
import { Loader2, CheckCircle2, XCircle } from 'lucide-react';
function GitHubCallbackContent() {
const searchParams = useSearchParams();
const router = useRouter();
const [status, setStatus] = useState<'loading' | 'success' | 'error'>('loading');
const [error, setError] = useState<string | null>(null);
useEffect(() => {
async function handleCallback() {
try {
const code = searchParams.get('code');
const state = searchParams.get('state');
const error = searchParams.get('error');
if (error) {
throw new Error(`GitHub OAuth error: ${error}`);
}
if (!code) {
throw new Error('No authorization code received');
}
// Verify state (CSRF protection)
const storedState = sessionStorage.getItem('github_oauth_state');
if (state !== storedState) {
throw new Error('Invalid state parameter');
}
sessionStorage.removeItem('github_oauth_state');
// Exchange code for token
const tokenData = await exchangeCodeForToken(code);
// Get GitHub user info
const githubUser = await getGitHubUser(tokenData.access_token);
// Store connection in Firebase
const user = auth.currentUser;
if (!user) {
throw new Error('User not authenticated');
}
const idToken = await user.getIdToken();
const response = await fetch('/api/github/connect', {
method: 'POST',
headers: {
'Authorization': `Bearer ${idToken}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
accessToken: tokenData.access_token,
githubUser,
}),
});
if (!response.ok) {
throw new Error('Failed to store GitHub connection');
}
setStatus('success');
// Redirect back to connections page after 2 seconds
setTimeout(() => {
const workspace = user.displayName || 'workspace';
router.push(`/${workspace}/connections`);
}, 2000);
} catch (err: any) {
console.error('[GitHub Callback] Error:', err);
setError(err.message);
setStatus('error');
}
}
handleCallback();
}, [searchParams, router]);
return (
<div className="flex min-h-screen items-center justify-center bg-background p-6">
<div className="w-full max-w-md space-y-6 text-center">
{status === 'loading' && (
<>
<Loader2 className="mx-auto h-12 w-12 animate-spin text-primary" />
<h1 className="text-2xl font-bold">Connecting to GitHub...</h1>
<p className="text-muted-foreground">
Please wait while we complete the connection.
</p>
</>
)}
{status === 'success' && (
<>
<CheckCircle2 className="mx-auto h-12 w-12 text-green-500" />
<h1 className="text-2xl font-bold">Successfully Connected!</h1>
<p className="text-muted-foreground">
Your GitHub account has been connected. Redirecting...
</p>
</>
)}
{status === 'error' && (
<>
<XCircle className="mx-auto h-12 w-12 text-red-500" />
<h1 className="text-2xl font-bold">Connection Failed</h1>
<p className="text-muted-foreground">{error}</p>
<button
onClick={() => router.push('/connections')}
className="mt-4 rounded-lg bg-primary px-6 py-2 text-white hover:bg-primary/90"
>
Back to Connections
</button>
</>
)}
</div>
</div>
);
}
export default function GitHubCallbackPage() {
return (
<Suspense
fallback={
<div className="flex min-h-screen items-center justify-center bg-background p-6">
<div className="w-full max-w-md space-y-6 text-center">
<Loader2 className="mx-auto h-12 w-12 animate-spin text-primary" />
<h1 className="text-2xl font-bold">Loading...</h1>
</div>
</div>
}
>
<GitHubCallbackContent />
</Suspense>
);
}