import { Form, useStoreState } from "@ariakit/react"; import { type Dispatch, type SetStateAction, useState } from "react"; import { Link, useSearchParams } from "wouter"; import { useAppConfig, useClient } from "../AppConfig/AppConfig.tsx"; import { cx } from "../class-names.ts"; import { interactiveText } from "../common.css.ts"; import { getSubmitFailureMessage } from "../failureMessages.ts"; import { Letterhead, LetterheadHeading, LetterheadParagraph, LetterheadSubmitButton, } from "../Letterhead/index.tsx"; import { button } from "../Letterhead/style.css.ts"; import { InputsStack, LetterheadCheckboxField, LetterheadFormActions, LetterheadHeader, LetterheadSubmitError, LetterheadTextField, } from "../LetterheadForm/index.tsx"; import { useForm } from "../use-form.ts"; import { validEmail, validPassword } from "../validations.ts"; import { AlreadyLoggedInView } from "./AlreadyLoggedInView.tsx"; import { FailureFallbackView } from "./FailureFallbackView.tsx"; import { LoadingView } from "./LoadingView.tsx"; import { NoConnectionView } from "./NoConnectionView.tsx"; import type { DefaultFormValues } from "./types.ts"; import { useFetchCurrentUser } from "./useFetchCurrentUser.tsx"; import { useRedirectPath } from "./useRedirectPath.ts"; type SetStep = Dispatch>; type InitialStepProps = { setStep: SetStep; defaultValues?: DefaultFormValues; }; function InitialStep(props: InitialStepProps) { const [params] = useSearchParams(); const { client, placeholders, hrefs } = useAppConfig(); const { defaultValues, setStep } = props; const { form, submitName } = useForm({ defaultValues: { email: defaultValues?.email ?? "", password: "", acceptedTos: false, subscribedToNewsletter: false, }, validate: { email: validEmail, password: validPassword, }, async onSubmit({ values }) { const op = await client.join(values); return op.mapFailure((failure) => { return getSubmitFailureMessage(failure, { 409: "🙀 This email address is already in use. Please choose a different one, or log in.", }); }); }, onSuccess(response, form) { setStep({ type: "SUBMIT_CODE", tokenId: response.tokenId, email: form.values.email, }); }, }); const emailValue = useStoreState(form, (s) => s.values.email); return ( Join Indie Tabletop Club Enter your email and choose a strong password. We will send you a one-time code to verify your account.
Subscribe to The Changelog, an at-most-once-a-month newsletter about updates and new releases. You can unsubscribe any time with one click. } /> I accept the{" "} Terms of Service . } /> Join {"Have an existing account? "} Log in {"."}
); } type SubmitCodeStepProps = { tokenId: string; setStep: SetStep; email: string; }; function SubmitCodeStep(props: SubmitCodeStepProps) { const { tokenId, email, setStep } = props; const client = useClient(); const { form, submitName } = useForm({ defaultValues: { code: "", }, async onSubmit({ values }) { const op = await client.verifyUser({ ...values, tokenId }); return op.mapFailure((failure) => { return getSubmitFailureMessage(failure, { 404: "🚫 This code is incorrect or expired. Please try again.", }); }); }, onSuccess() { setStep({ type: "SUCCESS" }); }, }); return (
Verify account We've sent a one-time code to {email}. Please, enter the code in the field below to verify your account. Verify
); } function SuccessStep() { const { hrefs } = useAppConfig(); const redirectPath = useRedirectPath(); return ( Success! Your Indie Tabletop Club account has been verified, yay! {redirectPath ? "Continue" : "Go to dashboard"} ); } type JoinStep = | { type: "INITIAL" } | { type: "SUBMIT_CODE"; tokenId: string; email: string } | { type: "SUCCESS" }; type JoinFlowProps = { defaultValues?: DefaultFormValues; }; function JoinFlow(props: JoinFlowProps) { const [step, setStep] = useState({ type: "INITIAL" }); switch (step.type) { case "INITIAL": { return ; } case "SUBMIT_CODE": { return ; } case "SUCCESS": { return ; } } } export type JoinCardProps = { defaultValues?: DefaultFormValues; }; /** * Allows the user to join Indie Tabletop Club. * * Will automatically use the `redirectTo` query param value as the redirect * location once the user creates and verifies their account. * * Otherwise a success screen will be shown and the user will be directed to * the dashboard. */ export function JoinCard(props: JoinCardProps) { const { result, latestAttemptTs, reload } = useFetchCurrentUser(); return result.unpack( (currentUser) => { return ; }, (failure) => { if (failure.type === "API_ERROR" && failure.code === 401) { return ; } if (failure.type === "NETWORK_ERROR") { return ( reload()} /> ); } return ; }, () => { return ; }, ); }