Merged in feat/SW-598 (pull request #1554)

feat: pass specialRequest.comment to create booking

* feat: pass specialRequest.comment to create booking


Approved-by: Simon.Emanuelsson
This commit is contained in:
Linus Flood
2025-03-18 10:43:28 +00:00
committed by Arvid Norlin
parent fc219aaec0
commit 0e0b065dd9
14 changed files with 89 additions and 51 deletions

View File

@@ -5,6 +5,7 @@ import { useIntl } from "react-intl"
import { useEnterDetailsStore } from "@/stores/enter-details" import { useEnterDetailsStore } from "@/stores/enter-details"
import SpecialRequests from "@/components/HotelReservation/EnterDetails/Details/SpecialRequests"
import Button from "@/components/TempDesignSystem/Button" import Button from "@/components/TempDesignSystem/Button"
import CountrySelect from "@/components/TempDesignSystem/Form/Country" import CountrySelect from "@/components/TempDesignSystem/Form/Country"
import Input from "@/components/TempDesignSystem/Form/Input" import Input from "@/components/TempDesignSystem/Form/Input"
@@ -53,6 +54,9 @@ export default function Details() {
lastName: initialData.lastName, lastName: initialData.lastName,
membershipNo: initialData.membershipNo, membershipNo: initialData.membershipNo,
phoneNumber: initialData.phoneNumber, phoneNumber: initialData.phoneNumber,
specialRequest: {
comment: room.specialRequest.comment,
},
}, },
}) })
@@ -114,6 +118,7 @@ export default function Details() {
type="tel" type="tel"
/> />
)} )}
<SpecialRequests />
</div> </div>
<footer className={styles.footer}> <footer className={styles.footer}>
<Button <Button

View File

@@ -1,5 +1,6 @@
import { z } from "zod" import { z } from "zod"
import { specialRequestSchema } from "@/components/HotelReservation/EnterDetails/Details/SpecialRequests/schema"
import { phoneValidator } from "@/utils/zod/phoneValidator" import { phoneValidator } from "@/utils/zod/phoneValidator"
// stringMatcher regex is copied from current web as specified by requirements. // stringMatcher regex is copied from current web as specified by requirements.
@@ -40,4 +41,5 @@ export const multiroomDetailsSchema = z.object({
} }
return true return true
}, "Invalid membership number format"), }, "Invalid membership number format"),
specialRequest: specialRequestSchema,
}) })

View File

@@ -6,6 +6,7 @@ import { useIntl } from "react-intl"
import { useEnterDetailsStore } from "@/stores/enter-details" import { useEnterDetailsStore } from "@/stores/enter-details"
import SpecialRequests from "@/components/HotelReservation/EnterDetails/Details/SpecialRequests"
import Button from "@/components/TempDesignSystem/Button" import Button from "@/components/TempDesignSystem/Button"
import CountrySelect from "@/components/TempDesignSystem/Form/Country" import CountrySelect from "@/components/TempDesignSystem/Form/Country"
import Input from "@/components/TempDesignSystem/Form/Input" import Input from "@/components/TempDesignSystem/Form/Input"
@@ -17,7 +18,6 @@ import JoinScandicFriendsCard from "./JoinScandicFriendsCard"
import MemberPriceModal from "./MemberPriceModal" import MemberPriceModal from "./MemberPriceModal"
import { guestDetailsSchema, signedInDetailsSchema } from "./schema" import { guestDetailsSchema, signedInDetailsSchema } from "./schema"
import Signup from "./Signup" import Signup from "./Signup"
import SpecialRequests from "./SpecialRequests"
import styles from "./details.module.css" import styles from "./details.module.css"
@@ -64,10 +64,9 @@ export default function Details({ user }: DetailsProps) {
membershipNo: initialData.membershipNo, membershipNo: initialData.membershipNo,
phoneNumber: user?.phoneNumber ?? initialData.phoneNumber, phoneNumber: user?.phoneNumber ?? initialData.phoneNumber,
zipCode: "zipCode" in initialData ? initialData.zipCode : undefined, zipCode: "zipCode" in initialData ? initialData.zipCode : undefined,
specialRequests: specialRequest: {
"specialRequests" in initialData comment: room.specialRequest.comment,
? initialData.specialRequests },
: undefined,
}, },
}) })

View File

