Files
web/apps/scandic-web/components/Forms/Signup/index.tsx
Anton Gunnarsson 80100e7631 Merged in monorepo-step-1 (pull request #1080)
Migrate to a monorepo setup - step 1

* Move web to subfolder /apps/scandic-web

* Yarn + transitive deps

- Move to yarn
- design-system package removed for now since yarn doesn't
support the parameter for token (ie project currently broken)
- Add missing transitive dependencies as Yarn otherwise
prevents these imports
- VS Code doesn't pick up TS path aliases unless you open
/apps/scandic-web instead of root (will be fixed with monorepo)

* Pin framer-motion to temporarily fix typing issue

https://github.com/adobe/react-spectrum/issues/7494

* Pin zod to avoid typ error

There seems to have been a breaking change in the types
returned by zod where error is now returned as undefined
instead of missing in the type. We should just handle this
but to avoid merge conflicts just pin the dependency for
now.

* Pin react-intl version

Pin version of react-intl to avoid tiny type issue where formatMessage
does not accept a generic any more. This will be fixed in a future
commit, but to avoid merge conflicts just pin for now.

* Pin typescript version

Temporarily pin version as newer versions as stricter and results in
a type error. Will be fixed in future commit after merge.

* Setup workspaces

* Add design-system as a monorepo package

* Remove unused env var DESIGN_SYSTEM_ACCESS_TOKEN

* Fix husky for monorepo setup

* Update netlify.toml

* Add lint script to root package.json

* Add stub readme

* Fix react-intl formatMessage types

* Test netlify.toml in root

* Remove root toml

* Update netlify.toml publish path

* Remove package-lock.json

* Update build for branch/preview builds


Approved-by: Linus Flood
2025-02-26 10:36:17 +00:00

230 lines
7.6 KiB
TypeScript

"use client"
import { zodResolver } from "@hookform/resolvers/zod"
import { useRouter } from "next/navigation"
import { FormProvider, useForm } from "react-hook-form"
import { useIntl } from "react-intl"
import {
membershipTermsAndConditions,
privacyPolicy,
} from "@/constants/currentWebHrefs"
import { trpc } from "@/lib/trpc/client"
import Button from "@/components/TempDesignSystem/Button"
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 NewPassword from "@/components/TempDesignSystem/Form/NewPassword"
import Phone from "@/components/TempDesignSystem/Form/Phone"
import Link from "@/components/TempDesignSystem/Link"
import Body from "@/components/TempDesignSystem/Text/Body"
import Caption from "@/components/TempDesignSystem/Text/Caption"
import Subtitle from "@/components/TempDesignSystem/Text/Subtitle"
import Title from "@/components/TempDesignSystem/Text/Title"
import { toast } from "@/components/TempDesignSystem/Toasts"
import useLang from "@/hooks/useLang"
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 country = intl.formatMessage({ id: "Country" })
const email = intl.formatMessage({ id: "Email address" })
const phoneNumber = intl.formatMessage({ id: "Phone number" })
const zipCode = intl.formatMessage({ id: "Zip code" })
const signupButtonText = intl.formatMessage({
id: "Join now",
})
const signup = trpc.user.signup.useMutation({
onSuccess: (data) => {
if (data.success && data.redirectUrl) {
router.push(data.redirectUrl)
}
},
onError: (error) => {
toast.error(intl.formatMessage({ id: "Something went wrong!" }))
console.error("Component Signup error:", error)
},
})
const methods = useForm<SignUpSchema>({
defaultValues: {
firstName: "",
lastName: "",
email: "",
phoneNumber: "",
dateOfBirth: "",
address: {
countryCode: "",
zipCode: "",
},
password: "",
termsAccepted: false,
},
mode: "all",
criteriaMode: "all",
resolver: zodResolver(signUpSchema),
reValidateMode: "onChange",
shouldFocusError: true,
})
async function onSubmit(data: SignUpSchema) {
signup.mutate({ ...data, language: lang })
}
return (
<section className={styles.formWrapper}>
<Title as="h3">{title}</Title>
<FormProvider {...methods}>
<form
className={styles.form}
id="register"
onSubmit={methods.handleSubmit(onSubmit)}
>
<section className={styles.userInfo}>
<div className={styles.container}>
<header>
<Subtitle type="two">
{intl.formatMessage({ id: "Contact information" })}
</Subtitle>
</header>
<div className={styles.nameInputs}>
<Input
label={intl.formatMessage({ id: "First name" })}
name="firstName"
registerOptions={{ required: true }}
/>
<Input
label={intl.formatMessage({ id: "Last name" })}
name="lastName"
registerOptions={{ required: true }}
/>
</div>
</div>
<div className={styles.dateField}>
<header>
<Caption type="bold">
{intl.formatMessage({ id: "Birth date" })}
</Caption>
</header>
<DateSelect
name="dateOfBirth"
registerOptions={{ required: true }}
/>
</div>
<div className={styles.container}>
<Input
label={zipCode}
name="address.zipCode"
registerOptions={{ required: true }}
/>
<CountrySelect
label={country}
name="address.countryCode"
registerOptions={{ required: true }}
/>
</div>
<Input
label={email}
name="email"
registerOptions={{ required: true }}
type="email"
/>
<Phone label={phoneNumber} name="phoneNumber" />
</section>
<section className={styles.password}>
<header>
<Subtitle type="two">
{intl.formatMessage({ id: "Password" })}
</Subtitle>
</header>
<NewPassword
name="password"
label={intl.formatMessage({ id: "Password" })}
/>
</section>
<section className={styles.terms}>
<header>
<Subtitle type="two">
{intl.formatMessage({ id: "Terms and conditions" })}
</Subtitle>
</header>
<Checkbox name="termsAccepted" registerOptions={{ required: true }}>
{intl.formatMessage({ id: "I accept" })}
</Checkbox>
{/* TODO: Update copy once ready */}
<Body>
{intl.formatMessage(
{
id: "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
variant="underscored"
color="peach80"
target="_blank"
href={membershipTermsAndConditions[lang]}
>
{str}
</Link>
),
privacyPolicy: (str) => (
<Link
variant="underscored"
color="peach80"
target="_blank"
href={privacyPolicy[lang]}
>
{str}
</Link>
),
}
)}
</Body>
</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"
theme="base"
intent="primary"
onClick={() => methods.trigger()}
data-testid="trigger-validation"
>
{signupButtonText}
</Button>
) : (
<Button
className={styles.signUpButton}
type="submit"
theme="base"
intent="primary"
disabled={methods.formState.isSubmitting || signup.isPending}
data-testid="submit"
>
{signupButtonText}
</Button>
)}
</form>
</FormProvider>
</section>
)
}