Press n or j to go to the next uncovered block, b, p or k for the previous block.
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 | 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 22x 55x 55x 55x 55x 7x 7x 7x 6x 6x 7x 6x 55x 7x 55x 20x | import { useMutation } from "@tanstack/react-query";
import Alert from "components/Alert";
import { doAntibot } from "features/antibot/antibot";
import { useAuthContext } from "features/auth/AuthProvider";
import { StyledButton, StyledInputLabel, StyledTextField } from "features/auth/useAuthStyles";
import { RpcError } from "grpc-web";
import { useTranslation } from "i18n";
import { AUTH, GLOBAL } from "i18n/namespaces";
import { useRef } from "react";
import { useForm } from "react-hook-form";
import { service } from "service";
import {
emailValidationPattern,
lowercaseAndTrimField,
nameMaxLength,
nameMinLength,
validateNameChars,
} from "utils/validation";
type SignupBasicInputs = {
name: string;
email: string;
};
interface BasicFormProps {
submitText?: string;
successCallback?: () => void;
inviteCode?: string;
}
export default function BasicForm({ submitText, successCallback, inviteCode }: BasicFormProps) {
const { t } = useTranslation([AUTH, GLOBAL]);
const { authActions } = useAuthContext();
const {
register,
handleSubmit,
formState: { errors },
} = useForm<SignupBasicInputs>({
mode: "onBlur",
shouldUnregister: false,
});
const mutation = useMutation<void, RpcError, SignupBasicInputs>({
mutationFn: async (data) => {
const sanitizedEmail = lowercaseAndTrimField(data.email);
const sanitizedName = data.name.trim();
const state = await service.auth.startSignup(sanitizedName, sanitizedEmail, inviteCode);
doAntibot("signup");
return authActions.updateSignupState(state);
},
onSettled() {
window.scroll({ top: 0, behavior: "smooth" });
},
onSuccess() {
Iif (successCallback !== undefined) {
successCallback();
}
},
});
const onSubmit = handleSubmit((data: SignupBasicInputs) => {
mutation.mutate(data);
});
const nameInputRef = useRef<HTMLInputElement>(undefined);
return (
<>
{mutation.error && <Alert severity="error">{mutation.error.message || ""}</Alert>}
<form onSubmit={onSubmit}>
<StyledInputLabel htmlFor="name">{t("auth:basic_form.name.field_label")}</StyledInputLabel>
<StyledTextField
id="name"
{...register("name", {
required: t("auth:basic_form.name.required_error"),
minLength: {
value: nameMinLength,
message: t("auth:basic_form.name.min_length_error"),
},
maxLength: {
value: nameMaxLength,
message: t("auth:basic_form.name.max_length_error"),
},
validate: (value: string) => validateNameChars(value) || t("auth:basic_form.name.invalid_characters_error"),
})}
fullWidth
name="name"
placeholder={t("auth:basic_form.name.field_label")}
variant="outlined"
inputRef={(el: HTMLInputElement | null) => {
if (!nameInputRef.current) el?.focus();
if (el) nameInputRef.current = el;
}}
helperText={errors?.name?.message ?? " "}
error={!!errors?.name?.message}
autoComplete="name"
/>
<StyledInputLabel htmlFor="email">{t("auth:basic_form.email.field_label")}</StyledInputLabel>
<StyledTextField
id="email"
{...register("email", {
pattern: {
message: t("auth:basic_form.email.empty_error"),
value: emailValidationPattern,
},
required: t("auth:basic_form.email.required_error"),
})}
fullWidth
name="email"
placeholder="you@couchers.org"
variant="outlined"
helperText={errors?.email?.message ?? " "}
error={!!errors?.email?.message}
autoComplete="email"
/>
<StyledButton onClick={onSubmit} type="submit" loading={mutation.isPending} fullWidth>
{submitText || t("global:continue")}
</StyledButton>
</form>
</>
);
}
|