@@ -2,6 +2,7 @@ import { z } from "zod"
import { dt } from "@/lib/dt" import { dt } from "@/lib/dt"
import { specialRequestSchema } from "@/components/HotelReservation/EnterDetails/Details/SpecialRequests/schema"
import { phoneValidator } from "@/utils/zod/phoneValidator" import { phoneValidator } from "@/utils/zod/phoneValidator"
// stringMatcher regex is copied from current web as specified by requirements. // stringMatcher regex is copied from current web as specified by requirements.
@@ -10,30 +11,6 @@ const stringMatcher =
const isValidString = (key: string) => stringMatcher.test(key) const isValidString = (key: string) => stringMatcher.test(key)
export enum FloorPreference {
LOW = "Low floor",
HIGH = "High floor",
}
export enum ElevatorPreference {
AWAY_FROM_ELEVATOR = "Away from elevator",
NEAR_ELEVATOR = "Near elevator",
}
const specialRequestsSchema = z
.object({
floorPreference: z
.nativeEnum(FloorPreference)
.or(z.literal("").transform((_) => undefined))
.optional(),
elevatorPreference: z
.nativeEnum(ElevatorPreference)
.or(z.literal("").transform((_) => undefined))
.optional(),
comments: z.string().default(""),
})
.optional()
export const baseDetailsSchema = z.object({ export const baseDetailsSchema = z.object({
countryCode: z.string().min(1, { message: "Country is required" }), countryCode: z.string().min(1, { message: "Country is required" }),
email: z.string().email({ message: "Email address is required" }), email: z.string().email({ message: "Email address is required" }),
@@ -50,7 +27,7 @@ export const baseDetailsSchema = z.object({
message: "Last name can't contain any special characters", message: "Last name can't contain any special characters",
}), }),
phoneNumber: phoneValidator(), phoneNumber: phoneValidator(),
specialRequests: specialRequestsSchema, specialRequest: specialRequestSchema,
}) })
export const notJoinDetailsSchema = baseDetailsSchema.merge( export const notJoinDetailsSchema = baseDetailsSchema.merge(
@@ -116,5 +93,5 @@ export const signedInDetailsSchema = z.object({
.transform((_) => false), .transform((_) => false),
dateOfBirth: z.string().default(""), dateOfBirth: z.string().default(""),
zipCode: z.string().default(""), zipCode: z.string().default(""),
specialRequests: specialRequestsSchema, specialRequest: specialRequestSchema,
}) })

View File

