141 lines
4.4 KiB
TypeScript
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>
|
|
);
|
|
}
|
|
|