import { useState } from 'react' import { Field, Form, Formik } from 'formik' import { Link, Navigate, useLocation, useNavigate, useSearchParams, } from 'react-router-dom' import { Helmet } from 'react-helmet-async' import { inferMutationInput, trpc } from '~/utils/trpc' import IVInputField from '~/components/IVInputField' import IVButton from '~/components/IVButton' import { tryLogin } from '~/utils/auth' import useHasSession from '~/utils/useHasSession' import IVSpinner from '~/components/IVSpinner' import classNames from 'classnames' import { ReferralInfo, referralInfoSchema } from '~/utils/referralSchema' import GoogleIcon from '~/icons/compiled/Google' import { DateTime } from 'luxon' import AuthPageHeader from '~/components/AuthPageHeader' import { REFERRAL_LOCAL_STORAGE_KEY } from '~/utils/isomorphicConsts' export default function SignupPage() { const [hasLoginError, setHasLoginError] = useState(false) const [hasPromoCode, setHasPromoCode] = useState(false) const createUser = trpc.useMutation('auth.signup') const checkEmail = trpc.useMutation('auth.signup.check-email') const [searchParams] = useSearchParams() const invitationId = searchParams.get('token') const exampleSlug = searchParams.get('example') || undefined const intendedPlanName = searchParams.get('plan') || undefined const integrations = trpc.useQuery(['dashboard.integrations']) const signupCheck = trpc.useQuery(['auth.signup.check', { invitationId }]) const { hasSession } = useHasSession() const navigate = useNavigate() const location = useLocation() const isEmailValidated = location.state === 'confirm' || !!signupCheck.data?.invitation const mutation = isEmailValidated ? createUser : checkEmail if (hasSession) { return } if (invitationId) { if ( signupCheck.error?.message?.includes( 'this invitation is for another email address' ) ) { // is logged in, should not be on the signup page return ( ) } if (signupCheck.data?.isLoginRequired) { return ( ) } if (!signupCheck.data) { return (
Sign up | Interval
) } } const accountDisabled = searchParams.has('ACCOUNT_DISABLED') const registrationDisabled = createUser.error?.data?.code === 'FORBIDDEN' || searchParams.has('REGISTRATION_DISABLED') const fallbackError = "Sorry, we failed to create your account. Are you sure an account doesn't already exist with that email address?" return (
Sign up | Interval
{accountDisabled ? ( <>

Sorry, we were unable to sign you in because the account with that email address has been disabled.

Please reach out to help@interval.com with any questions or if you think this is a mistake.

) : registrationDisabled ? ( <>

Sorry, we are not currently accepting new user registrations.

We've added your information to our waitlist and will reach out as soon as registrations are available again. Thank you for your interest!

) : ( <>
> initialValues={{ email: signupCheck.data?.invitation?.email || '', firstName: '', lastName: '', password: '', organizationName: '', invitationId, timeZoneName: DateTime.now().zoneName ?? '', onboardingExampleSlug: exampleSlug, intendedPlanName, }} onSubmit={async (values, formikHelpers) => { if (createUser.isLoading) return if (!isEmailValidated) { checkEmail.mutate( { email: values.email }, { async onSuccess() { // resets submitCount to 0 so validation warnings aren't shown while typing formikHelpers.resetForm({ values }) navigate(location.pathname + location.search, { state: 'confirm', }) }, } ) return } let referralInfo: ReferralInfo | undefined if (typeof window !== undefined) { const savedData = window.sessionStorage.getItem( REFERRAL_LOCAL_STORAGE_KEY ) if (savedData) { try { referralInfo = referralInfoSchema.parse( JSON.parse(savedData) ) } catch (err) { console.error('Invalid referral info', err) } } } createUser.mutate( { ...values, referralInfo }, { async onSuccess(user) { // We could probably combine these into a single endpoint but I think this is fine const r = await tryLogin({ email: user.email, password: values.password, }) if (r.ok) { window.location.assign('/dashboard/develop/actions') } else { setHasLoginError(true) } }, } ) }} validate={values => { if (values.password.length && values.password.length < 6) { return { password: 'Password must be at least 6 characters', } } return {} }} > {({ values, errors, touched }) => { const googleParams = new URLSearchParams() if (invitationId) googleParams.set('token', invitationId) if (intendedPlanName) googleParams.set('plan', intendedPlanName) return (
{signupCheck.data?.invitation && (
{signupCheck.data?.invitation.organization.name} {' '} is inviting you to join Interval.
)} {integrations.data?.workos && !isEmailValidated && !signupCheck.data?.invitation && ( <>
Continue with Google } className="w-full" />

or
)} {isEmailValidated && ( <>
{(!invitationId || signupCheck.isError) && ( )} {!signupCheck.data?.invitation && ( <> {hasPromoCode ? ( ) : ( )} )} )} {mutation.isError && (

{mutation.error.message || fallbackError}

{mutation.error.message?.includes( 'already exists' ) && (

Want to{' '} log in ?

)}
)} {hasLoginError && (
Your account was created, but there was a problem logging in. Please contact us for support.
)}
{!signupCheck.data?.invitation && (
Already have an Interval account?
)}
) }} )}
) }