@@ -39,7 +39,7 @@ export default function SpecialRequests() {
<Select <Select
label={intl.formatMessage({ id: "Floor preference" })} label={intl.formatMessage({ id: "Floor preference" })}
name="specialRequests.floorPreference" name="specialRequest.floorPreference"
items={[ items={[
noPreferenceItem, noPreferenceItem,
{ {
@@ -54,7 +54,7 @@ export default function SpecialRequests() {
/> />
<Select <Select
label={intl.formatMessage({ id: "Elevator preference" })} label={intl.formatMessage({ id: "Elevator preference" })}
name="specialRequests.elevatorPreference" name="specialRequest.elevatorPreference"
items={[ items={[
noPreferenceItem, noPreferenceItem,
{ {
@@ -75,7 +75,7 @@ export default function SpecialRequests() {
label={intl.formatMessage({ label={intl.formatMessage({
id: "Is there anything else you would like us to know before your arrival?", id: "Is there anything else you would like us to know before your arrival?",
})} })}
name="specialRequests.comments" name="specialRequest.comment"
/> />
</div> </div>
</div> </div>

View File

@@ -0,0 +1,25 @@
import { z } from "zod"
export enum FloorPreference {
LOW = "Low floor",
HIGH = "High floor",
}
export enum ElevatorPreference {
AWAY_FROM_ELEVATOR = "Away from elevator",
NEAR_ELEVATOR = "Near elevator",
}
export const specialRequestSchema = z
.object({
floorPreference: z
.nativeEnum(FloorPreference)
.or(z.literal("").transform((_) => undefined))
.optional(),
elevatorPreference: z
.nativeEnum(ElevatorPreference)
.or(z.literal("").transform((_) => undefined))
.optional(),
comment: z.string().default(""),
})
.optional()

View File

@@ -255,24 +255,24 @@ export default function PaymentClient({
const guarantee = data.guarantee const guarantee = data.guarantee
const useSavedCard = savedCreditCard const useSavedCard = savedCreditCard
? { ? {
card: { card: {
alias: savedCreditCard.alias, alias: savedCreditCard.alias,
expiryDate: savedCreditCard.expirationDate, expiryDate: savedCreditCard.expirationDate,
cardType: savedCreditCard.cardType, cardType: savedCreditCard.cardType,
}, },
} }
: {} : {}
const shouldUsePayment = !isFlexRate || guarantee const shouldUsePayment = !isFlexRate || guarantee
const payment = shouldUsePayment const payment = shouldUsePayment
? { ? {
paymentMethod: paymentMethod, paymentMethod: paymentMethod,
...useSavedCard, ...useSavedCard,
success: `${paymentRedirectUrl}/success`, success: `${paymentRedirectUrl}/success`,
error: `${paymentRedirectUrl}/error`, error: `${paymentRedirectUrl}/error`,
cancel: `${paymentRedirectUrl}/cancel`, cancel: `${paymentRedirectUrl}/cancel`,
} }
: undefined : undefined
trackPaymentEvent({ trackPaymentEvent({
@@ -334,7 +334,7 @@ export default function PaymentClient({
}, },
rateCode: rateCode:
(room.guest.join || room.guest.membershipNo) && (room.guest.join || room.guest.membershipNo) &&
booking.rooms[idx].counterRateCode booking.rooms[idx].counterRateCode
? booking.rooms[idx].counterRateCode ? booking.rooms[idx].counterRateCode
: booking.rooms[idx].rateCode, : booking.rooms[idx].rateCode,
roomPrice: { roomPrice: {
@@ -343,6 +343,11 @@ export default function PaymentClient({
}, },
roomTypeCode: room.bedType!.roomTypeCode, // A selection has been made in order to get to this step. roomTypeCode: room.bedType!.roomTypeCode, // A selection has been made in order to get to this step.
smsConfirmationRequested: data.smsConfirmation, smsConfirmationRequested: data.smsConfirmation,
specialRequest: {
comment: room.specialRequest.comment
? room.specialRequest.comment
: undefined,
},
})), })),
}) })
}, },
@@ -445,7 +450,7 @@ export default function PaymentClient({
value={paymentMethod} value={paymentMethod}
label={ label={
PAYMENT_METHOD_TITLES[ PAYMENT_METHOD_TITLES[
paymentMethod as PaymentMethodEnum paymentMethod as PaymentMethodEnum
] ]
} }
/> />

View File

@@ -67,6 +67,9 @@ const rooms: RoomState[] = [
isAvailable: true, isAvailable: true,
mustBeGuaranteed: false, mustBeGuaranteed: false,
isFlexRate: false, isFlexRate: false,
specialRequest: {
comment: "",
},
}, },
steps: { steps: {
[StepEnum.selectBed]: { [StepEnum.selectBed]: {
@@ -107,6 +110,9 @@ const rooms: RoomState[] = [
isAvailable: true, isAvailable: true,
mustBeGuaranteed: false, mustBeGuaranteed: false,
isFlexRate: false, isFlexRate: false,
specialRequest: {
comment: "",
},
}, },
steps: { steps: {
[StepEnum.selectBed]: { [StepEnum.selectBed]: {

View File

@@ -1,4 +1,5 @@
"use client" "use client"
import { import {
Label as AriaLabel, Label as AriaLabel,
Text, Text,
@@ -47,12 +48,11 @@ export default function TextArea({
validationBehavior="aria" validationBehavior="aria"
value={field.value} value={field.value}
> >
<AriaLabel className={styles.container} htmlFor={name}> <AriaLabel className={styles.container}>
<Body asChild fontOnly> <Body asChild fontOnly>
<AriaTextArea <AriaTextArea
{...field} {...field}
aria-labelledby={field.name} aria-labelledby={field.name}
id={name}
placeholder={placeholder} placeholder={placeholder}
readOnly={readOnly} readOnly={readOnly}
required={!!registerOptions.required} required={!!registerOptions.required}

View File

@@ -126,7 +126,13 @@ export default function EnterDetailsProvider({
storedRoom.room.guest storedRoom.room.guest
) )
} }
if (
!currentRoom.room.specialRequest.comment &&
storedRoom.room.specialRequest.comment
) {
currentRoom.room.specialRequest.comment =
storedRoom.room.specialRequest.comment
}
const validGuest = const validGuest =
idx > 0 idx > 0
? multiroomDetailsSchema.safeParse(currentRoom.room.guest) ? multiroomDetailsSchema.safeParse(currentRoom.room.guest)

View File

@@ -29,6 +29,9 @@ const roomsSchema = z
phoneNumber: z.string(), phoneNumber: z.string(),
}), }),
smsConfirmationRequested: z.boolean(), smsConfirmationRequested: z.boolean(),
specialRequest: z.object({
comment: z.string().optional(),
}),
packages: z.object({ packages: z.object({
breakfast: z.boolean(), breakfast: z.boolean(),
allergyFriendly: z.boolean(), allergyFriendly: z.boolean(),

View File

@@ -111,6 +111,9 @@ export function createDetailsStore(
? deepmerge(defaultGuestState, extractGuestFromUser(user)) ? deepmerge(defaultGuestState, extractGuestFromUser(user))
: defaultGuestState, : defaultGuestState,
roomPrice: getRoomPrice(room.roomRate, isMember && idx === 0), roomPrice: getRoomPrice(room.roomRate, isMember && idx === 0),
specialRequest: {
comment: "",
},
}, },
currentStep, currentStep,
@@ -377,6 +380,10 @@ export function createDetailsStore(
currentRoom.guest.join = data.join currentRoom.guest.join = data.join
currentRoom.guest.lastName = data.lastName currentRoom.guest.lastName = data.lastName
if (data.specialRequest?.comment) {
currentRoom.specialRequest.comment = data.specialRequest.comment
}
if (data.join) { if (data.join) {
currentRoom.guest.membershipNo = undefined currentRoom.guest.membershipNo = undefined
} else { } else {

View File

@@ -51,6 +51,9 @@ export interface Room extends InitialRoomData {
childrenInRoom: Child[] | undefined childrenInRoom: Child[] | undefined
guest: DetailsSchema | MultiroomDetailsSchema | SignedInDetailsSchema guest: DetailsSchema | MultiroomDetailsSchema | SignedInDetailsSchema
roomPrice: RoomPrice roomPrice: RoomPrice
specialRequest: {
comment: string
}
} }
export interface RoomState { export interface RoomState {