feat(LOY-268): Feature branch for profiling consent work * feat: Add feature branch for profile and consent work * Merged in feat/LOY-268-profile-consent-banner-comp (pull request #2908) Feat/LOY-358 profile consent banner component * feat: Add feature branch for profile and consent work * feat(LOY-268): create banner * feat(LOY-268): Create personalization banner component Approved-by: Chuma Mcphoy (We Ahead) * feat(LOY-268): create banner * feat(LOY-268): Create personalization banner component * Merged in feat/profile-consent-contentstack (pull request #2921) Feat(LOY-389): Profile consent in Contentstack * feat(LOY-268): create banner * feat(LOY-268): Create personalization banner component * chore(LOY-348): add profiling consent as CS entry * chore(LOY-348): add banner as dynamic content Approved-by: Chuma Mcphoy (We Ahead) * Merged in feat/LOY-347-Profile-consent-modal-phase-1 (pull request #2901) Feat(LOY-347): Profiling Consent Modal (phase 1) * feat(LOY-347): Profile Consent Modal base functionality * feat(LOY-347): Add Icon * feat(LOY-347): Add Benefit Cards to Profile consent modal * feat(LOY-347): Add accordion to profile consent modal * fix(LOY-347): scroll behaviour * Fix(LOY-347): fade in/out animations of profile consent modal * fix(LOY-347): White Ellipsis Around Icons * feat(LOY-347): Implement ability to open modal from banner * fix(LOY-347): minor fixes * fix(LOY-347): replace old spa icon * fix(LOY-347): re-add env vars * fix(LOY-347): File renaming and cleanup * chore(LOY-347): Update readme * fix(LOY-347): use correct space var * fix(LOY-347): Add TODO comment for adding link to accordion Approved-by: Matilda Landström * Merged in fix/LOY-386-profiling-consent-modal-contentstack (pull request #2930) Fix(LOY-386): profiling consent modal contentstack * feat(LOY-347): Profile Consent Modal base functionality * feat(LOY-347): Add Icon * feat(LOY-347): Add Benefit Cards to Profile consent modal * feat(LOY-347): Add accordion to profile consent modal * fix(LOY-347): scroll behaviour * Fix(LOY-347): fade in/out animations of profile consent modal * fix(LOY-347): White Ellipsis Around Icons * feat(LOY-347): Implement ability to open modal from banner * fix(LOY-347): minor fixes * fix(LOY-347): replace old spa icon * fix(LOY-347): re-add env vars * fix(LOY-347): File renaming and cleanup * fix(LOY-386): Use contentstack content for profile consent modal * fix(LOY-386): beneift cards schema transform * chore(LOY-386): remove usememo * fix(LOY-386): fix modalcontent check * fix(LOY-386): remove uneeded vars Approved-by: Matilda Landström * Merged in feat/LOY-412-profiling-consent-in-signup (pull request #2976) Feat(LOY-412): profiling consent in signup * feat(LOY-268): create banner * feat(LOY-268): Create personalization banner component * Merged in feat/profile-consent-contentstack (pull request #2921) Feat(LOY-389): Profile consent in Contentstack * feat(LOY-268): create banner * feat(LOY-268): Create personalization banner component * chore(LOY-348): add profiling consent as CS entry * chore(LOY-348): add banner as dynamic content Approved-by: Chuma Mcphoy (We Ahead) * Merged in feat/LOY-347-Profile-consent-modal-phase-1 (pull request #2901) Feat(LOY-347): Profiling Consent Modal (phase 1) * feat(LOY-347): Profile Consent Modal base functionality * feat(LOY-347): Add Icon * feat(LOY-347): Add Benefit Cards to Profile consent modal * feat(LOY-347): Add accordion to profile consent modal * fix(LOY-347): scroll behaviour * Fix(LOY-347): fade in/out animations of profile consent modal * fix(LOY-347): White Ellipsis Around Ico… * Merged in fix/lokalise-ids (pull request #3013) fix: add ids to translations in Profiling Consent * fix: add ids to translations Approved-by: Erik Tiekstra Approved-by: Chuma Mcphoy (We Ahead) * Merged in LOY-436-my-pages-profiling-consent (pull request #3011) LOY-436: Profiling Consent on My Profile, no api Approved-by: Chuma Mcphoy (We Ahead) * Merged in feat/LOY-418-profiling-consent-ui-text-update (pull request #3080) Feat/LOY-418: Profiling consent ui and text update * chore(LOY-418): update /consent buttons * chore(LOY-418): update legal texts Approved-by: Chuma Mcphoy (We Ahead) * Merged in feat/LOY-268-profiling-consent-api (pull request #3088) Approved-by: Chuma Mcphoy (We Ahead) * Merged in feat/LOY-413-Signup-API-Profiling-Consent (pull request #3105) Feat/LOY-413 Signup API Profiling Consent * feat(LOY-413): signup profiling consent * chore(LOY-413): remove todo * fix(LOY-413): only pass in profilingConsent if true * fix(LOY-413): proper spread of profilingConsent in signup input Approved-by: Christel Westerberg * Merged in fix/LOY-413-use-v2-for-signup-call (pull request #3112) fix(LOY-413): use v2 endpoint for profile POST in signup * fix(LOY-413): use v2 endpoint for profile POST in signup Approved-by: Erik Tiekstra * Merged in feat/LOY-268-profiling-consent-improvements (pull request #3094) Feat/LOY-268: Profiling consent improvements * Merged in feat/profile-consent-contentstack (pull request #2921) Feat(LOY-389): Profile consent in Contentstack * feat(LOY-268): create banner * feat(LOY-268): Create personalization banner component * chore(LOY-348): add profiling consent as CS entry * chore(LOY-348): add banner as dynamic content Approved-by: Chuma Mcphoy (We Ahead) * Merged in feat/LOY-347-Profile-consent-modal-phase-1 (pull request #2901) Feat(LOY-347): Profiling Consent Modal (phase 1) * feat(LOY-347): Profile Consent Modal base functionality * feat(LOY-347): Add Icon * feat(LOY-347): Add Benefit Cards to Profile consent modal * feat(LOY-347): Add accordion to profile consent modal * fix(LOY-347): scroll behaviour * Fix(LOY-347): fade in/out animations of profile consent modal * fix(LOY-347): White Ellipsis Around Icons * feat(LOY-347): Implement ability to open modal from banner * fix(LOY-347): min… * Merged in fix/update-graphql (pull request #3130) fix: update graphql * fix: update graphql Approved-by: Chuma Mcphoy (We Ahead) * Merged in feat/LOY-414-prof-consent-tracking (pull request #3127) Feat/LOY-414 profile consent tracking + credit card ui update * chore(LOY-414): create track link function * chore(LOY-414): add cta tracking * chore(LOY-414): add profileConsent to userInfo datalayer * chore(LOY-414): update credit card ui * chore(LOY-414): update tracking specs * chore(LOY-414): add pageView tracking to modal Approved-by: Chuma Mcphoy (We Ahead) * fix: remove old flag * Merged in fix/LOY-268-prof-consent-button-fix (pull request #3162) fix(LOY-268): add button as link * fix(LOY-268): add button as link Approved-by: Chuma Mcphoy (We Ahead) Approved-by: Matilda Landström
437 lines
15 KiB
TypeScript
437 lines
15 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 { membershipTermsAndConditions } from "@scandic-hotels/common/constants/routes/membershipTermsAndConditions"
|
|
import { privacyPolicyRoutes } from "@scandic-hotels/common/constants/routes/privacyPolicyRoutes"
|
|
import { logger } from "@scandic-hotels/common/logger"
|
|
import {
|
|
formatPhoneNumber,
|
|
getDefaultCountryFromLang,
|
|
} from "@scandic-hotels/common/utils/phone"
|
|
import { Button } from "@scandic-hotels/design-system/Button"
|
|
import Checkbox from "@scandic-hotels/design-system/Form/Checkbox"
|
|
import CountrySelect from "@scandic-hotels/design-system/Form/Country"
|
|
import DateSelect from "@scandic-hotels/design-system/Form/Date"
|
|
import Phone from "@scandic-hotels/design-system/Form/Phone"
|
|
import { MaterialIcon } from "@scandic-hotels/design-system/Icons/MaterialIcon"
|
|
import Link from "@scandic-hotels/design-system/OldDSLink"
|
|
import { toast } from "@scandic-hotels/design-system/Toast"
|
|
import { Typography } from "@scandic-hotels/design-system/Typography"
|
|
import { useFormTracking } from "@scandic-hotels/tracking/useFormTracking"
|
|
import { trpc } from "@scandic-hotels/trpc/client"
|
|
import {
|
|
signupErrors,
|
|
type SignUpSchema,
|
|
signUpSchema,
|
|
} from "@scandic-hotels/trpc/routers/user/schemas"
|
|
|
|
import ProfilingConsentModalReadOnly from "@/components/MyPages/ProfilingConsent/Modal/ReadOnly"
|
|
import Input from "@/components/TempDesignSystem/Form/Input"
|
|
import PasswordInput from "@/components/TempDesignSystem/Form/PasswordInput"
|
|
import useLang from "@/hooks/useLang"
|
|
import { getFormattedCountryList } from "@/utils/countries"
|
|
import { getErrorMessage } from "@/utils/getErrorMessage"
|
|
import { requestOpen } from "@/utils/profilingConsent"
|
|
import { trackLinkClick } from "@/utils/tracking/profilingConsent"
|
|
|
|
// 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({
|
|
id: "signUp.joinNow",
|
|
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({
|
|
id: "signUp.accountExistsError",
|
|
defaultMessage:
|
|
"An account with this email already exists. Please try signing in instead.",
|
|
})
|
|
)
|
|
return
|
|
}
|
|
|
|
toast.error(
|
|
intl.formatMessage({
|
|
id: "errorMessage.somethingWentWrong",
|
|
defaultMessage: "Something went wrong!",
|
|
})
|
|
)
|
|
logger.error("Component Signup error:", error)
|
|
},
|
|
})
|
|
|
|
const methods = useForm<SignUpSchema>({
|
|
defaultValues: {
|
|
firstName: "",
|
|
lastName: "",
|
|
email: "",
|
|
phoneNumber: "",
|
|
phoneNumberCC: getDefaultCountryFromLang(lang),
|
|
dateOfBirth: "",
|
|
address: {
|
|
countryCode: "",
|
|
zipCode: "",
|
|
},
|
|
password: "",
|
|
termsAccepted: false,
|
|
profilingConsent: false,
|
|
},
|
|
mode: "all",
|
|
criteriaMode: "all",
|
|
resolver: zodResolver(signUpSchema),
|
|
reValidateMode: "onChange",
|
|
shouldFocusError: true,
|
|
})
|
|
|
|
const {
|
|
control,
|
|
subscribe,
|
|
formState: { errors },
|
|
} = 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()
|
|
}
|
|
|
|
function openPersonalizationModal() {
|
|
trackLinkClick({
|
|
position: "signup",
|
|
name: "read more about personalization at scandic",
|
|
})
|
|
requestOpen()
|
|
}
|
|
|
|
return (
|
|
<div className={styles.formWrapper}>
|
|
<ProfilingConsentModalReadOnly />
|
|
{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({
|
|
id: "signUp.contactInformation",
|
|
defaultMessage: "Contact information",
|
|
})}
|
|
</h3>
|
|
</Typography>
|
|
</header>
|
|
<div className={styles.nameInputs}>
|
|
<Input
|
|
label={intl.formatMessage({
|
|
id: "common.firstName",
|
|
defaultMessage: "First name",
|
|
})}
|
|
name="firstName"
|
|
registerOptions={{ required: true }}
|
|
/>
|
|
<Input
|
|
label={intl.formatMessage({
|
|
id: "common.lastName",
|
|
defaultMessage: "Last name",
|
|
})}
|
|
name="lastName"
|
|
registerOptions={{ required: true }}
|
|
/>
|
|
</div>
|
|
</div>
|
|
<div className={styles.dateField}>
|
|
<Typography variant="Body/Supporting text (caption)/smBold">
|
|
<p>
|
|
{intl.formatMessage({
|
|
id: "common.birthDate",
|
|
defaultMessage: "Birth date",
|
|
})}
|
|
</p>
|
|
</Typography>
|
|
<DateSelect
|
|
labels={{
|
|
day: intl.formatMessage({
|
|
id: "common.day",
|
|
defaultMessage: "Day",
|
|
}),
|
|
month: intl.formatMessage({
|
|
id: "common.month",
|
|
defaultMessage: "Month",
|
|
}),
|
|
year: intl.formatMessage({
|
|
id: "common.year",
|
|
defaultMessage: "Year",
|
|
}),
|
|
errorMessage: getErrorMessage(
|
|
intl,
|
|
errors.dateOfBirth?.message?.toString()
|
|
),
|
|
}}
|
|
lang={lang}
|
|
name="dateOfBirth"
|
|
registerOptions={{ required: true }}
|
|
/>
|
|
</div>
|
|
<div className={cx(styles.container, styles.additional)}>
|
|
<Input
|
|
label={intl.formatMessage({
|
|
id: "common.zipCode",
|
|
defaultMessage: "Zip code",
|
|
})}
|
|
name="address.zipCode"
|
|
registerOptions={{ required: true }}
|
|
/>
|
|
<CountrySelect
|
|
countries={getFormattedCountryList(intl)}
|
|
errorMessage={getErrorMessage(
|
|
intl,
|
|
errors.address?.countryCode?.message
|
|
)}
|
|
label={intl.formatMessage({
|
|
id: "common.country",
|
|
defaultMessage: "Country",
|
|
})}
|
|
lang={lang}
|
|
name="address.countryCode"
|
|
registerOptions={{ required: true }}
|
|
/>
|
|
<Input
|
|
label={intl.formatMessage({
|
|
id: "common.emailAddress",
|
|
defaultMessage: "Email address",
|
|
})}
|
|
name="email"
|
|
registerOptions={{ required: true }}
|
|
type="email"
|
|
/>
|
|
<Phone
|
|
countryLabel={intl.formatMessage({
|
|
id: "common.countryCode",
|
|
defaultMessage: "Country code",
|
|
})}
|
|
countriesWithTranslatedName={getFormattedCountryList(intl)}
|
|
defaultCountryCode={getDefaultCountryFromLang(lang)}
|
|
errorMessage={getErrorMessage(
|
|
intl,
|
|
errors.phoneNumber?.message
|
|
)}
|
|
label={intl.formatMessage({
|
|
id: "common.phoneNumber",
|
|
defaultMessage: "Phone number",
|
|
})}
|
|
name="phoneNumber"
|
|
/>
|
|
</div>
|
|
</section>
|
|
<section className={styles.password}>
|
|
<header>
|
|
<Typography variant="Title/Subtitle/md">
|
|
<h3>
|
|
{intl.formatMessage({
|
|
id: "common.password",
|
|
defaultMessage: "Password",
|
|
})}
|
|
</h3>
|
|
</Typography>
|
|
</header>
|
|
<PasswordInput
|
|
name="password"
|
|
label={intl.formatMessage({
|
|
id: "common.password",
|
|
defaultMessage: "Password",
|
|
})}
|
|
isNewPassword
|
|
/>
|
|
</section>
|
|
|
|
<section className={styles.personalization}>
|
|
<header>
|
|
<Typography variant="Title/Subtitle/md">
|
|
<h3>
|
|
{intl.formatMessage({
|
|
id: "signup.UnlockYourPersonalizedExperience",
|
|
defaultMessage: "Unlock your personalized experience!",
|
|
})}
|
|
</h3>
|
|
</Typography>
|
|
</header>
|
|
<Checkbox
|
|
name="profilingConsent"
|
|
registerOptions={{
|
|
required: false,
|
|
}}
|
|
>
|
|
{intl.formatMessage({
|
|
id: "signup.yesConsent",
|
|
defaultMessage:
|
|
"I consent to Scandic using my information to give me even more personalized travel inspiration and offers from Scandic and trusted Scandic Friends partners. This means Scandic may use information about my interactions with Scandic Friends partners, and share details of my interactions with Scandic with those partners, to make the experience even more relevant to me.",
|
|
})}
|
|
</Checkbox>
|
|
<div className={styles.personalizationMoreInfo}>
|
|
<MaterialIcon icon="person" color="Icon/Interactive/Default" />
|
|
<p>
|
|
{intl.formatMessage({
|
|
id: "signup.GetATailoredProfile",
|
|
defaultMessage:
|
|
"Get a tailored profile that adapts to your interests.",
|
|
})}
|
|
</p>
|
|
</div>
|
|
<div className={styles.personalizationMoreInfo}>
|
|
<MaterialIcon
|
|
icon="featured_seasonal_and_gifts"
|
|
color="Icon/Interactive/Default"
|
|
/>
|
|
<p>
|
|
{intl.formatMessage({
|
|
id: "signup.PersonalizedOffersEarlyAccess",
|
|
defaultMessage:
|
|
"Personalized offers, early access to campaigns, and deals you won't find anywhere else!",
|
|
})}
|
|
</p>
|
|
</div>
|
|
<Button
|
|
variant="Text"
|
|
typography="Link/sm"
|
|
color="Primary"
|
|
className={styles.personalizationButton}
|
|
onClick={openPersonalizationModal}
|
|
>
|
|
{intl.formatMessage({
|
|
id: "signup.ReadMoreAboutPersonalization",
|
|
defaultMessage: "Read more about personalization at Scandic",
|
|
})}
|
|
</Button>
|
|
</section>
|
|
|
|
<section className={styles.terms}>
|
|
<header>
|
|
<Typography variant="Title/Subtitle/md">
|
|
<h3>
|
|
{intl.formatMessage({
|
|
id: "signUp.termsAndConditions",
|
|
defaultMessage: "Terms and conditions",
|
|
})}
|
|
</h3>
|
|
</Typography>
|
|
</header>
|
|
<Checkbox
|
|
name="termsAccepted"
|
|
registerOptions={{
|
|
required: true,
|
|
}}
|
|
errorCodeMessages={{
|
|
[signupErrors.TERMS_REQUIRED]: intl.formatMessage({
|
|
id: "common.mustAcceptTermsError",
|
|
defaultMessage: "You must accept the terms and conditions",
|
|
}),
|
|
}}
|
|
>
|
|
{intl.formatMessage({
|
|
id: "signUp.iAccept",
|
|
defaultMessage: "I accept",
|
|
})}
|
|
</Checkbox>
|
|
<Typography variant="Body/Paragraph/mdRegular">
|
|
<p>
|
|
{intl.formatMessage(
|
|
{
|
|
id: "signUp.termsAndConditionsDescription",
|
|
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={privacyPolicyRoutes[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>
|
|
)
|
|
}
|