import { useForm } from "@tanstack/react-form"; import { Link, createFileRoute, useNavigate } from "@tanstack/react-router"; import { useState } from "react"; import { AuthPageCard, AuthMethodChooser, authRedirectSearchSchema, useAuthPageState, } from "@/client/features/auth/AuthPage"; import { getFieldError, getFormError } from "@/client/lib/forms"; import { captureClientEvent } from "@/client/lib/posthog"; import { authClient } from "@/lib/auth-client"; import { getSignInSearch, getVerifyEmailSearch } from "@/lib/auth-redirect"; import { z } from "zod"; const signInSchema = z.object({ email: z.string().trim().email("Enter a valid email address."), password: z.string().min(1, "Enter your password."), }); export const Route = createFileRoute("/_auth/sign-in")({ validateSearch: authRedirectSearchSchema, component: SignInPage, }); function SignInPage() { const search = Route.useSearch(); const navigate = useNavigate(); const { redirectTo, oauthQuery, isHostedMode, isSessionMode } = useAuthPageState(search.redirect); const authCallbackURL = redirectTo; // `team` mode has no Google button, so go straight to the email/password form. const [showEmailForm, setShowEmailForm] = useState(!isHostedMode); const [isStartingGoogle, setIsStartingGoogle] = useState(false); const [socialError, setSocialError] = useState(null); const form = useForm({ defaultValues: { email: "", password: "", }, validators: { onSubmit: signInSchema, }, onSubmit: async ({ formApi, value }) => { try { const email = value.email.trim(); captureClientEvent("auth:sign_in_submit", { redirect_to: redirectTo, }); const result = await authClient.signIn.email({ email, password: value.password, callbackURL: authCallbackURL, ...(oauthQuery ? { oauth_query: oauthQuery } : {}), }); if (!result.error) { captureClientEvent("auth:sign_in_success", { redirect_to: redirectTo, }); return; } if (result.error.status === 403) { captureClientEvent("auth:sign_in_block_unverified", { redirect_to: redirectTo, }); // Email not verified yet: send them to the verification page (which // shows "check your inbox" + resend) instead of leaving them on a // sign-in form that will keep rejecting them. void navigate({ to: "/verify-email", search: getVerifyEmailSearch(email, redirectTo), }); return; } formApi.setErrorMap({ onSubmit: { form: result.error.message || "We couldn't sign you in.", fields: {}, }, }); } catch { formApi.setErrorMap({ onSubmit: { form: "Unable to sign in right now. Please try again.", fields: {}, }, }); } }, }); async function handleContinueWithGoogle() { setSocialError(null); setIsStartingGoogle(true); try { captureClientEvent("auth:sign_in_google_start", { redirect_to: redirectTo, }); const result = await authClient.signIn.social({ provider: "google", callbackURL: authCallbackURL, }); if (result.error) { setSocialError( result.error.message || "Google sign in is not available right now.", ); setIsStartingGoogle(false); } } catch { setSocialError("Google sign in is not available right now."); setIsStartingGoogle(false); } } return ( {showEmailForm ? ( Forgot password? ) : null} Create account ) : null } > {!showEmailForm ? ( <> { void handleContinueWithGoogle(); }} onContinueWithEmail={() => { setShowEmailForm(true); setSocialError(null); }} /> {socialError ? (

{socialError}

) : null} ) : (
{ event.preventDefault(); void form.handleSubmit(); }} > {(field) => { const error = getFieldError(field.state.meta.errors); return (
field.handleChange(event.target.value)} autoComplete="email" disabled={!isSessionMode} required /> {error ? (

{error}

) : null}
); }}
{(field) => { const error = getFieldError(field.state.meta.errors); return (
field.handleChange(event.target.value)} autoComplete="current-password" disabled={!isSessionMode} required /> {error ? (

{error}

) : null}
); }}
({ submitError: state.errorMap.onSubmit, isSubmitting: state.isSubmitting, })} > {({ submitError, isSubmitting }) => { const errorMessage = getFormError(submitError); return ( <> {errorMessage ? (

{errorMessage}

) : null} ); }}
)}
); }