feat(SW-3125): Move client trpc setup * Move client trpc to package * Client setup in partner-sas * Add todo Approved-by: Linus Flood
293 lines
9.5 KiB
TypeScript
293 lines
9.5 KiB
TypeScript
"use client"
|
|
|
|
import { zodResolver } from "@hookform/resolvers/zod"
|
|
import { cx } from "class-variance-authority"
|
|
import { useRouter } from "next/navigation"
|
|
import { FormProvider, useForm } from "react-hook-form"
|
|
import { useIntl } from "react-intl"
|
|
|
|
import { Button } from "@scandic-hotels/design-system/Button"
|
|
import { Typography } from "@scandic-hotels/design-system/Typography"
|
|
import { trpc } from "@scandic-hotels/trpc/client"
|
|
import {
|
|
type SignUpSchema,
|
|
signUpSchema,
|
|
} from "@scandic-hotels/trpc/routers/user/schemas"
|
|
|
|
import { getDefaultCountryFromLang } from "@/constants/languages"
|
|
import {
|
|
membershipTermsAndConditions,
|
|
privacyPolicy,
|
|
} from "@/constants/webHrefs"
|
|
|
|
import Checkbox from "@/components/TempDesignSystem/Form/Checkbox"
|
|
import CountrySelect from "@/components/TempDesignSystem/Form/Country"
|
|
import DateSelect from "@/components/TempDesignSystem/Form/Date"
|
|
import Input from "@/components/TempDesignSystem/Form/Input"
|
|
import PasswordInput from "@/components/TempDesignSystem/Form/PasswordInput"
|
|
import Phone from "@/components/TempDesignSystem/Form/Phone"
|
|
import Link from "@/components/TempDesignSystem/Link"
|
|
import { toast } from "@/components/TempDesignSystem/Toasts"
|
|
import { useFormTracking } from "@/components/TrackingSDK/hooks"
|
|
import useLang from "@/hooks/useLang"
|
|
import { formatPhoneNumber } from "@/utils/phone"
|
|
|
|
// import { type SignUpSchema, signUpSchema } from "./schema"
|
|
import styles from "./form.module.css"
|
|
|
|
import type { SignUpFormProps } from "@/types/components/form/signupForm"
|
|
|
|
export default function SignupForm({ title }: SignUpFormProps) {
|
|
const intl = useIntl()
|
|
const router = useRouter()
|
|
const lang = useLang()
|
|
|
|
const signupButtonText = intl.formatMessage({
|
|
defaultMessage: "Join now",
|
|
})
|
|
|
|
const signup = trpc.user.signup.useMutation({
|
|
onSuccess: (data) => {
|
|
if (data.success && data.redirectUrl) {
|
|
router.push(data.redirectUrl)
|
|
}
|
|
},
|
|
onError: (error) => {
|
|
if (error.data?.code === "CONFLICT") {
|
|
toast.error(
|
|
intl.formatMessage({
|
|
defaultMessage:
|
|
"An account with this email already exists. Please try signing in instead.",
|
|
})
|
|
)
|
|
return
|
|
}
|
|
|
|
toast.error(
|
|
intl.formatMessage({
|
|
defaultMessage: "Something went wrong!",
|
|
})
|
|
)
|
|
console.error("Component Signup error:", error)
|
|
},
|
|
})
|
|
|
|
const methods = useForm<SignUpSchema>({
|
|
defaultValues: {
|
|
firstName: "",
|
|
lastName: "",
|
|
email: "",
|
|
phoneNumber: "",
|
|
phoneNumberCC: getDefaultCountryFromLang(lang),
|
|
dateOfBirth: "",
|
|
address: {
|
|
countryCode: "",
|
|
zipCode: "",
|
|
},
|
|
password: "",
|
|
termsAccepted: false,
|
|
},
|
|
mode: "all",
|
|
criteriaMode: "all",
|
|
resolver: zodResolver(signUpSchema),
|
|
reValidateMode: "onChange",
|
|
shouldFocusError: true,
|
|
})
|
|
|
|
const { control, subscribe } = methods
|
|
|
|
const { trackFormSubmit } = useFormTracking("signup", subscribe, control)
|
|
|
|
async function onSubmit(data: SignUpSchema) {
|
|
const phoneNumber = formatPhoneNumber(data.phoneNumber, data.phoneNumberCC)
|
|
signup.mutate({ ...data, phoneNumber, language: lang })
|
|
trackFormSubmit()
|
|
}
|
|
|
|
return (
|
|
<div className={styles.formWrapper}>
|
|
{title ? (
|
|
<Typography variant="Title/md">
|
|
<h2>{title}</h2>
|
|
</Typography>
|
|
) : null}
|
|
<FormProvider {...methods}>
|
|
<form
|
|
className={styles.form}
|
|
id="register"
|
|
onSubmit={methods.handleSubmit(onSubmit)}
|
|
>
|
|
<section className={styles.userInfo}>
|
|
<div className={styles.container}>
|
|
<header>
|
|
<Typography variant="Title/Subtitle/md">
|
|
<h3>
|
|
{intl.formatMessage({
|
|
defaultMessage: "Contact information",
|
|
})}
|
|
</h3>
|
|
</Typography>
|
|
</header>
|
|
<div className={styles.nameInputs}>
|
|
<Input
|
|
label={intl.formatMessage({
|
|
defaultMessage: "First name",
|
|
})}
|
|
name="firstName"
|
|
registerOptions={{ required: true }}
|
|
/>
|
|
<Input
|
|
label={intl.formatMessage({
|
|
defaultMessage: "Last name",
|
|
})}
|
|
name="lastName"
|
|
registerOptions={{ required: true }}
|
|
/>
|
|
</div>
|
|
</div>
|
|
<div className={styles.dateField}>
|
|
<Typography variant="Body/Supporting text (caption)/smBold">
|
|
<p>
|
|
{intl.formatMessage({
|
|
defaultMessage: "Birth date",
|
|
})}
|
|
</p>
|
|
</Typography>
|
|
<DateSelect
|
|
name="dateOfBirth"
|
|
registerOptions={{ required: true }}
|
|
/>
|
|
</div>
|
|
<div className={cx(styles.container, styles.additional)}>
|
|
<Input
|
|
label={intl.formatMessage({
|
|
defaultMessage: "Zip code",
|
|
})}
|
|
name="address.zipCode"
|
|
registerOptions={{ required: true }}
|
|
/>
|
|
<CountrySelect
|
|
label={intl.formatMessage({
|
|
defaultMessage: "Country",
|
|
})}
|
|
name="address.countryCode"
|
|
registerOptions={{ required: true }}
|
|
/>
|
|
<Input
|
|
label={intl.formatMessage({
|
|
defaultMessage: "Email address",
|
|
})}
|
|
name="email"
|
|
registerOptions={{ required: true }}
|
|
type="email"
|
|
/>
|
|
<Phone
|
|
label={intl.formatMessage({
|
|
defaultMessage: "Phone number",
|
|
})}
|
|
name="phoneNumber"
|
|
/>
|
|
</div>
|
|
</section>
|
|
<section className={styles.password}>
|
|
<header>
|
|
<Typography variant="Title/Subtitle/md">
|
|
<h3>
|
|
{intl.formatMessage({
|
|
defaultMessage: "Password",
|
|
})}
|
|
</h3>
|
|
</Typography>
|
|
</header>
|
|
<PasswordInput
|
|
name="password"
|
|
label={intl.formatMessage({
|
|
defaultMessage: "Password",
|
|
})}
|
|
isNewPassword
|
|
/>
|
|
</section>
|
|
<section className={styles.terms}>
|
|
<header>
|
|
<Typography variant="Title/Subtitle/md">
|
|
<h3>
|
|
{intl.formatMessage({
|
|
defaultMessage: "Terms and conditions",
|
|
})}
|
|
</h3>
|
|
</Typography>
|
|
</header>
|
|
<Checkbox name="termsAccepted" registerOptions={{ required: true }}>
|
|
{intl.formatMessage({
|
|
defaultMessage: "I accept",
|
|
})}
|
|
</Checkbox>
|
|
<Typography variant="Body/Paragraph/mdRegular">
|
|
<p>
|
|
{intl.formatMessage(
|
|
{
|
|
defaultMessage:
|
|
"By accepting the <termsAndConditionsLink>Terms and Conditions for Scandic Friends</termsAndConditionsLink> I understand that my personal data will be processed in accordance with <privacyPolicy>Scandic's Privacy Policy</privacyPolicy>.",
|
|
},
|
|
{
|
|
termsAndConditionsLink: (str) => (
|
|
<Link
|
|
textDecoration="underline"
|
|
color="Text/Interactive/Secondary"
|
|
target="_blank"
|
|
href={membershipTermsAndConditions[lang]}
|
|
>
|
|
{str}
|
|
</Link>
|
|
),
|
|
privacyPolicy: (str) => (
|
|
<Link
|
|
textDecoration="underline"
|
|
color="Text/Interactive/Secondary"
|
|
target="_blank"
|
|
href={privacyPolicy[lang]}
|
|
>
|
|
{str}
|
|
</Link>
|
|
),
|
|
}
|
|
)}
|
|
</p>
|
|
</Typography>
|
|
</section>
|
|
|
|
{/*
|
|
This is a manual validation trigger workaround:
|
|
- The Controller component (which Input uses) doesn't re-render on submit,
|
|
which prevents automatic error display.
|
|
- Future fix requires Input component refactoring (out of scope for now).
|
|
*/}
|
|
{!methods.formState.isValid ? (
|
|
<Button
|
|
className={styles.signUpButton}
|
|
type="submit"
|
|
variant="Primary"
|
|
onPress={() => methods.trigger()}
|
|
typography="Body/Paragraph/mdBold"
|
|
data-testid="trigger-validation"
|
|
>
|
|
{signupButtonText}
|
|
</Button>
|
|
) : (
|
|
<Button
|
|
className={styles.signUpButton}
|
|
type="submit"
|
|
variant="Primary"
|
|
isDisabled={methods.formState.isSubmitting || signup.isPending}
|
|
typography="Body/Paragraph/mdBold"
|
|
data-testid="submit"
|
|
>
|
|
{signupButtonText}
|
|
</Button>
|
|
)}
|
|
</form>
|
|
</FormProvider>
|
|
</div>
|
|
)
|
|
}
|