From aea963740b7be8dd65b23d1c75f790a7240ef2e2 Mon Sep 17 00:00:00 2001 From: Pontus Dreij Date: Thu, 17 Oct 2024 13:47:37 +0200 Subject: [PATCH 01/14] feat(sw-453): Added selected types count --- .../(standard)/select-rate/page.tsx | 10 ++++-- .../SelectRate/RoomFilter/index.tsx | 34 +++++++++++++++++++ .../RoomFilter/roomFilter.module.css | 5 +++ .../SelectRate/RoomSelection/index.tsx | 6 ++-- i18n/dictionaries/da.json | 2 +- i18n/dictionaries/de.json | 1 + i18n/dictionaries/en.json | 6 ++-- i18n/dictionaries/fi.json | 4 ++- i18n/dictionaries/no.json | 4 ++- i18n/dictionaries/sv.json | 4 ++- .../hotelReservation/selectRate/roomFilter.ts | 3 ++ .../selectRate/roomSelection.ts | 2 +- 12 files changed, 67 insertions(+), 14 deletions(-) create mode 100644 components/HotelReservation/SelectRate/RoomFilter/index.tsx create mode 100644 components/HotelReservation/SelectRate/RoomFilter/roomFilter.module.css create mode 100644 types/components/hotelReservation/selectRate/roomFilter.ts diff --git a/app/[lang]/(live)/(public)/hotelreservation/(standard)/select-rate/page.tsx b/app/[lang]/(live)/(public)/hotelreservation/(standard)/select-rate/page.tsx index 2d80356ac..8ef2b4cd4 100644 --- a/app/[lang]/(live)/(public)/hotelreservation/(standard)/select-rate/page.tsx +++ b/app/[lang]/(live)/(public)/hotelreservation/(standard)/select-rate/page.tsx @@ -2,6 +2,7 @@ import { getProfileSafely } from "@/lib/trpc/memoizedRequests" import { serverClient } from "@/lib/trpc/server" import HotelInfoCard from "@/components/HotelReservation/SelectRate/HotelInfoCard" +import RoomFilter from "@/components/HotelReservation/SelectRate/RoomFilter" import RoomSelection from "@/components/HotelReservation/SelectRate/RoomSelection" import getHotelReservationQueryParams from "@/components/HotelReservation/SelectRate/RoomSelection/utils" import { setLang } from "@/i18n/serverContext" @@ -23,7 +24,7 @@ export default async function SelectRatePage({ const adults = selectRoomParamsObject.room[0].adults // TODO: Handle multiple rooms const children = selectRoomParamsObject.room[0].child?.length // TODO: Handle multiple rooms - const [hotelData, roomConfigurations, user] = await Promise.all([ + const [hotelData, roomsAvailability, user] = await Promise.all([ serverClient().hotel.hotelData.get({ hotelId: searchParams.hotel, language: params.lang, @@ -39,7 +40,7 @@ export default async function SelectRatePage({ getProfileSafely(), ]) - if (!roomConfigurations) { + if (!roomsAvailability) { return "No rooms found" // TODO: Add a proper error message } @@ -54,8 +55,11 @@ export default async function SelectRatePage({
+ diff --git a/components/HotelReservation/SelectRate/RoomFilter/index.tsx b/components/HotelReservation/SelectRate/RoomFilter/index.tsx new file mode 100644 index 000000000..ff3edc852 --- /dev/null +++ b/components/HotelReservation/SelectRate/RoomFilter/index.tsx @@ -0,0 +1,34 @@ +"use client" + +import { useIntl } from "react-intl" + +import Checkbox from "@/components/TempDesignSystem/Checkbox" +import Body from "@/components/TempDesignSystem/Text/Body" +import Caption from "@/components/TempDesignSystem/Text/Caption" + +import styles from "./roomFilter.module.css" + +import { RoomFilterProps } from "@/types/components/hotelReservation/selectRate/roomFilter" + +function RoomFilter({ numberOfRooms }: RoomFilterProps) { + const intl = useIntl() + return ( +
+ + {numberOfRooms}{" "} + {intl.formatMessage( + { id: "Room types available" }, + { numberOfRooms: numberOfRooms } + )} + +
+
+ + Accessibility room +
+
+
+ ) +} + +export default RoomFilter diff --git a/components/HotelReservation/SelectRate/RoomFilter/roomFilter.module.css b/components/HotelReservation/SelectRate/RoomFilter/roomFilter.module.css new file mode 100644 index 000000000..06caf5149 --- /dev/null +++ b/components/HotelReservation/SelectRate/RoomFilter/roomFilter.module.css @@ -0,0 +1,5 @@ +.container { + display: flex; + flex-direction: row; + justify-content: space-between; +} diff --git a/components/HotelReservation/SelectRate/RoomSelection/index.tsx b/components/HotelReservation/SelectRate/RoomSelection/index.tsx index c4c5e2e87..8592e64ea 100644 --- a/components/HotelReservation/SelectRate/RoomSelection/index.tsx +++ b/components/HotelReservation/SelectRate/RoomSelection/index.tsx @@ -12,7 +12,7 @@ import { RoomSelectionProps } from "@/types/components/hotelReservation/selectRa import { Rate } from "@/types/components/hotelReservation/selectRate/selectRate" export default function RoomSelection({ - roomConfigurations, + roomsAvailability, roomCategories, user, }: RoomSelectionProps) { @@ -54,10 +54,10 @@ export default function RoomSelection({ onSubmit={handleSubmit} >
    - {roomConfigurations.roomConfigurations.map((roomConfiguration) => ( + {roomsAvailability.roomConfigurations.map((roomConfiguration) => (
  • Date: Thu, 24 Oct 2024 08:35:34 +0200 Subject: [PATCH 02/14] feat(SW-453) added filter options --- .../(standard)/select-rate/page.tsx | 4 +- .../SelectRate/RoomFilter/index.tsx | 87 ++++++++++++++++--- .../RoomFilter/roomFilter.module.css | 6 ++ .../Checkbox/checkbox.module.css | 40 --------- .../TempDesignSystem/Checkbox/checkbox.ts | 7 -- .../TempDesignSystem/Checkbox/index.tsx | 49 ----------- .../Form/Checkbox/checkbox.module.css | 2 +- i18n/dictionaries/da.json | 8 +- i18n/dictionaries/de.json | 8 +- i18n/dictionaries/en.json | 10 ++- i18n/dictionaries/fi.json | 8 +- i18n/dictionaries/no.json | 8 +- i18n/dictionaries/sv.json | 8 +- server/routers/hotels/query.ts | 1 + server/routers/hotels/schemas/room.ts | 6 ++ .../hotelReservation/selectRate/roomFilter.ts | 6 ++ 16 files changed, 138 insertions(+), 120 deletions(-) delete mode 100644 components/TempDesignSystem/Checkbox/checkbox.module.css delete mode 100644 components/TempDesignSystem/Checkbox/checkbox.ts delete mode 100644 components/TempDesignSystem/Checkbox/index.tsx diff --git a/app/[lang]/(live)/(public)/hotelreservation/(standard)/select-rate/page.tsx b/app/[lang]/(live)/(public)/hotelreservation/(standard)/select-rate/page.tsx index 8ef2b4cd4..05103acf3 100644 --- a/app/[lang]/(live)/(public)/hotelreservation/(standard)/select-rate/page.tsx +++ b/app/[lang]/(live)/(public)/hotelreservation/(standard)/select-rate/page.tsx @@ -21,8 +21,8 @@ export default async function SelectRatePage({ const selectRoomParams = new URLSearchParams(searchParams) const selectRoomParamsObject = getHotelReservationQueryParams(selectRoomParams) - const adults = selectRoomParamsObject.room[0].adults // TODO: Handle multiple rooms - const children = selectRoomParamsObject.room[0].child?.length // TODO: Handle multiple rooms + const adults = selectRoomParamsObject.room?.[0].adults // TODO: Handle multiple rooms + const children = selectRoomParamsObject.room?.[0].child?.length // TODO: Handle multiple rooms const [hotelData, roomsAvailability, user] = await Promise.all([ serverClient().hotel.hotelData.get({ diff --git a/components/HotelReservation/SelectRate/RoomFilter/index.tsx b/components/HotelReservation/SelectRate/RoomFilter/index.tsx index ff3edc852..d775f8f46 100644 --- a/components/HotelReservation/SelectRate/RoomFilter/index.tsx +++ b/components/HotelReservation/SelectRate/RoomFilter/index.tsx @@ -1,32 +1,93 @@ "use client" +import { zodResolver } from "@hookform/resolvers/zod" +import { useRef } from "react" +import { FormProvider, useForm } from "react-hook-form" import { useIntl } from "react-intl" -import Checkbox from "@/components/TempDesignSystem/Checkbox" +import { roomFilterSchema } from "@/server/routers/hotels/schemas/room" + +import Checkbox from "@/components/TempDesignSystem/Form/Checkbox" import Body from "@/components/TempDesignSystem/Text/Body" import Caption from "@/components/TempDesignSystem/Text/Caption" import styles from "./roomFilter.module.css" -import { RoomFilterProps } from "@/types/components/hotelReservation/selectRate/roomFilter" +import { + RoomFilterFormData, + RoomFilterProps, +} from "@/types/components/hotelReservation/selectRate/roomFilter" function RoomFilter({ numberOfRooms }: RoomFilterProps) { const intl = useIntl() + const methods = useForm({ + defaultValues: { + allergyFriendly: false, + petFriendly: false, + accessibility: false, + }, + mode: "all", + reValidateMode: "onChange", + resolver: zodResolver(roomFilterSchema), + }) + + const formRef = useRef(null) + const { watch, setValue } = methods + const petFriendly = watch("petFriendly") + const allergyFriendly = watch("allergyFriendly") + + const onSubmit = (data: RoomFilterFormData) => { + if (data.petFriendly) { + setValue("allergyFriendly", false) + } else if (data.allergyFriendly) { + setValue("petFriendly", false) + } + console.log("Form submitted with data:", data) + } + return (
    - {numberOfRooms}{" "} - {intl.formatMessage( - { id: "Room types available" }, - { numberOfRooms: numberOfRooms } - )} + {intl.formatMessage({ id: "Room types available" }, { numberOfRooms })} -
    -
    - - Accessibility room -
    -
    + +
    +
    + formRef.current?.requestSubmit()} + > + + {intl.formatMessage({ id: "Accessibility room" })} + + + { + setValue("petFriendly", !petFriendly) + formRef.current?.requestSubmit() + }} + registerOptions={{ disabled: allergyFriendly }} + > + + {intl.formatMessage({ id: "Pet room" })} + + + { + setValue("allergyFriendly", !allergyFriendly) + formRef.current?.requestSubmit() + }} + registerOptions={{ disabled: petFriendly }} + > + + {intl.formatMessage({ id: "Allergy room" })} + + +
    +
    +
    ) } diff --git a/components/HotelReservation/SelectRate/RoomFilter/roomFilter.module.css b/components/HotelReservation/SelectRate/RoomFilter/roomFilter.module.css index 06caf5149..3c715f5c3 100644 --- a/components/HotelReservation/SelectRate/RoomFilter/roomFilter.module.css +++ b/components/HotelReservation/SelectRate/RoomFilter/roomFilter.module.css @@ -3,3 +3,9 @@ flex-direction: row; justify-content: space-between; } + +.roomsFilter { + display: flex; + flex-direction: row; + gap: var(--Spacing-x3); +} diff --git a/components/TempDesignSystem/Checkbox/checkbox.module.css b/components/TempDesignSystem/Checkbox/checkbox.module.css deleted file mode 100644 index c831ba525..000000000 --- a/components/TempDesignSystem/Checkbox/checkbox.module.css +++ /dev/null @@ -1,40 +0,0 @@ -.container { - display: flex; - flex-direction: column; - color: var(--text-color); -} - -.container[data-selected] .checkbox { - border: none; - background: var(--UI-Input-Controls-Fill-Selected); -} - -.checkboxContainer { - display: flex; - align-items: flex-start; - gap: var(--Spacing-x-one-and-half); -} - -.checkbox { - width: 24px; - height: 24px; - min-width: 24px; - background-color: var(--UI-Input-Controls-Surface-Normal); - border: 2px solid var(--UI-Input-Controls-Border-Normal); - border-radius: var(--Corner-radius-Small); - transition: all 200ms; - display: flex; - align-items: center; - justify-content: center; - transition: all 200ms; - forced-color-adjust: none; - cursor: pointer; -} - -.error { - align-items: center; - color: var(--Scandic-Red-60); - display: flex; - gap: var(--Spacing-x-half); - margin-top: var(--Spacing-x1); -} diff --git a/components/TempDesignSystem/Checkbox/checkbox.ts b/components/TempDesignSystem/Checkbox/checkbox.ts deleted file mode 100644 index 8588b7401..000000000 --- a/components/TempDesignSystem/Checkbox/checkbox.ts +++ /dev/null @@ -1,7 +0,0 @@ -import { RegisterOptions } from "react-hook-form" - -export interface CheckboxProps - extends React.InputHTMLAttributes { - name: string - registerOptions?: RegisterOptions -} diff --git a/components/TempDesignSystem/Checkbox/index.tsx b/components/TempDesignSystem/Checkbox/index.tsx deleted file mode 100644 index fde1742ff..000000000 --- a/components/TempDesignSystem/Checkbox/index.tsx +++ /dev/null @@ -1,49 +0,0 @@ -import { Checkbox as AriaCheckbox } from "react-aria-components" -import { useController, useFormContext } from "react-hook-form" - -import { InfoCircleIcon } from "@/components/Icons" -import CheckIcon from "@/components/Icons/Check" -import Caption from "@/components/TempDesignSystem/Text/Caption" - -import { CheckboxProps } from "./checkbox" - -import styles from "./checkbox.module.css" - -export default function Checkbox({ - name, - children, - registerOptions, -}: React.PropsWithChildren) { - const { control } = useFormContext() - const { field, fieldState } = useController({ - control, - name, - rules: registerOptions, - }) - - return ( - - {({ isSelected }) => ( - <> -
    -
    - {isSelected && } -
    - {children} -
    - {children && fieldState.error ? ( - - - {fieldState.error.message} - - ) : null} - - )} -
    - ) -} diff --git a/components/TempDesignSystem/Form/Checkbox/checkbox.module.css b/components/TempDesignSystem/Form/Checkbox/checkbox.module.css index 99077e212..2e924b226 100644 --- a/components/TempDesignSystem/Form/Checkbox/checkbox.module.css +++ b/components/TempDesignSystem/Form/Checkbox/checkbox.module.css @@ -16,7 +16,7 @@ .checkboxContainer { display: flex; - align-items: flex-start; + align-items: center; gap: var(--Spacing-x-one-and-half); } diff --git a/i18n/dictionaries/da.json b/i18n/dictionaries/da.json index b9c1622c4..ae660a5fe 100644 --- a/i18n/dictionaries/da.json +++ b/i18n/dictionaries/da.json @@ -5,13 +5,16 @@ "A photo of the room": "Et foto af værelset", "About meetings & conferences": "About meetings & conferences", "About the hotel": "About the hotel", + "Accessibility room": "Handicapvenligt værelse", "Activities": "Aktiviteter", "Add code": "Tilføj kode", "Add new card": "Tilføj nyt kort", + "Add room": "Tilføj værelse", "Address": "Adresse", "Adults": "voksne", "Airport": "Lufthavn", "All our breakfast buffets offer gluten free, vegan, and allergy-friendly options.": "Alle vores morgenmadsbuffeter tilbyder glutenfrie, veganske og allergivenlige muligheder.", + "Allergy room": "Allergivenligt værelse", "Already a friend?": "Allerede en ven?", "Amenities": "Faciliteter", "Amusement park": "Forlystelsespark", @@ -214,6 +217,7 @@ "Pay later": "Betal senere", "Pay now": "Betal nu", "Payment info": "Betalingsoplysninger", + "Pet room": "Kæledyrsrum", "Phone": "Telefon", "Phone is required": "Telefonnummer er påkrævet", "Phone number": "Telefonnummer", @@ -243,7 +247,7 @@ "Retype new password": "Gentag den nye adgangskode", "Room & Terms": "Værelse & Vilkår", "Room facilities": "Værelsesfaciliteter", - "Room types available": "værelse {numberOfRooms, plural, one {# type} other {# types}} tilgængelig", + "Room types available": "{numberOfRooms, plural, one {# room type} other {# room types}} tilgængelig", "Rooms": "Værelser", "Rooms & Guests": "Værelser & gæster", "Sauna and gym": "Sauna and gym", @@ -376,6 +380,8 @@ "to": "til", "uppercase letter": "stort bogstav", "{amount} out of {total}": "{amount} ud af {total}", + "room type": "værelsestype", + "room types": "værelsestyper", "{amount} {currency}": "{amount} {currency}", "{difference}{amount} {currency}": "{difference}{amount} {currency}", "{width} cm × {length} cm": "{width} cm × {length} cm" diff --git a/i18n/dictionaries/de.json b/i18n/dictionaries/de.json index a4df8d378..e69b84d40 100644 --- a/i18n/dictionaries/de.json +++ b/i18n/dictionaries/de.json @@ -5,13 +5,16 @@ "A photo of the room": "Ein Foto des Zimmers", "About meetings & conferences": "About meetings & conferences", "About the hotel": "Über das Hotel", + "Accessibility room": "Barrierefreies Zimmer", "Activities": "Aktivitäten", "Add code": "Code hinzufügen", "Add new card": "Neue Karte hinzufügen", + "Add room": "Zimmer hinzufügen", "Address": "Adresse", "Adults": "Erwachsene", "Airport": "Flughafen", "All our breakfast buffets offer gluten free, vegan, and allergy-friendly options.": "Alle unsere Frühstücksbuffets bieten glutenfreie, vegane und allergikerfreundliche Speisen.", + "Allergy room": "Allergiefreundliches Zimmer", "Already a friend?": "Sind wir schon Freunde?", "Amenities": "Annehmlichkeiten", "Amusement park": "Vergnügungspark", @@ -214,6 +217,7 @@ "Pay later": "Später bezahlen", "Pay now": "Jetzt bezahlen", "Payment info": "Zahlungsinformationen", + "Pet room": "Haustierfreundliches Zimmer", "Phone": "Telefon", "Phone is required": "Telefon ist erforderlich", "Phone number": "Telefonnummer", @@ -244,7 +248,7 @@ "Room": "Zimmer", "Room & Terms": "Zimmer & Bedingungen", "Room facilities": "Zimmerausstattung", - "Room types available": "zimmer {numberOfRooms, plural, one {# type} other {# types}} verfügbar", + "Room types available": "{numberOfRooms, plural, one {# room type} other {# room types}} verfügbar", "Rooms": "Räume", "Rooms & Guests": "Zimmer & Gäste", "Sauna and gym": "Sauna and gym", @@ -377,6 +381,8 @@ "to": "zu", "uppercase letter": "großbuchstabe", "{amount} out of {total}": "{amount} von {total}", + "room type": "zimmerart", + "room types": "zimmerarten", "{amount} {currency}": "{amount} {currency}", "{difference}{amount} {currency}": "{difference}{amount} {currency}", "{width} cm × {length} cm": "{width} cm × {length} cm" diff --git a/i18n/dictionaries/en.json b/i18n/dictionaries/en.json index cf5551d18..59762c705 100644 --- a/i18n/dictionaries/en.json +++ b/i18n/dictionaries/en.json @@ -5,16 +5,19 @@ "A photo of the room": "A photo of the room", "About meetings & conferences": "About meetings & conferences", "About the hotel": "About the hotel", + "Accessibility room": "Accessibility room", "Activities": "Activities", "Add Room": "Add room", "Add code": "Add code", "Add new card": "Add new card", "Add to calendar": "Add to calendar", + "Add room": "Add room", "Address": "Address", "Adults": "Adults", "Age": "Age", "Airport": "Airport", "All our breakfast buffets offer gluten free, vegan, and allergy-friendly options.": "All our breakfast buffets offer gluten free, vegan, and allergy-friendly options.", + "Allergy room": "Allergy room", "Already a friend?": "Already a friend?", "Amenities": "Amenities", "Amusement park": "Amusement park", @@ -224,6 +227,7 @@ "Pay now": "Pay now", "Payment info": "Payment info", "Payment received": "Payment received", + "Pet room": "Pet room", "Phone": "Phone", "Phone is required": "Phone is required", "Phone number": "Phone number", @@ -256,7 +260,7 @@ "Retype new password": "Retype new password", "Room & Terms": "Room & Terms", "Room facilities": "Room facilities", - "Room types available": "room {numberOfRooms, plural, one {# type} other {# types}} available", + "Room types available": "{numberOfRooms, plural, one {# room type} other {# room types}} available", "Rooms": "Rooms", "Rooms & Guests": "Rooms & Guests", "Sauna and gym": "Sauna and gym", @@ -391,11 +395,11 @@ "number": "number", "or": "or", "points": "Points", + "room type": "room type", + "room types": "room types", "special character": "special character", "spendable points expiring by": "{points} spendable points expiring by {date}", "to": "to", - "type": "type", - "types": "types", "uppercase letter": "uppercase letter", "{amount} out of {total}": "{amount} out of {total}", "{amount} {currency}": "{amount} {currency}", diff --git a/i18n/dictionaries/fi.json b/i18n/dictionaries/fi.json index e8f430f16..12fb36018 100644 --- a/i18n/dictionaries/fi.json +++ b/i18n/dictionaries/fi.json @@ -5,13 +5,16 @@ "A photo of the room": "Kuva huoneesta", "About meetings & conferences": "About meetings & conferences", "About the hotel": "Tietoja hotellista", + "Accessibility room": "Esteettömyyshuone", "Activities": "Aktiviteetit", "Add code": "Lisää koodi", "Add new card": "Lisää uusi kortti", + "Add room": "Lisää huone", "Address": "Osoite", "Adults": "Aikuista", "Airport": "Lentokenttä", "All our breakfast buffets offer gluten free, vegan, and allergy-friendly options.": "Kaikki aamiaisbuffettimme tarjoavat gluteenittomia, vegaanisia ja allergiaystävällisiä vaihtoehtoja.", + "Allergy room": "Allergiahuone", "Already a friend?": "Oletko jo ystävä?", "Amenities": "Mukavuudet", "Amusement park": "Huvipuisto", @@ -214,6 +217,7 @@ "Pay later": "Maksa myöhemmin", "Pay now": "Maksa nyt", "Payment info": "Maksutiedot", + "Pet room": "Lemmikkihuone", "Phone": "Puhelin", "Phone is required": "Puhelin vaaditaan", "Phone number": "Puhelinnumero", @@ -243,7 +247,7 @@ "Retype new password": "Kirjoita uusi salasana uudelleen", "Room & Terms": "Huone & Ehdot", "Room facilities": "Huoneen varustelu", - "Room types available": "huoneen {numberOfRooms, plural, one {# type} other {# types}} saatavilla", + "Room types available": "{numberOfRooms, plural, one {# room type} other {# room types}} saatavilla", "Rooms": "Huoneet", "Rooms & Guests": "Huoneet & Vieraat", "Rooms & Guestss": "Huoneet & Vieraat", @@ -379,6 +383,8 @@ "{amount} out of {total}": "{amount}/{total}", "type": "tyyppi", "types": "tyypit", + "room type": "huonetyyppi", + "room types": "huonetyypit", "{amount} {currency}": "{amount} {currency}", "{difference}{amount} {currency}": "{difference}{amount} {currency}", "{width} cm × {length} cm": "{width} cm × {length} cm" diff --git a/i18n/dictionaries/no.json b/i18n/dictionaries/no.json index 0cdbf99e9..fa02a3b96 100644 --- a/i18n/dictionaries/no.json +++ b/i18n/dictionaries/no.json @@ -5,13 +5,16 @@ "A photo of the room": "Et bilde av rommet", "About meetings & conferences": "About meetings & conferences", "About the hotel": "Om hotellet", + "Accessibility room": "Tilgjengelighetsrom", "Activities": "Aktiviteter", "Add code": "Legg til kode", "Add new card": "Legg til nytt kort", + "Add room": "Legg til rom", "Address": "Adresse", "Adults": "Voksne", "Airport": "Flyplass", "All our breakfast buffets offer gluten free, vegan, and allergy-friendly options.": "Alle våre frokostbufféer tilbyr glutenfrie, veganske og allergivennlige alternativer.", + "Allergy room": "Allergihus", "Already a friend?": "Allerede Friend?", "Amenities": "Fasiliteter", "Amusement park": "Tivoli", @@ -212,6 +215,7 @@ "Pay later": "Betal senere", "Pay now": "Betal nå", "Payment info": "Betalingsinformasjon", + "Pet room": "Kjæledyrrom", "Phone": "Telefon", "Phone is required": "Telefon kreves", "Phone number": "Telefonnummer", @@ -241,7 +245,7 @@ "Retype new password": "Skriv inn nytt passord på nytt", "Room & Terms": "Rom & Vilkår", "Room facilities": "Romfasiliteter", - "Room types available": "romtyper {numberOfRooms, plural, one {# type} other {# types}} tilgjengelig", + "Room types available": "{numberOfRooms, plural, one {# room type} other {# room types}} tilgjengelig", "Rooms": "Rom", "Rooms & Guests": "Rom og gjester", "Sauna and gym": "Sauna and gym", @@ -375,6 +379,8 @@ "{amount} out of {total}": "{amount} av {total}", "type": "type", "types": "typer", + "room type": "romtype", + "room types": "romtyper", "{amount} {currency}": "{amount} {currency}", "{difference}{amount} {currency}": "{difference}{amount} {currency}", "{width} cm × {length} cm": "{width} cm × {length} cm" diff --git a/i18n/dictionaries/sv.json b/i18n/dictionaries/sv.json index c88675fe9..5ee85ccde 100644 --- a/i18n/dictionaries/sv.json +++ b/i18n/dictionaries/sv.json @@ -5,13 +5,16 @@ "A photo of the room": "Ett foto av rummet", "About meetings & conferences": "About meetings & conferences", "About the hotel": "Om hotellet", + "Accessibility room": "Tillgänglighetsrum", "Activities": "Aktiviteter", "Add code": "Lägg till kod", "Add new card": "Lägg till nytt kort", + "Add room": "Lägg till rum", "Address": "Adress", "Adults": "Vuxna", "Airport": "Flygplats", "All our breakfast buffets offer gluten free, vegan, and allergy-friendly options.": "Alla våra frukostbufféer erbjuder glutenfria, veganska och allergivänliga alternativ.", + "Allergy room": "Allergirum", "Already a friend?": "Är du redan en vän?", "Amenities": "Bekvämligheter", "Amusement park": "Nöjespark", @@ -212,6 +215,7 @@ "Pay later": "Betala senare", "Pay now": "Betala nu", "Payment info": "Betalningsinformation", + "Pet room": "Husdjursrum", "Phone": "Telefon", "Phone is required": "Telefonnummer är obligatorisk", "Phone number": "Telefonnummer", @@ -241,7 +245,7 @@ "Retype new password": "Upprepa nytt lösenord", "Room & Terms": "Rum & Villkor", "Room facilities": "Rumfaciliteter", - "Room types available": "rumstyper {numberOfRooms, plural, one {# type} other {# types}} tillgängliga", + "Room types available": "{numberOfRooms, plural, one {# room type} other {# room types}} tillgängliga", "Rooms": "Rum", "Rooms & Guests": "Rum och gäster", "Sauna and gym": "Sauna and gym", @@ -376,6 +380,8 @@ "{amount} out of {total}": "{amount} av {total}", "type": "typ", "types": "typer", + "room type": "rumtyp", + "room types": "rumstyper", "{amount} {currency}": "{amount} {currency}", "{difference}{amount} {currency}": "{difference}{amount} {currency}", "{width} cm × {length} cm": "{width} cm × {length} cm" diff --git a/server/routers/hotels/query.ts b/server/routers/hotels/query.ts index b5b5ff34b..289429ca7 100644 --- a/server/routers/hotels/query.ts +++ b/server/routers/hotels/query.ts @@ -441,6 +441,7 @@ export const hotelQueryRouter = router({ }, params ) + if (!apiResponse.ok) { const text = await apiResponse.text() roomsAvailabilityFailCounter.add(1, { diff --git a/server/routers/hotels/schemas/room.ts b/server/routers/hotels/schemas/room.ts index 19f922db0..af3a3e8bc 100644 --- a/server/routers/hotels/schemas/room.ts +++ b/server/routers/hotels/schemas/room.ts @@ -92,3 +92,9 @@ export const roomSchema = z roomFacilities: data.attributes.roomFacilities, } }) + +export const roomFilterSchema = z.object({ + accessibility: z.boolean(), + petFriendly: z.boolean(), + allergyFriendly: z.boolean(), +}) diff --git a/types/components/hotelReservation/selectRate/roomFilter.ts b/types/components/hotelReservation/selectRate/roomFilter.ts index 3ac0c6be5..ac31cfdbd 100644 --- a/types/components/hotelReservation/selectRate/roomFilter.ts +++ b/types/components/hotelReservation/selectRate/roomFilter.ts @@ -1,3 +1,9 @@ +import { z } from "zod" + +import { roomFilterSchema } from "@/server/routers/hotels/schemas/room" + export interface RoomFilterProps { numberOfRooms: number } + +export interface RoomFilterFormData extends z.output {} From 260c9096f60ce38ac63b81efee2a5ead3348571d Mon Sep 17 00:00:00 2001 From: Pontus Dreij Date: Thu, 24 Oct 2024 09:26:50 +0200 Subject: [PATCH 03/14] feat(sw-453): fixed lang files --- i18n/dictionaries/da.json | 7 ++----- i18n/dictionaries/de.json | 7 ++----- i18n/dictionaries/en.json | 2 +- i18n/dictionaries/fi.json | 9 ++------- i18n/dictionaries/no.json | 9 ++------- 5 files changed, 9 insertions(+), 25 deletions(-) diff --git a/i18n/dictionaries/da.json b/i18n/dictionaries/da.json index ae660a5fe..adf12745e 100644 --- a/i18n/dictionaries/da.json +++ b/i18n/dictionaries/da.json @@ -351,14 +351,12 @@ "Zoom in": "Zoom ind", "Zoom out": "Zoom ud", "as of today": "pr. dags dato", - "booking.accommodatesUpTo": "Plads til {nrOfGuests, plural, one {# person} other {op til # personer}}", "booking.adults": "{totalAdults, plural, one {# voksen} other {# voksne}}", "booking.children": "{totalChildren, plural, one {# barn} other {# børn}}", "booking.guests": "Maks {nrOfGuests, plural, one {# gæst} other {# gæster}}", "booking.nights": "{totalNights, plural, one {# nat} other {# nætter}}", "booking.rooms": "{totalRooms, plural, one {# værelse} other {# værelser}}", "booking.terms": "Ved at betale med en af de tilgængelige betalingsmetoder, accepterer jeg vilkårene for denne booking og de generelle Vilkår og betingelser, og forstår, at Scandic vil behandle min personlige data i forbindelse med denne booking i henhold til Scandics Privatlivspolitik. Jeg accepterer, at Scandic kræver et gyldigt kreditkort under min besøg i tilfælde af, at noget er tilbagebetalt.", - "booking.thisRoomIsEquippedWith": "Dette værelse er udstyret med", "by": "inden", "characters": "tegn", "guest": "gæst", @@ -375,13 +373,12 @@ "number": "nummer", "or": "eller", "points": "Point", + "room type": "værelsestype", + "room types": "værelsestyper", "special character": "speciel karakter", "spendable points expiring by": "{points} Brugbare point udløber den {date}", "to": "til", "uppercase letter": "stort bogstav", - "{amount} out of {total}": "{amount} ud af {total}", - "room type": "værelsestype", - "room types": "værelsestyper", "{amount} {currency}": "{amount} {currency}", "{difference}{amount} {currency}": "{difference}{amount} {currency}", "{width} cm × {length} cm": "{width} cm × {length} cm" diff --git a/i18n/dictionaries/de.json b/i18n/dictionaries/de.json index e69b84d40..e7c3ee819 100644 --- a/i18n/dictionaries/de.json +++ b/i18n/dictionaries/de.json @@ -352,14 +352,12 @@ "Zoom in": "Vergrößern", "Zoom out": "Verkleinern", "as of today": "Stand heute", - "booking.accommodatesUpTo": "Bietet Platz für {nrOfGuests, plural, one {# Person } other {bis zu # Personen}}", "booking.adults": "{totalAdults, plural, one {# erwachsene} other {# erwachsene}}", "booking.children": "{totalChildren, plural, one {# kind} other {# kinder}}", "booking.guests": "Max {nrOfGuests, plural, one {# gast} other {# gäste}}", "booking.nights": "{totalNights, plural, one {# nacht} other {# Nächte}}", "booking.rooms": "{totalRooms, plural, one {# zimmer} other {# räume}}", "booking.terms": "Ved at betale med en af de tilgængelige betalingsmetoder, accepterer jeg vilkårene for denne booking og de generelle Vilkår og betingelser, og forstår, at Scandic vil behandle min personlige data i forbindelse med denne booking i henhold til Scandics Privatlivspolitik. Jeg accepterer, at Scandic kræver et gyldigt kreditkort under min besøg i tilfælde af, at noget er tilbagebetalt.", - "booking.thisRoomIsEquippedWith": "Dieses Zimmer ist ausgestattet mit", "by": "bis", "characters": "figuren", "guest": "gast", @@ -376,13 +374,12 @@ "number": "nummer", "or": "oder", "points": "Punkte", + "room type": "zimmerart", + "room types": "zimmerarten", "special character": "sonderzeichen", "spendable points expiring by": "{points} Einlösbare punkte verfallen bis zum {date}", "to": "zu", "uppercase letter": "großbuchstabe", - "{amount} out of {total}": "{amount} von {total}", - "room type": "zimmerart", - "room types": "zimmerarten", "{amount} {currency}": "{amount} {currency}", "{difference}{amount} {currency}": "{difference}{amount} {currency}", "{width} cm × {length} cm": "{width} cm × {length} cm" diff --git a/i18n/dictionaries/en.json b/i18n/dictionaries/en.json index 59762c705..cb2f91ecc 100644 --- a/i18n/dictionaries/en.json +++ b/i18n/dictionaries/en.json @@ -10,8 +10,8 @@ "Add Room": "Add room", "Add code": "Add code", "Add new card": "Add new card", - "Add to calendar": "Add to calendar", "Add room": "Add room", + "Add to calendar": "Add to calendar", "Address": "Address", "Adults": "Adults", "Age": "Age", diff --git a/i18n/dictionaries/fi.json b/i18n/dictionaries/fi.json index 12fb36018..9307bd321 100644 --- a/i18n/dictionaries/fi.json +++ b/i18n/dictionaries/fi.json @@ -352,14 +352,12 @@ "Zoom in": "Lähennä", "Zoom out": "Loitonna", "as of today": "tänään", - "booking.accommodatesUpTo": "Huoneeseen {nrOfGuests, plural, one {# person} other {mahtuu 2 henkilöä}}", "booking.adults": "{totalAdults, plural, one {# aikuinen} other {# aikuiset}}", "booking.children": "{totalChildren, plural, one {# lapsi} other {# lasta}}", "booking.guests": "Max {nrOfGuests, plural, one {# vieras} other {# vieraita}}", "booking.nights": "{totalNights, plural, one {# yö} other {# yötä}}", "booking.rooms": "{totalRooms, plural, one {# huone} other {# sviitti}}", "booking.terms": "Maksamalla minkä tahansa saatavilla olevan maksutavan avulla hyväksyn tämän varauksen ehdot ja yleiset ehdot ja ehtoja, ja ymmärrän, että Scandic käsittelee minun henkilötietoni tässä varauksessa mukaisesti Scandicin tietosuojavaltuuden mukaisesti. Hyväksyn myös, että Scandic vaatii validin luottokortin majoituksen ajan, jos jokin jää maksamatta.", - "booking.thisRoomIsEquippedWith": "Tämä huone on varustettu", "by": "mennessä", "characters": "hahmoja", "guest": "Vieras", @@ -376,15 +374,12 @@ "number": "määrä", "or": "tai", "points": "pistettä", + "room type": "huonetyyppi", + "room types": "huonetyypit", "special character": "erikoishahmo", "spendable points expiring by": "{points} pistettä vanhenee {date} mennessä", "to": "to", "uppercase letter": "iso kirjain", - "{amount} out of {total}": "{amount}/{total}", - "type": "tyyppi", - "types": "tyypit", - "room type": "huonetyyppi", - "room types": "huonetyypit", "{amount} {currency}": "{amount} {currency}", "{difference}{amount} {currency}": "{difference}{amount} {currency}", "{width} cm × {length} cm": "{width} cm × {length} cm" diff --git a/i18n/dictionaries/no.json b/i18n/dictionaries/no.json index fa02a3b96..e7b95db27 100644 --- a/i18n/dictionaries/no.json +++ b/i18n/dictionaries/no.json @@ -349,13 +349,11 @@ "Zoom in": "Zoom inn", "Zoom out": "Zoom ut", "as of today": "per i dag", - "booking.accommodatesUpTo": "Plass til {nrOfGuests, plural, one {# person} other {opptil # personer}}", "booking.adults": "{totalAdults, plural, one {# voksen} other {# voksne}}", "booking.children": "{totalChildren, plural, one {# barn} other {# barn}}", "booking.guests": "Maks {nrOfGuests, plural, one {# gjest} other {# gjester}}", "booking.nights": "{totalNights, plural, one {# natt} other {# netter}}", "booking.rooms": "{totalRooms, plural, one {# rom} other {# rom}}", - "booking.thisRoomIsEquippedWith": "Dette rommet er utstyrt med", "by": "innen", "characters": "tegn", "guest": "gjest", @@ -372,15 +370,12 @@ "number": "antall", "or": "eller", "points": "poeng", + "room type": "romtype", + "room types": "romtyper", "special character": "spesiell karakter", "spendable points expiring by": "{points} Brukbare poeng utløper innen {date}", "to": "til", "uppercase letter": "stor bokstav", - "{amount} out of {total}": "{amount} av {total}", - "type": "type", - "types": "typer", - "room type": "romtype", - "room types": "romtyper", "{amount} {currency}": "{amount} {currency}", "{difference}{amount} {currency}": "{difference}{amount} {currency}", "{width} cm × {length} cm": "{width} cm × {length} cm" From 7b36139684d6ebc5978e3e3e87f334c4bfaad34e Mon Sep 17 00:00:00 2001 From: Pontus Dreij Date: Fri, 25 Oct 2024 12:55:13 +0200 Subject: [PATCH 04/14] feat(sw-453): implemented filter from packages --- .../(standard)/select-rate/page.module.css | 24 ---- .../(standard)/select-rate/page.tsx | 42 ++++--- .../EnterDetails/Payment/index.tsx | 2 +- .../SelectRate/RoomFilter/index.tsx | 115 ++++++++---------- .../SelectRate/Rooms/index.tsx | 53 ++++++++ .../SelectRate/Rooms/rooms.module.css | 8 ++ i18n/dictionaries/da.json | 6 +- i18n/dictionaries/de.json | 6 +- i18n/dictionaries/en.json | 7 +- i18n/dictionaries/fi.json | 6 +- i18n/dictionaries/no.json | 6 +- i18n/dictionaries/sv.json | 6 +- lib/api/endpoints.ts | 1 + lib/api/index.ts | 2 +- server/routers/hotels/query.ts | 97 +++++++++++++++ server/routers/hotels/schemas/packages.ts | 59 +++++++++ server/routers/hotels/schemas/room.ts | 6 - server/tokenManager.ts | 2 +- .../hotelReservation/selectRate/room.ts | 6 + .../hotelReservation/selectRate/roomFilter.ts | 9 +- 20 files changed, 330 insertions(+), 133 deletions(-) delete mode 100644 app/[lang]/(live)/(public)/hotelreservation/(standard)/select-rate/page.module.css create mode 100644 components/HotelReservation/SelectRate/Rooms/index.tsx create mode 100644 components/HotelReservation/SelectRate/Rooms/rooms.module.css create mode 100644 server/routers/hotels/schemas/packages.ts create mode 100644 types/components/hotelReservation/selectRate/room.ts diff --git a/app/[lang]/(live)/(public)/hotelreservation/(standard)/select-rate/page.module.css b/app/[lang]/(live)/(public)/hotelreservation/(standard)/select-rate/page.module.css deleted file mode 100644 index 464c8ce65..000000000 --- a/app/[lang]/(live)/(public)/hotelreservation/(standard)/select-rate/page.module.css +++ /dev/null @@ -1,24 +0,0 @@ -.page { - min-height: 100dvh; - padding-top: var(--Spacing-x6); - padding-left: var(--Spacing-x2); - padding-right: var(--Spacing-x2); - background-color: var(--Scandic-Brand-Warm-White); -} - -.content { - max-width: var(--max-width); - margin: 0 auto; - display: flex; - flex-direction: column; - gap: var(--Spacing-x7); - padding: var(--Spacing-x2); -} - -.main { - flex-grow: 1; -} - -.summary { - max-width: 340px; -} diff --git a/app/[lang]/(live)/(public)/hotelreservation/(standard)/select-rate/page.tsx b/app/[lang]/(live)/(public)/hotelreservation/(standard)/select-rate/page.tsx index 05103acf3..d43330adb 100644 --- a/app/[lang]/(live)/(public)/hotelreservation/(standard)/select-rate/page.tsx +++ b/app/[lang]/(live)/(public)/hotelreservation/(standard)/select-rate/page.tsx @@ -1,14 +1,12 @@ import { getProfileSafely } from "@/lib/trpc/memoizedRequests" import { serverClient } from "@/lib/trpc/server" +import { RoomPackageCode } from "@/server/routers/hotels/schemas/packages" import HotelInfoCard from "@/components/HotelReservation/SelectRate/HotelInfoCard" -import RoomFilter from "@/components/HotelReservation/SelectRate/RoomFilter" -import RoomSelection from "@/components/HotelReservation/SelectRate/RoomSelection" +import Rooms from "@/components/HotelReservation/SelectRate/Rooms" import getHotelReservationQueryParams from "@/components/HotelReservation/SelectRate/RoomSelection/utils" import { setLang } from "@/i18n/serverContext" -import styles from "./page.module.css" - import { SelectRateSearchParams } from "@/types/components/hotelReservation/selectRate/selectRate" import { LangParams, PageArgs } from "@/types/params" @@ -24,7 +22,7 @@ export default async function SelectRatePage({ const adults = selectRoomParamsObject.room?.[0].adults // TODO: Handle multiple rooms const children = selectRoomParamsObject.room?.[0].child?.length // TODO: Handle multiple rooms - const [hotelData, roomsAvailability, user] = await Promise.all([ + const [hotelData, roomsAvailability, packages, user] = await Promise.all([ serverClient().hotel.hotelData.get({ hotelId: searchParams.hotel, language: params.lang, @@ -37,6 +35,18 @@ export default async function SelectRatePage({ adults, children, }), + serverClient().hotel.packages.get({ + hotelId: searchParams.hotel, + startDate: searchParams.fromDate, + endDate: searchParams.toDate, + adults: adults, + children: children, + packageCodes: [ + RoomPackageCode.ACCE, + RoomPackageCode.PETR, + RoomPackageCode.ALLG, + ], + }), getProfileSafely(), ]) @@ -51,20 +61,14 @@ export default async function SelectRatePage({ const roomCategories = hotelData?.included return ( -
    + <> -
    -
    - - -
    -
    -
    + + ) } diff --git a/components/HotelReservation/EnterDetails/Payment/index.tsx b/components/HotelReservation/EnterDetails/Payment/index.tsx index 0da2b79e2..ba126c1f2 100644 --- a/components/HotelReservation/EnterDetails/Payment/index.tsx +++ b/components/HotelReservation/EnterDetails/Payment/index.tsx @@ -22,7 +22,7 @@ import { useEnterDetailsStore } from "@/stores/enter-details" import LoadingSpinner from "@/components/LoadingSpinner" import Button from "@/components/TempDesignSystem/Button" -import Checkbox from "@/components/TempDesignSystem/Checkbox" +import Checkbox from "@/components/TempDesignSystem/Form/Checkbox" import Link from "@/components/TempDesignSystem/Link" import Body from "@/components/TempDesignSystem/Text/Body" import Caption from "@/components/TempDesignSystem/Text/Caption" diff --git a/components/HotelReservation/SelectRate/RoomFilter/index.tsx b/components/HotelReservation/SelectRate/RoomFilter/index.tsx index d775f8f46..3c3586994 100644 --- a/components/HotelReservation/SelectRate/RoomFilter/index.tsx +++ b/components/HotelReservation/SelectRate/RoomFilter/index.tsx @@ -1,49 +1,60 @@ "use client" import { zodResolver } from "@hookform/resolvers/zod" -import { useRef } from "react" +import { useCallback, useEffect, useMemo } from "react" import { FormProvider, useForm } from "react-hook-form" import { useIntl } from "react-intl" +import { z } from "zod" -import { roomFilterSchema } from "@/server/routers/hotels/schemas/room" +import { RoomPackageCode } from "@/server/routers/hotels/schemas/packages" +import Chip from "@/components/TempDesignSystem/Chip" import Checkbox from "@/components/TempDesignSystem/Form/Checkbox" import Body from "@/components/TempDesignSystem/Text/Body" import Caption from "@/components/TempDesignSystem/Text/Caption" import styles from "./roomFilter.module.css" -import { - RoomFilterFormData, - RoomFilterProps, -} from "@/types/components/hotelReservation/selectRate/roomFilter" +import { RoomFilterProps } from "@/types/components/hotelReservation/selectRate/roomFilter" + +export default function RoomFilter({ + numberOfRooms, + onFilter, + filterOptions, +}: RoomFilterProps) { + const initialFilterValues = useMemo( + () => + filterOptions.reduce( + (acc, option) => { + acc[option.code] = false + return acc + }, + {} as Record + ), + [filterOptions] + ) -function RoomFilter({ numberOfRooms }: RoomFilterProps) { const intl = useIntl() - const methods = useForm({ - defaultValues: { - allergyFriendly: false, - petFriendly: false, - accessibility: false, - }, + const methods = useForm>({ + defaultValues: initialFilterValues, mode: "all", reValidateMode: "onChange", - resolver: zodResolver(roomFilterSchema), + resolver: zodResolver(z.object({})), }) - const formRef = useRef(null) - const { watch, setValue } = methods - const petFriendly = watch("petFriendly") - const allergyFriendly = watch("allergyFriendly") + const { watch, getValues, handleSubmit } = methods + const petFriendly = watch(RoomPackageCode.PETR) + const allergyFriendly = watch(RoomPackageCode.ALLG) - const onSubmit = (data: RoomFilterFormData) => { - if (data.petFriendly) { - setValue("allergyFriendly", false) - } else if (data.allergyFriendly) { - setValue("petFriendly", false) - } - console.log("Form submitted with data:", data) - } + const submitFilter = useCallback(() => { + const data = getValues() + onFilter(data) + }, [onFilter, getValues]) + + useEffect(() => { + const subscription = watch(() => handleSubmit(submitFilter)()) + return () => subscription.unsubscribe() + }, [handleSubmit, watch, submitFilter]) return (
    @@ -51,45 +62,27 @@ function RoomFilter({ numberOfRooms }: RoomFilterProps) { {intl.formatMessage({ id: "Room types available" }, { numberOfRooms })} -
    +
    - formRef.current?.requestSubmit()} - > - - {intl.formatMessage({ id: "Accessibility room" })} - - - { - setValue("petFriendly", !petFriendly) - formRef.current?.requestSubmit() - }} - registerOptions={{ disabled: allergyFriendly }} - > - - {intl.formatMessage({ id: "Pet room" })} - - - { - setValue("allergyFriendly", !allergyFriendly) - formRef.current?.requestSubmit() - }} - registerOptions={{ disabled: petFriendly }} - > - - {intl.formatMessage({ id: "Allergy room" })} - - + {filterOptions.map((option) => ( + + + {intl.formatMessage({ id: option.description })} + + + ))}
    ) } - -export default RoomFilter diff --git a/components/HotelReservation/SelectRate/Rooms/index.tsx b/components/HotelReservation/SelectRate/Rooms/index.tsx new file mode 100644 index 000000000..963f266d4 --- /dev/null +++ b/components/HotelReservation/SelectRate/Rooms/index.tsx @@ -0,0 +1,53 @@ +"use client" + +import { useState } from "react" + +import { RoomsAvailability } from "@/server/routers/hotels/output" + +import RoomFilter from "../RoomFilter" +import RoomSelection from "../RoomSelection" + +import styles from "./rooms.module.css" + +import { RoomProps } from "@/types/components/hotelReservation/selectRate/room" +import { RoomPackageCodes } from "@/types/components/hotelReservation/selectRate/roomFilter" + +export default function Rooms({ + roomsAvailability, + roomCategories = [], + user, + packages, +}: RoomProps) { + const [rooms, setRooms] = useState(roomsAvailability) + + function handleFilter(filter: Record) { + const selectedCodes = Object.keys(filter).filter((key) => filter[key]) + + if (selectedCodes.length === 0) { + setRooms(roomsAvailability) + return + } + + const filteredRooms = roomsAvailability.roomConfigurations.filter((room) => + room.features.some((feature) => + selectedCodes.includes(feature.code as RoomPackageCodes) + ) + ) + setRooms({ ...roomsAvailability, roomConfigurations: filteredRooms }) + } + + return ( +
    + + +
    + ) +} diff --git a/components/HotelReservation/SelectRate/Rooms/rooms.module.css b/components/HotelReservation/SelectRate/Rooms/rooms.module.css new file mode 100644 index 000000000..a8e573530 --- /dev/null +++ b/components/HotelReservation/SelectRate/Rooms/rooms.module.css @@ -0,0 +1,8 @@ +.content { + max-width: var(--max-width); + margin: 0 auto; + display: flex; + flex-direction: column; + gap: var(--Spacing-x7); + padding: var(--Spacing-x2); +} diff --git a/i18n/dictionaries/da.json b/i18n/dictionaries/da.json index adf12745e..af4c667b8 100644 --- a/i18n/dictionaries/da.json +++ b/i18n/dictionaries/da.json @@ -5,7 +5,7 @@ "A photo of the room": "Et foto af værelset", "About meetings & conferences": "About meetings & conferences", "About the hotel": "About the hotel", - "Accessibility room": "Handicapvenligt værelse", + "Accessible Room": "Tilgængelighedsrum", "Activities": "Aktiviteter", "Add code": "Tilføj kode", "Add new card": "Tilføj nyt kort", @@ -14,7 +14,7 @@ "Adults": "voksne", "Airport": "Lufthavn", "All our breakfast buffets offer gluten free, vegan, and allergy-friendly options.": "Alle vores morgenmadsbuffeter tilbyder glutenfrie, veganske og allergivenlige muligheder.", - "Allergy room": "Allergivenligt værelse", + "Allergy Room": "Allergirum", "Already a friend?": "Allerede en ven?", "Amenities": "Faciliteter", "Amusement park": "Forlystelsespark", @@ -217,7 +217,7 @@ "Pay later": "Betal senere", "Pay now": "Betal nu", "Payment info": "Betalingsoplysninger", - "Pet room": "Kæledyrsrum", + "Pet Room": "Kæledyrsrum", "Phone": "Telefon", "Phone is required": "Telefonnummer er påkrævet", "Phone number": "Telefonnummer", diff --git a/i18n/dictionaries/de.json b/i18n/dictionaries/de.json index e7c3ee819..c9483e349 100644 --- a/i18n/dictionaries/de.json +++ b/i18n/dictionaries/de.json @@ -5,7 +5,7 @@ "A photo of the room": "Ein Foto des Zimmers", "About meetings & conferences": "About meetings & conferences", "About the hotel": "Über das Hotel", - "Accessibility room": "Barrierefreies Zimmer", + "Accessible Room": "Barrierefreies Zimmer", "Activities": "Aktivitäten", "Add code": "Code hinzufügen", "Add new card": "Neue Karte hinzufügen", @@ -14,7 +14,7 @@ "Adults": "Erwachsene", "Airport": "Flughafen", "All our breakfast buffets offer gluten free, vegan, and allergy-friendly options.": "Alle unsere Frühstücksbuffets bieten glutenfreie, vegane und allergikerfreundliche Speisen.", - "Allergy room": "Allergiefreundliches Zimmer", + "Allergy Room": "Allergikerzimmer", "Already a friend?": "Sind wir schon Freunde?", "Amenities": "Annehmlichkeiten", "Amusement park": "Vergnügungspark", @@ -217,7 +217,7 @@ "Pay later": "Später bezahlen", "Pay now": "Jetzt bezahlen", "Payment info": "Zahlungsinformationen", - "Pet room": "Haustierfreundliches Zimmer", + "Pet Room": "Haustierzimmer", "Phone": "Telefon", "Phone is required": "Telefon ist erforderlich", "Phone number": "Telefonnummer", diff --git a/i18n/dictionaries/en.json b/i18n/dictionaries/en.json index cb2f91ecc..0511cf5a9 100644 --- a/i18n/dictionaries/en.json +++ b/i18n/dictionaries/en.json @@ -5,7 +5,7 @@ "A photo of the room": "A photo of the room", "About meetings & conferences": "About meetings & conferences", "About the hotel": "About the hotel", - "Accessibility room": "Accessibility room", + "Accessible Room": "Accessibility room", "Activities": "Activities", "Add Room": "Add room", "Add code": "Add code", @@ -17,7 +17,7 @@ "Age": "Age", "Airport": "Airport", "All our breakfast buffets offer gluten free, vegan, and allergy-friendly options.": "All our breakfast buffets offer gluten free, vegan, and allergy-friendly options.", - "Allergy room": "Allergy room", + "Allergy Room": "Allergy room", "Already a friend?": "Already a friend?", "Amenities": "Amenities", "Amusement park": "Amusement park", @@ -227,7 +227,7 @@ "Pay now": "Pay now", "Payment info": "Payment info", "Payment received": "Payment received", - "Pet room": "Pet room", + "Pet Room": "Pet room", "Phone": "Phone", "Phone is required": "Phone is required", "Phone number": "Phone number", @@ -258,6 +258,7 @@ "Restaurant & Bar": "Restaurant & Bar", "Restaurants & Bars": "Restaurants & Bars", "Retype new password": "Retype new password", + "Room": "Room", "Room & Terms": "Room & Terms", "Room facilities": "Room facilities", "Room types available": "{numberOfRooms, plural, one {# room type} other {# room types}} available", diff --git a/i18n/dictionaries/fi.json b/i18n/dictionaries/fi.json index 9307bd321..c8427a306 100644 --- a/i18n/dictionaries/fi.json +++ b/i18n/dictionaries/fi.json @@ -5,7 +5,7 @@ "A photo of the room": "Kuva huoneesta", "About meetings & conferences": "About meetings & conferences", "About the hotel": "Tietoja hotellista", - "Accessibility room": "Esteettömyyshuone", + "Accessible Room": "Esteetön huone", "Activities": "Aktiviteetit", "Add code": "Lisää koodi", "Add new card": "Lisää uusi kortti", @@ -14,7 +14,7 @@ "Adults": "Aikuista", "Airport": "Lentokenttä", "All our breakfast buffets offer gluten free, vegan, and allergy-friendly options.": "Kaikki aamiaisbuffettimme tarjoavat gluteenittomia, vegaanisia ja allergiaystävällisiä vaihtoehtoja.", - "Allergy room": "Allergiahuone", + "Allergy Room": "Allergiahuone", "Already a friend?": "Oletko jo ystävä?", "Amenities": "Mukavuudet", "Amusement park": "Huvipuisto", @@ -217,7 +217,7 @@ "Pay later": "Maksa myöhemmin", "Pay now": "Maksa nyt", "Payment info": "Maksutiedot", - "Pet room": "Lemmikkihuone", + "Pet Room": "Lemmikkihuone", "Phone": "Puhelin", "Phone is required": "Puhelin vaaditaan", "Phone number": "Puhelinnumero", diff --git a/i18n/dictionaries/no.json b/i18n/dictionaries/no.json index e7b95db27..69a1fbec4 100644 --- a/i18n/dictionaries/no.json +++ b/i18n/dictionaries/no.json @@ -5,7 +5,7 @@ "A photo of the room": "Et bilde av rommet", "About meetings & conferences": "About meetings & conferences", "About the hotel": "Om hotellet", - "Accessibility room": "Tilgjengelighetsrom", + "Accessible Room": "Tilgjengelighetsrom", "Activities": "Aktiviteter", "Add code": "Legg til kode", "Add new card": "Legg til nytt kort", @@ -14,7 +14,7 @@ "Adults": "Voksne", "Airport": "Flyplass", "All our breakfast buffets offer gluten free, vegan, and allergy-friendly options.": "Alle våre frokostbufféer tilbyr glutenfrie, veganske og allergivennlige alternativer.", - "Allergy room": "Allergihus", + "Allergy Room": "Allergirom", "Already a friend?": "Allerede Friend?", "Amenities": "Fasiliteter", "Amusement park": "Tivoli", @@ -215,7 +215,7 @@ "Pay later": "Betal senere", "Pay now": "Betal nå", "Payment info": "Betalingsinformasjon", - "Pet room": "Kjæledyrrom", + "Pet Room": "Kjæledyrsrom", "Phone": "Telefon", "Phone is required": "Telefon kreves", "Phone number": "Telefonnummer", diff --git a/i18n/dictionaries/sv.json b/i18n/dictionaries/sv.json index 5ee85ccde..c2f90d520 100644 --- a/i18n/dictionaries/sv.json +++ b/i18n/dictionaries/sv.json @@ -5,7 +5,7 @@ "A photo of the room": "Ett foto av rummet", "About meetings & conferences": "About meetings & conferences", "About the hotel": "Om hotellet", - "Accessibility room": "Tillgänglighetsrum", + "Accessible Room": "Tillgänglighetsrum", "Activities": "Aktiviteter", "Add code": "Lägg till kod", "Add new card": "Lägg till nytt kort", @@ -14,7 +14,7 @@ "Adults": "Vuxna", "Airport": "Flygplats", "All our breakfast buffets offer gluten free, vegan, and allergy-friendly options.": "Alla våra frukostbufféer erbjuder glutenfria, veganska och allergivänliga alternativ.", - "Allergy room": "Allergirum", + "Allergy Room": "Allergirum", "Already a friend?": "Är du redan en vän?", "Amenities": "Bekvämligheter", "Amusement park": "Nöjespark", @@ -215,7 +215,7 @@ "Pay later": "Betala senare", "Pay now": "Betala nu", "Payment info": "Betalningsinformation", - "Pet room": "Husdjursrum", + "Pet Room": "Husdjursrum", "Phone": "Telefon", "Phone is required": "Telefonnummer är obligatorisk", "Phone number": "Telefonnummer", diff --git a/lib/api/endpoints.ts b/lib/api/endpoints.ts index 66bc36ec3..be1aee2fd 100644 --- a/lib/api/endpoints.ts +++ b/lib/api/endpoints.ts @@ -23,6 +23,7 @@ export namespace endpoints { rewards = `${profile}/reward`, tierRewards = `${profile}/TierRewards`, subscriberId = `${profile}/SubscriberId`, + packages = "package/v1/packages/hotel", } } diff --git a/lib/api/index.ts b/lib/api/index.ts index 9e32ac0cc..475e0da4e 100644 --- a/lib/api/index.ts +++ b/lib/api/index.ts @@ -37,7 +37,7 @@ export async function get( const searchParams = new URLSearchParams(params) if (searchParams.size) { searchParams.forEach((value, key) => { - url.searchParams.set(key, value) + url.searchParams.append(key, value) }) url.searchParams.sort() } diff --git a/server/routers/hotels/query.ts b/server/routers/hotels/query.ts index 289429ca7..883b3a3ee 100644 --- a/server/routers/hotels/query.ts +++ b/server/routers/hotels/query.ts @@ -25,6 +25,10 @@ import { getHotelPageCounter, validateHotelPageRefs, } from "../contentstack/hotelPage/utils" +import { + getRoomPackagesInputSchema, + getRoomPackagesSchema, +} from "./schemas/packages" import { getHotelInputSchema, getHotelsAvailabilityInputSchema, @@ -57,6 +61,14 @@ const getHotelCounter = meter.createCounter("trpc.hotel.get") const getHotelSuccessCounter = meter.createCounter("trpc.hotel.get-success") const getHotelFailCounter = meter.createCounter("trpc.hotel.get-fail") +const getPackagesCounter = meter.createCounter("trpc.hotel.packages.get") +const getPackagesSuccessCounter = meter.createCounter( + "trpc.hotel.packages.get-success" +) +const getPackagesFailCounter = meter.createCounter( + "trpc.hotel.packages.get-fail" +) + const hotelsAvailabilityCounter = meter.createCounter( "trpc.hotel.availability.hotels" ) @@ -694,4 +706,89 @@ export const hotelQueryRouter = router({ return locations }), }), + packages: router({ + get: serviceProcedure + .input(getRoomPackagesInputSchema) + .query(async ({ input, ctx }) => { + const { hotelId, startDate, endDate, adults, children, packageCodes } = + input + + const searchParams = new URLSearchParams({ + startDate, + endDate, + adults: adults.toString(), + children: children.toString(), + }) + + packageCodes.forEach((code) => { + searchParams.append("packageCodes", code) + }) + + const params = searchParams.toString() + + getPackagesCounter.add(1, { + hotelId, + }) + console.info( + "api.hotels.packages start", + JSON.stringify({ query: { hotelId, params } }) + ) + + const apiResponse = await api.get( + `${api.endpoints.v1.packages}/${hotelId}`, + { + headers: { + Authorization: `Bearer ${ctx.serviceToken}`, + }, + }, + params + ) + + if (!apiResponse.ok) { + getPackagesFailCounter.add(1, { + hotelId, + error_type: "http_error", + error: JSON.stringify({ + status: apiResponse.status, + statusText: apiResponse.statusText, + }), + }) + console.error( + "api.hotels.packages error", + JSON.stringify({ query: { hotelId, params } }) + ) + throw serverErrorByStatus(apiResponse.status, apiResponse) + } + + const apiJson = await apiResponse.json() + const validatedPackagesData = getRoomPackagesSchema.safeParse(apiJson) + + if (!validatedPackagesData.success) { + getHotelFailCounter.add(1, { + hotelId, + error_type: "validation_error", + error: JSON.stringify(validatedPackagesData.error), + }) + + console.error( + "api.hotels.packages validation error", + JSON.stringify({ + query: { hotelId, params }, + error: validatedPackagesData.error, + }) + ) + throw badRequestError() + } + + getPackagesSuccessCounter.add(1, { + hotelId, + }) + console.info( + "api.hotels.packages success", + JSON.stringify({ query: { hotelId, params: params } }) + ) + + return validatedPackagesData.data + }), + }), }) diff --git a/server/routers/hotels/schemas/packages.ts b/server/routers/hotels/schemas/packages.ts new file mode 100644 index 000000000..d7ff6a4e1 --- /dev/null +++ b/server/routers/hotels/schemas/packages.ts @@ -0,0 +1,59 @@ +import { z } from "zod" + +export enum RoomPackageCode { + PETR = "PETR", + ALLG = "ALLG", + ACCE = "ACCE", +} + +export const getRoomPackagesInputSchema = z.object({ + hotelId: z.string(), + startDate: z.string(), + endDate: z.string(), + adults: z.number(), + children: z.number().optional().default(0), + packageCodes: z.array(z.string()).optional().default([]), +}) + +const packagesSchema = z.array( + z.object({ + code: z.enum([ + RoomPackageCode.PETR, + RoomPackageCode.ALLG, + RoomPackageCode.ACCE, + ]), + itemCode: z.string(), + description: z.string(), + currency: z.string(), + calculatedPrice: z.number(), + inventories: z.array( + z.object({ + date: z.string(), + total: z.number(), + available: z.number(), + }) + ), + }) +) + +export const getRoomPackagesSchema = z + .object({ + data: z.object({ + attributes: z.object({ + hotelId: z.number(), + packages: packagesSchema, + }), + relationships: z + .object({ + links: z.array( + z.object({ + url: z.string(), + type: z.string(), + }) + ), + }) + .optional(), + type: z.string(), + }), + }) + .transform((data) => data.data.attributes.packages) diff --git a/server/routers/hotels/schemas/room.ts b/server/routers/hotels/schemas/room.ts index af3a3e8bc..19f922db0 100644 --- a/server/routers/hotels/schemas/room.ts +++ b/server/routers/hotels/schemas/room.ts @@ -92,9 +92,3 @@ export const roomSchema = z roomFacilities: data.attributes.roomFacilities, } }) - -export const roomFilterSchema = z.object({ - accessibility: z.boolean(), - petFriendly: z.boolean(), - allergyFriendly: z.boolean(), -}) diff --git a/server/tokenManager.ts b/server/tokenManager.ts index 24180d017..980ca071d 100644 --- a/server/tokenManager.ts +++ b/server/tokenManager.ts @@ -74,7 +74,7 @@ export async function getServiceToken() { if (env.HIDE_FOR_NEXT_RELEASE) { scopes = ["profile"] } else { - scopes = ["profile", "hotel", "booking"] + scopes = ["profile", "hotel", "booking", "package"] } const tag = generateServiceTokenTag(scopes) const getCachedJwt = unstable_cache( diff --git a/types/components/hotelReservation/selectRate/room.ts b/types/components/hotelReservation/selectRate/room.ts new file mode 100644 index 000000000..b84e5c667 --- /dev/null +++ b/types/components/hotelReservation/selectRate/room.ts @@ -0,0 +1,6 @@ +import { RoomPackageData } from "./roomFilter" +import { RoomSelectionProps } from "./roomSelection" + +export interface RoomProps extends RoomSelectionProps { + packages: RoomPackageData +} diff --git a/types/components/hotelReservation/selectRate/roomFilter.ts b/types/components/hotelReservation/selectRate/roomFilter.ts index ac31cfdbd..c70f8f1d0 100644 --- a/types/components/hotelReservation/selectRate/roomFilter.ts +++ b/types/components/hotelReservation/selectRate/roomFilter.ts @@ -1,9 +1,14 @@ import { z } from "zod" -import { roomFilterSchema } from "@/server/routers/hotels/schemas/room" +import { getRoomPackagesSchema } from "@/server/routers/hotels/schemas/packages" export interface RoomFilterProps { numberOfRooms: number + onFilter: (filter: Record) => void + filterOptions: RoomPackageData } -export interface RoomFilterFormData extends z.output {} +export interface RoomPackageData + extends z.output {} + +export type RoomPackageCodes = RoomPackageData[number]["code"] From 8da94fc25973fa516a5d1e25a80bd17ddecc7663 Mon Sep 17 00:00:00 2001 From: Pontus Dreij Date: Mon, 28 Oct 2024 10:43:53 +0100 Subject: [PATCH 05/14] feat(SW-453): Fixed new filter buttons and updated price in summary --- .../(standard)/select-rate/page.tsx | 8 +-- .../SelectRate/RoomFilter/index.tsx | 49 +++++++++------- .../RoomFilter/roomFilter.module.css | 10 +++- .../RoomSelection/FlexibilityOption/index.tsx | 2 + .../RoomSelection/RateSummary/index.tsx | 23 ++++++++ .../RateSummary/rateSummary.module.css | 5 ++ .../RoomSelection/RoomCard/index.tsx | 43 +++++++++----- .../RoomCard/roomCard.module.css | 9 ++- .../SelectRate/RoomSelection/index.tsx | 2 + .../RoomSelection/roomSelection.module.css | 1 - .../SelectRate/Rooms/index.tsx | 5 +- .../SelectRate/Rooms/rooms.module.css | 2 +- .../HotelReservation/SelectRate/utils.ts | 19 +++++++ components/Icons/Allergy.tsx | 36 ++++++++++++ components/Icons/Pets.tsx | 2 +- components/Icons/Wheelchair.tsx | 40 +++++++++++++ components/Icons/icon.module.css | 5 ++ components/Icons/index.tsx | 2 + components/Icons/variants.ts | 1 + .../Form/FilterChip/Checkbox.tsx | 7 +++ .../Form/FilterChip/_Chip/chip.module.css | 27 +++++++++ .../Form/FilterChip/_Chip/index.tsx | 57 +++++++++++++++++++ .../Tooltip/tooltip.module.css | 9 ++- i18n/dictionaries/da.json | 1 + i18n/dictionaries/de.json | 1 + i18n/dictionaries/en.json | 1 + i18n/dictionaries/fi.json | 1 + i18n/dictionaries/no.json | 1 + i18n/dictionaries/sv.json | 1 + server/routers/hotels/output.ts | 12 +++- server/routers/hotels/schemas/packages.ts | 12 ++-- types/components/form/filterChip.ts | 16 ++++++ .../selectRate/flexibilityOption.ts | 1 + .../selectRate/rateSummary.ts | 2 + .../hotelReservation/selectRate/room.ts | 6 -- .../hotelReservation/selectRate/roomCard.ts | 1 + .../hotelReservation/selectRate/roomFilter.ts | 5 ++ .../selectRate/roomSelection.ts | 3 + .../hotelReservation/selectRate/selectRate.ts | 1 + 39 files changed, 367 insertions(+), 62 deletions(-) create mode 100644 components/HotelReservation/SelectRate/utils.ts create mode 100644 components/Icons/Allergy.tsx create mode 100644 components/Icons/Wheelchair.tsx create mode 100644 components/TempDesignSystem/Form/FilterChip/Checkbox.tsx create mode 100644 components/TempDesignSystem/Form/FilterChip/_Chip/chip.module.css create mode 100644 components/TempDesignSystem/Form/FilterChip/_Chip/index.tsx create mode 100644 types/components/form/filterChip.ts delete mode 100644 types/components/hotelReservation/selectRate/room.ts diff --git a/app/[lang]/(live)/(public)/hotelreservation/(standard)/select-rate/page.tsx b/app/[lang]/(live)/(public)/hotelreservation/(standard)/select-rate/page.tsx index d43330adb..603bbbf4a 100644 --- a/app/[lang]/(live)/(public)/hotelreservation/(standard)/select-rate/page.tsx +++ b/app/[lang]/(live)/(public)/hotelreservation/(standard)/select-rate/page.tsx @@ -1,12 +1,12 @@ import { getProfileSafely } from "@/lib/trpc/memoizedRequests" import { serverClient } from "@/lib/trpc/server" -import { RoomPackageCode } from "@/server/routers/hotels/schemas/packages" import HotelInfoCard from "@/components/HotelReservation/SelectRate/HotelInfoCard" import Rooms from "@/components/HotelReservation/SelectRate/Rooms" import getHotelReservationQueryParams from "@/components/HotelReservation/SelectRate/RoomSelection/utils" import { setLang } from "@/i18n/serverContext" +import { RoomPackageCodeEnum } from "@/types/components/hotelReservation/selectRate/roomFilter" import { SelectRateSearchParams } from "@/types/components/hotelReservation/selectRate/selectRate" import { LangParams, PageArgs } from "@/types/params" @@ -42,9 +42,9 @@ export default async function SelectRatePage({ adults: adults, children: children, packageCodes: [ - RoomPackageCode.ACCE, - RoomPackageCode.PETR, - RoomPackageCode.ALLG, + RoomPackageCodeEnum.ACCE, + RoomPackageCodeEnum.PETR, + RoomPackageCodeEnum.ALLG, ], }), getProfileSafely(), diff --git a/components/HotelReservation/SelectRate/RoomFilter/index.tsx b/components/HotelReservation/SelectRate/RoomFilter/index.tsx index 3c3586994..836dd5b49 100644 --- a/components/HotelReservation/SelectRate/RoomFilter/index.tsx +++ b/components/HotelReservation/SelectRate/RoomFilter/index.tsx @@ -6,16 +6,19 @@ import { FormProvider, useForm } from "react-hook-form" import { useIntl } from "react-intl" import { z } from "zod" -import { RoomPackageCode } from "@/server/routers/hotels/schemas/packages" - -import Chip from "@/components/TempDesignSystem/Chip" -import Checkbox from "@/components/TempDesignSystem/Form/Checkbox" +import { InfoCircleIcon } from "@/components/Icons" +import CheckboxChip from "@/components/TempDesignSystem/Form/FilterChip/Checkbox" import Body from "@/components/TempDesignSystem/Text/Body" -import Caption from "@/components/TempDesignSystem/Text/Caption" +import { Tooltip } from "@/components/TempDesignSystem/Tooltip" + +import { getIconForFeatureCode } from "../utils" import styles from "./roomFilter.module.css" -import { RoomFilterProps } from "@/types/components/hotelReservation/selectRate/roomFilter" +import { + RoomFilterProps, + RoomPackageCodeEnum, +} from "@/types/components/hotelReservation/selectRate/roomFilter" export default function RoomFilter({ numberOfRooms, @@ -43,8 +46,8 @@ export default function RoomFilter({ }) const { watch, getValues, handleSubmit } = methods - const petFriendly = watch(RoomPackageCode.PETR) - const allergyFriendly = watch(RoomPackageCode.ALLG) + const petFriendly = watch(RoomPackageCodeEnum.PETR) + const allergyFriendly = watch(RoomPackageCodeEnum.ALLG) const submitFilter = useCallback(() => { const data = getValues() @@ -65,21 +68,27 @@ export default function RoomFilter({
    {filterOptions.map((option) => ( - - - {intl.formatMessage({ id: option.description })} - - + label={intl.formatMessage({ id: option.description })} + disabled={ + (option.code === RoomPackageCodeEnum.ALLG && petFriendly) || + (option.code === RoomPackageCodeEnum.PETR && allergyFriendly) + } + selected={getValues(option.code)} + Icon={getIconForFeatureCode(option.code)} + /> ))} + + +
    diff --git a/components/HotelReservation/SelectRate/RoomFilter/roomFilter.module.css b/components/HotelReservation/SelectRate/RoomFilter/roomFilter.module.css index 3c715f5c3..c0eff095a 100644 --- a/components/HotelReservation/SelectRate/RoomFilter/roomFilter.module.css +++ b/components/HotelReservation/SelectRate/RoomFilter/roomFilter.module.css @@ -2,10 +2,18 @@ display: flex; flex-direction: row; justify-content: space-between; + align-items: center; } .roomsFilter { display: flex; flex-direction: row; - gap: var(--Spacing-x3); + gap: var(--Spacing-x1); + align-items: center; +} + +.roomsFilter .infoIcon, +.roomsFilter .infoIcon path { + stroke: var(--UI-Text-Medium-contrast); + fill: transparent; } diff --git a/components/HotelReservation/SelectRate/RoomSelection/FlexibilityOption/index.tsx b/components/HotelReservation/SelectRate/RoomSelection/FlexibilityOption/index.tsx index a523305ae..a0a92bb66 100644 --- a/components/HotelReservation/SelectRate/RoomSelection/FlexibilityOption/index.tsx +++ b/components/HotelReservation/SelectRate/RoomSelection/FlexibilityOption/index.tsx @@ -19,6 +19,7 @@ export default function FlexibilityOption({ priceInformation, roomType, roomTypeCode, + features, handleSelectRate, }: FlexibilityOptionProps) { const [rootDiv, setRootDiv] = useState(undefined) @@ -52,6 +53,7 @@ export default function FlexibilityOption({ priceName: name, public: publicPrice, member: memberPrice, + features, } handleSelectRate(rate) } diff --git a/components/HotelReservation/SelectRate/RoomSelection/RateSummary/index.tsx b/components/HotelReservation/SelectRate/RoomSelection/RateSummary/index.tsx index b929bfe76..98915efa6 100644 --- a/components/HotelReservation/SelectRate/RoomSelection/RateSummary/index.tsx +++ b/components/HotelReservation/SelectRate/RoomSelection/RateSummary/index.tsx @@ -7,15 +7,28 @@ import Subtitle from "@/components/TempDesignSystem/Text/Subtitle" import styles from "./rateSummary.module.css" import { RateSummaryProps } from "@/types/components/hotelReservation/selectRate/rateSummary" +import { RoomPackageCodeEnum } from "@/types/components/hotelReservation/selectRate/roomFilter" export default function RateSummary({ rateSummary, isUserLoggedIn, + packages, }: RateSummaryProps) { const intl = useIntl() const priceToShow = isUserLoggedIn ? rateSummary.member : rateSummary.public + const isPetRoomSelect = rateSummary.features.some( + (feature) => feature.code === RoomPackageCodeEnum.PETR + ) + + const petRoomPackage = packages.find( + (pkg) => pkg.code === RoomPackageCodeEnum.PETR + ) + + const petRoomPrice = petRoomPackage ? petRoomPackage.calculatedPrice : null + const petRoomCurrency = petRoomPackage ? petRoomPackage.currency : null + return (
    @@ -34,6 +47,16 @@ export default function RateSummary({ {priceToShow?.requestedPrice?.currency}
    + {isPetRoomSelect && ( +
    + + + {petRoomPrice} {petRoomCurrency} + + + {intl.formatMessage({ id: "Pet charge" })} + +
    + )} diff --git a/components/HotelReservation/SelectRate/RoomSelection/RateSummary/rateSummary.module.css b/components/HotelReservation/SelectRate/RoomSelection/RateSummary/rateSummary.module.css index c8352efb1..07e9841b4 100644 --- a/components/HotelReservation/SelectRate/RoomSelection/RateSummary/rateSummary.module.css +++ b/components/HotelReservation/SelectRate/RoomSelection/RateSummary/rateSummary.module.css @@ -15,3 +15,8 @@ display: flex; gap: var(--Spacing-x4); } + +.petInfo { + border-left: 1px solid var(--Primary-Light-On-Surface-Divider-subtle); + padding-left: var(--Spacing-x2); +} diff --git a/components/HotelReservation/SelectRate/RoomSelection/RoomCard/index.tsx b/components/HotelReservation/SelectRate/RoomSelection/RoomCard/index.tsx index 1afec6119..3b161d8c7 100644 --- a/components/HotelReservation/SelectRate/RoomSelection/RoomCard/index.tsx +++ b/components/HotelReservation/SelectRate/RoomSelection/RoomCard/index.tsx @@ -1,5 +1,6 @@ "use client" +import { createElement } from "react" import { useIntl } from "react-intl" import { RateDefinition } from "@/server/routers/hotels/output" @@ -12,6 +13,7 @@ import Subtitle from "@/components/TempDesignSystem/Text/Subtitle" import ImageGallery from "../../ImageGallery" import RoomSidePeek from "../RoomSidePeek" +import { getIconForFeatureCode } from "../../utils" import styles from "./roomCard.module.css" @@ -47,16 +49,16 @@ export default function RoomCard({ : undefined } - function getPriceForRate( + function getPriceInformationForRate( rate: typeof saveRate | typeof changeRate | typeof flexRate ) { return rateDefinitions.find((def) => def.rateCode === rate?.rateCode) ?.generalTerms } + const selectedRoom = roomCategories.find( (room) => room.name === roomConfiguration.roomType ) - const roomSize = selectedRoom?.roomSize const occupancy = selectedRoom?.occupancy.total const roomDescription = selectedRoom?.descriptions.short @@ -68,7 +70,6 @@ export default function RoomCard({
    - {/*TODO: Handle pluralisation*/} {intl.formatMessage( { id: "booking.guests", @@ -105,44 +106,58 @@ export default function RoomCard({ value="non-refundable" paymentTerm={intl.formatMessage({ id: "Pay now" })} product={findProductForRate(saveRate)} - priceInformation={getPriceForRate(saveRate)} + priceInformation={getPriceInformationForRate(saveRate)} handleSelectRate={handleSelectRate} roomType={roomConfiguration.roomType} roomTypeCode={roomConfiguration.roomTypeCode} + features={roomConfiguration.features} />
    {mainImage && (
    - {roomConfiguration.roomsLeft < 5 && ( - - {`${roomConfiguration.roomsLeft} ${intl.formatMessage({ id: "Left" })}`} - - )} +
    + {roomConfiguration.roomsLeft < 5 && ( + + {`${roomConfiguration.roomsLeft} ${intl.formatMessage({ id: "Left" })}`} + + )} + {roomConfiguration.features.map((feature) => ( + + {createElement(getIconForFeatureCode(feature.code), { + width: 16, + height: 16, + color: "burgundy", + })} + + ))} +
    {/*NOTE: images from the test API are hosted on test3.scandichotels.com, which can't be accessed unless on Scandic's Wifi or using Citrix. */} {images && ( diff --git a/components/HotelReservation/SelectRate/RoomSelection/RoomCard/roomCard.module.css b/components/HotelReservation/SelectRate/RoomSelection/RoomCard/roomCard.module.css index ef5d9b8fc..537c1b30a 100644 --- a/components/HotelReservation/SelectRate/RoomSelection/RoomCard/roomCard.module.css +++ b/components/HotelReservation/SelectRate/RoomSelection/RoomCard/roomCard.module.css @@ -64,10 +64,17 @@ gap: var(--Spacing-x2); } -.roomsLeft { +.chipContainer { position: absolute; + z-index: 1; top: 12px; left: 12px; + display: flex; + flex-direction: row; + gap: var(--Spacing-x1); +} + +.chip { background-color: var(--Main-Grey-White); padding: var(--Spacing-x-half) var(--Spacing-x1); border-radius: var(--Corner-radius-Small); diff --git a/components/HotelReservation/SelectRate/RoomSelection/index.tsx b/components/HotelReservation/SelectRate/RoomSelection/index.tsx index 8592e64ea..351efd5b3 100644 --- a/components/HotelReservation/SelectRate/RoomSelection/index.tsx +++ b/components/HotelReservation/SelectRate/RoomSelection/index.tsx @@ -15,6 +15,7 @@ export default function RoomSelection({ roomsAvailability, roomCategories, user, + packages, }: RoomSelectionProps) { const [rateSummary, setRateSummary] = useState(null) @@ -69,6 +70,7 @@ export default function RoomSelection({ )} diff --git a/components/HotelReservation/SelectRate/RoomSelection/roomSelection.module.css b/components/HotelReservation/SelectRate/RoomSelection/roomSelection.module.css index 66a27302e..1dab63afb 100644 --- a/components/HotelReservation/SelectRate/RoomSelection/roomSelection.module.css +++ b/components/HotelReservation/SelectRate/RoomSelection/roomSelection.module.css @@ -3,7 +3,6 @@ } .roomList { - margin-top: var(--Spacing-x4); list-style: none; display: grid; grid-template-columns: 1fr; diff --git a/components/HotelReservation/SelectRate/Rooms/index.tsx b/components/HotelReservation/SelectRate/Rooms/index.tsx index 963f266d4..0cd98156e 100644 --- a/components/HotelReservation/SelectRate/Rooms/index.tsx +++ b/components/HotelReservation/SelectRate/Rooms/index.tsx @@ -9,15 +9,15 @@ import RoomSelection from "../RoomSelection" import styles from "./rooms.module.css" -import { RoomProps } from "@/types/components/hotelReservation/selectRate/room" import { RoomPackageCodes } from "@/types/components/hotelReservation/selectRate/roomFilter" +import { RoomSelectionProps } from "@/types/components/hotelReservation/selectRate/roomSelection" export default function Rooms({ roomsAvailability, roomCategories = [], user, packages, -}: RoomProps) { +}: RoomSelectionProps) { const [rooms, setRooms] = useState(roomsAvailability) function handleFilter(filter: Record) { @@ -47,6 +47,7 @@ export default function Rooms({ roomsAvailability={rooms} roomCategories={roomCategories} user={user} + packages={packages} />
    ) diff --git a/components/HotelReservation/SelectRate/Rooms/rooms.module.css b/components/HotelReservation/SelectRate/Rooms/rooms.module.css index a8e573530..5e2bca00b 100644 --- a/components/HotelReservation/SelectRate/Rooms/rooms.module.css +++ b/components/HotelReservation/SelectRate/Rooms/rooms.module.css @@ -3,6 +3,6 @@ margin: 0 auto; display: flex; flex-direction: column; - gap: var(--Spacing-x7); + gap: var(--Spacing-x2); padding: var(--Spacing-x2); } diff --git a/components/HotelReservation/SelectRate/utils.ts b/components/HotelReservation/SelectRate/utils.ts new file mode 100644 index 000000000..6002b0705 --- /dev/null +++ b/components/HotelReservation/SelectRate/utils.ts @@ -0,0 +1,19 @@ +import { AllergyIcon,PetsIcon, WheelchairIcon } from "@/components/Icons" + +import { + RoomPackageCodeEnum, + RoomPackageCodes, +} from "@/types/components/hotelReservation/selectRate/roomFilter" + +export function getIconForFeatureCode(featureCode: RoomPackageCodes) { + switch (featureCode) { + case RoomPackageCodeEnum.ACCE: + return WheelchairIcon + case RoomPackageCodeEnum.ALLG: + return AllergyIcon + case RoomPackageCodeEnum.PETR: + return PetsIcon + default: + return PetsIcon + } +} diff --git a/components/Icons/Allergy.tsx b/components/Icons/Allergy.tsx new file mode 100644 index 000000000..0fe399445 --- /dev/null +++ b/components/Icons/Allergy.tsx @@ -0,0 +1,36 @@ +import { iconVariants } from "./variants" + +import type { IconProps } from "@/types/components/icon" + +export default function AllergyIcon({ className, color, ...props }: IconProps) { + const classNames = iconVariants({ className, color }) + return ( + + + + + + + + + ) +} diff --git a/components/Icons/Pets.tsx b/components/Icons/Pets.tsx index 679a3afee..e5e475b2e 100644 --- a/components/Icons/Pets.tsx +++ b/components/Icons/Pets.tsx @@ -16,7 +16,7 @@ export default function PetsIcon({ className, color, ...props }: IconProps) { > ) diff --git a/components/Icons/Wheelchair.tsx b/components/Icons/Wheelchair.tsx new file mode 100644 index 000000000..991951761 --- /dev/null +++ b/components/Icons/Wheelchair.tsx @@ -0,0 +1,40 @@ +import { iconVariants } from "./variants" + +import type { IconProps } from "@/types/components/icon" + +export default function WheelchairIcon({ + className, + color, + ...props +}: IconProps) { + const classNames = iconVariants({ className, color }) + return ( + + + + + + + + + ) +} diff --git a/components/Icons/icon.module.css b/components/Icons/icon.module.css index ec5a15fd4..68ace50b3 100644 --- a/components/Icons/icon.module.css +++ b/components/Icons/icon.module.css @@ -76,3 +76,8 @@ .baseButtonTextOnFillNormal * { fill: var(--Base-Button-Text-On-Fill-Normal); } + +.disabled, +.disabled * { + fill: var(--Base-Text-Disabled); +} diff --git a/components/Icons/index.tsx b/components/Icons/index.tsx index fc7407ce8..ce23296fe 100644 --- a/components/Icons/index.tsx +++ b/components/Icons/index.tsx @@ -4,6 +4,7 @@ export { default as AccessibilityIcon } from "./Accessibility" export { default as AccountCircleIcon } from "./AccountCircle" export { default as AirIcon } from "./Air" export { default as AirplaneIcon } from "./Airplane" +export { default as AllergyIcon } from "./Allergy" export { default as ArrowRightIcon } from "./ArrowRight" export { default as BarIcon } from "./Bar" export { default as BathtubIcon } from "./Bathtub" @@ -111,6 +112,7 @@ export { default as TshirtIcon } from "./Tshirt" export { default as TshirtWashIcon } from "./TshirtWash" export { default as TvCastingIcon } from "./TvCasting" export { default as WarningTriangle } from "./WarningTriangle" +export { default as WheelchairIcon } from "./Wheelchair" export { default as WifiIcon } from "./Wifi" export { default as WindowCurtainsAltIcon } from "./WindowCurtainsAlt" export { default as WindowNotAvailableIcon } from "./WindowNotAvailable" diff --git a/components/Icons/variants.ts b/components/Icons/variants.ts index d319c466e..09d69364f 100644 --- a/components/Icons/variants.ts +++ b/components/Icons/variants.ts @@ -20,6 +20,7 @@ const config = { white: styles.white, uiTextHighContrast: styles.uiTextHighContrast, uiTextMediumContrast: styles.uiTextMediumContrast, + disabled: styles.disabled, }, }, defaultVariants: { diff --git a/components/TempDesignSystem/Form/FilterChip/Checkbox.tsx b/components/TempDesignSystem/Form/FilterChip/Checkbox.tsx new file mode 100644 index 000000000..c2697d560 --- /dev/null +++ b/components/TempDesignSystem/Form/FilterChip/Checkbox.tsx @@ -0,0 +1,7 @@ +import Chip from "./_Chip" + +import type { FilterChipCheckboxProps } from "@/types/components/form/filterChip" + +export default function CheckboxChip(props: FilterChipCheckboxProps) { + return +} diff --git a/components/TempDesignSystem/Form/FilterChip/_Chip/chip.module.css b/components/TempDesignSystem/Form/FilterChip/_Chip/chip.module.css new file mode 100644 index 000000000..19e1db812 --- /dev/null +++ b/components/TempDesignSystem/Form/FilterChip/_Chip/chip.module.css @@ -0,0 +1,27 @@ +.label { + display: flex; + align-items: center; + gap: var(--Spacing-x-half); + padding: var(--Spacing-x1) var(--Spacing-x-one-and-half); + border: 1px solid var(--Base-Border-Subtle); + border-radius: var(--Corner-radius-Small); + background-color: var(--Base-Surface-Secondary-light-Normal); + cursor: pointer; +} + +.label[data-selected="true"], +.label[data-selected="true"]:hover { + background-color: var(--Primary-Light-Surface-Normal); + border-color: var(--Base-Border-Hover); +} + +.label:hover { + background-color: var(--Base-Surface-Primary-light-Hover-alt); + border-color: var(--Base-Border-Subtle); +} + +.label[data-disabled="true"] { + background-color: var(--Base-Button-Primary-Fill-Disabled); + border-color: var(--Base-Button-Primary-Fill-Disabled); + cursor: not-allowed; +} diff --git a/components/TempDesignSystem/Form/FilterChip/_Chip/index.tsx b/components/TempDesignSystem/Form/FilterChip/_Chip/index.tsx new file mode 100644 index 000000000..c88f17d29 --- /dev/null +++ b/components/TempDesignSystem/Form/FilterChip/_Chip/index.tsx @@ -0,0 +1,57 @@ +import { useMemo } from "react" +import { useFormContext } from "react-hook-form" + +import { HeartIcon } from "@/components/Icons" +import Caption from "@/components/TempDesignSystem/Text/Caption" + +import styles from "./chip.module.css" + +import { FilterChipProps } from "@/types/components/form/filterChip" + +export default function FilterChip({ + Icon = HeartIcon, + iconHeight = 20, + iconWidth = 20, + id, + name, + label, + type, + value, + selected, + disabled, +}: FilterChipProps) { + const { register } = useFormContext() + + const color = useMemo(() => { + if (selected) return "burgundy" + if (disabled) return "disabled" + return "uiTextPlaceholder" + }, [selected, disabled]) + + return ( + + ) +} diff --git a/components/TempDesignSystem/Tooltip/tooltip.module.css b/components/TempDesignSystem/Tooltip/tooltip.module.css index 2a6b00b43..da8e50cbd 100644 --- a/components/TempDesignSystem/Tooltip/tooltip.module.css +++ b/components/TempDesignSystem/Tooltip/tooltip.module.css @@ -15,6 +15,7 @@ opacity: 0; transition: opacity 0.3s; max-width: 200px; + min-width: 150px; } .tooltipContainer:hover .tooltip { @@ -31,11 +32,15 @@ } .top { - bottom: 100%; + bottom: calc(100% + 8px); } .bottom { - top: 100%; + top: calc(100% + 8px); +} + +.bottom.arrowRight { + right: 0; } .tooltip::before { diff --git a/i18n/dictionaries/da.json b/i18n/dictionaries/da.json index af4c667b8..74d3e165b 100644 --- a/i18n/dictionaries/da.json +++ b/i18n/dictionaries/da.json @@ -218,6 +218,7 @@ "Pay now": "Betal nu", "Payment info": "Betalingsoplysninger", "Pet Room": "Kæledyrsrum", + "Pet-friendly rooms have an additional fee of 20 EUR per stay": "Kæledyrsrum har en ekstra gebyr på 20 EUR per ophold", "Phone": "Telefon", "Phone is required": "Telefonnummer er påkrævet", "Phone number": "Telefonnummer", diff --git a/i18n/dictionaries/de.json b/i18n/dictionaries/de.json index c9483e349..adb01b099 100644 --- a/i18n/dictionaries/de.json +++ b/i18n/dictionaries/de.json @@ -218,6 +218,7 @@ "Pay now": "Jetzt bezahlen", "Payment info": "Zahlungsinformationen", "Pet Room": "Haustierzimmer", + "Pet-friendly rooms have an additional fee of 20 EUR per stay": "Haustierzimmer haben einen zusätzlichen Preis von 20 EUR pro Aufenthalt", "Phone": "Telefon", "Phone is required": "Telefon ist erforderlich", "Phone number": "Telefonnummer", diff --git a/i18n/dictionaries/en.json b/i18n/dictionaries/en.json index 0511cf5a9..2e92e936a 100644 --- a/i18n/dictionaries/en.json +++ b/i18n/dictionaries/en.json @@ -228,6 +228,7 @@ "Payment info": "Payment info", "Payment received": "Payment received", "Pet Room": "Pet room", + "Pet-friendly rooms have an additional fee of 20 EUR per stay": "Pet-friendly rooms have an additional fee of 20 EUR per stay", "Phone": "Phone", "Phone is required": "Phone is required", "Phone number": "Phone number", diff --git a/i18n/dictionaries/fi.json b/i18n/dictionaries/fi.json index c8427a306..5f464d30f 100644 --- a/i18n/dictionaries/fi.json +++ b/i18n/dictionaries/fi.json @@ -218,6 +218,7 @@ "Pay now": "Maksa nyt", "Payment info": "Maksutiedot", "Pet Room": "Lemmikkihuone", + "Pet-friendly rooms have an additional fee of 20 EUR per stay": "Lemmikkihuoneen lisäkustannus on 20 EUR per majoitus", "Phone": "Puhelin", "Phone is required": "Puhelin vaaditaan", "Phone number": "Puhelinnumero", diff --git a/i18n/dictionaries/no.json b/i18n/dictionaries/no.json index 69a1fbec4..8ef615923 100644 --- a/i18n/dictionaries/no.json +++ b/i18n/dictionaries/no.json @@ -216,6 +216,7 @@ "Pay now": "Betal nå", "Payment info": "Betalingsinformasjon", "Pet Room": "Kjæledyrsrom", + "Pet-friendly rooms have an additional fee of 20 EUR per stay": "Kjæledyrsrom har en tilleggsavgift på 20 EUR per opphold", "Phone": "Telefon", "Phone is required": "Telefon kreves", "Phone number": "Telefonnummer", diff --git a/i18n/dictionaries/sv.json b/i18n/dictionaries/sv.json index c2f90d520..df8d576c8 100644 --- a/i18n/dictionaries/sv.json +++ b/i18n/dictionaries/sv.json @@ -216,6 +216,7 @@ "Pay now": "Betala nu", "Payment info": "Betalningsinformation", "Pet Room": "Husdjursrum", + "Pet-friendly rooms have an additional fee of 20 EUR per stay": "Husdjursrum har en extra avgift på 20 EUR per vistelse", "Phone": "Telefon", "Phone is required": "Telefonnummer är obligatorisk", "Phone number": "Telefonnummer", diff --git a/server/routers/hotels/output.ts b/server/routers/hotels/output.ts index 0f545d673..91032ffdc 100644 --- a/server/routers/hotels/output.ts +++ b/server/routers/hotels/output.ts @@ -7,6 +7,7 @@ import { imageMetaDataSchema, imageSizesSchema } from "./schemas/image" import { roomSchema } from "./schemas/room" import { getPoiGroupByCategoryName } from "./utils" +import { RoomPackageCodeEnum } from "@/types/components/hotelReservation/selectRate/roomFilter" import { AlertTypeEnum } from "@/types/enums/alert" import { FacilityEnum } from "@/types/enums/facilities" import { PointOfInterestCategoryNameEnum } from "@/types/hotel" @@ -545,7 +546,16 @@ const roomConfigurationSchema = z.object({ roomTypeCode: z.string().optional(), roomType: z.string(), roomsLeft: z.number(), - features: z.array(z.object({ inventory: z.number(), code: z.string() })), + features: z.array( + z.object({ + inventory: z.number(), + code: z.enum([ + RoomPackageCodeEnum.PETR, + RoomPackageCodeEnum.ALLG, + RoomPackageCodeEnum.ACCE, + ]), + }) + ), products: z.array(productSchema), }) diff --git a/server/routers/hotels/schemas/packages.ts b/server/routers/hotels/schemas/packages.ts index d7ff6a4e1..a239c6b50 100644 --- a/server/routers/hotels/schemas/packages.ts +++ b/server/routers/hotels/schemas/packages.ts @@ -1,10 +1,6 @@ import { z } from "zod" -export enum RoomPackageCode { - PETR = "PETR", - ALLG = "ALLG", - ACCE = "ACCE", -} +import { RoomPackageCodeEnum } from "@/types/components/hotelReservation/selectRate/roomFilter" export const getRoomPackagesInputSchema = z.object({ hotelId: z.string(), @@ -18,9 +14,9 @@ export const getRoomPackagesInputSchema = z.object({ const packagesSchema = z.array( z.object({ code: z.enum([ - RoomPackageCode.PETR, - RoomPackageCode.ALLG, - RoomPackageCode.ACCE, + RoomPackageCodeEnum.PETR, + RoomPackageCodeEnum.ALLG, + RoomPackageCodeEnum.ACCE, ]), itemCode: z.string(), description: z.string(), diff --git a/types/components/form/filterChip.ts b/types/components/form/filterChip.ts new file mode 100644 index 000000000..3ff40673d --- /dev/null +++ b/types/components/form/filterChip.ts @@ -0,0 +1,16 @@ +type FilterChipType = "checkbox" | "radio" + +export interface FilterChipProps { + Icon?: React.ElementType + iconHeight?: number + iconWidth?: number + id?: string + label: string + name: string + type: FilterChipType + value?: string + selected?: boolean + disabled?: boolean +} + +export type FilterChipCheckboxProps = Omit diff --git a/types/components/hotelReservation/selectRate/flexibilityOption.ts b/types/components/hotelReservation/selectRate/flexibilityOption.ts index 1835c3b65..1a432dc32 100644 --- a/types/components/hotelReservation/selectRate/flexibilityOption.ts +++ b/types/components/hotelReservation/selectRate/flexibilityOption.ts @@ -18,6 +18,7 @@ export type FlexibilityOptionProps = { priceInformation?: Array roomType: RoomConfiguration["roomType"] roomTypeCode: RoomConfiguration["roomTypeCode"] + features: RoomConfiguration["features"] handleSelectRate: (rate: Rate) => void } diff --git a/types/components/hotelReservation/selectRate/rateSummary.ts b/types/components/hotelReservation/selectRate/rateSummary.ts index 672df21dd..c9685f180 100644 --- a/types/components/hotelReservation/selectRate/rateSummary.ts +++ b/types/components/hotelReservation/selectRate/rateSummary.ts @@ -1,6 +1,8 @@ +import { RoomPackageData } from "./roomFilter" import { Rate } from "./selectRate" export interface RateSummaryProps { rateSummary: Rate isUserLoggedIn: boolean + packages: RoomPackageData } diff --git a/types/components/hotelReservation/selectRate/room.ts b/types/components/hotelReservation/selectRate/room.ts deleted file mode 100644 index b84e5c667..000000000 --- a/types/components/hotelReservation/selectRate/room.ts +++ /dev/null @@ -1,6 +0,0 @@ -import { RoomPackageData } from "./roomFilter" -import { RoomSelectionProps } from "./roomSelection" - -export interface RoomProps extends RoomSelectionProps { - packages: RoomPackageData -} diff --git a/types/components/hotelReservation/selectRate/roomCard.ts b/types/components/hotelReservation/selectRate/roomCard.ts index a6ed91ac1..99552916d 100644 --- a/types/components/hotelReservation/selectRate/roomCard.ts +++ b/types/components/hotelReservation/selectRate/roomCard.ts @@ -3,6 +3,7 @@ import { RoomConfiguration, } from "@/server/routers/hotels/output" +import { RoomPackageCodes } from "./roomFilter" import { Rate } from "./selectRate" import { RoomData } from "@/types/hotel" diff --git a/types/components/hotelReservation/selectRate/roomFilter.ts b/types/components/hotelReservation/selectRate/roomFilter.ts index c70f8f1d0..aaf807508 100644 --- a/types/components/hotelReservation/selectRate/roomFilter.ts +++ b/types/components/hotelReservation/selectRate/roomFilter.ts @@ -2,6 +2,11 @@ import { z } from "zod" import { getRoomPackagesSchema } from "@/server/routers/hotels/schemas/packages" +export enum RoomPackageCodeEnum { + PETR = "PETR", + ALLG = "ALLG", + ACCE = "ACCE", +} export interface RoomFilterProps { numberOfRooms: number onFilter: (filter: Record) => void diff --git a/types/components/hotelReservation/selectRate/roomSelection.ts b/types/components/hotelReservation/selectRate/roomSelection.ts index fd633fa50..03e84245e 100644 --- a/types/components/hotelReservation/selectRate/roomSelection.ts +++ b/types/components/hotelReservation/selectRate/roomSelection.ts @@ -1,5 +1,7 @@ import { RoomsAvailability } from "@/server/routers/hotels/output" +import { RoomPackageData } from "./roomFilter" + import { RoomData } from "@/types/hotel" import { SafeUser } from "@/types/user" @@ -7,4 +9,5 @@ export interface RoomSelectionProps { roomsAvailability: RoomsAvailability roomCategories: RoomData[] user: SafeUser + packages: RoomPackageData } diff --git a/types/components/hotelReservation/selectRate/selectRate.ts b/types/components/hotelReservation/selectRate/selectRate.ts index da8792133..b22c010c0 100644 --- a/types/components/hotelReservation/selectRate/selectRate.ts +++ b/types/components/hotelReservation/selectRate/selectRate.ts @@ -26,4 +26,5 @@ export interface Rate { priceName: string public: Product["productType"]["public"] member: Product["productType"]["member"] + features: RoomConfiguration["features"] } From 917f44f3238082e07893222c00ec9ff17615515c Mon Sep 17 00:00:00 2001 From: Pontus Dreij Date: Mon, 28 Oct 2024 13:11:24 +0100 Subject: [PATCH 06/14] feat(sw-453): fixed mobile view and some improvements --- .../(standard)/select-rate/page.tsx | 2 + .../MobileToggleButton/index.tsx | 9 +- .../SelectRate/RoomFilter/index.tsx | 45 +++++++-- .../RoomFilter/roomFilter.module.css | 24 +++++ .../FlexibilityOption/PriceList/index.tsx | 6 ++ .../PriceList/priceList.module.css | 5 + .../RoomSelection/RateSummary/index.tsx | 65 +++++++++++-- .../RateSummary/rateSummary.module.css | 42 +++++++- .../RoomSelection/RoomCard/index.tsx | 95 ++++++++----------- .../SelectRate/RoomSelection/index.tsx | 26 +++-- .../SelectRate/Rooms/index.tsx | 30 +++--- .../Form/FilterChip/_Chip/chip.module.css | 10 ++ .../Form/FilterChip/_Chip/index.tsx | 2 +- i18n/dictionaries/da.json | 5 + i18n/dictionaries/de.json | 5 + i18n/dictionaries/en.json | 5 + i18n/dictionaries/fi.json | 5 + i18n/dictionaries/no.json | 5 + i18n/dictionaries/sv.json | 13 ++- .../selectRate/rateSummary.ts | 3 + 20 files changed, 299 insertions(+), 103 deletions(-) diff --git a/app/[lang]/(live)/(public)/hotelreservation/(standard)/select-rate/page.tsx b/app/[lang]/(live)/(public)/hotelreservation/(standard)/select-rate/page.tsx index 603bbbf4a..48a0b30b1 100644 --- a/app/[lang]/(live)/(public)/hotelreservation/(standard)/select-rate/page.tsx +++ b/app/[lang]/(live)/(public)/hotelreservation/(standard)/select-rate/page.tsx @@ -58,6 +58,8 @@ export default async function SelectRatePage({ return "No hotel data found" // TODO: Add a proper error message } + console.log(selectRoomParamsObject) + const roomCategories = hotelData?.included return ( diff --git a/components/BookingWidget/MobileToggleButton/index.tsx b/components/BookingWidget/MobileToggleButton/index.tsx index 3bc438c41..a58bdd1b2 100644 --- a/components/BookingWidget/MobileToggleButton/index.tsx +++ b/components/BookingWidget/MobileToggleButton/index.tsx @@ -68,7 +68,14 @@ export default function MobileToggleButton({ {`${selectedFromDate} - ${selectedToDate} (${intl.formatMessage( { id: "booking.nights" }, { totalNights: nights } - )}) ${intl.formatMessage({ id: "booking.adults" }, { totalAdults })}, ${intl.formatMessage({ id: "booking.children" }, { totalChildren })}, ${intl.formatMessage({ id: "booking.rooms" }, { totalRooms })}`} + )}) ${intl.formatMessage({ id: "booking.adults" }, { totalAdults })}, ${ + totalChildren > 0 + ? intl.formatMessage( + { id: "booking.children" }, + { totalChildren } + ) + ", " + : "" + }${intl.formatMessage({ id: "booking.rooms" }, { totalRooms })}`}
diff --git a/components/HotelReservation/SelectRate/RoomFilter/index.tsx b/components/HotelReservation/SelectRate/RoomFilter/index.tsx index 836dd5b49..19fd4d8b7 100644 --- a/components/HotelReservation/SelectRate/RoomFilter/index.tsx +++ b/components/HotelReservation/SelectRate/RoomFilter/index.tsx @@ -9,6 +9,7 @@ import { z } from "zod" import { InfoCircleIcon } from "@/components/Icons" import CheckboxChip from "@/components/TempDesignSystem/Form/FilterChip/Checkbox" import Body from "@/components/TempDesignSystem/Text/Body" +import Caption from "@/components/TempDesignSystem/Text/Caption" import { Tooltip } from "@/components/TempDesignSystem/Tooltip" import { getIconForFeatureCode } from "../utils" @@ -49,6 +50,12 @@ export default function RoomFilter({ const petFriendly = watch(RoomPackageCodeEnum.PETR) const allergyFriendly = watch(RoomPackageCodeEnum.ALLG) + const selectedFilters = useMemo(() => getValues(), [getValues]) + + const tooltipText = intl.formatMessage({ + id: "Pet-friendly rooms have an additional fee of 20 EUR per stay", + }) + const submitFilter = useCallback(() => { const data = getValues() onFilter(data) @@ -61,9 +68,33 @@ export default function RoomFilter({ return (
- - {intl.formatMessage({ id: "Room types available" }, { numberOfRooms })} - +
+ + {intl.formatMessage( + { id: "Room types available" }, + { numberOfRooms } + )} + +
+
+
+ + {intl.formatMessage({ id: "Filter" })} + + + {Object.entries(selectedFilters) + .filter(([_, value]) => value) + .map(([key]) => intl.formatMessage({ id: key })) + .join(", ")} + +
+ + {intl.formatMessage( + { id: "Room types available" }, + { numberOfRooms } + )} + +
@@ -80,13 +111,7 @@ export default function RoomFilter({ Icon={getIconForFeatureCode(option.code)} /> ))} - +
diff --git a/components/HotelReservation/SelectRate/RoomFilter/roomFilter.module.css b/components/HotelReservation/SelectRate/RoomFilter/roomFilter.module.css index c0eff095a..9cce04e43 100644 --- a/components/HotelReservation/SelectRate/RoomFilter/roomFilter.module.css +++ b/components/HotelReservation/SelectRate/RoomFilter/roomFilter.module.css @@ -17,3 +17,27 @@ stroke: var(--UI-Text-Medium-contrast); fill: transparent; } +.filterInfo { + display: flex; + flex-direction: row; + gap: var(--Spacing-x-half); + align-items: flex-end; +} + +.infoDesktop { + display: none; +} + +.infoMobile { + display: block; +} + +@media (min-width: 768px) { + .infoDesktop { + display: block; + } + + .infoMobile { + display: none; + } +} diff --git a/components/HotelReservation/SelectRate/RoomSelection/FlexibilityOption/PriceList/index.tsx b/components/HotelReservation/SelectRate/RoomSelection/FlexibilityOption/PriceList/index.tsx index 76f366adb..dc7ca20fc 100644 --- a/components/HotelReservation/SelectRate/RoomSelection/FlexibilityOption/PriceList/index.tsx +++ b/components/HotelReservation/SelectRate/RoomSelection/FlexibilityOption/PriceList/index.tsx @@ -40,6 +40,9 @@ export default function PriceList({ {publicLocalPrice.currency} + + /{intl.formatMessage({ id: "night" })} +
) : ( @@ -64,6 +67,9 @@ export default function PriceList({ {memberLocalPrice.currency} + + /{intl.formatMessage({ id: "night" })} +
) : ( diff --git a/components/HotelReservation/SelectRate/RoomSelection/FlexibilityOption/PriceList/priceList.module.css b/components/HotelReservation/SelectRate/RoomSelection/FlexibilityOption/PriceList/priceList.module.css index 7320cf1be..4f3431525 100644 --- a/components/HotelReservation/SelectRate/RoomSelection/FlexibilityOption/PriceList/priceList.module.css +++ b/components/HotelReservation/SelectRate/RoomSelection/FlexibilityOption/PriceList/priceList.module.css @@ -12,3 +12,8 @@ display: flex; gap: var(--Spacing-x-half); } + +.perNight { + font-weight: 400; + font-size: var(--typography-Caption-Regular-fontSize); +} diff --git a/components/HotelReservation/SelectRate/RoomSelection/RateSummary/index.tsx b/components/HotelReservation/SelectRate/RoomSelection/RateSummary/index.tsx index 98915efa6..3b9f6dbf6 100644 --- a/components/HotelReservation/SelectRate/RoomSelection/RateSummary/index.tsx +++ b/components/HotelReservation/SelectRate/RoomSelection/RateSummary/index.tsx @@ -2,6 +2,8 @@ import { useIntl } from "react-intl" import Button from "@/components/TempDesignSystem/Button" import Body from "@/components/TempDesignSystem/Text/Body" +import Caption from "@/components/TempDesignSystem/Text/Caption" +import Footnote from "@/components/TempDesignSystem/Text/Footnote" import Subtitle from "@/components/TempDesignSystem/Text/Subtitle" import styles from "./rateSummary.module.css" @@ -13,12 +15,19 @@ export default function RateSummary({ rateSummary, isUserLoggedIn, packages, + roomsAvailability, }: RateSummaryProps) { const intl = useIntl() + const { + member, + public: publicRate, + features, + roomType, + priceName, + } = rateSummary + const priceToShow = isUserLoggedIn ? member : publicRate - const priceToShow = isUserLoggedIn ? rateSummary.member : rateSummary.public - - const isPetRoomSelect = rateSummary.features.some( + const isPetRoomSelect = features.some( (feature) => feature.code === RoomPackageCodeEnum.PETR ) @@ -26,17 +35,23 @@ export default function RateSummary({ (pkg) => pkg.code === RoomPackageCodeEnum.PETR ) - const petRoomPrice = petRoomPackage ? petRoomPackage.calculatedPrice : null - const petRoomCurrency = petRoomPackage ? petRoomPackage.currency : null + const petRoomPrice = petRoomPackage?.calculatedPrice ?? null + const petRoomCurrency = petRoomPackage?.currency ?? null + + const checkInDate = new Date(roomsAvailability.checkInDate) + const checkOutDate = new Date(roomsAvailability.checkOutDate) + const nights = Math.ceil( + (checkOutDate.getTime() - checkInDate.getTime()) / (1000 * 60 * 60 * 24) + ) return (
- {rateSummary.roomType} - {rateSummary.priceName} + {roomType} + {priceName}
-
+
{priceToShow?.localPrice.pricePerStay}{" "} {priceToShow?.localPrice.currency} @@ -47,6 +62,38 @@ export default function RateSummary({ {priceToShow?.requestedPrice?.currency}
+
+ + {intl.formatMessage({ id: "Total price" })} + + + {priceToShow?.localPrice.pricePerStay}{" "} + {priceToShow?.localPrice.currency} + + + {intl.formatMessage( + { id: "booking.nights" }, + { totalNights: nights } + )} + ,{" "} + {intl.formatMessage( + { id: "booking.adults" }, + { totalAdults: roomsAvailability.occupancy?.adults } + )} + {roomsAvailability.occupancy?.children && ( + <> + ,{" "} + {intl.formatMessage( + { id: "booking.children" }, + { totalChildren: roomsAvailability.occupancy.children } + )} + + )} + +
{isPetRoomSelect && (
@@ -57,7 +104,7 @@ export default function RateSummary({
)} -
diff --git a/components/HotelReservation/SelectRate/RoomSelection/RateSummary/rateSummary.module.css b/components/HotelReservation/SelectRate/RoomSelection/RateSummary/rateSummary.module.css index 07e9841b4..5cb5a4229 100644 --- a/components/HotelReservation/SelectRate/RoomSelection/RateSummary/rateSummary.module.css +++ b/components/HotelReservation/SelectRate/RoomSelection/RateSummary/rateSummary.module.css @@ -5,7 +5,7 @@ left: 0; right: 0; background-color: var(--Base-Surface-Primary-light-Normal); - padding: var(--Spacing-x3) var(--Spacing-x7) var(--Spacing-x5); + padding: var(--Spacing-x2) var(--Spacing-x3) var(--Spacing-x5); display: flex; justify-content: space-between; align-items: center; @@ -13,10 +13,50 @@ .summaryPrice { display: flex; + width: 100%; gap: var(--Spacing-x4); } .petInfo { border-left: 1px solid var(--Primary-Light-On-Surface-Divider-subtle); padding-left: var(--Spacing-x2); + display: none; +} + +.summaryText { + display: none; +} + +.summaryPriceTextDesktop { + display: none; +} + +.continueButton { + margin-left: auto; + height: fit-content; + width: 100%; +} + +.summaryPriceTextMobile { + white-space: nowrap; +} + +@media (min-width: 768px) { + .summary { + padding: var(--Spacing-x3) var(--Spacing-x7) var(--Spacing-x5); + } + .petInfo, + .summaryText, + .summaryPriceTextDesktop { + display: block; + } + .summaryPriceTextMobile { + display: none; + } + .summaryPrice { + width: auto; + } + .continueButton { + width: auto; + } } diff --git a/components/HotelReservation/SelectRate/RoomSelection/RoomCard/index.tsx b/components/HotelReservation/SelectRate/RoomSelection/RoomCard/index.tsx index 3b161d8c7..d1ab92174 100644 --- a/components/HotelReservation/SelectRate/RoomSelection/RoomCard/index.tsx +++ b/components/HotelReservation/SelectRate/RoomSelection/RoomCard/index.tsx @@ -12,8 +12,8 @@ import Footnote from "@/components/TempDesignSystem/Text/Footnote" import Subtitle from "@/components/TempDesignSystem/Text/Subtitle" import ImageGallery from "../../ImageGallery" -import RoomSidePeek from "../RoomSidePeek" import { getIconForFeatureCode } from "../../utils" +import RoomSidePeek from "../RoomSidePeek" import styles from "./roomCard.module.css" @@ -26,17 +26,19 @@ export default function RoomCard({ handleSelectRate, }: RoomCardProps) { const intl = useIntl() - const saveRate = rateDefinitions.find( - // TODO: Update string when API has decided - (rate) => rate.cancellationRule === "NonCancellable" - ) - const changeRate = rateDefinitions.find( - // TODO: Update string when API has decided - (rate) => rate.cancellationRule === "Modifiable" - ) - const flexRate = rateDefinitions.find( - // TODO: Update string when API has decided - (rate) => rate.cancellationRule === "CancellableBefore6PM" + + // TODO: Update string when API has decided + const rateTypes = { + saveRate: "NonCancellable", + changeRate: "Modifiable", + flexRate: "CancellableBefore6PM", + } + + const rates = Object.fromEntries( + Object.entries(rateTypes).map(([key, rule]) => [ + key, + rateDefinitions.find((rate) => rate.cancellationRule === rule), + ]) ) function findProductForRate(rate: RateDefinition | undefined) { @@ -49,9 +51,7 @@ export default function RoomCard({ : undefined } - function getPriceInformationForRate( - rate: typeof saveRate | typeof changeRate | typeof flexRate - ) { + function getPriceInformationForRate(rate: RateDefinition | undefined) { return rateDefinitions.find((def) => def.rateCode === rate?.rateCode) ?.generalTerms } @@ -59,10 +59,7 @@ export default function RoomCard({ const selectedRoom = roomCategories.find( (room) => room.name === roomConfiguration.roomType ) - const roomSize = selectedRoom?.roomSize - const occupancy = selectedRoom?.occupancy.total - const roomDescription = selectedRoom?.descriptions.short - const images = selectedRoom?.images + const { roomSize, occupancy, descriptions, images } = selectedRoom || {} const mainImage = images?.[0] return ( @@ -74,7 +71,7 @@ export default function RoomCard({ { id: "booking.guests", }, - { nrOfGuests: occupancy } + { nrOfGuests: occupancy?.total } )} @@ -93,7 +90,7 @@ export default function RoomCard({ {roomConfiguration.roomType} - {roomDescription} + {descriptions?.short}
{intl.formatMessage({ @@ -101,39 +98,29 @@ export default function RoomCard({ })}
- - - + {Object.entries(rates).map(([key, rate]) => ( + + ))}
diff --git a/components/HotelReservation/SelectRate/RoomSelection/index.tsx b/components/HotelReservation/SelectRate/RoomSelection/index.tsx index 351efd5b3..3c0305190 100644 --- a/components/HotelReservation/SelectRate/RoomSelection/index.tsx +++ b/components/HotelReservation/SelectRate/RoomSelection/index.tsx @@ -1,6 +1,6 @@ "use client" import { useRouter, useSearchParams } from "next/navigation" -import { useState } from "react" +import { useMemo,useState } from "react" import RateSummary from "./RateSummary" import RoomCard from "./RoomCard" @@ -23,27 +23,32 @@ export default function RoomSelection({ const searchParams = useSearchParams() const isUserLoggedIn = !!user - function handleSubmit(e: React.FormEvent) { - e.preventDefault() - const searchParamsObject = getHotelReservationQueryParams(searchParams) + const { roomConfigurations, rateDefinitions } = roomsAvailability - const queryParams = new URLSearchParams(searchParams) + const queryParams = useMemo(() => { + const params = new URLSearchParams(searchParams) + const searchParamsObject = getHotelReservationQueryParams(searchParams) searchParamsObject.room.forEach((item, index) => { if (rateSummary?.roomTypeCode) { - queryParams.set(`room[${index}].roomtype`, rateSummary.roomTypeCode) + params.set(`room[${index}].roomtype`, rateSummary.roomTypeCode) } if (rateSummary?.public?.rateCode) { - queryParams.set(`room[${index}].ratecode`, rateSummary.public.rateCode) + params.set(`room[${index}].ratecode`, rateSummary.public.rateCode) } if (rateSummary?.member?.rateCode) { - queryParams.set( + params.set( `room[${index}].counterratecode`, rateSummary.member.rateCode ) } }) + return params + }, [searchParams, rateSummary]) + + function handleSubmit(e: React.FormEvent) { + e.preventDefault() router.push(`select-bed?${queryParams}`) } @@ -55,10 +60,10 @@ export default function RoomSelection({ onSubmit={handleSubmit} >
    - {roomsAvailability.roomConfigurations.map((roomConfiguration) => ( + {roomConfigurations.map((roomConfiguration) => (
  • )} diff --git a/components/HotelReservation/SelectRate/Rooms/index.tsx b/components/HotelReservation/SelectRate/Rooms/index.tsx index 0cd98156e..74fe0e811 100644 --- a/components/HotelReservation/SelectRate/Rooms/index.tsx +++ b/components/HotelReservation/SelectRate/Rooms/index.tsx @@ -1,6 +1,6 @@ "use client" -import { useState } from "react" +import { useCallback,useState } from "react" import { RoomsAvailability } from "@/server/routers/hotels/output" @@ -20,21 +20,25 @@ export default function Rooms({ }: RoomSelectionProps) { const [rooms, setRooms] = useState(roomsAvailability) - function handleFilter(filter: Record) { - const selectedCodes = Object.keys(filter).filter((key) => filter[key]) + const handleFilter = useCallback( + (filter: Record) => { + const selectedCodes = Object.keys(filter).filter((key) => filter[key]) - if (selectedCodes.length === 0) { - setRooms(roomsAvailability) - return - } + if (selectedCodes.length === 0) { + setRooms(roomsAvailability) + return + } - const filteredRooms = roomsAvailability.roomConfigurations.filter((room) => - room.features.some((feature) => - selectedCodes.includes(feature.code as RoomPackageCodes) + const filteredRooms = roomsAvailability.roomConfigurations.filter( + (room) => + room.features.some((feature) => + selectedCodes.includes(feature.code as RoomPackageCodes) + ) ) - ) - setRooms({ ...roomsAvailability, roomConfigurations: filteredRooms }) - } + setRooms({ ...roomsAvailability, roomConfigurations: filteredRooms }) + }, + [roomsAvailability] + ) return (
    diff --git a/components/TempDesignSystem/Form/FilterChip/_Chip/chip.module.css b/components/TempDesignSystem/Form/FilterChip/_Chip/chip.module.css index 19e1db812..44fa78a14 100644 --- a/components/TempDesignSystem/Form/FilterChip/_Chip/chip.module.css +++ b/components/TempDesignSystem/Form/FilterChip/_Chip/chip.module.css @@ -25,3 +25,13 @@ border-color: var(--Base-Button-Primary-Fill-Disabled); cursor: not-allowed; } + +.caption { + display: none; +} + +@media (min-width: 768px) { + .caption { + display: block; + } +} diff --git a/components/TempDesignSystem/Form/FilterChip/_Chip/index.tsx b/components/TempDesignSystem/Form/FilterChip/_Chip/index.tsx index c88f17d29..528469df1 100644 --- a/components/TempDesignSystem/Form/FilterChip/_Chip/index.tsx +++ b/components/TempDesignSystem/Form/FilterChip/_Chip/index.tsx @@ -40,7 +40,7 @@ export default function FilterChip({ height={iconHeight} width={iconWidth} /> - + {label} {amount} {currency}/night per adult": "{amount} {currency}/nat pr. voksen", "A destination or hotel name is needed to be able to search for a hotel room.": "Et destinations- eller hotelnavn er nødvendigt for at kunne søge efter et hotelværelse.", "A photo of the room": "Et foto af værelset", + "ACCE": "Tilgængelighed", + "ALLG": "Allergi", "About meetings & conferences": "About meetings & conferences", "About the hotel": "About the hotel", "Accessible Room": "Tilgængelighedsrum", @@ -107,6 +109,7 @@ "FAQ": "Ofte stillede spørgsmål", "Failed to delete credit card, please try again later.": "Kunne ikke slette kreditkort. Prøv venligst igen senere.", "Fair": "Messe", + "Filter": "Filter", "Find booking": "Find booking", "Find hotels": "Find hotel", "First name": "Fornavn", @@ -211,6 +214,7 @@ "Open menu": "Åbn menuen", "Open my pages menu": "Åbn mine sider menuen", "Overview": "Oversigt", + "PETR": "Kæledyr", "Parking": "Parkering", "Parking / Garage": "Parkering / Garage", "Password": "Adgangskode", @@ -300,6 +304,7 @@ "Things nearby HOTEL_NAME": "Ting i nærheden af {hotelName}", "Total Points": "Samlet antal point", "Total incl VAT": "Inkl. moms", + "Total price": "Samlet pris", "Tourist": "Turist", "Transaction date": "Overførselsdato", "Transactions": "Transaktioner", diff --git a/i18n/dictionaries/de.json b/i18n/dictionaries/de.json index adb01b099..0029444d2 100644 --- a/i18n/dictionaries/de.json +++ b/i18n/dictionaries/de.json @@ -3,6 +3,8 @@ "{amount} {currency}/night per adult": "{amount} {currency}/Nacht pro Erwachsener", "A destination or hotel name is needed to be able to search for a hotel room.": "Ein Reiseziel oder Hotelname wird benötigt, um nach einem Hotelzimmer suchen zu können.", "A photo of the room": "Ein Foto des Zimmers", + "ACCE": "Zugänglichkeit", + "ALLG": "Allergie", "About meetings & conferences": "About meetings & conferences", "About the hotel": "Über das Hotel", "Accessible Room": "Barrierefreies Zimmer", @@ -107,6 +109,7 @@ "FAQ": "Häufig gestellte Fragen", "Failed to delete credit card, please try again later.": "Kreditkarte konnte nicht gelöscht werden. Bitte versuchen Sie es später noch einmal.", "Fair": "Messe", + "Filter": "Filter", "Find booking": "Buchung finden", "Find hotels": "Hotels finden", "First name": "Vorname", @@ -211,6 +214,7 @@ "Open menu": "Menü öffnen", "Open my pages menu": "Meine Seiten Menü öffnen", "Overview": "Übersicht", + "PETR": "Haustier", "Parking": "Parken", "Parking / Garage": "Parken / Garage", "Password": "Passwort", @@ -301,6 +305,7 @@ "Things nearby HOTEL_NAME": "Dinge in der Nähe von {hotelName}", "Total Points": "Gesamtpunktzahl", "Total incl VAT": "Gesamt inkl. MwSt.", + "Total price": "Gesamtpreis", "Tourist": "Tourist", "Transaction date": "Transaktionsdatum", "Transactions": "Transaktionen", diff --git a/i18n/dictionaries/en.json b/i18n/dictionaries/en.json index 2e92e936a..8aebd5004 100644 --- a/i18n/dictionaries/en.json +++ b/i18n/dictionaries/en.json @@ -3,6 +3,8 @@ "{amount} {currency}/night per adult": "{amount} {currency}/night per adult", "A destination or hotel name is needed to be able to search for a hotel room.": "A destination or hotel name is needed to be able to search for a hotel room.", "A photo of the room": "A photo of the room", + "ACCE": "Accessibility", + "ALLG": "Allergy", "About meetings & conferences": "About meetings & conferences", "About the hotel": "About the hotel", "Accessible Room": "Accessibility room", @@ -114,6 +116,7 @@ "FAQ": "FAQ", "Failed to delete credit card, please try again later.": "Failed to delete credit card, please try again later.", "Fair": "Fair", + "Filter": "Filter", "Find booking": "Find booking", "Find hotels": "Find hotels", "First name": "First name", @@ -220,6 +223,7 @@ "Open menu": "Open menu", "Open my pages menu": "Open my pages menu", "Overview": "Overview", + "PETR": "Pet", "Parking": "Parking", "Parking / Garage": "Parking / Garage", "Password": "Password", @@ -315,6 +319,7 @@ "Total Points": "Total Points", "Total cost": "Total cost", "Total incl VAT": "Total incl VAT", + "Total price": "Total price", "Tourist": "Tourist", "Transaction date": "Transaction date", "Transactions": "Transactions", diff --git a/i18n/dictionaries/fi.json b/i18n/dictionaries/fi.json index 5f464d30f..bb417d8c8 100644 --- a/i18n/dictionaries/fi.json +++ b/i18n/dictionaries/fi.json @@ -3,6 +3,8 @@ "{amount} {currency}/night per adult": "{amount} {currency}/yö per aikuinen", "A destination or hotel name is needed to be able to search for a hotel room.": "Kohteen tai hotellin nimi tarvitaan, jotta hotellihuonetta voidaan hakea.", "A photo of the room": "Kuva huoneesta", + "ACCE": "Saavutettavuus", + "ALLG": "Allergia", "About meetings & conferences": "About meetings & conferences", "About the hotel": "Tietoja hotellista", "Accessible Room": "Esteetön huone", @@ -107,6 +109,7 @@ "FAQ": "Usein kysytyt kysymykset", "Failed to delete credit card, please try again later.": "Luottokortin poistaminen epäonnistui, yritä myöhemmin uudelleen.", "Fair": "Messukeskus", + "Filter": "Suodatin", "Find booking": "Etsi varaus", "Find hotels": "Etsi hotelleja", "First name": "Etunimi", @@ -211,6 +214,7 @@ "Open menu": "Avaa valikko", "Open my pages menu": "Avaa omat sivut -valikko", "Overview": "Yleiskatsaus", + "PETR": "Lemmikki", "Parking": "Pysäköinti", "Parking / Garage": "Pysäköinti / Autotalli", "Password": "Salasana", @@ -301,6 +305,7 @@ "Things nearby HOTEL_NAME": "Lähellä olevia asioita {hotelName}", "Total Points": "Kokonaispisteet", "Total incl VAT": "Yhteensä sis. alv", + "Total price": "Kokonaishinta", "Tourist": "Turisti", "Transaction date": "Tapahtuman päivämäärä", "Transactions": "Tapahtumat", diff --git a/i18n/dictionaries/no.json b/i18n/dictionaries/no.json index 8ef615923..76d2745db 100644 --- a/i18n/dictionaries/no.json +++ b/i18n/dictionaries/no.json @@ -3,6 +3,8 @@ "{amount} {currency}/night per adult": "{amount} {currency}/natt per voksen", "A destination or hotel name is needed to be able to search for a hotel room.": "Et reisemål eller hotellnavn er nødvendig for å kunne søke etter et hotellrom.", "A photo of the room": "Et bilde av rommet", + "ACCE": "Tilgjengelighet", + "ALLG": "Allergi", "About meetings & conferences": "About meetings & conferences", "About the hotel": "Om hotellet", "Accessible Room": "Tilgjengelighetsrom", @@ -106,6 +108,7 @@ "FAQ": "Ofte stilte spørsmål", "Failed to delete credit card, please try again later.": "Kunne ikke slette kredittkortet, prøv igjen senere.", "Fair": "Messe", + "Filter": "Filter", "Find booking": "Finn booking", "Find hotels": "Finn hotell", "First name": "Fornavn", @@ -209,6 +212,7 @@ "Open menu": "Åpne menyen", "Open my pages menu": "Åpne mine sider menyen", "Overview": "Oversikt", + "PETR": "Kjæledyr", "Parking": "Parkering", "Parking / Garage": "Parkering / Garasje", "Password": "Passord", @@ -298,6 +302,7 @@ "Things nearby HOTEL_NAME": "Ting i nærheten av {hotelName}", "Total Points": "Totale poeng", "Total incl VAT": "Sum inkl mva", + "Total price": "Totalpris", "Tourist": "Turist", "Transaction date": "Transaksjonsdato", "Transactions": "Transaksjoner", diff --git a/i18n/dictionaries/sv.json b/i18n/dictionaries/sv.json index df8d576c8..dd1b443e2 100644 --- a/i18n/dictionaries/sv.json +++ b/i18n/dictionaries/sv.json @@ -3,6 +3,8 @@ "{amount} {currency}/night per adult": "{amount} {currency}/natt per vuxen", "A destination or hotel name is needed to be able to search for a hotel room.": "Ett destinations- eller hotellnamn behövs för att kunna söka efter ett hotellrum.", "A photo of the room": "Ett foto av rummet", + "ACCE": "Tillgänglighet", + "ALLG": "Allergi", "About meetings & conferences": "About meetings & conferences", "About the hotel": "Om hotellet", "Accessible Room": "Tillgänglighetsrum", @@ -106,6 +108,7 @@ "FAQ": "FAQ", "Failed to delete credit card, please try again later.": "Det gick inte att ta bort kreditkortet, försök igen senare.", "Fair": "Mässa", + "Filter": "Filter", "Find booking": "Hitta bokning", "Find hotels": "Hitta hotell", "First name": "Förnamn", @@ -209,6 +212,7 @@ "Open menu": "Öppna menyn", "Open my pages menu": "Öppna mina sidor menyn", "Overview": "Översikt", + "PETR": "Husdjur", "Parking": "Parkering", "Parking / Garage": "Parkering / Garage", "Password": "Lösenord", @@ -298,6 +302,7 @@ "Things nearby HOTEL_NAME": "Saker i närheten av {hotelName}", "Total Points": "Poäng totalt", "Total incl VAT": "Totalt inkl moms", + "Total price": "Totalpris", "Tourist": "Turist", "Transaction date": "Transaktionsdatum", "Transactions": "Transaktioner", @@ -374,15 +379,15 @@ "number": "nummer", "or": "eller", "points": "poäng", + "room type": "rumtyp", + "room types": "rumstyper", "special character": "speciell karaktär", "spendable points expiring by": "{points} poäng förfaller {date}", "to": "till", - "uppercase letter": "stor bokstav", - "{amount} out of {total}": "{amount} av {total}", "type": "typ", "types": "typer", - "room type": "rumtyp", - "room types": "rumstyper", + "uppercase letter": "stor bokstav", + "{amount} out of {total}": "{amount} av {total}", "{amount} {currency}": "{amount} {currency}", "{difference}{amount} {currency}": "{difference}{amount} {currency}", "{width} cm × {length} cm": "{width} cm × {length} cm" diff --git a/types/components/hotelReservation/selectRate/rateSummary.ts b/types/components/hotelReservation/selectRate/rateSummary.ts index c9685f180..73af97078 100644 --- a/types/components/hotelReservation/selectRate/rateSummary.ts +++ b/types/components/hotelReservation/selectRate/rateSummary.ts @@ -1,3 +1,5 @@ +import { RoomsAvailability } from "@/server/routers/hotels/output" + import { RoomPackageData } from "./roomFilter" import { Rate } from "./selectRate" @@ -5,4 +7,5 @@ export interface RateSummaryProps { rateSummary: Rate isUserLoggedIn: boolean packages: RoomPackageData + roomsAvailability: RoomsAvailability } From 3b2b39fdc6f0fe633d0d96a0376c3cbb0912b362 Mon Sep 17 00:00:00 2001 From: Pontus Dreij Date: Mon, 28 Oct 2024 13:13:09 +0100 Subject: [PATCH 07/14] feat(sw-453): removed log --- .../(public)/hotelreservation/(standard)/select-rate/page.tsx | 2 -- 1 file changed, 2 deletions(-) diff --git a/app/[lang]/(live)/(public)/hotelreservation/(standard)/select-rate/page.tsx b/app/[lang]/(live)/(public)/hotelreservation/(standard)/select-rate/page.tsx index 48a0b30b1..603bbbf4a 100644 --- a/app/[lang]/(live)/(public)/hotelreservation/(standard)/select-rate/page.tsx +++ b/app/[lang]/(live)/(public)/hotelreservation/(standard)/select-rate/page.tsx @@ -58,8 +58,6 @@ export default async function SelectRatePage({ return "No hotel data found" // TODO: Add a proper error message } - console.log(selectRoomParamsObject) - const roomCategories = hotelData?.included return ( From c14b413a34eb9284079bbb3d79bd7bbddf18c0fa Mon Sep 17 00:00:00 2001 From: Pontus Dreij Date: Mon, 28 Oct 2024 13:58:37 +0100 Subject: [PATCH 08/14] feat(sw-453): fixed filter bug with key --- .../HotelReservation/SelectRate/RoomSelection/index.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/components/HotelReservation/SelectRate/RoomSelection/index.tsx b/components/HotelReservation/SelectRate/RoomSelection/index.tsx index 3c0305190..b6e686faa 100644 --- a/components/HotelReservation/SelectRate/RoomSelection/index.tsx +++ b/components/HotelReservation/SelectRate/RoomSelection/index.tsx @@ -1,6 +1,6 @@ "use client" import { useRouter, useSearchParams } from "next/navigation" -import { useMemo,useState } from "react" +import { useMemo, useState } from "react" import RateSummary from "./RateSummary" import RoomCard from "./RoomCard" @@ -61,7 +61,7 @@ export default function RoomSelection({ >
      {roomConfigurations.map((roomConfiguration) => ( -
    • +
    • Date: Mon, 28 Oct 2024 17:04:24 +0100 Subject: [PATCH 09/14] feat(sw-453): fixed pr comments and default filter --- .../(standard)/select-rate/page.tsx | 18 +++++++++---- .../SelectRate/RoomFilter/index.tsx | 12 +++++---- .../RoomSelection/RateSummary/index.tsx | 13 +++++----- .../RoomSelection/RoomCard/index.tsx | 22 +++++++--------- .../SelectRate/RoomSelection/index.tsx | 4 +-- .../SelectRate/Rooms/index.tsx | 26 ++++++++++++------- .../HotelReservation/SelectRate/utils.ts | 10 +++---- i18n/dictionaries/da.json | 4 +++ i18n/dictionaries/de.json | 4 +++ i18n/dictionaries/en.json | 1 + i18n/dictionaries/fi.json | 4 +++ i18n/dictionaries/no.json | 4 +++ i18n/dictionaries/sv.json | 1 + server/routers/hotels/output.ts | 6 ++--- server/routers/hotels/schemas/packages.ts | 6 ++--- .../hotelReservation/selectRate/roomCard.ts | 1 - .../hotelReservation/selectRate/roomFilter.ts | 6 ++--- 17 files changed, 87 insertions(+), 55 deletions(-) diff --git a/app/[lang]/(live)/(public)/hotelreservation/(standard)/select-rate/page.tsx b/app/[lang]/(live)/(public)/hotelreservation/(standard)/select-rate/page.tsx index 603bbbf4a..85c546997 100644 --- a/app/[lang]/(live)/(public)/hotelreservation/(standard)/select-rate/page.tsx +++ b/app/[lang]/(live)/(public)/hotelreservation/(standard)/select-rate/page.tsx @@ -1,3 +1,5 @@ +import { notFound } from "next/navigation" + import { getProfileSafely } from "@/lib/trpc/memoizedRequests" import { serverClient } from "@/lib/trpc/server" @@ -17,10 +19,16 @@ export default async function SelectRatePage({ setLang(params.lang) const selectRoomParams = new URLSearchParams(searchParams) + console.log(selectRoomParams) const selectRoomParamsObject = getHotelReservationQueryParams(selectRoomParams) - const adults = selectRoomParamsObject.room?.[0].adults // TODO: Handle multiple rooms - const children = selectRoomParamsObject.room?.[0].child?.length // TODO: Handle multiple rooms + + if (!selectRoomParamsObject.room) { + return notFound() + } + + const adults = selectRoomParamsObject.room[0].adults // TODO: Handle multiple rooms + const children = selectRoomParamsObject.room[0].child?.length // TODO: Handle multiple rooms const [hotelData, roomsAvailability, packages, user] = await Promise.all([ serverClient().hotel.hotelData.get({ @@ -42,9 +50,9 @@ export default async function SelectRatePage({ adults: adults, children: children, packageCodes: [ - RoomPackageCodeEnum.ACCE, - RoomPackageCodeEnum.PETR, - RoomPackageCodeEnum.ALLG, + RoomPackageCodeEnum.ACCESSIBILITY_ROOM, + RoomPackageCodeEnum.PET_ROOM, + RoomPackageCodeEnum.ALLERGY_ROOM, ], }), getProfileSafely(), diff --git a/components/HotelReservation/SelectRate/RoomFilter/index.tsx b/components/HotelReservation/SelectRate/RoomFilter/index.tsx index 19fd4d8b7..ab3bd5626 100644 --- a/components/HotelReservation/SelectRate/RoomFilter/index.tsx +++ b/components/HotelReservation/SelectRate/RoomFilter/index.tsx @@ -17,7 +17,7 @@ import { getIconForFeatureCode } from "../utils" import styles from "./roomFilter.module.css" import { - RoomFilterProps, + type RoomFilterProps, RoomPackageCodeEnum, } from "@/types/components/hotelReservation/selectRate/roomFilter" @@ -47,8 +47,8 @@ export default function RoomFilter({ }) const { watch, getValues, handleSubmit } = methods - const petFriendly = watch(RoomPackageCodeEnum.PETR) - const allergyFriendly = watch(RoomPackageCodeEnum.ALLG) + const petFriendly = watch(RoomPackageCodeEnum.PET_ROOM) + const allergyFriendly = watch(RoomPackageCodeEnum.ALLERGY_ROOM) const selectedFilters = useMemo(() => getValues(), [getValues]) @@ -104,8 +104,10 @@ export default function RoomFilter({ key={option.code} label={intl.formatMessage({ id: option.description })} disabled={ - (option.code === RoomPackageCodeEnum.ALLG && petFriendly) || - (option.code === RoomPackageCodeEnum.PETR && allergyFriendly) + (option.code === RoomPackageCodeEnum.ALLERGY_ROOM && + petFriendly) || + (option.code === RoomPackageCodeEnum.PET_ROOM && + allergyFriendly) } selected={getValues(option.code)} Icon={getIconForFeatureCode(option.code)} diff --git a/components/HotelReservation/SelectRate/RoomSelection/RateSummary/index.tsx b/components/HotelReservation/SelectRate/RoomSelection/RateSummary/index.tsx index 3b9f6dbf6..cc594aed1 100644 --- a/components/HotelReservation/SelectRate/RoomSelection/RateSummary/index.tsx +++ b/components/HotelReservation/SelectRate/RoomSelection/RateSummary/index.tsx @@ -1,3 +1,4 @@ +import { differenceInCalendarDays } from "date-fns" import { useIntl } from "react-intl" import Button from "@/components/TempDesignSystem/Button" @@ -27,12 +28,12 @@ export default function RateSummary({ } = rateSummary const priceToShow = isUserLoggedIn ? member : publicRate - const isPetRoomSelect = features.some( - (feature) => feature.code === RoomPackageCodeEnum.PETR + const isPetRoomSelected = features.some( + (feature) => feature.code === RoomPackageCodeEnum.PET_ROOM ) const petRoomPackage = packages.find( - (pkg) => pkg.code === RoomPackageCodeEnum.PETR + (pkg) => pkg.code === RoomPackageCodeEnum.PET_ROOM ) const petRoomPrice = petRoomPackage?.calculatedPrice ?? null @@ -40,9 +41,7 @@ export default function RateSummary({ const checkInDate = new Date(roomsAvailability.checkInDate) const checkOutDate = new Date(roomsAvailability.checkOutDate) - const nights = Math.ceil( - (checkOutDate.getTime() - checkInDate.getTime()) / (1000 * 60 * 60 * 24) - ) + const nights = differenceInCalendarDays(checkOutDate, checkInDate) return (
      @@ -94,7 +93,7 @@ export default function RateSummary({ )}
      - {isPetRoomSelect && ( + {isPetRoomSelected && (
      + {petRoomPrice} {petRoomCurrency} diff --git a/components/HotelReservation/SelectRate/RoomSelection/RoomCard/index.tsx b/components/HotelReservation/SelectRate/RoomSelection/RoomCard/index.tsx index d1ab92174..665d91b31 100644 --- a/components/HotelReservation/SelectRate/RoomSelection/RoomCard/index.tsx +++ b/components/HotelReservation/SelectRate/RoomSelection/RoomCard/index.tsx @@ -27,20 +27,18 @@ export default function RoomCard({ }: RoomCardProps) { const intl = useIntl() - // TODO: Update string when API has decided - const rateTypes = { - saveRate: "NonCancellable", - changeRate: "Modifiable", - flexRate: "CancellableBefore6PM", + const rates = { + saveRate: rateDefinitions.find( + (rate) => rate.cancellationRule === "NonCancellable" + ), + changeRate: rateDefinitions.find( + (rate) => rate.cancellationRule === "Modifiable" + ), + flexRate: rateDefinitions.find( + (rate) => rate.cancellationRule === "CancellableBefore6PM" + ), } - const rates = Object.fromEntries( - Object.entries(rateTypes).map(([key, rule]) => [ - key, - rateDefinitions.find((rate) => rate.cancellationRule === rule), - ]) - ) - function findProductForRate(rate: RateDefinition | undefined) { return rate ? roomConfiguration.products.find( diff --git a/components/HotelReservation/SelectRate/RoomSelection/index.tsx b/components/HotelReservation/SelectRate/RoomSelection/index.tsx index b6e686faa..9929a7451 100644 --- a/components/HotelReservation/SelectRate/RoomSelection/index.tsx +++ b/components/HotelReservation/SelectRate/RoomSelection/index.tsx @@ -8,8 +8,8 @@ import getHotelReservationQueryParams from "./utils" import styles from "./roomSelection.module.css" -import { RoomSelectionProps } from "@/types/components/hotelReservation/selectRate/roomSelection" -import { Rate } from "@/types/components/hotelReservation/selectRate/selectRate" +import type { RoomSelectionProps } from "@/types/components/hotelReservation/selectRate/roomSelection" +import type { Rate } from "@/types/components/hotelReservation/selectRate/selectRate" export default function RoomSelection({ roomsAvailability, diff --git a/components/HotelReservation/SelectRate/Rooms/index.tsx b/components/HotelReservation/SelectRate/Rooms/index.tsx index 74fe0e811..ea0901f44 100644 --- a/components/HotelReservation/SelectRate/Rooms/index.tsx +++ b/components/HotelReservation/SelectRate/Rooms/index.tsx @@ -1,6 +1,6 @@ "use client" -import { useCallback,useState } from "react" +import { useCallback, useState } from "react" import { RoomsAvailability } from "@/server/routers/hotels/output" @@ -9,8 +9,7 @@ import RoomSelection from "../RoomSelection" import styles from "./rooms.module.css" -import { RoomPackageCodes } from "@/types/components/hotelReservation/selectRate/roomFilter" -import { RoomSelectionProps } from "@/types/components/hotelReservation/selectRate/roomSelection" +import type { RoomSelectionProps } from "@/types/components/hotelReservation/selectRate/roomSelection" export default function Rooms({ roomsAvailability, @@ -18,26 +17,35 @@ export default function Rooms({ user, packages, }: RoomSelectionProps) { - const [rooms, setRooms] = useState(roomsAvailability) + const defaultRooms = roomsAvailability.roomConfigurations.filter( + (room) => room.features.length === 0 + ) + const [rooms, setRooms] = useState({ + ...roomsAvailability, + roomConfigurations: defaultRooms, + }) + + console.log(rooms) const handleFilter = useCallback( (filter: Record) => { const selectedCodes = Object.keys(filter).filter((key) => filter[key]) if (selectedCodes.length === 0) { - setRooms(roomsAvailability) + setRooms({ + ...roomsAvailability, + roomConfigurations: defaultRooms, + }) return } const filteredRooms = roomsAvailability.roomConfigurations.filter( (room) => - room.features.some((feature) => - selectedCodes.includes(feature.code as RoomPackageCodes) - ) + room.features.some((feature) => selectedCodes.includes(feature.code)) ) setRooms({ ...roomsAvailability, roomConfigurations: filteredRooms }) }, - [roomsAvailability] + [roomsAvailability, defaultRooms] ) return ( diff --git a/components/HotelReservation/SelectRate/utils.ts b/components/HotelReservation/SelectRate/utils.ts index 6002b0705..d91476bb5 100644 --- a/components/HotelReservation/SelectRate/utils.ts +++ b/components/HotelReservation/SelectRate/utils.ts @@ -1,17 +1,17 @@ -import { AllergyIcon,PetsIcon, WheelchairIcon } from "@/components/Icons" +import { AllergyIcon, PetsIcon, WheelchairIcon } from "@/components/Icons" import { RoomPackageCodeEnum, - RoomPackageCodes, + type RoomPackageCodes, } from "@/types/components/hotelReservation/selectRate/roomFilter" export function getIconForFeatureCode(featureCode: RoomPackageCodes) { switch (featureCode) { - case RoomPackageCodeEnum.ACCE: + case RoomPackageCodeEnum.ACCESSIBILITY_ROOM: return WheelchairIcon - case RoomPackageCodeEnum.ALLG: + case RoomPackageCodeEnum.ALLERGY_ROOM: return AllergyIcon - case RoomPackageCodeEnum.PETR: + case RoomPackageCodeEnum.PET_ROOM: return PetsIcon default: return PetsIcon diff --git a/i18n/dictionaries/da.json b/i18n/dictionaries/da.json index 625646fb8..e47484466 100644 --- a/i18n/dictionaries/da.json +++ b/i18n/dictionaries/da.json @@ -7,6 +7,7 @@ "ALLG": "Allergi", "About meetings & conferences": "About meetings & conferences", "About the hotel": "About the hotel", + "Accessibility": "Tilgængelighed", "Accessible Room": "Tilgængelighedsrum", "Activities": "Aktiviteter", "Add code": "Tilføj kode", @@ -357,12 +358,14 @@ "Zoom in": "Zoom ind", "Zoom out": "Zoom ud", "as of today": "pr. dags dato", + "booking.accommodatesUpTo": "Plads til {nrOfGuests, plural, one {# person} other {op til # personer}}", "booking.adults": "{totalAdults, plural, one {# voksen} other {# voksne}}", "booking.children": "{totalChildren, plural, one {# barn} other {# børn}}", "booking.guests": "Maks {nrOfGuests, plural, one {# gæst} other {# gæster}}", "booking.nights": "{totalNights, plural, one {# nat} other {# nætter}}", "booking.rooms": "{totalRooms, plural, one {# værelse} other {# værelser}}", "booking.terms": "Ved at betale med en af de tilgængelige betalingsmetoder, accepterer jeg vilkårene for denne booking og de generelle Vilkår og betingelser, og forstår, at Scandic vil behandle min personlige data i forbindelse med denne booking i henhold til Scandics Privatlivspolitik. Jeg accepterer, at Scandic kræver et gyldigt kreditkort under min besøg i tilfælde af, at noget er tilbagebetalt.", + "booking.thisRoomIsEquippedWith": "Dette værelse er udstyret med", "by": "inden", "characters": "tegn", "guest": "gæst", @@ -385,6 +388,7 @@ "spendable points expiring by": "{points} Brugbare point udløber den {date}", "to": "til", "uppercase letter": "stort bogstav", + "{amount} out of {total}": "{amount} ud af {total}", "{amount} {currency}": "{amount} {currency}", "{difference}{amount} {currency}": "{difference}{amount} {currency}", "{width} cm × {length} cm": "{width} cm × {length} cm" diff --git a/i18n/dictionaries/de.json b/i18n/dictionaries/de.json index 0029444d2..88a5092e5 100644 --- a/i18n/dictionaries/de.json +++ b/i18n/dictionaries/de.json @@ -7,6 +7,7 @@ "ALLG": "Allergie", "About meetings & conferences": "About meetings & conferences", "About the hotel": "Über das Hotel", + "Accessibility": "Zugänglichkeit", "Accessible Room": "Barrierefreies Zimmer", "Activities": "Aktivitäten", "Add code": "Code hinzufügen", @@ -358,12 +359,14 @@ "Zoom in": "Vergrößern", "Zoom out": "Verkleinern", "as of today": "Stand heute", + "booking.accommodatesUpTo": "Bietet Platz für {nrOfGuests, plural, one {# Person } other {bis zu # Personen}}", "booking.adults": "{totalAdults, plural, one {# erwachsene} other {# erwachsene}}", "booking.children": "{totalChildren, plural, one {# kind} other {# kinder}}", "booking.guests": "Max {nrOfGuests, plural, one {# gast} other {# gäste}}", "booking.nights": "{totalNights, plural, one {# nacht} other {# Nächte}}", "booking.rooms": "{totalRooms, plural, one {# zimmer} other {# räume}}", "booking.terms": "Ved at betale med en af de tilgængelige betalingsmetoder, accepterer jeg vilkårene for denne booking og de generelle Vilkår og betingelser, og forstår, at Scandic vil behandle min personlige data i forbindelse med denne booking i henhold til Scandics Privatlivspolitik. Jeg accepterer, at Scandic kræver et gyldigt kreditkort under min besøg i tilfælde af, at noget er tilbagebetalt.", + "booking.thisRoomIsEquippedWith": "Dieses Zimmer ist ausgestattet mit", "by": "bis", "characters": "figuren", "guest": "gast", @@ -386,6 +389,7 @@ "spendable points expiring by": "{points} Einlösbare punkte verfallen bis zum {date}", "to": "zu", "uppercase letter": "großbuchstabe", + "{amount} out of {total}": "{amount} von {total}", "{amount} {currency}": "{amount} {currency}", "{difference}{amount} {currency}": "{difference}{amount} {currency}", "{width} cm × {length} cm": "{width} cm × {length} cm" diff --git a/i18n/dictionaries/en.json b/i18n/dictionaries/en.json index 8aebd5004..75fbbd805 100644 --- a/i18n/dictionaries/en.json +++ b/i18n/dictionaries/en.json @@ -7,6 +7,7 @@ "ALLG": "Allergy", "About meetings & conferences": "About meetings & conferences", "About the hotel": "About the hotel", + "Accessibility": "Accessibility", "Accessible Room": "Accessibility room", "Activities": "Activities", "Add Room": "Add room", diff --git a/i18n/dictionaries/fi.json b/i18n/dictionaries/fi.json index bb417d8c8..6f2f28b63 100644 --- a/i18n/dictionaries/fi.json +++ b/i18n/dictionaries/fi.json @@ -7,6 +7,7 @@ "ALLG": "Allergia", "About meetings & conferences": "About meetings & conferences", "About the hotel": "Tietoja hotellista", + "Accessibility": "Saavutettavuus", "Accessible Room": "Esteetön huone", "Activities": "Aktiviteetit", "Add code": "Lisää koodi", @@ -358,12 +359,14 @@ "Zoom in": "Lähennä", "Zoom out": "Loitonna", "as of today": "tänään", + "booking.accommodatesUpTo": "Huoneeseen {nrOfGuests, plural, one {# person} other {mahtuu 2 henkilöä}}", "booking.adults": "{totalAdults, plural, one {# aikuinen} other {# aikuiset}}", "booking.children": "{totalChildren, plural, one {# lapsi} other {# lasta}}", "booking.guests": "Max {nrOfGuests, plural, one {# vieras} other {# vieraita}}", "booking.nights": "{totalNights, plural, one {# yö} other {# yötä}}", "booking.rooms": "{totalRooms, plural, one {# huone} other {# sviitti}}", "booking.terms": "Maksamalla minkä tahansa saatavilla olevan maksutavan avulla hyväksyn tämän varauksen ehdot ja yleiset ehdot ja ehtoja, ja ymmärrän, että Scandic käsittelee minun henkilötietoni tässä varauksessa mukaisesti Scandicin tietosuojavaltuuden mukaisesti. Hyväksyn myös, että Scandic vaatii validin luottokortin majoituksen ajan, jos jokin jää maksamatta.", + "booking.thisRoomIsEquippedWith": "Tämä huone on varustettu", "by": "mennessä", "characters": "hahmoja", "guest": "Vieras", @@ -386,6 +389,7 @@ "spendable points expiring by": "{points} pistettä vanhenee {date} mennessä", "to": "to", "uppercase letter": "iso kirjain", + "{amount} out of {total}": "{amount}/{total}", "{amount} {currency}": "{amount} {currency}", "{difference}{amount} {currency}": "{difference}{amount} {currency}", "{width} cm × {length} cm": "{width} cm × {length} cm" diff --git a/i18n/dictionaries/no.json b/i18n/dictionaries/no.json index 76d2745db..0b45a4673 100644 --- a/i18n/dictionaries/no.json +++ b/i18n/dictionaries/no.json @@ -7,6 +7,7 @@ "ALLG": "Allergi", "About meetings & conferences": "About meetings & conferences", "About the hotel": "Om hotellet", + "Accessibility": "Tilgjengelighet", "Accessible Room": "Tilgjengelighetsrom", "Activities": "Aktiviteter", "Add code": "Legg til kode", @@ -355,11 +356,13 @@ "Zoom in": "Zoom inn", "Zoom out": "Zoom ut", "as of today": "per i dag", + "booking.accommodatesUpTo": "Plass til {nrOfGuests, plural, one {# person} other {opptil # personer}}", "booking.adults": "{totalAdults, plural, one {# voksen} other {# voksne}}", "booking.children": "{totalChildren, plural, one {# barn} other {# barn}}", "booking.guests": "Maks {nrOfGuests, plural, one {# gjest} other {# gjester}}", "booking.nights": "{totalNights, plural, one {# natt} other {# netter}}", "booking.rooms": "{totalRooms, plural, one {# rom} other {# rom}}", + "booking.thisRoomIsEquippedWith": "Dette rommet er utstyrt med", "by": "innen", "characters": "tegn", "guest": "gjest", @@ -382,6 +385,7 @@ "spendable points expiring by": "{points} Brukbare poeng utløper innen {date}", "to": "til", "uppercase letter": "stor bokstav", + "{amount} out of {total}": "{amount} av {total}", "{amount} {currency}": "{amount} {currency}", "{difference}{amount} {currency}": "{difference}{amount} {currency}", "{width} cm × {length} cm": "{width} cm × {length} cm" diff --git a/i18n/dictionaries/sv.json b/i18n/dictionaries/sv.json index dd1b443e2..406aa4cb4 100644 --- a/i18n/dictionaries/sv.json +++ b/i18n/dictionaries/sv.json @@ -7,6 +7,7 @@ "ALLG": "Allergi", "About meetings & conferences": "About meetings & conferences", "About the hotel": "Om hotellet", + "Accessibility": "Tillgänglighet", "Accessible Room": "Tillgänglighetsrum", "Activities": "Aktiviteter", "Add code": "Lägg till kod", diff --git a/server/routers/hotels/output.ts b/server/routers/hotels/output.ts index 91032ffdc..182575c2d 100644 --- a/server/routers/hotels/output.ts +++ b/server/routers/hotels/output.ts @@ -550,9 +550,9 @@ const roomConfigurationSchema = z.object({ z.object({ inventory: z.number(), code: z.enum([ - RoomPackageCodeEnum.PETR, - RoomPackageCodeEnum.ALLG, - RoomPackageCodeEnum.ACCE, + RoomPackageCodeEnum.PET_ROOM, + RoomPackageCodeEnum.ALLERGY_ROOM, + RoomPackageCodeEnum.ACCESSIBILITY_ROOM, ]), }) ), diff --git a/server/routers/hotels/schemas/packages.ts b/server/routers/hotels/schemas/packages.ts index a239c6b50..2f12f7255 100644 --- a/server/routers/hotels/schemas/packages.ts +++ b/server/routers/hotels/schemas/packages.ts @@ -14,9 +14,9 @@ export const getRoomPackagesInputSchema = z.object({ const packagesSchema = z.array( z.object({ code: z.enum([ - RoomPackageCodeEnum.PETR, - RoomPackageCodeEnum.ALLG, - RoomPackageCodeEnum.ACCE, + RoomPackageCodeEnum.PET_ROOM, + RoomPackageCodeEnum.ALLERGY_ROOM, + RoomPackageCodeEnum.ACCESSIBILITY_ROOM, ]), itemCode: z.string(), description: z.string(), diff --git a/types/components/hotelReservation/selectRate/roomCard.ts b/types/components/hotelReservation/selectRate/roomCard.ts index 99552916d..a6ed91ac1 100644 --- a/types/components/hotelReservation/selectRate/roomCard.ts +++ b/types/components/hotelReservation/selectRate/roomCard.ts @@ -3,7 +3,6 @@ import { RoomConfiguration, } from "@/server/routers/hotels/output" -import { RoomPackageCodes } from "./roomFilter" import { Rate } from "./selectRate" import { RoomData } from "@/types/hotel" diff --git a/types/components/hotelReservation/selectRate/roomFilter.ts b/types/components/hotelReservation/selectRate/roomFilter.ts index aaf807508..d42669295 100644 --- a/types/components/hotelReservation/selectRate/roomFilter.ts +++ b/types/components/hotelReservation/selectRate/roomFilter.ts @@ -3,9 +3,9 @@ import { z } from "zod" import { getRoomPackagesSchema } from "@/server/routers/hotels/schemas/packages" export enum RoomPackageCodeEnum { - PETR = "PETR", - ALLG = "ALLG", - ACCE = "ACCE", + PET_ROOM = "PETR", + ALLERGY_ROOM = "ALLG", + ACCESSIBILITY_ROOM = "ACCE", } export interface RoomFilterProps { numberOfRooms: number From c679fa83e2ff71a0f7bdc61fca61eccc989b216c Mon Sep 17 00:00:00 2001 From: Pontus Dreij Date: Mon, 28 Oct 2024 17:06:42 +0100 Subject: [PATCH 10/14] feat(sw-453): removed log --- .../(public)/hotelreservation/(standard)/select-rate/page.tsx | 1 - 1 file changed, 1 deletion(-) diff --git a/app/[lang]/(live)/(public)/hotelreservation/(standard)/select-rate/page.tsx b/app/[lang]/(live)/(public)/hotelreservation/(standard)/select-rate/page.tsx index 85c546997..2da2d78a6 100644 --- a/app/[lang]/(live)/(public)/hotelreservation/(standard)/select-rate/page.tsx +++ b/app/[lang]/(live)/(public)/hotelreservation/(standard)/select-rate/page.tsx @@ -19,7 +19,6 @@ export default async function SelectRatePage({ setLang(params.lang) const selectRoomParams = new URLSearchParams(searchParams) - console.log(selectRoomParams) const selectRoomParamsObject = getHotelReservationQueryParams(selectRoomParams) From 153822b738520fff36d523355a71b29f69983707 Mon Sep 17 00:00:00 2001 From: Pontus Dreij Date: Mon, 28 Oct 2024 17:07:27 +0100 Subject: [PATCH 11/14] feat(sw-453): removed log --- components/HotelReservation/SelectRate/Rooms/index.tsx | 2 -- 1 file changed, 2 deletions(-) diff --git a/components/HotelReservation/SelectRate/Rooms/index.tsx b/components/HotelReservation/SelectRate/Rooms/index.tsx index ea0901f44..923bfa35c 100644 --- a/components/HotelReservation/SelectRate/Rooms/index.tsx +++ b/components/HotelReservation/SelectRate/Rooms/index.tsx @@ -25,8 +25,6 @@ export default function Rooms({ roomConfigurations: defaultRooms, }) - console.log(rooms) - const handleFilter = useCallback( (filter: Record) => { const selectedCodes = Object.keys(filter).filter((key) => filter[key]) From 83cd9cf5183f941ecb299da1bb745f3e5fcbcb88 Mon Sep 17 00:00:00 2001 From: Pontus Dreij Date: Mon, 28 Oct 2024 17:22:21 +0100 Subject: [PATCH 12/14] feat(sw-453): import types --- .../hotelreservation/(standard)/select-rate/page.tsx | 2 +- .../SelectRate/RoomSelection/RateSummary/index.tsx | 2 +- .../HotelReservation/SelectRate/RoomSelection/utils.ts | 2 +- .../hotelReservation/selectRate/rateSummary.ts | 7 +++---- .../hotelReservation/selectRate/roomSelection.ts | 10 ++++------ 5 files changed, 10 insertions(+), 13 deletions(-) diff --git a/app/[lang]/(live)/(public)/hotelreservation/(standard)/select-rate/page.tsx b/app/[lang]/(live)/(public)/hotelreservation/(standard)/select-rate/page.tsx index 2da2d78a6..b361aaa10 100644 --- a/app/[lang]/(live)/(public)/hotelreservation/(standard)/select-rate/page.tsx +++ b/app/[lang]/(live)/(public)/hotelreservation/(standard)/select-rate/page.tsx @@ -9,7 +9,7 @@ import getHotelReservationQueryParams from "@/components/HotelReservation/Select import { setLang } from "@/i18n/serverContext" import { RoomPackageCodeEnum } from "@/types/components/hotelReservation/selectRate/roomFilter" -import { SelectRateSearchParams } from "@/types/components/hotelReservation/selectRate/selectRate" +import type { SelectRateSearchParams } from "@/types/components/hotelReservation/selectRate/selectRate" import { LangParams, PageArgs } from "@/types/params" export default async function SelectRatePage({ diff --git a/components/HotelReservation/SelectRate/RoomSelection/RateSummary/index.tsx b/components/HotelReservation/SelectRate/RoomSelection/RateSummary/index.tsx index cc594aed1..b7ecc3c63 100644 --- a/components/HotelReservation/SelectRate/RoomSelection/RateSummary/index.tsx +++ b/components/HotelReservation/SelectRate/RoomSelection/RateSummary/index.tsx @@ -9,7 +9,7 @@ import Subtitle from "@/components/TempDesignSystem/Text/Subtitle" import styles from "./rateSummary.module.css" -import { RateSummaryProps } from "@/types/components/hotelReservation/selectRate/rateSummary" +import type { RateSummaryProps } from "@/types/components/hotelReservation/selectRate/rateSummary" import { RoomPackageCodeEnum } from "@/types/components/hotelReservation/selectRate/roomFilter" export default function RateSummary({ diff --git a/components/HotelReservation/SelectRate/RoomSelection/utils.ts b/components/HotelReservation/SelectRate/RoomSelection/utils.ts index 0b1ab884a..1ae94cc9c 100644 --- a/components/HotelReservation/SelectRate/RoomSelection/utils.ts +++ b/components/HotelReservation/SelectRate/RoomSelection/utils.ts @@ -1,6 +1,6 @@ import { getFormattedUrlQueryParams } from "@/utils/url" -import { SelectRateSearchParams } from "@/types/components/hotelReservation/selectRate/selectRate" +import type { SelectRateSearchParams } from "@/types/components/hotelReservation/selectRate/selectRate" function getHotelReservationQueryParams(searchParams: URLSearchParams) { return getFormattedUrlQueryParams(searchParams, { diff --git a/types/components/hotelReservation/selectRate/rateSummary.ts b/types/components/hotelReservation/selectRate/rateSummary.ts index 73af97078..f6c0f03b6 100644 --- a/types/components/hotelReservation/selectRate/rateSummary.ts +++ b/types/components/hotelReservation/selectRate/rateSummary.ts @@ -1,7 +1,6 @@ -import { RoomsAvailability } from "@/server/routers/hotels/output" - -import { RoomPackageData } from "./roomFilter" -import { Rate } from "./selectRate" +import type { RoomsAvailability } from "@/server/routers/hotels/output" +import type { RoomPackageData } from "./roomFilter" +import type { Rate } from "./selectRate" export interface RateSummaryProps { rateSummary: Rate diff --git a/types/components/hotelReservation/selectRate/roomSelection.ts b/types/components/hotelReservation/selectRate/roomSelection.ts index 03e84245e..8d006779c 100644 --- a/types/components/hotelReservation/selectRate/roomSelection.ts +++ b/types/components/hotelReservation/selectRate/roomSelection.ts @@ -1,9 +1,7 @@ -import { RoomsAvailability } from "@/server/routers/hotels/output" - -import { RoomPackageData } from "./roomFilter" - -import { RoomData } from "@/types/hotel" -import { SafeUser } from "@/types/user" +import type { RoomData } from "@/types/hotel" +import type { SafeUser } from "@/types/user" +import type { RoomsAvailability } from "@/server/routers/hotels/output" +import type { RoomPackageData } from "./roomFilter" export interface RoomSelectionProps { roomsAvailability: RoomsAvailability From f35ccbd9978281785a7305c5e7dff189518c2f39 Mon Sep 17 00:00:00 2001 From: Pontus Dreij Date: Tue, 29 Oct 2024 08:34:52 +0100 Subject: [PATCH 13/14] feat(sw-453): correct filter --- components/HotelReservation/SelectRate/Rooms/index.tsx | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/components/HotelReservation/SelectRate/Rooms/index.tsx b/components/HotelReservation/SelectRate/Rooms/index.tsx index 923bfa35c..8f9030149 100644 --- a/components/HotelReservation/SelectRate/Rooms/index.tsx +++ b/components/HotelReservation/SelectRate/Rooms/index.tsx @@ -39,7 +39,9 @@ export default function Rooms({ const filteredRooms = roomsAvailability.roomConfigurations.filter( (room) => - room.features.some((feature) => selectedCodes.includes(feature.code)) + selectedCodes.every((selectedCode) => + room.features.some((feature) => feature.code === selectedCode) + ) ) setRooms({ ...roomsAvailability, roomConfigurations: filteredRooms }) }, From 3705ff0120e7dfeb13c318f2ce8a4d8ae23f0b1a Mon Sep 17 00:00:00 2001 From: Pontus Dreij Date: Tue, 29 Oct 2024 11:09:29 +0100 Subject: [PATCH 14/14] feat(sw-453): removed useMemo on getValues --- .gitignore | 3 +++ components/HotelReservation/SelectRate/RoomFilter/index.tsx | 2 +- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index 4663988fb..8a6f4e73e 100644 --- a/.gitignore +++ b/.gitignore @@ -42,5 +42,8 @@ certificates #vscode .vscode/ +#cursor +.cursorrules + # localfile with all the CSS variables exported from design system variables.css \ No newline at end of file diff --git a/components/HotelReservation/SelectRate/RoomFilter/index.tsx b/components/HotelReservation/SelectRate/RoomFilter/index.tsx index ab3bd5626..eb7c442b3 100644 --- a/components/HotelReservation/SelectRate/RoomFilter/index.tsx +++ b/components/HotelReservation/SelectRate/RoomFilter/index.tsx @@ -50,7 +50,7 @@ export default function RoomFilter({ const petFriendly = watch(RoomPackageCodeEnum.PET_ROOM) const allergyFriendly = watch(RoomPackageCodeEnum.ALLERGY_ROOM) - const selectedFilters = useMemo(() => getValues(), [getValues]) + const selectedFilters = getValues() const tooltipText = intl.formatMessage({ id: "Pet-friendly rooms have an additional fee of 20 EUR per stay",