'use client'; import React, { useMemo, useState } from 'react'; import { Button } from '@/components/ui/buttons'; import { Input } from '@/components/ui/inputs'; import { Title, Text } from '@/components/ui/typography'; import { inputHasText } from '@/lib/text'; import { isValidEmail } from '@/lib/email'; import { useMemoizedFn } from '@/hooks'; import Link from 'next/link'; import { BusterRoutes, createBusterRoute } from '@/routes/busterRoutes'; import Google from '@/components/ui/icons/customIcons/Google'; import Microsoft from '@/components/ui/icons/customIcons/Microsoft'; import Github from '@/components/ui/icons/customIcons/Github'; import Cookies from 'js-cookie'; import { PolicyCheck } from './PolicyCheck'; import { rustErrorHandler } from '@/api/buster_rest/errors'; import { cn } from '@/lib/classMerge'; import { SuccessCard } from '@/components/ui/card/SuccessCard'; import { useHotkeys } from 'react-hotkeys-hook'; import { signInWithAzure, signInWithEmailAndPassword, signInWithGithub, signInWithGoogle, signUp } from '@/lib/supabase/signIn'; const DEFAULT_CREDENTIALS = { email: process.env.NEXT_PUBLIC_USER!, password: process.env.NEXT_PUBLIC_USER_PASSWORD! }; export const LoginForm: React.FC<{}> = ({}) => { const [loading, setLoading] = useState<'google' | 'github' | 'azure' | 'email' | null>(null); const [errorMessages, setErrorMessages] = useState([]); const [signUpFlow, setSignUpFlow] = useState(true); const [signUpSuccess, setSignUpSuccess] = useState(false); const errorFallback = (error: any) => { const errorMessage = rustErrorHandler(error); if (errorMessage?.message) { setErrorMessages(['Invalid email or password']); } else { setErrorMessages(['An error occurred']); } }; const onSignInWithUsernameAndPassword = useMemoizedFn( async ({ email, password }: { email: string; password: string }) => { setLoading('email'); try { const res = await signInWithEmailAndPassword({ email, password }); if (res?.error) throw res.error; } catch (error: any) { errorFallback(error); setLoading(null); } } ); const onSignInWithGoogle = useMemoizedFn(async () => { setLoading('google'); try { const res = await signInWithGoogle(); if (res?.error) throw res.error; } catch (error: any) { errorFallback(error); setLoading(null); } }); const onSignInWithGithub = useMemoizedFn(async () => { setLoading('github'); try { const res = await signInWithGithub(); if (res?.error) throw res.error; } catch (error: any) { errorFallback(error); setLoading(null); } }); const onSignInWithAzure = useMemoizedFn(async () => { setLoading('azure'); try { const res = await signInWithAzure(); if (res?.error) throw res.error; } catch (error: any) { errorFallback(error); } setLoading('azure'); }); const onSignUp = useMemoizedFn(async (d: { email: string; password: string }) => { setLoading('email'); try { const res = await signUp(d); if (res?.error) throw res.error; setSignUpSuccess(true); } catch (error: any) { errorFallback(error); setLoading(null); } }); const onSubmitClick = useMemoizedFn((d: { email: string; password: string }) => { try { setErrorMessages([]); setLoading('email'); if (signUpFlow) onSignUp(d); else onSignInWithUsernameAndPassword(d); } catch (error: any) { const errorMessage = rustErrorHandler(error); if (errorMessage?.message == 'User already registered') { onSignInWithUsernameAndPassword(d); return; } if (errorMessage?.message) { setErrorMessages([errorMessage.message]); } else { setErrorMessages(['An error occurred']); } setLoading(null); } }); return (
{signUpSuccess ? ( ) : ( )}
); }; const LoginOptions: React.FC<{ onSubmitClick: (d: { email: string; password: string }) => void; onSignInWithGoogle: () => void; onSignInWithGithub: () => void; onSignInWithAzure: () => void; setSignUpFlow: (value: boolean) => void; errorMessages: string[]; loading: 'google' | 'github' | 'azure' | 'email' | null; setErrorMessages: (value: string[]) => void; signUpFlow: boolean; }> = ({ onSubmitClick, onSignInWithGoogle, onSignInWithGithub, onSignInWithAzure, setSignUpFlow, errorMessages, loading, setErrorMessages, signUpFlow }) => { const [email, setEmail] = useState(''); const [password, setPassword] = useState(''); const [password2, setPassword2] = useState(''); const [passwordCheck, setPasswordCheck] = useState(false); const disableSubmitButton = !inputHasText(password) || !inputHasText(password2) || password !== password2 || !passwordCheck; const clearAllCookies = useMemoizedFn(() => { Object.keys(Cookies.get()).forEach((cookieName) => { Cookies.remove(cookieName); }); //also clear local storage localStorage.clear(); sessionStorage.clear(); }); const onSubmitClickPreflight = useMemoizedFn(async (d: { email: string; password: string }) => { clearAllCookies(); onSubmitClick(d); }); useHotkeys( 'meta+shift+b', (e) => { setSignUpFlow(false); onSubmitClickPreflight({ email: DEFAULT_CREDENTIALS.email, password: DEFAULT_CREDENTIALS.password }); }, { preventDefault: true } ); return ( <>
{ v.preventDefault(); onSubmitClickPreflight({ email, password }); }}>
{ setEmail(v.target.value); }} disabled={!!loading} autoComplete="email" tabIndex={4} />
{ setPassword(v.target.value); }} disabled={!!loading} id="password" type="password" name="password" placeholder="Password" autoComplete="new-password" tabIndex={5} />
{signUpFlow && ( <> { setPassword2(v.target.value); }} disabled={!!loading} id="password2" type="password" name="password2" placeholder="Confirm password" autoComplete="new-password" tabIndex={6} /> {password && ( )} )}
{errorMessages.map((message, index) => ( ))}
{!signUpFlow && }
); }; const SignUpSuccess: React.FC<{ setSignUpSuccess: (value: boolean) => void; setSignUpFlow: (value: boolean) => void; }> = ({ setSignUpSuccess, setSignUpFlow }) => { return ( { setSignUpSuccess(false); setSignUpFlow(true); }}> Go to Login ]} /> ); }; const WelcomeText: React.FC<{ signUpFlow: boolean; }> = ({ signUpFlow }) => { const text = !signUpFlow ? `Sign in` : `Sign up for free`; return ( {text} ); }; const LoginAlertMessage: React.FC<{ message: string; }> = ({ message }) => { return ( {message} ); }; const AlreadyHaveAccount: React.FC<{ setErrorMessages: (value: string[]) => void; setPassword2: (value: string) => void; setSignUpFlow: (value: boolean) => void; signUpFlow: boolean; }> = React.memo(({ setErrorMessages, setPassword2, setSignUpFlow, signUpFlow }) => { return (
{signUpFlow ? `Already have an account? ` : `Don't already have an account? `} { setErrorMessages([]); setPassword2(''); setSignUpFlow(!signUpFlow); }}> {!signUpFlow ? `Sign up` : `Sign in`}
); }); AlreadyHaveAccount.displayName = 'AlreadyHaveAccount'; const ResetPasswordLink: React.FC<{ email: string; tabIndex?: number }> = ({ email, tabIndex }) => { const scrubbedEmail = useMemo(() => { if (!email || !isValidEmail(email)) return ''; try { return encodeURIComponent(email.trim()); } catch (error) { console.error('Error encoding email:', error); return ''; } }, [email]); return ( Reset password ); };