sunatest / frontend /src /components /GoogleSignIn.tsx
llama1's picture
Upload 781 files
5da4770 verified
'use client';
import { useEffect, useCallback, useState } from 'react';
import Script from 'next/script';
import { createClient } from '@/lib/supabase/client';
import { useAuthMethodTracking } from '@/lib/stores/auth-tracking';
import { FcGoogle } from "react-icons/fc";
import { Loader2 } from 'lucide-react';
import { toast } from 'sonner';
declare global {
interface Window {
handleGoogleSignIn?: (response: GoogleSignInResponse) => void;
google: {
accounts: {
id: {
initialize: (config: GoogleInitializeConfig) => void;
prompt: (
callback?: (notification: GoogleNotification) => void,
) => void;
cancel: () => void;
};
};
};
}
}
interface GoogleSignInResponse {
credential: string;
clientId?: string;
select_by?: string;
}
interface GoogleInitializeConfig {
client_id: string | undefined;
callback: ((response: GoogleSignInResponse) => void) | undefined;
nonce?: string;
use_fedcm?: boolean;
context?: string;
itp_support?: boolean;
}
interface GoogleNotification {
isNotDisplayed: () => boolean;
getNotDisplayedReason: () => string;
isSkippedMoment: () => boolean;
getSkippedReason: () => string;
isDismissedMoment: () => boolean;
getDismissedReason: () => string;
}
interface GoogleSignInProps {
returnUrl?: string;
}
export default function GoogleSignIn({ returnUrl }: GoogleSignInProps) {
const googleClientId = process.env.NEXT_PUBLIC_GOOGLE_CLIENT_ID;
const [isLoading, setIsLoading] = useState(false);
const [isGoogleLoaded, setIsGoogleLoaded] = useState(false);
const { wasLastMethod, markAsUsed } = useAuthMethodTracking('google');
const handleGoogleSignIn = useCallback(
async (response: GoogleSignInResponse) => {
try {
setIsLoading(true);
const supabase = createClient();
console.log('Starting Google sign in process');
markAsUsed();
const { error } = await supabase.auth.signInWithIdToken({
provider: 'google',
token: response.credential,
});
if (error) throw error;
console.log(
'Google sign in successful, preparing redirect to:',
returnUrl || '/dashboard',
);
setTimeout(() => {
console.log('Executing redirect now to:', returnUrl || '/dashboard');
window.location.href = returnUrl || '/dashboard';
}, 500);
} catch (error) {
console.error('Error signing in with Google:', error);
setIsLoading(false);
toast.error('Google sign-in failed. Please try again.');
}
},
[returnUrl, markAsUsed],
);
const handleCustomGoogleSignIn = useCallback(() => {
if (isLoading) return;
if (!window.google || !googleClientId || !isGoogleLoaded) {
console.error('Google sign-in not properly initialized');
toast.error('Google sign-in not ready. Please try again.');
return;
}
try {
setIsLoading(true);
const timeoutId = setTimeout(() => {
console.log('Google sign-in timeout - resetting loading state');
setIsLoading(false);
toast.error('Google sign-in popup failed to load. Please try again.');
}, 5000);
window.google.accounts.id.prompt((notification) => {
clearTimeout(timeoutId);
if (notification.isNotDisplayed() || notification.isSkippedMoment()) {
console.log('Google sign-in was not displayed or skipped:',
notification.isNotDisplayed() ? notification.getNotDisplayedReason() : notification.getSkippedReason()
);
setIsLoading(false);
} else if (notification.isDismissedMoment()) {
console.log('Google sign-in was dismissed:', notification.getDismissedReason());
setIsLoading(false);
}
});
} catch (error) {
console.error('Error showing Google sign-in prompt:', error);
setIsLoading(false);
toast.error('Failed to start Google sign-in. Please try again.');
}
}, [googleClientId, isGoogleLoaded, isLoading]);
useEffect(() => {
window.handleGoogleSignIn = handleGoogleSignIn;
if (window.google && googleClientId && !isGoogleLoaded) {
window.google.accounts.id.initialize({
client_id: googleClientId,
callback: handleGoogleSignIn,
use_fedcm: true,
context: 'signin',
itp_support: true,
});
setIsGoogleLoaded(true);
}
return () => {
delete window.handleGoogleSignIn;
};
}, [googleClientId, handleGoogleSignIn, isGoogleLoaded]);
if (!googleClientId) {
return (
<button
disabled
className="w-full h-12 flex items-center justify-center gap-2 text-sm font-medium tracking-wide rounded-full bg-background border border-border opacity-60 cursor-not-allowed"
>
<FcGoogle className="w-4 h-4 mr-2" />
Google Sign-In Not Configured
</button>
);
}
return (
<>
<div
id="g_id_onload"
data-client_id={googleClientId}
data-context="signin"
data-ux_mode="popup"
data-auto_prompt="false"
data-itp_support="true"
data-callback="handleGoogleSignIn"
style={{ display: 'none' }}
/>
<div className="relative">
<button
onClick={handleCustomGoogleSignIn}
disabled={isLoading || !isGoogleLoaded}
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 Google...' : 'Sign in with Google'
}
type="button"
>
{isLoading ? (
<Loader2 className="w-4 h-4 mr-2 animate-spin" />
) : (
<FcGoogle className="w-4 h-4 mr-2" />
)}
<span className="font-medium">
{isLoading ? 'Signing in...' : 'Continue with Google'}
</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>
<Script
src="https://accounts.google.com/gsi/client"
strategy="afterInteractive"
onLoad={() => {
if (window.google && googleClientId && !isGoogleLoaded) {
window.google.accounts.id.initialize({
client_id: googleClientId,
callback: handleGoogleSignIn,
use_fedcm: true,
context: 'signin',
itp_support: true,
});
setIsGoogleLoaded(true);
}
}}
/>
</>
);
}