feat: add conditional signup values

This commit is contained in:
Christel Westerberg
2024-10-16 12:09:18 +02:00
parent 5870a31275
commit 74c2a9d1e1
18 changed files with 347 additions and 110 deletions

View File

@@ -0,0 +1,67 @@
"use server"
import { parsePhoneNumber } from "libphonenumber-js"
import { z } from "zod"
import { serviceServerActionProcedure } from "@/server/trpc"
import { phoneValidator } from "@/utils/phoneValidator"
const registerUserPayload = z.object({
firstName: z.string(),
lastName: z.string(),
dateOfBirth: z.string(),
address: z.object({
countryCode: z.string(),
zipCode: z.string(),
}),
email: z.string(),
phoneNumber: phoneValidator("Phone is required"),
})
export const registerUserBookingFlow = serviceServerActionProcedure
.input(registerUserPayload)
.mutation(async function ({ ctx, input }) {
const payload = {
...input,
language: ctx.lang,
phoneNumber: parsePhoneNumber(input.phoneNumber)
.formatNational()
.replace(/\s+/g, ""),
}
// TODO: Consume the API to register the user as soon as passwordless signup is enabled.
// let apiResponse
// try {
// apiResponse = await api.post(api.endpoints.v1.profile, {
// body: payload,
// headers: {
// Authorization: `Bearer ${ctx.serviceToken}`,
// },
// })
// } catch (error) {
// console.error("Unexpected error", error)
// return { success: false, error: "Unexpected error" }
// }
// if (!apiResponse.ok) {
// const text = await apiResponse.text()
// console.error(text)
// console.error(
// "registerUserBookingFlow api error",
// JSON.stringify({
// query: input,
// error: {
// status: apiResponse.status,
// statusText: apiResponse.statusText,
// error: text,
// },
// })
// )
// return { success: false, error: "API error" }
// }
// const json = await apiResponse.json()
// console.log("registerUserBookingFlow: json", json)
return { success: true, data: payload }
})

View File

