sunatest / frontend /src /components /GithubSignIn.tsx
llama1's picture
Upload 781 files
5da4770 verified
'use client';
import { useState, useEffect, useCallback } from 'react';
import { useTheme } from 'next-themes';
import { toast } from 'sonner';
import { Icons } from './home/icons';
import { FaGithub } from "react-icons/fa";
import { useAuthMethodTracking } from '@/lib/stores/auth-tracking';
import { Loader2 } from 'lucide-react';
interface GitHubSignInProps {
returnUrl?: string;
}
interface AuthMessage {
type: 'github-auth-success' | 'github-auth-error';
message?: string;
returnUrl?: string;
}
export default function GitHubSignIn({ returnUrl }: GitHubSignInProps) {
const [isLoading, setIsLoading] = useState(false);
const { resolvedTheme } = useTheme();
const { wasLastMethod, markAsUsed } = useAuthMethodTracking('github');
const cleanupAuthState = useCallback(() => {
sessionStorage.removeItem('isGitHubAuthInProgress');
setIsLoading(false);
}, []);
const handleSuccess = useCallback(
(data: AuthMessage) => {
cleanupAuthState();
markAsUsed();
setTimeout(() => {
window.location.href = data.returnUrl || returnUrl || '/dashboard';
}, 100);
},
[cleanupAuthState, returnUrl, markAsUsed],
);
const handleError = useCallback(
(data: AuthMessage) => {
cleanupAuthState();
toast.error(data.message || 'GitHub sign-in failed. Please try again.');
},
[cleanupAuthState],
);
useEffect(() => {
const handleMessage = (event: MessageEvent<AuthMessage>) => {
if (event.origin !== window.location.origin) {
console.warn(
'Rejected message from unauthorized origin:',
event.origin,
);
return;
}
if (!event.data?.type || typeof event.data.type !== 'string') {
return;
}
switch (event.data.type) {
case 'github-auth-success':
handleSuccess(event.data);
break;
case 'github-auth-error':
handleError(event.data);
break;
default:
break;
}
};
window.addEventListener('message', handleMessage);
return () => {
window.removeEventListener('message', handleMessage);
};
}, [handleSuccess, handleError]);
useEffect(() => {
return () => {
cleanupAuthState();
};
}, [cleanupAuthState]);
const handleGitHubSignIn = async () => {
if (isLoading) return;
let popupInterval: NodeJS.Timeout | null = null;
try {
setIsLoading(true);
if (returnUrl) {
sessionStorage.setItem('github-returnUrl', returnUrl || '/dashboard');
}
const popup = window.open(
`${window.location.origin}/auth/github-popup`,
'GitHubOAuth',
'width=500,height=600,scrollbars=yes,resizable=yes,status=yes,location=yes',
);
if (!popup) {
throw new Error(
'Popup was blocked. Please enable popups and try again.',
);
}
sessionStorage.setItem('isGitHubAuthInProgress', '1');
popupInterval = setInterval(() => {
if (popup.closed) {
if (popupInterval) {
clearInterval(popupInterval);
popupInterval = null;
}
setTimeout(() => {
if (sessionStorage.getItem('isGitHubAuthInProgress')) {
cleanupAuthState();
toast.error('GitHub sign-in was cancelled or not completed.');
}
}, 500);
}
}, 1000);
} catch (error) {
console.error('GitHub sign-in error:', error);
if (popupInterval) {
clearInterval(popupInterval);
}
cleanupAuthState();
toast.error(
error instanceof Error
? error.message
: 'Failed to start GitHub sign-in',
);
}
};
return (
<div className="relative">
<button
onClick={handleGitHubSignIn}
disabled={isLoading}
className="w-full h-12 flex items-center justify-center text-sm font-medium tracking-wide rounded-full bg-background text-foreground border border-border hover:bg-accent/30 transition-all duration-200 disabled:opacity-60 disabled:cursor-not-allowed font-sans"
aria-label={
isLoading ? 'Signing in with GitHub...' : 'Sign in with GitHub'
}
type="button"
>
{isLoading ? (
<Loader2 className="w-4 h-4 mr-2 animate-spin" />
) : (
<FaGithub className="w-4 h-4 mr-2" />
)}
<span className="font-medium">
{isLoading ? 'Signing in...' : 'Continue with GitHub'}
</span>
</button>
{wasLastMethod && (
<div className="absolute -top-1 -right-1 w-3 h-3 bg-green-500 rounded-full border-2 border-background shadow-sm">
<div className="w-full h-full bg-green-500 rounded-full animate-pulse" />
</div>
)}
</div>
);
}