@@ -56,7 +56,7 @@ export default function AdultSelector({ roomIndex = 0 }: AdultSelectorProps) {
return (
<section className={styles.container}>
<Caption color="uiTextHighContrast" textTransform="bold">
<Caption color="uiTextHighContrast" type="bold">
{adultsLabel}
</Caption>
<Counter

View File

@@ -50,7 +50,7 @@ export default function ChildSelector({ roomIndex = 0 }: ChildSelectorProps) {
return (
<>
<section className={styles.container}>
<Caption color="uiTextHighContrast" textTransform="bold">
<Caption color="uiTextHighContrast" type="bold">
{childrenLabel}
</Caption>
<Counter

View File

@@ -0,0 +1,97 @@
"use client"
import { useEffect, useState } from "react"
import { useWatch } from "react-hook-form"
import { useIntl } from "react-intl"
import { privacyPolicy } from "@/constants/currentWebHrefs"
import Checkbox from "@/components/TempDesignSystem/Form/Checkbox"
import CheckboxCard from "@/components/TempDesignSystem/Form/ChoiceCard/Checkbox"
import DateSelect from "@/components/TempDesignSystem/Form/Date"
import Input from "@/components/TempDesignSystem/Form/Input"
import Link from "@/components/TempDesignSystem/Link"
import Body from "@/components/TempDesignSystem/Text/Body"
import Caption from "@/components/TempDesignSystem/Text/Caption"
import useLang from "@/hooks/useLang"
import styles from "./signup.module.css"
export default function Signup({ name }: { name: string }) {
const lang = useLang()
const intl = useIntl()
const [isJoinChecked, setIsJoinChecked] = useState(false)
const joinValue = useWatch({ name })
useEffect(() => {
// In order to avoid hydration errors the state needs to be set as side effect,
// since the join value can come from search params
setIsJoinChecked(joinValue)
}, [joinValue])
const list = [
{ title: intl.formatMessage({ id: "Earn bonus nights & points" }) },
{ title: intl.formatMessage({ id: "Get member benefits & offers" }) },
{ title: intl.formatMessage({ id: "Join at no cost" }) },
]
return (
<div className={styles.container}>
<CheckboxCard
highlightSubtitle
list={list}
name={name}
subtitle={intl.formatMessage(
{
id: "{difference}{amount} {currency}",
},
{
amount: "491",
currency: "SEK",
difference: "-",
}
)}
title={intl.formatMessage({ id: "Join Scandic Friends" })}
/>
{isJoinChecked ? (
<div className={styles.additionalFormData}>
<div className={styles.dateField}>
<header>
<Caption type="bold">
{intl.formatMessage({ id: "Birth date" })} *
</Caption>
</header>
<DateSelect
name="dateOfBirth"
registerOptions={{ required: true }}
/>
<Input
name="zipCode"
label={intl.formatMessage({ id: "Zip code" })}
registerOptions={{ required: true }}
/>
</div>
<div>
<Checkbox name="termsAccepted" registerOptions={{ required: true }}>
<Body>
{intl.formatMessage({
id: "Yes, I accept the Terms and conditions for Scandic Friends and understand that Scandic will process my personal data in accordance with",
})}{" "}
<Link
variant="underscored"
color="peach80"
target="_blank"
href={privacyPolicy[lang]}
>
{intl.formatMessage({ id: "Scandic's Privacy Policy." })}
</Link>
</Body>
</Checkbox>
</div>
</div>
) : null}
</div>
)
}

View File

@@ -0,0 +1,15 @@
.container {
display: grid;
grid-column: 1/-1;
gap: var(--Spacing-x3);
}
.additionalFormData {
display: grid;
gap: var(--Spacing-x4);
}
.dateField {
display: grid;
gap: var(--Spacing-x1);
}

View File

@@ -6,14 +6,16 @@ import { useIntl } from "react-intl"
import { useEnterDetailsStore } from "@/stores/enter-details"
import { registerUserBookingFlow } from "@/actions/registerUserBookingFlow"
import Button from "@/components/TempDesignSystem/Button"
import CheckboxCard from "@/components/TempDesignSystem/Form/ChoiceCard/Checkbox"
import CountrySelect from "@/components/TempDesignSystem/Form/Country"
import Input from "@/components/TempDesignSystem/Form/Input"
import Phone from "@/components/TempDesignSystem/Form/Phone"
import Body from "@/components/TempDesignSystem/Text/Body"
import { toast } from "@/components/TempDesignSystem/Toasts"
import { detailsSchema, signedInDetailsSchema } from "./schema"
import Signup from "./Signup"
import styles from "./details.module.css"
@@ -25,28 +27,30 @@ import type {
const formID = "enter-details"
export default function Details({ user }: DetailsProps) {
const intl = useIntl()
const list = [
{ title: intl.formatMessage({ id: "Earn bonus nights & points" }) },
{ title: intl.formatMessage({ id: "Get member benefits & offers" }) },
{ title: intl.formatMessage({ id: "Join at no cost" }) },
]
const initialData = useEnterDetailsStore((state) => ({
countryCode: state.data.countryCode,
email: state.data.email,
firstname: state.data.firstname,
lastname: state.data.lastname,
firstName: state.data.firstName,
lastName: state.data.lastName,
phoneNumber: state.data.phoneNumber,
join: state.data.join,
dateOfBirth: state.data.dateOfBirth,
zipCode: state.data.zipCode,
termsAccepted: state.data.termsAccepted,
}))
const methods = useForm<DetailsSchema>({
defaultValues: {
countryCode: user?.address?.countryCode ?? initialData.countryCode,
email: user?.email ?? initialData.email,
firstname: user?.firstName ?? initialData.firstname,
lastname: user?.lastName ?? initialData.lastname,
firstName: user?.firstName ?? initialData.firstName,
lastName: user?.lastName ?? initialData.lastName,
phoneNumber: user?.phoneNumber ?? initialData.phoneNumber,
//@ts-expect-error: We use a literal for join to be true or false, which does not convert to a boolean
join: initialData.join,
dateOfBirth: initialData.dateOfBirth,
zipCode: initialData.zipCode,
termsAccepted: initialData.termsAccepted,
},
criteriaMode: "all",
mode: "all",
@@ -56,10 +60,39 @@ export default function Details({ user }: DetailsProps) {
const completeStep = useEnterDetailsStore((state) => state.completeStep)
// const errorMessage = intl.formatMessage({
// id: "An error occurred. Please try again.",
// })
const onSubmit = useCallback(
(values: DetailsSchema) => {
async function (values: DetailsSchema) {
if (values.join) {
const signupVals = {
firstName: values.firstName,
lastName: values.lastName,
email: values.email,
phoneNumber: values.phoneNumber,
address: {
zipCode: values.zipCode,
countryCode: values.countryCode,
},
dateOfBirth: values.dateOfBirth,
}
const res = await registerUserBookingFlow(signupVals)
if (!res.success) {
// if (res.error) {
// toast.error(res.error)
// } else {
// toast.error(errorMessage)
// }
return
}
console.log("Signed up user: ", res)
}
completeStep(values)
},
[completeStep]
)
@@ -77,14 +110,14 @@ export default function Details({ user }: DetailsProps) {
onSubmit={methods.handleSubmit(onSubmit)}
>
<Input
label={intl.formatMessage({ id: "Firstname" })}
name="firstname"
label={intl.formatMessage({ id: "First name" })}
name="firstName"
readOnly={!!user}
registerOptions={{ required: true }}
/>
<Input
label={intl.formatMessage({ id: "Lastname" })}
name="lastname"
label={intl.formatMessage({ id: "Last name" })}
name="lastName"
readOnly={!!user}
registerOptions={{ required: true }}
/>
@@ -109,26 +142,9 @@ export default function Details({ user }: DetailsProps) {
readOnly={!!user}
registerOptions={{ required: true }}
/>
{user ? null : <Signup name="join" />}
</form>
<footer className={styles.footer}>
{user ? null : (
<CheckboxCard
highlightSubtitle
list={list}
name="join"
subtitle={intl.formatMessage(
{
id: "{difference}{amount} {currency}",
},
{
amount: "491",
currency: "SEK",
difference: "-",
}
)}
title={intl.formatMessage({ id: "Join Scandic Friends" })}
/>
)}
<Button
disabled={!methods.formState.isValid}
form={formID}

View File

@@ -2,18 +2,49 @@ import { z } from "zod"
import { phoneValidator } from "@/utils/phoneValidator"
export const detailsSchema = z.object({
export const baseDetailsSchema = z.object({
countryCode: z.string(),
email: z.string().email(),
firstname: z.string(),
lastname: z.string(),
firstName: z.string(),
lastName: z.string(),
phoneNumber: phoneValidator(),
})
export const notJoinDetailsSchema = baseDetailsSchema.merge(
z.object({
join: z.literal(false),
zipCode: z.string().optional(),
dateOfBirth: z.string().optional(),
termsAccepted: z.boolean().default(false),
})
)
export const joinDetailsSchema = baseDetailsSchema.merge(
z.object({
join: z.literal(true),
zipCode: z.string().min(1, { message: "Zip code is required" }),
dateOfBirth: z.string(),
termsAccepted: z.literal(true, {
errorMap: (err, ctx) => {
switch (err.code) {
case "invalid_literal":
return { message: "You must accept the terms and conditions" }
}
return { message: ctx.defaultError }
},
}),
})
)
export const detailsSchema = z.discriminatedUnion("join", [
notJoinDetailsSchema,
joinDetailsSchema,
])
export const signedInDetailsSchema = z.object({
countryCode: z.string().optional(),
email: z.string().email().optional(),
firstname: z.string().optional(),
lastname: z.string().optional(),
firstName: z.string().optional(),
lastName: z.string().optional(),
phoneNumber: phoneValidator().optional(),
})

View File

@@ -20,54 +20,30 @@ export default function SectionAccordion({
children,
}: React.PropsWithChildren<SectionAccordionProps>) {
const intl = useIntl()
const [isComplete, setIsComplete] = useState(false)
const currentStep = useEnterDetailsStore((state) => state.currentStep)
const [isComplete, setIsComplete] = useState(false)
const [isOpen, setIsOpen] = useState(false)
const isValid = useEnterDetailsStore((state) => state.isValid[step])
const navigate = useEnterDetailsStore((state) => state.navigate)
const contentRef = useRef<HTMLDivElement>(null)
const circleRef = useRef<HTMLDivElement>(null)
const isOpen = currentStep === step
useEffect(() => {
const content = contentRef.current
const circle = circleRef.current
if (content) {
if (isOpen) {
content.style.maxHeight = `${content.scrollHeight}px`
} else {
content.style.maxHeight = "0"
}
}
if (circle) {
if (isOpen) {
circle.style.backgroundColor = `var(--UI-Text-Placeholder);`
} else {
circle.style.backgroundColor = `var(--Base-Surface-Subtle-Hover);`
}
}
}, [isOpen])
useEffect(() => {
// We need to set the state on mount because of hydration errors
setIsComplete(isValid)
}, [isValid])
useEffect(() => {
setIsOpen(currentStep === step)
}, [currentStep, step])
function onModify() {
navigate(step)
}
return (
<section className={styles.wrapper} data-open={isOpen}>
<section className={styles.wrapper} data-open={isOpen} data-step={step}>
<div className={styles.iconWrapper}>
<div
className={styles.circle}
data-checked={isComplete}
ref={circleRef}
>
<div className={styles.circle} data-checked={isComplete}>
{isComplete ? (
<CheckIcon color="white" height="16" width="16" />
) : null}
@@ -79,6 +55,7 @@ export default function SectionAccordion({
<Footnote
asChild
textTransform="uppercase"
type="label"
color="uiTextPlaceholder"
>
<h2>{header}</h2>
@@ -105,9 +82,7 @@ export default function SectionAccordion({
</Button>
)}
</header>
<div className={styles.content} ref={contentRef}>
{children}
</div>
<div className={styles.content}>{children}</div>
</div>
</section>
)

View File

@@ -22,12 +22,14 @@
}
.main {
display: flex;
flex-direction: column;
display: grid;
gap: var(--Spacing-x3);
width: 100%;
border-bottom: 1px solid var(--Primary-Light-On-Surface-Divider-subtle);
padding-bottom: var(--Spacing-x3);
transition: 0.4s ease-out;
grid-template-rows: 2em 0fr;
}
.headerContainer {
@@ -70,12 +72,23 @@
background-color: var(--Base-Surface-Subtle-Hover);
}
.wrapper[data-open="true"] .main {
grid-template-rows: 2em 1fr;
}
.content {
overflow: hidden;
transition: max-height 0.4s ease-out;
max-height: 0;
}
@keyframes allowOverflow {
0% {
overflow: hidden;
}
100% {
overflow: visible;
}
}
.wrapper[data-open="true"] .content {
max-height: 1000px;
animation: allowOverflow 0.4s 0.4s ease;
}

View File

@@ -20,8 +20,8 @@ import type { DateProps } from "./date"
export default function DateSelect({ name, registerOptions = {} }: DateProps) {
const intl = useIntl()
const d = useWatch({ name })
const { control, setValue } = useFormContext()
const currentValue = useWatch({ name })
const { control, setValue, trigger } = useFormContext()
const { field } = useController({
control,
name,
@@ -47,7 +47,7 @@ export default function DateSelect({ name, registerOptions = {} }: DateProps) {
}))
const years = rangeArray(1900, currentYear - 18)
.reverse()
.map((year) => ({ value: year, label: `${year}` }))
.map((year) => ({ value: year, label: year.toString() }))
function createOnSelect(selector: DateName) {
/**
@@ -68,6 +68,8 @@ export default function DateSelect({ name, registerOptions = {} }: DateProps) {
const month = selector === DateName.month ? value : newSegments.month
if (year !== null && month !== null) {
newSegments.daysInMonth = dt().year(year).month(month).daysInMonth()
} else if (month !== null) {
newSegments.daysInMonth = dt().month(month).daysInMonth()
}
}
@@ -79,6 +81,7 @@ export default function DateSelect({ name, registerOptions = {} }: DateProps) {
.set("date", Math.min(newSegments.date!, newSegments.daysInMonth))
setValue(name, newDate.format("YYYY-MM-DD"))
trigger(name)
}
setDateSegment(newSegments)
}
@@ -95,9 +98,9 @@ export default function DateSelect({ name, registerOptions = {} }: DateProps) {
* date, but we can't check isNan since
* we recieve the date as "1999-01-01"
*/
dateValue = dt(d).isValid() ? parseDate(d) : null
dateValue = dt(currentValue).isValid() ? parseDate(currentValue) : null
} catch (error) {
console.error(error)
console.warn("Known error for parse date in DateSelect: ", error)
}
return (
@@ -133,6 +136,7 @@ export default function DateSelect({ name, registerOptions = {} }: DateProps) {
defaultSelectedKey={
segment.isPlaceholder ? undefined : segment.value
}
value={segment.isPlaceholder ? undefined : segment.value}
/>
</div>
)
@@ -151,6 +155,7 @@ export default function DateSelect({ name, registerOptions = {} }: DateProps) {
defaultSelectedKey={
segment.isPlaceholder ? undefined : segment.value
}
value={segment.isPlaceholder ? undefined : segment.value}
/>
</div>
)
@@ -169,6 +174,7 @@ export default function DateSelect({ name, registerOptions = {} }: DateProps) {
defaultSelectedKey={
segment.isPlaceholder ? undefined : segment.value
}
value={segment.isPlaceholder ? undefined : segment.value}
/>
</div>
)

View File

@@ -15,6 +15,7 @@
"Already a friend?": "Allerede en ven?",
"Amenities": "Faciliteter",
"Amusement park": "Forlystelsespark",
"An error occurred. Please try again.": "Der opstod en fejl. Prøv venligst igen.",
"An error occurred trying to manage your preferences, please try again later.": "Der opstod en fejl under forsøget på at administrere dine præferencer. Prøv venligst igen senere.",
"An error occurred when adding a credit card, please try again later.": "Der opstod en fejl under tilføjelse af et kreditkort. Prøv venligst igen senere.",
"An error occurred when trying to update profile.": "Der opstod en fejl under forsøg på at opdatere profilen.",
@@ -105,7 +106,7 @@
"Fair": "Messe",
"Find booking": "Find booking",
"Find hotels": "Find hotel",
"Firstname": "Fornavn",
"First name": "Fornavn",
"Flexibility": "Fleksibilitet",
"Follow us": "Følg os",
"Former Scandic Hotel": "Tidligere Scandic Hotel",
@@ -138,7 +139,7 @@
"Join at no cost": "Tilmeld dig uden omkostninger",
"King bed": "Kingsize-seng",
"Language": "Sprog",
"Lastname": "Efternavn",
"Last name": "Efternavn",
"Latest searches": "Seneste søgninger",
"Left": "tilbage",
"Level": "Niveau",

View File

@@ -15,6 +15,7 @@
"Already a friend?": "Sind wir schon Freunde?",
"Amenities": "Annehmlichkeiten",
"Amusement park": "Vergnügungspark",
"An error occurred. Please try again.": "Es ist ein Fehler aufgetreten. Bitte versuchen Sie es erneut.",
"An error occurred trying to manage your preferences, please try again later.": "Beim Versuch, Ihre Einstellungen zu verwalten, ist ein Fehler aufgetreten. Bitte versuchen Sie es später erneut.",
"An error occurred when adding a credit card, please try again later.": "Beim Hinzufügen einer Kreditkarte ist ein Fehler aufgetreten. Bitte versuchen Sie es später erneut.",
"An error occurred when trying to update profile.": "Beim Versuch, das Profil zu aktualisieren, ist ein Fehler aufgetreten.",
@@ -105,7 +106,7 @@
"Fair": "Messe",
"Find booking": "Buchung finden",
"Find hotels": "Hotels finden",
"Firstname": "Vorname",
"First name": "Vorname",
"Flexibility": "Flexibilität",
"Follow us": "Folgen Sie uns",
"Former Scandic Hotel": "Ehemaliges Scandic Hotel",
@@ -138,7 +139,7 @@
"Join at no cost": "Kostenlos beitreten",
"King bed": "Kingsize-Bett",
"Language": "Sprache",
"Lastname": "Nachname",
"Last name": "Nachname",
"Latest searches": "Letzte Suchanfragen",
"Left": "übrig",
"Level": "Level",

View File

@@ -16,6 +16,7 @@
"Already a friend?": "Already a friend?",
"Amenities": "Amenities",
"Amusement park": "Amusement park",
"An error occurred. Please try again.": "An error occurred. Please try again.",
"An error occurred trying to manage your preferences, please try again later.": "An error occurred trying to manage your preferences, please try again later.",
"An error occurred when adding a credit card, please try again later.": "An error occurred when adding a credit card, please try again later.",
"An error occurred when trying to update profile.": "An error occurred when trying to update profile.",
@@ -108,7 +109,7 @@
"Fair": "Fair",
"Find booking": "Find booking",
"Find hotels": "Find hotels",
"Firstname": "Firstname",
"First name": "First name",
"Flexibility": "Flexibility",
"Follow us": "Follow us",
"Former Scandic Hotel": "Former Scandic Hotel",
@@ -141,7 +142,7 @@
"Join at no cost": "Join at no cost",
"King bed": "King bed",
"Language": "Language",
"Lastname": "Lastname",
"Last name": "Last name",
"Latest searches": "Latest searches",
"Left": "left",
"Level": "Level",

View File

@@ -15,6 +15,7 @@
"Already a friend?": "Oletko jo ystävä?",
"Amenities": "Mukavuudet",
"Amusement park": "Huvipuisto",
"An error occurred. Please try again.": "Tapahtui virhe. Yritä uudelleen.",
"An error occurred trying to manage your preferences, please try again later.": "Asetusten hallinnassa tapahtui virhe. Yritä myöhemmin uudelleen.",
"An error occurred when adding a credit card, please try again later.": "Luottokorttia lisättäessä tapahtui virhe. Yritä myöhemmin uudelleen.",
"An error occurred when trying to update profile.": "Profiilia päivitettäessä tapahtui virhe.",
@@ -105,7 +106,7 @@
"Fair": "Messukeskus",
"Find booking": "Etsi varaus",
"Find hotels": "Etsi hotelleja",
"Firstname": "Etunimi",
"First name": "Etunimi",
"Flexibility": "Joustavuus",
"Follow us": "Seuraa meitä",
"Former Scandic Hotel": "Entinen Scandic-hotelli",
@@ -138,7 +139,7 @@
"Join at no cost": "Liity maksutta",
"King bed": "King-vuode",
"Language": "Kieli",
"Lastname": "Sukunimi",
"Last name": "Sukunimi",
"Latest searches": "Viimeisimmät haut",
"Left": "jäljellä",
"Level": "Level",

View File

@@ -15,6 +15,7 @@
"Already a friend?": "Allerede Friend?",
"Amenities": "Fasiliteter",
"Amusement park": "Tivoli",
"An error occurred. Please try again.": "Det oppsto en feil. Vennligst prøv igjen.",
"An error occurred trying to manage your preferences, please try again later.": "Det oppstod en feil under forsøket på å administrere innstillingene dine. Prøv igjen senere.",
"An error occurred when adding a credit card, please try again later.": "Det oppstod en feil ved å legge til et kredittkort. Prøv igjen senere.",
"An error occurred when trying to update profile.": "Det oppstod en feil under forsøk på å oppdatere profilen.",
@@ -104,7 +105,7 @@
"Fair": "Messe",
"Find booking": "Finn booking",
"Find hotels": "Finn hotell",
"Firstname": "Fornavn",
"First name": "Fornavn",
"Flexibility": "Fleksibilitet",
"Follow us": "Følg oss",
"Former Scandic Hotel": "Tidligere Scandic-hotell",
@@ -136,7 +137,7 @@
"Join at no cost": "Bli med uten kostnad",
"King bed": "King-size-seng",
"Language": "Språk",
"Lastname": "Etternavn",
"Last name": "Etternavn",
"Latest searches": "Siste søk",
"Left": "igjen",
"Level": "Nivå",

View File

@@ -15,6 +15,7 @@
"Already a friend?": "Är du redan en vän?",
"Amenities": "Bekvämligheter",
"Amusement park": "Nöjespark",
"An error occurred. Please try again.": "Ett fel uppstod. Försök igen.",
"An error occurred trying to manage your preferences, please try again later.": "Ett fel uppstod när du försökte hantera dina inställningar, försök igen senare.",
"An error occurred when adding a credit card, please try again later.": "Ett fel uppstod när ett kreditkort lades till, försök igen senare.",
"An error occurred when trying to update profile.": "Ett fel uppstod när du försökte uppdatera profilen.",
@@ -104,7 +105,7 @@
"Fair": "Mässa",
"Find booking": "Hitta bokning",
"Find hotels": "Hitta hotell",
"Firstname": "Förnamn",
"First name": "Förnamn",
"Flexibility": "Flexibilitet",
"Follow us": "Följ oss",
"Former Scandic Hotel": "Tidigare Scandichotell",
@@ -136,7 +137,7 @@
"Join at no cost": "Gå med utan kostnad",
"King bed": "King size-säng",
"Language": "Språk",
"Lastname": "Efternamn",
"Last name": "Efternamn",
"Latest searches": "Senaste sökningarna",
"Left": "kvar",
"Level": "Nivå",

View File

@@ -22,7 +22,10 @@ interface EnterDetailsState {
activeSidePeek: SidePeekEnum | null
isValid: Record<StepEnum, boolean>
completeStep: (updatedData: Partial<EnterDetailsState["data"]>) => void
navigate: (step: StepEnum, searchParams?: Record<string, string>) => void
navigate: (
step: StepEnum,
searchParams?: Record<string, string | boolean>
) => void
openSidePeek: (key: SidePeekEnum | null) => void
closeSidePeek: () => void
}
@@ -37,26 +40,34 @@ export function initEditDetailsState(currentStep: StepEnum) {
breakfast: undefined,
countryCode: "",
email: "",
firstname: "",
lastname: "",
firstName: "",
lastName: "",
phoneNumber: "",
join: false,
zipCode: "",
dateOfBirth: undefined,
termsAccepted: false,
}
let inputData = {}
if (search?.size) {
const searchParams: Record<string, string> = {}
const searchParams: Record<string, string | boolean> = {}
search.forEach((value, key) => {
searchParams[key] = value
// Handle boolean values
if (value === "true" || value === "false") {
searchParams[key] = JSON.parse(value) as true | false
} else {
searchParams[key] = value
}
})
inputData = searchParams
} else if (sessionData) {
inputData = JSON.parse(sessionData)
}
const validPaths = [StepEnum.selectBed]
let initialData = defaultData
let initialData: EnterDetailsState["data"] = defaultData
const isValid = {
[StepEnum.selectBed]: false,
@@ -100,7 +111,7 @@ export function initEditDetailsState(currentStep: StepEnum) {
const query = new URLSearchParams(window.location.search)
if (searchParams) {
Object.entries(searchParams).forEach(([key, value]) => {
query.set(key, value)
query.set(key, value ? value.toString() : "")
})
}

View File

@@ -4,7 +4,7 @@ import { detailsSchema } from "@/components/HotelReservation/EnterDetails/Detail
import type { SafeUser } from "@/types/user"
export interface DetailsSchema extends z.output<typeof detailsSchema> {}
export type DetailsSchema = z.output<typeof detailsSchema>
export interface DetailsProps {
user: SafeUser