From 117cbcd20d6e22ddf62abd92a4901266b064bd51 Mon Sep 17 00:00:00 2001 From: Christel Westerberg Date: Fri, 15 Nov 2024 16:31:11 +0100 Subject: [PATCH 01/31] feat: add new design for join scandic friends checkbox --- .../ChildSelector/ChildInfoSelector.tsx | 3 +- .../EnterDetails/Details/Signup/index.tsx | 24 +--- components/LoginButton/index.tsx | 26 ++--- .../TempDesignSystem/Form/Checkbox/index.tsx | 6 +- .../Form/ChoiceCard/Checkbox.tsx | 7 -- .../Form/JoinScandicFriendsCard/index.tsx | 108 ++++++++++++++++++ .../joinScandicFriendsCard.module.css | 30 +++++ .../TempDesignSystem/Link/link.module.css | 21 +++- components/TempDesignSystem/Link/variants.ts | 2 + i18n/dictionaries/da.json | 4 +- i18n/dictionaries/de.json | 4 +- i18n/dictionaries/en.json | 6 +- i18n/dictionaries/fi.json | 4 +- i18n/dictionaries/no.json | 4 +- i18n/dictionaries/sv.json | 4 +- types/components/tracking.ts | 1 + 16 files changed, 189 insertions(+), 65 deletions(-) delete mode 100644 components/TempDesignSystem/Form/ChoiceCard/Checkbox.tsx create mode 100644 components/TempDesignSystem/Form/JoinScandicFriendsCard/index.tsx create mode 100644 components/TempDesignSystem/Form/JoinScandicFriendsCard/joinScandicFriendsCard.module.css diff --git a/components/GuestsRoomsPicker/ChildSelector/ChildInfoSelector.tsx b/components/GuestsRoomsPicker/ChildSelector/ChildInfoSelector.tsx index b5a34c168..8252f2d2a 100644 --- a/components/GuestsRoomsPicker/ChildSelector/ChildInfoSelector.tsx +++ b/components/GuestsRoomsPicker/ChildSelector/ChildInfoSelector.tsx @@ -33,11 +33,10 @@ export default function ChildInfoSelector({ const ageLabel = intl.formatMessage({ id: "Age" }) const bedLabel = intl.formatMessage({ id: "Bed" }) const errorMessage = intl.formatMessage({ id: "Child age is required" }) - const { setValue, formState, register, trigger } = useFormContext() + const { setValue, formState, register } = useFormContext() function updateSelectedBed(bed: number) { setValue(`rooms.${roomIndex}.child.${index}.bed`, bed) - trigger() } function updateSelectedAge(age: number) { diff --git a/components/HotelReservation/EnterDetails/Details/Signup/index.tsx b/components/HotelReservation/EnterDetails/Details/Signup/index.tsx index 74a17e911..1846a7cd1 100644 --- a/components/HotelReservation/EnterDetails/Details/Signup/index.tsx +++ b/components/HotelReservation/EnterDetails/Details/Signup/index.tsx @@ -7,9 +7,9 @@ import { useIntl } from "react-intl" import { privacyPolicy } from "@/constants/currentWebHrefs" import Checkbox from "@/components/TempDesignSystem/Form/Checkbox" -import CheckboxCard from "@/components/TempDesignSystem/Form/ChoiceCard/Checkbox" import DateSelect from "@/components/TempDesignSystem/Form/Date" import Input from "@/components/TempDesignSystem/Form/Input" +import JoinScandicFriendsCard from "@/components/TempDesignSystem/Form/JoinScandicFriendsCard" import Link from "@/components/TempDesignSystem/Link" import Body from "@/components/TempDesignSystem/Text/Body" import Caption from "@/components/TempDesignSystem/Text/Caption" @@ -31,29 +31,11 @@ export default function Signup({ name }: { name: string }) { setIsJoinChecked(joinValue) }, [joinValue]) - const list = [ - { title: intl.formatMessage({ id: "Earn bonus nights & points" }) }, - { title: intl.formatMessage({ id: "Get member benefits & offers" }) }, - { title: intl.formatMessage({ id: "Join at no cost" }) }, - ] - return (
- {isJoinChecked ? (
diff --git a/components/LoginButton/index.tsx b/components/LoginButton/index.tsx index f6b163b38..6258f6e88 100644 --- a/components/LoginButton/index.tsx +++ b/components/LoginButton/index.tsx @@ -13,19 +13,16 @@ import { trackLoginClick } from "@/utils/tracking" import { TrackingPosition } from "@/types/components/tracking" export default function LoginButton({ - className, position, trackingId, children, - color = "black", - variant = "navigation", -}: PropsWithChildren<{ - className: string - trackingId: string - position: TrackingPosition - color?: LinkProps["color"] - variant?: "navigation" | "signupVerification" -}>) { + ...props +}: PropsWithChildren< + { + trackingId: string + position: TrackingPosition + } & Omit +>) { const lang = useLang() const pathName = useLazyPathname({ includeSearchParams: true }) @@ -45,14 +42,7 @@ export default function LoginButton({ }, [position, trackingId]) return ( - + {children} ) diff --git a/components/TempDesignSystem/Form/Checkbox/index.tsx b/components/TempDesignSystem/Form/Checkbox/index.tsx index 60bd81935..bd4639e39 100644 --- a/components/TempDesignSystem/Form/Checkbox/index.tsx +++ b/components/TempDesignSystem/Form/Checkbox/index.tsx @@ -12,6 +12,7 @@ import styles from "./checkbox.module.css" import { CheckboxProps } from "@/types/components/checkbox" export default function Checkbox({ + className, name, children, registerOptions, @@ -25,16 +26,17 @@ export default function Checkbox({ return ( {({ isSelected }) => ( <> - + {isSelected && } {children} diff --git a/components/TempDesignSystem/Form/ChoiceCard/Checkbox.tsx b/components/TempDesignSystem/Form/ChoiceCard/Checkbox.tsx deleted file mode 100644 index ca32951cb..000000000 --- a/components/TempDesignSystem/Form/ChoiceCard/Checkbox.tsx +++ /dev/null @@ -1,7 +0,0 @@ -import Card from "./_Card" - -import type { CheckboxProps } from "./_Card/card" - -export default function CheckboxCard(props: CheckboxProps) { - return -} diff --git a/components/TempDesignSystem/Form/JoinScandicFriendsCard/index.tsx b/components/TempDesignSystem/Form/JoinScandicFriendsCard/index.tsx new file mode 100644 index 000000000..629a904fc --- /dev/null +++ b/components/TempDesignSystem/Form/JoinScandicFriendsCard/index.tsx @@ -0,0 +1,108 @@ +"use client" + +import { useIntl } from "react-intl" + +import { privacyPolicy } from "@/constants/currentWebHrefs" + +import { CheckIcon } from "@/components/Icons" +import LoginButton from "@/components/LoginButton" +import useLang from "@/hooks/useLang" + +import Link from "../../Link" +import Caption from "../../Text/Caption" +import Footnote from "../../Text/Footnote" +import Checkbox from "../Checkbox" + +import styles from "./joinScandicFriendsCard.module.css" + +type JoinScandicFriendsCardProps = { + name: string + difference: { price: number; currency: string } +} + +export default function JoinScandicFriendsCard({ + name, + difference, +}: JoinScandicFriendsCardProps) { + const lang = useLang() + const intl = useIntl() + + const list = [ + { title: intl.formatMessage({ id: "Earn bonus nights & points" }) }, + { title: intl.formatMessage({ id: "Get member benefits & offers" }) }, + { title: intl.formatMessage({ id: "Join at no cost" }) }, + ] + + return ( + + ) +} diff --git a/components/TempDesignSystem/Form/JoinScandicFriendsCard/joinScandicFriendsCard.module.css b/components/TempDesignSystem/Form/JoinScandicFriendsCard/joinScandicFriendsCard.module.css new file mode 100644 index 000000000..42dcc86d0 --- /dev/null +++ b/components/TempDesignSystem/Form/JoinScandicFriendsCard/joinScandicFriendsCard.module.css @@ -0,0 +1,30 @@ +.cardContainer { + align-self: flex-start; + background-color: var(--Base-Surface-Primary-light-Normal); + border: 1px solid var(--Base-Border-Subtle); + border-radius: var(--Corner-radius-Large); + display: grid; + cursor: pointer; + gap: var(--Spacing-x2); + padding: var(--Spacing-x-one-and-half) var(--Spacing-x2); + width: min(100%, 600px); +} + +.header { + display: grid; + gap: var(--Spacing-x-one-and-half); + grid-template-columns: auto 1fr auto; +} + +.checkBox { + align-self: center; +} + +.list { + display: flex; + gap: var(--Spacing-x1); +} + +.listItem { + display: flex; +} diff --git a/components/TempDesignSystem/Link/link.module.css b/components/TempDesignSystem/Link/link.module.css index 138c8094e..948574ed2 100644 --- a/components/TempDesignSystem/Link/link.module.css +++ b/components/TempDesignSystem/Link/link.module.css @@ -16,7 +16,7 @@ .breadcrumb { font-family: var(--typography-Footnote-Bold-fontFamily); font-size: var(--typography-Footnote-Bold-fontSize); - font-weight: var(--typography-Footnote-Bold-fontWeight); + font-weight: 450; /* var(--typography-Footnote-Bold-fontWeight); */ letter-spacing: var(--typography-Footnote-Bold-letterSpacing); line-height: var(--typography-Footnote-Bold-lineHeight); } @@ -24,7 +24,7 @@ .link.breadcrumb { font-family: var(--typography-Footnote-Bold-fontFamily); font-size: var(--typography-Footnote-Bold-fontSize); - font-weight: var(--typography-Footnote-Bold-fontWeight); + font-weight: 450; /* var(--typography-Footnote-Bold-fontWeight); */ letter-spacing: var(--typography-Footnote-Bold-letterSpacing); line-height: var(--typography-Footnote-Bold-lineHeight); } @@ -128,6 +128,15 @@ color: #000; } +.uiTextPlaceholder { + color: var(--Base-Text-Placeholder); +} + +.uiTextPlaceholder:hover, +.uiTextPlaceholder:active { + color: var(--Base-Text-Medium-contrast); +} + .burgundy { color: var(--Base-Text-High-contrast); } @@ -211,6 +220,14 @@ line-height: var(--typography-Caption-Regular-lineHeight); } +.tiny { + font-family: var(--typography-Footnote-Regular-fontFamily); + font-size: var(--typography-Footnote-Regular-fontSize); + font-weight: var(--typography-Footnote-Regular-fontWeight); + letter-spacing: var(--typography-Footnote-Regular-letterSpacing); + line-height: var(--typography-Footnote-Regular-lineHeight); +} + .activeSmall { font-family: var(--typography-Caption-Bold-fontFamily); font-size: var(--typography-Caption-Bold-fontSize); diff --git a/components/TempDesignSystem/Link/variants.ts b/components/TempDesignSystem/Link/variants.ts index 61c7f8493..0a0c1dd6d 100644 --- a/components/TempDesignSystem/Link/variants.ts +++ b/components/TempDesignSystem/Link/variants.ts @@ -17,10 +17,12 @@ export const linkVariants = cva(styles.link, { peach80: styles.peach80, white: styles.white, red: styles.red, + uiTextPlaceholder: styles.uiTextPlaceholder, }, size: { small: styles.small, regular: styles.regular, + tiny: styles.tiny, }, textDecoration: { none: styles.noDecoration, diff --git a/i18n/dictionaries/da.json b/i18n/dictionaries/da.json index 6a2c12e20..7ec920d05 100644 --- a/i18n/dictionaries/da.json +++ b/i18n/dictionaries/da.json @@ -238,6 +238,7 @@ "Number of parking spots": "Antal parkeringspladser", "OTHER PAYMENT METHODS": "ANDRE BETALINGSMETODER", "On your journey": "På din rejse", + "Only pay {amount} {currency}": "Betal kun {amount} {currency}", "Open": "Åben", "Open gift(s)": "Åbne {amount, plural, one {gave} other {gaver}}", "Open image gallery": "Åbn billedgalleri", @@ -458,6 +459,5 @@ "to": "til", "uppercase letter": "stort bogstav", "{amount} out of {total}": "{amount} ud af {total}", - "{amount} {currency}": "{amount} {currency}", - "{difference}{amount} {currency}": "{difference}{amount} {currency}" + "{amount} {currency}": "{amount} {currency}" } diff --git a/i18n/dictionaries/de.json b/i18n/dictionaries/de.json index 5968f5cee..80b3b6116 100644 --- a/i18n/dictionaries/de.json +++ b/i18n/dictionaries/de.json @@ -236,6 +236,7 @@ "Number of parking spots": "Anzahl der Parkplätze", "OTHER PAYMENT METHODS": "ANDERE BEZAHLMETHODE", "On your journey": "Auf deiner Reise", + "Only pay {amount} {currency}": "Nur bezahlen {amount} {currency}", "Open": "Offen", "Open gift(s)": "{amount, plural, one {Geschenk} other {Geschenke}} öffnen", "Open image gallery": "Bildergalerie öffnen", @@ -456,6 +457,5 @@ "to": "zu", "uppercase letter": "großbuchstabe", "{amount} out of {total}": "{amount} von {total}", - "{amount} {currency}": "{amount} {currency}", - "{difference}{amount} {currency}": "{difference}{amount} {currency}" + "{amount} {currency}": "{amount} {currency}" } diff --git a/i18n/dictionaries/en.json b/i18n/dictionaries/en.json index 910af641a..6080fc36b 100644 --- a/i18n/dictionaries/en.json +++ b/i18n/dictionaries/en.json @@ -255,6 +255,7 @@ "Number of parking spots": "Number of parking spots", "OTHER PAYMENT METHODS": "OTHER PAYMENT METHODS", "On your journey": "On your journey", + "Only pay {amount} {currency}": "Only pay {amount} {currency}", "Open": "Open", "Open gift(s)": "Open {amount, plural, one {gift} other {gifts}}", "Open image gallery": "Open image gallery", @@ -427,7 +428,6 @@ "Which room class suits you the best?": "Which room class suits you the best?", "Year": "Year", "Yes": "Yes", - "Yes, I accept the Terms and conditions for Scandic Friends and understand that Scandic will process my personal data in accordance with": "Yes, I accept the Terms and conditions for Scandic Friends and understand that Scandic will process my personal data in accordance with", "Yes, discard changes": "Yes, discard changes", "Yes, remove my card": "Yes, remove my card", "You can always change your mind later and add breakfast at the hotel.": "You can always change your mind later and add breakfast at the hotel.", @@ -490,12 +490,12 @@ "points": "Points", "room type": "room type", "room types": "room types", + "signup.terms": "By signing up you accept the Scandic Friends Terms and Conditions. Your membership is valid until further notice, and you can terminate your membership at any time by sending an email to Scandic’s customer service", "special character": "special character", "spendable points expiring by": "{points} spendable points expiring by {date}", "to": "to", "uppercase letter": "uppercase letter", "{amount} out of {total}": "{amount} out of {total}", "{amount} {currency}": "{amount} {currency}", - "{card} ending with {cardno}": "{card} ending with {cardno}", - "{difference}{amount} {currency}": "{difference}{amount} {currency}" + "{card} ending with {cardno}": "{card} ending with {cardno}" } diff --git a/i18n/dictionaries/fi.json b/i18n/dictionaries/fi.json index bc79d0cf0..0c1a08d35 100644 --- a/i18n/dictionaries/fi.json +++ b/i18n/dictionaries/fi.json @@ -238,6 +238,7 @@ "Number of parking spots": "Pysäköintipaikkojen määrä", "OTHER PAYMENT METHODS": "MUISE KORT", "On your journey": "Matkallasi", + "Only pay {amount} {currency}": "Vain maksaa {amount} {currency}", "Open": "Avata", "Open gift(s)": "{amount, plural, one {Avoin lahja} other {Avoimet lahjat}}", "Open image gallery": "Avaa kuvagalleria", @@ -456,6 +457,5 @@ "to": "to", "uppercase letter": "iso kirjain", "{amount} out of {total}": "{amount}/{total}", - "{amount} {currency}": "{amount} {currency}", - "{difference}{amount} {currency}": "{difference}{amount} {currency}" + "{amount} {currency}": "{amount} {currency}" } diff --git a/i18n/dictionaries/no.json b/i18n/dictionaries/no.json index a5dd81fc4..2aca61a85 100644 --- a/i18n/dictionaries/no.json +++ b/i18n/dictionaries/no.json @@ -236,6 +236,7 @@ "Number of parking spots": "Antall parkeringsplasser", "OTHER PAYMENT METHODS": "ANDRE BETALINGSMETODER", "On your journey": "På reisen din", + "Only pay {amount} {currency}": "Bare betal {amount} {currency}", "Open": "Åpen", "Open gift(s)": "{amount, plural, one {Åpen gave} other {Åpnen gaver}}", "Open image gallery": "Åpne bildegalleri", @@ -454,6 +455,5 @@ "to": "til", "uppercase letter": "stor bokstav", "{amount} out of {total}": "{amount} av {total}", - "{amount} {currency}": "{amount} {currency}", - "{difference}{amount} {currency}": "{difference}{amount} {currency}" + "{amount} {currency}": "{amount} {currency}" } diff --git a/i18n/dictionaries/sv.json b/i18n/dictionaries/sv.json index 0c5816f51..db300b7c2 100644 --- a/i18n/dictionaries/sv.json +++ b/i18n/dictionaries/sv.json @@ -236,6 +236,7 @@ "Number of parking spots": "Antal parkeringsplatser", "OTHER PAYMENT METHODS": "ANDRE BETALINGSMETODER", "On your journey": "På din resa", + "Only pay {amount} {currency}": "Betala endast {amount} {currency}", "Open": "Öppna", "Open gift(s)": "Öppna {amount, plural, one {gåva} other {gåvor}}", "Open image gallery": "Öppna bildgalleri", @@ -457,6 +458,5 @@ "types": "typer", "uppercase letter": "stor bokstav", "{amount} out of {total}": "{amount} av {total}", - "{amount} {currency}": "{amount} {currency}", - "{difference}{amount} {currency}": "{difference}{amount} {currency}" + "{amount} {currency}": "{amount} {currency}" } diff --git a/types/components/tracking.ts b/types/components/tracking.ts index aa42ae5fe..fa6e82a95 100644 --- a/types/components/tracking.ts +++ b/types/components/tracking.ts @@ -80,3 +80,4 @@ export type TrackingPosition = | "hamburger menu" | "join scandic friends sidebar" | "sign up verification" + | "enter details" From 4c9e9de11350f06fe924af827749e3b8b5171f91 Mon Sep 17 00:00:00 2001 From: Christel Westerberg Date: Mon, 18 Nov 2024 13:13:41 +0100 Subject: [PATCH 02/31] fix: add mobile design for details form --- .../EnterDetails/Details/details.module.css | 32 ++++++++++++- .../Form/Checkbox/checkbox.module.css | 1 + .../Form/JoinScandicFriendsCard/index.tsx | 48 ++++++++++--------- .../joinScandicFriendsCard.module.css | 3 +- 4 files changed, 57 insertions(+), 27 deletions(-) diff --git a/components/HotelReservation/EnterDetails/Details/details.module.css b/components/HotelReservation/EnterDetails/Details/details.module.css index f89dfa7cc..2dc4266ed 100644 --- a/components/HotelReservation/EnterDetails/Details/details.module.css +++ b/components/HotelReservation/EnterDetails/Details/details.module.css @@ -1,11 +1,12 @@ .form { display: grid; - gap: var(--Spacing-x2); + gap: var(--Spacing-x1); + padding: var(--Spacing-x3) 0px; } .container { display: grid; - gap: var(--Spacing-x2); + gap: var(--Spacing-x1); grid-template-columns: 1fr 1fr; width: min(100%, 600px); } @@ -23,3 +24,30 @@ justify-items: flex-start; margin-top: var(--Spacing-x1); } + +@media screen and (min-width: 1367px) { + .form { + gap: var(--Spacing-x2); + padding: var(--Spacing-x3) 0px; + } + + .container { + gap: var(--Spacing-x2); + grid-template-columns: 1fr 1fr; + width: min(100%, 600px); + } + + .country, + .email, + .membershipNo, + .phone { + grid-column: 1/-1; + } + + .footer { + display: grid; + gap: var(--Spacing-x3); + justify-items: flex-start; + margin-top: var(--Spacing-x1); + } +} diff --git a/components/TempDesignSystem/Form/Checkbox/checkbox.module.css b/components/TempDesignSystem/Form/Checkbox/checkbox.module.css index 2e924b226..11c558af9 100644 --- a/components/TempDesignSystem/Form/Checkbox/checkbox.module.css +++ b/components/TempDesignSystem/Form/Checkbox/checkbox.module.css @@ -2,6 +2,7 @@ display: flex; flex-direction: column; color: var(--text-color); + cursor: pointer; } .container[data-selected] .checkbox { diff --git a/components/TempDesignSystem/Form/JoinScandicFriendsCard/index.tsx b/components/TempDesignSystem/Form/JoinScandicFriendsCard/index.tsx index 629a904fc..70f03e6fe 100644 --- a/components/TempDesignSystem/Form/JoinScandicFriendsCard/index.tsx +++ b/components/TempDesignSystem/Form/JoinScandicFriendsCard/index.tsx @@ -34,29 +34,31 @@ export default function JoinScandicFriendsCard({ ] return ( -
diff --git a/components/Forms/BookingWidget/index.tsx b/components/Forms/BookingWidget/index.tsx index b47ae74aa..3f7e5aa82 100644 --- a/components/Forms/BookingWidget/index.tsx +++ b/components/Forms/BookingWidget/index.tsx @@ -20,7 +20,7 @@ const formId = "booking-widget" export default function Form({ locations, type, - setIsOpen, + onClose, }: BookingWidgetFormProps) { const router = useRouter() const lang = useLang() @@ -56,7 +56,7 @@ export default function Form({ ) }) }) - setIsOpen(false) + onClose() router.push(`${bookingFlowPage}?${bookingWidgetParams.toString()}`) } diff --git a/types/components/form/bookingwidget.ts b/types/components/form/bookingwidget.ts index 887c4eae2..bded82de3 100644 --- a/types/components/form/bookingwidget.ts +++ b/types/components/form/bookingwidget.ts @@ -4,7 +4,7 @@ import type { Location, Locations } from "@/types/trpc/routers/hotel/locations" export interface BookingWidgetFormProps { locations: Locations type?: BookingWidgetType - setIsOpen: (isOpen: boolean) => void + onClose: () => void } export interface BookingWidgetFormContentProps { From d5c6b6809c9333a854daa13d71f120d056d4b9c1 Mon Sep 17 00:00:00 2001 From: Christel Westerberg Date: Mon, 18 Nov 2024 15:07:23 +0100 Subject: [PATCH 04/31] fix: responsivity of fields and order of signup form --- .../FormContent/formContent.module.css | 10 +- components/Forms/Signup/index.tsx | 1 - .../EnterDetails/Details/Signup/index.tsx | 68 +++------- .../EnterDetails/Details/details.module.css | 32 +---- .../EnterDetails/Details/index.tsx | 37 +++--- .../EnterDetails/Details/schema.ts | 10 -- .../EnterDetails/SectionAccordion/index.tsx | 48 ++++--- .../sectionAccordion.module.css | 63 ++++----- .../SelectedRoom/selectedRoom.module.css | 2 +- .../Form/ChoiceCard/_Card/card.module.css | 1 + .../Form/Date/date.module.css | 11 ++ .../TempDesignSystem/Form/Date/index.tsx | 1 + .../Form/JoinScandicFriendsCard/index.tsx | 72 +++++------ .../joinScandicFriendsCard.module.css | 38 +++++- .../TempDesignSystem/Form/Phone/index.tsx | 120 +++++++++--------- .../Form/Phone/phone.module.css | 11 ++ stores/details.ts | 1 - 17 files changed, 264 insertions(+), 262 deletions(-) diff --git a/components/Forms/Edit/Profile/FormContent/formContent.module.css b/components/Forms/Edit/Profile/FormContent/formContent.module.css index 64eb85410..aec012aa6 100644 --- a/components/Forms/Edit/Profile/FormContent/formContent.module.css +++ b/components/Forms/Edit/Profile/FormContent/formContent.module.css @@ -3,12 +3,14 @@ align-self: flex-start; display: grid; gap: var(--Spacing-x2); + container-name: addressContainer; + container-type: inline-size; } .container { display: grid; gap: var(--Spacing-x2); - grid-template-columns: max(164px) 1fr; + grid-template-columns: minmax(100px, 164px) 1fr; } @media (min-width: 768px) { @@ -16,3 +18,9 @@ display: none; } } + +@container addressContainer (max-width: 350px) { + .container { + grid-template-columns: 1fr; + } +} diff --git a/components/Forms/Signup/index.tsx b/components/Forms/Signup/index.tsx index ad66f6282..d6d5fc7da 100644 --- a/components/Forms/Signup/index.tsx +++ b/components/Forms/Signup/index.tsx @@ -48,7 +48,6 @@ export default function SignupForm({ link, subtitle, title }: SignUpFormProps) { zipCode: "", }, password: "", - termsAccepted: false, }, mode: "all", criteriaMode: "all", diff --git a/components/HotelReservation/EnterDetails/Details/Signup/index.tsx b/components/HotelReservation/EnterDetails/Details/Signup/index.tsx index 1846a7cd1..85559aa4c 100644 --- a/components/HotelReservation/EnterDetails/Details/Signup/index.tsx +++ b/components/HotelReservation/EnterDetails/Details/Signup/index.tsx @@ -4,14 +4,8 @@ import { useEffect, useState } from "react" import { useWatch } from "react-hook-form" import { useIntl } from "react-intl" -import { privacyPolicy } from "@/constants/currentWebHrefs" - -import Checkbox from "@/components/TempDesignSystem/Form/Checkbox" import DateSelect from "@/components/TempDesignSystem/Form/Date" import Input from "@/components/TempDesignSystem/Form/Input" -import JoinScandicFriendsCard from "@/components/TempDesignSystem/Form/JoinScandicFriendsCard" -import Link from "@/components/TempDesignSystem/Link" -import Body from "@/components/TempDesignSystem/Text/Body" import Caption from "@/components/TempDesignSystem/Text/Caption" import useLang from "@/hooks/useLang" @@ -31,49 +25,27 @@ export default function Signup({ name }: { name: string }) { setIsJoinChecked(joinValue) }, [joinValue]) - return ( -
- + - {isJoinChecked ? ( -
-
-
- - {intl.formatMessage({ id: "Birth date" })} * - -
- - -
-
- - - {intl.formatMessage({ - id: "Yes, I accept the Terms and conditions for Scandic Friends and understand that Scandic will process my personal data in accordance with", - })}{" "} - - {intl.formatMessage({ id: "Scandic's Privacy Policy." })} - - - -
-
- ) : null} +
+
+ + {intl.formatMessage({ id: "Birth date" })} * + +
+ +
+ ) : ( + ) } diff --git a/components/HotelReservation/EnterDetails/Details/details.module.css b/components/HotelReservation/EnterDetails/Details/details.module.css index 2dc4266ed..c6571ea9e 100644 --- a/components/HotelReservation/EnterDetails/Details/details.module.css +++ b/components/HotelReservation/EnterDetails/Details/details.module.css @@ -1,34 +1,30 @@ .form { display: grid; - gap: var(--Spacing-x1); - padding: var(--Spacing-x3) 0px; + gap: var(--Spacing-x3); + margin-bottom: var(--Spacing-x3); } .container { display: grid; - gap: var(--Spacing-x1); - grid-template-columns: 1fr 1fr; + gap: var(--Spacing-x2); width: min(100%, 600px); } +.header, .country, .email, -.membershipNo, +.signup, .phone { grid-column: 1/-1; } .footer { - display: grid; - gap: var(--Spacing-x3); - justify-items: flex-start; margin-top: var(--Spacing-x1); } -@media screen and (min-width: 1367px) { +@media screen and (min-width: 768px) { .form { - gap: var(--Spacing-x2); - padding: var(--Spacing-x3) 0px; + gap: var(--Spacing-x3); } .container { @@ -36,18 +32,4 @@ grid-template-columns: 1fr 1fr; width: min(100%, 600px); } - - .country, - .email, - .membershipNo, - .phone { - grid-column: 1/-1; - } - - .footer { - display: grid; - gap: var(--Spacing-x3); - justify-items: flex-start; - margin-top: var(--Spacing-x1); - } } diff --git a/components/HotelReservation/EnterDetails/Details/index.tsx b/components/HotelReservation/EnterDetails/Details/index.tsx index dd5959c31..3c0c55b9b 100644 --- a/components/HotelReservation/EnterDetails/Details/index.tsx +++ b/components/HotelReservation/EnterDetails/Details/index.tsx @@ -10,6 +10,7 @@ import { useStepsStore } from "@/stores/steps" import Button from "@/components/TempDesignSystem/Button" import CountrySelect from "@/components/TempDesignSystem/Form/Country" import Input from "@/components/TempDesignSystem/Form/Input" +import JoinScandicFriendsCard from "@/components/TempDesignSystem/Form/JoinScandicFriendsCard" import Phone from "@/components/TempDesignSystem/Form/Phone" import Footnote from "@/components/TempDesignSystem/Text/Footnote" @@ -35,7 +36,6 @@ export default function Details({ user }: DetailsProps) { join: state.data.join, dateOfBirth: state.data.dateOfBirth, zipCode: state.data.zipCode, - termsAccepted: state.data.termsAccepted, membershipNo: state.data.membershipNo, })) @@ -52,7 +52,6 @@ export default function Details({ user }: DetailsProps) { join: initialData.join, dateOfBirth: initialData.dateOfBirth, zipCode: initialData.zipCode, - termsAccepted: initialData.termsAccepted, membershipNo: initialData.membershipNo, }, criteriaMode: "all", @@ -69,6 +68,7 @@ export default function Details({ user }: DetailsProps) { [completeStep, updateDetails] ) + const joinValue = methods.watch("join") return ( - {user ? null : } - - {intl.formatMessage({ id: "Guest information" })} - + {user ? null : ( + + )}
+ + {intl.formatMessage({ id: "Guest information" })} + - {user || methods.watch("join") ? null : ( - + {user ? null : ( +
+ +
)}
diff --git a/components/HotelReservation/EnterDetails/Details/schema.ts b/components/HotelReservation/EnterDetails/Details/schema.ts index 2b8075da9..abb29ac2b 100644 --- a/components/HotelReservation/EnterDetails/Details/schema.ts +++ b/components/HotelReservation/EnterDetails/Details/schema.ts @@ -15,7 +15,6 @@ export const notJoinDetailsSchema = baseDetailsSchema.merge( join: z.literal(false), zipCode: z.string().optional(), dateOfBirth: z.string().optional(), - termsAccepted: z.boolean().default(false), membershipNo: z .string() .optional() @@ -39,15 +38,6 @@ export const joinDetailsSchema = baseDetailsSchema.merge( join: z.literal(true), zipCode: z.string().min(1, { message: "Zip code is required" }), dateOfBirth: z.string().min(1, { message: "Date of birth is required" }), - termsAccepted: z.literal(true, { - errorMap: (err, ctx) => { - switch (err.code) { - case "invalid_literal": - return { message: "You must accept the terms and conditions" } - } - return { message: ctx.defaultError } - }, - }), membershipNo: z.string().optional(), }) ) diff --git a/components/HotelReservation/EnterDetails/SectionAccordion/index.tsx b/components/HotelReservation/EnterDetails/SectionAccordion/index.tsx index ce548ae74..a740fb224 100644 --- a/components/HotelReservation/EnterDetails/SectionAccordion/index.tsx +++ b/components/HotelReservation/EnterDetails/SectionAccordion/index.tsx @@ -66,7 +66,7 @@ export default function SectionAccordion({ const textColor = isComplete || isOpen ? "uiTextHighContrast" : "baseTextDisabled" return ( -
+
{isComplete ? ( @@ -74,31 +74,27 @@ export default function SectionAccordion({ ) : null}
-
-
- -
-
-
{children}
-
-
-
+ {isComplete && !isOpen && ( + + )} + + +
{children}
+
) } diff --git a/components/HotelReservation/EnterDetails/SectionAccordion/sectionAccordion.module.css b/components/HotelReservation/EnterDetails/SectionAccordion/sectionAccordion.module.css index ed91cb9e2..f4990b28b 100644 --- a/components/HotelReservation/EnterDetails/SectionAccordion/sectionAccordion.module.css +++ b/components/HotelReservation/EnterDetails/SectionAccordion/sectionAccordion.module.css @@ -1,15 +1,25 @@ -.wrapper { - position: relative; - display: flex; - flex-direction: row; - gap: var(--Spacing-x-one-and-half); +.main { + gap: var(--Spacing-x3); + width: 100%; padding-top: var(--Spacing-x3); + transition: 0.4s ease-out; + + display: grid; + grid-template-areas: "circle header" "content content"; + grid-template-columns: auto 1fr; + grid-template-rows: 2.4em 0fr; + + column-gap: var(--Spacing-x-one-and-half); } -.wrapper:last-child .main { +.main:last-child .main { border-bottom: none; } +.header { + grid-area: header; +} + .modifyButton { display: grid; grid-template-areas: "title button" "selection button"; @@ -17,6 +27,7 @@ background-color: transparent; border: none; width: 100%; + padding: 0; } .title { @@ -29,15 +40,6 @@ justify-self: flex-end; } -.main { - display: grid; - width: 100%; - border-bottom: 1px solid var(--Primary-Light-On-Surface-Divider-subtle); - padding-bottom: var(--Spacing-x3); - transition: 0.4s ease-out; - grid-template-rows: 2em 0fr; -} - .selection { font-weight: 450; font-size: var(--typography-Title-4-fontSize); @@ -46,6 +48,7 @@ .iconWrapper { position: relative; + grid-area: circle; } .circle { @@ -63,42 +66,42 @@ background-color: var(--UI-Input-Controls-Fill-Selected); } -.wrapper[data-open="true"] .circle[data-checked="false"] { +.main[data-open="true"] .circle[data-checked="false"] { background-color: var(--UI-Text-Placeholder); } -.wrapper[data-open="false"] .circle[data-checked="false"] { +.main[data-open="false"] .circle[data-checked="false"] { background-color: var(--Base-Surface-Subtle-Hover); } -.wrapper[data-open="true"] .main { - grid-template-rows: 2em 1fr; +.main[data-open="true"] { + grid-template-rows: 2.4em 1fr; + gap: var(--Spacing-x3); } .content { overflow: hidden; + grid-area: content; + border-bottom: 1px solid var(--Primary-Light-On-Surface-Divider-subtle); } -.contentWrapper { - padding-top: var(--Spacing-x3); -} - -@media screen and (min-width: 1367px) { - .wrapper { +@media screen and (min-width: 768px) { + .main { gap: var(--Spacing-x3); + grid-template-areas: "circle header" "circle content"; } .iconWrapper { top: var(--Spacing-x1); } - .wrapper:not(:last-child)::after { + .main:not(:last-child) .iconWrapper::after { position: absolute; left: 12px; - bottom: 0; - top: var(--Spacing-x7); - height: 100%; + bottom: calc(0px - var(--Spacing-x7)); + top: 24px; + content: ""; border-left: 1px solid var(--Primary-Light-On-Surface-Divider-subtle); } -} \ No newline at end of file +} diff --git a/components/HotelReservation/EnterDetails/SelectedRoom/selectedRoom.module.css b/components/HotelReservation/EnterDetails/SelectedRoom/selectedRoom.module.css index 3149cf709..e979c1ee2 100644 --- a/components/HotelReservation/EnterDetails/SelectedRoom/selectedRoom.module.css +++ b/components/HotelReservation/EnterDetails/SelectedRoom/selectedRoom.module.css @@ -63,7 +63,7 @@ justify-content: flex-start; } -@media screen and (min-width: 1367px) { +@media screen and (min-width: 768px) { .wrapper { gap: var(--Spacing-x3); padding-top: var(--Spacing-x3); diff --git a/components/TempDesignSystem/Form/ChoiceCard/_Card/card.module.css b/components/TempDesignSystem/Form/ChoiceCard/_Card/card.module.css index fa7d6d13a..69044abb2 100644 --- a/components/TempDesignSystem/Form/ChoiceCard/_Card/card.module.css +++ b/components/TempDesignSystem/Form/ChoiceCard/_Card/card.module.css @@ -9,6 +9,7 @@ padding: var(--Spacing-x-one-and-half) var(--Spacing-x2); transition: all 200ms ease; width: min(100%, 600px); + height: 100%; } .label:hover { diff --git a/components/TempDesignSystem/Form/Date/date.module.css b/components/TempDesignSystem/Form/Date/date.module.css index fde0b7e03..7f492a293 100644 --- a/components/TempDesignSystem/Form/Date/date.module.css +++ b/components/TempDesignSystem/Form/Date/date.module.css @@ -1,4 +1,8 @@ /* Leaving, will most likely get deleted */ +.datePicker { + container-name: datePickerContainer; + container-type: inline-size; +} .container { display: grid; gap: var(--Spacing-x2); @@ -27,3 +31,10 @@ .year.invalid > div > div { border-color: var(--Scandic-Red-60); } + +@container datePickerContainer (max-width: 350px) { + .container { + display: flex; + flex-direction: column; + } +} diff --git a/components/TempDesignSystem/Form/Date/index.tsx b/components/TempDesignSystem/Form/Date/index.tsx index 4e8f4a7af..fa4b27528 100644 --- a/components/TempDesignSystem/Form/Date/index.tsx +++ b/components/TempDesignSystem/Form/Date/index.tsx @@ -115,6 +115,7 @@ export default function DateSelect({ name, registerOptions = {} }: DateProps) { ref={field.ref} value={dateValue} data-testid={name} + className={styles.datePicker} > diff --git a/components/TempDesignSystem/Form/JoinScandicFriendsCard/index.tsx b/components/TempDesignSystem/Form/JoinScandicFriendsCard/index.tsx index 70f03e6fe..8a6c4383c 100644 --- a/components/TempDesignSystem/Form/JoinScandicFriendsCard/index.tsx +++ b/components/TempDesignSystem/Form/JoinScandicFriendsCard/index.tsx @@ -35,43 +35,41 @@ export default function JoinScandicFriendsCard({ return (
-
- -
- - {intl.formatMessage( - { - id: "Only pay {amount} {currency}", - }, - { - amount: intl.formatNumber(difference.price), - currency: difference.currency, - } - )} - - - {intl.formatMessage({ id: "Join Scandic Friends" })} - -
-
- - - {intl.formatMessage({ id: "Already a friend?" })}{" "} - +
+ + {intl.formatMessage( + { + id: "Only pay {amount} {currency}", + }, + { + amount: intl.formatNumber(difference.price), + currency: difference.currency, + } + )} + + - {intl.formatMessage({ id: "Log in" })} - - -
+ {intl.formatMessage({ id: "Join Scandic Friends" })} + +
+ + + + {intl.formatMessage({ id: "Already a friend?" })}{" "} + + {intl.formatMessage({ id: "Log in" })} + +
{list.map((item) => ( @@ -84,7 +82,7 @@ export default function JoinScandicFriendsCard({ ))}
- + {intl.formatMessage( { id: "signup.terms", diff --git a/components/TempDesignSystem/Form/JoinScandicFriendsCard/joinScandicFriendsCard.module.css b/components/TempDesignSystem/Form/JoinScandicFriendsCard/joinScandicFriendsCard.module.css index 8238c0bf2..507633fe4 100644 --- a/components/TempDesignSystem/Form/JoinScandicFriendsCard/joinScandicFriendsCard.module.css +++ b/components/TempDesignSystem/Form/JoinScandicFriendsCard/joinScandicFriendsCard.module.css @@ -4,26 +4,52 @@ border: 1px solid var(--Base-Border-Subtle); border-radius: var(--Corner-radius-Large); display: grid; - gap: var(--Spacing-x2); + gap: var(--Spacing-x-one-and-half); padding: var(--Spacing-x-one-and-half) var(--Spacing-x2); + grid-template-areas: + "checkbox" + "list" + "login" + "terms"; width: min(100%, 600px); } -.header { - display: grid; - gap: var(--Spacing-x-one-and-half); - grid-template-columns: 1fr auto; +.login { + grid-area: login; } .checkBox { align-self: center; + grid-area: checkbox; } .list { - display: flex; + display: grid; + grid-area: list; gap: var(--Spacing-x1); } .listItem { display: flex; } + +.terms { + border-top: 1px solid var(--Base-Border-Normal); + grid-area: terms; + padding-top: var(--Spacing-x1); +} + +@media screen and (min-width: 768px) { + .cardContainer { + grid-template-columns: 1fr auto; + gap: var(--Spacing-x2); + grid-template-areas: + "checkbox login" + "list list" + "terms terms"; + } + .list { + display: flex; + gap: var(--Spacing-x1); + } +} diff --git a/components/TempDesignSystem/Form/Phone/index.tsx b/components/TempDesignSystem/Form/Phone/index.tsx index 9c413fc0a..5ff8f3482 100644 --- a/components/TempDesignSystem/Form/Phone/index.tsx +++ b/components/TempDesignSystem/Form/Phone/index.tsx @@ -78,67 +78,69 @@ export default function Phone({ } return ( -
- ( - - )} - /> - - + + )} /> - - + + + + +
) } diff --git a/components/TempDesignSystem/Form/Phone/phone.module.css b/components/TempDesignSystem/Form/Phone/phone.module.css index f2b75136b..31de5be30 100644 --- a/components/TempDesignSystem/Form/Phone/phone.module.css +++ b/components/TempDesignSystem/Form/Phone/phone.module.css @@ -1,3 +1,7 @@ +.wrapper { + container-name: phoneContainer; + container-type: inline-size; +} .phone { display: grid; gap: var(--Spacing-x2); @@ -100,3 +104,10 @@ justify-self: flex-start; padding: 0; } + +@container phoneContainer (max-width: 350px) { + .phone { + display: flex; + flex-direction: column; + } +} diff --git a/stores/details.ts b/stores/details.ts index 5d23248e5..a382ad7d7 100644 --- a/stores/details.ts +++ b/stores/details.ts @@ -94,7 +94,6 @@ export function createDetailsStore( state.data.membershipNo = data.membershipNo } state.data.phoneNumber = data.phoneNumber - state.data.termsAccepted = data.termsAccepted state.data.zipCode = data.zipCode }) ) From bd0720dc0ff61525b14b27a766e56faeed11ca98 Mon Sep 17 00:00:00 2001 From: Christel Westerberg Date: Mon, 18 Nov 2024 15:30:12 +0100 Subject: [PATCH 05/31] fix: break out css variables --- .../hotelreservation/(standard)/step/page.tsx | 9 +++- .../Details}/JoinScandicFriendsCard/index.tsx | 42 +++++++++---------- .../joinScandicFriendsCard.module.css | 0 .../EnterDetails/Details/index.tsx | 10 ++--- .../EnterDetails/SectionAccordion/index.tsx | 2 +- .../sectionAccordion.module.css | 27 ++++++------ components/LoginButton/index.tsx | 9 ++-- .../Form/ChoiceCard/_Card/card.module.css | 2 - .../TempDesignSystem/Link/link.module.css | 4 +- .../hotelReservation/enterDetails/details.ts | 8 ++++ 10 files changed, 63 insertions(+), 50 deletions(-) rename components/{TempDesignSystem/Form => HotelReservation/EnterDetails/Details}/JoinScandicFriendsCard/index.tsx (75%) rename components/{TempDesignSystem/Form => HotelReservation/EnterDetails/Details}/JoinScandicFriendsCard/joinScandicFriendsCard.module.css (100%) diff --git a/app/[lang]/(live)/(public)/hotelreservation/(standard)/step/page.tsx b/app/[lang]/(live)/(public)/hotelreservation/(standard)/step/page.tsx index 648cdff93..7ffe57562 100644 --- a/app/[lang]/(live)/(public)/hotelreservation/(standard)/step/page.tsx +++ b/app/[lang]/(live)/(public)/hotelreservation/(standard)/step/page.tsx @@ -111,6 +111,13 @@ export default async function StepPage({ publicPrice: roomAvailability.publicRate!.localPrice.pricePerStay, } + const memberPrice = roomAvailability.memberRate + ? { + price: roomAvailability.memberRate.localPrice.pricePerStay, + currency: roomAvailability.memberRate.localPrice.currency, + } + : undefined + return ( -
+
- - {intl.formatMessage( - { - id: "Only pay {amount} {currency}", - }, - { - amount: intl.formatNumber(difference.price), - currency: difference.currency, - } - )} - + {memberPrice ? ( + + {saveOnJoiningLabel} + + ) : null} ({ countryCode: state.data.countryCode, @@ -68,7 +68,6 @@ export default function Details({ user }: DetailsProps) { [completeStep, updateDetails] ) - const joinValue = methods.watch("join") return ( {user ? null : ( - + )}
+
{isComplete ? ( diff --git a/components/HotelReservation/EnterDetails/SectionAccordion/sectionAccordion.module.css b/components/HotelReservation/EnterDetails/SectionAccordion/sectionAccordion.module.css index f4990b28b..0bbcf851c 100644 --- a/components/HotelReservation/EnterDetails/SectionAccordion/sectionAccordion.module.css +++ b/components/HotelReservation/EnterDetails/SectionAccordion/sectionAccordion.module.css @@ -1,4 +1,7 @@ -.main { +.accordion { + --header-height: 2.4em; + --circle-height: 24px; + gap: var(--Spacing-x3); width: 100%; padding-top: var(--Spacing-x3); @@ -7,12 +10,12 @@ display: grid; grid-template-areas: "circle header" "content content"; grid-template-columns: auto 1fr; - grid-template-rows: 2.4em 0fr; + grid-template-rows: var(--header-height) 0fr; column-gap: var(--Spacing-x-one-and-half); } -.main:last-child .main { +.accordion:last-child { border-bottom: none; } @@ -52,8 +55,8 @@ } .circle { - width: 24px; - height: 24px; + width: var(--circle-height); + height: var(--circle-height); border-radius: 100px; transition: background-color 0.4s; border: 2px solid var(--Base-Border-Inverted); @@ -66,16 +69,16 @@ background-color: var(--UI-Input-Controls-Fill-Selected); } -.main[data-open="true"] .circle[data-checked="false"] { +.accordion[data-open="true"] .circle[data-checked="false"] { background-color: var(--UI-Text-Placeholder); } -.main[data-open="false"] .circle[data-checked="false"] { +.accordion[data-open="false"] .circle[data-checked="false"] { background-color: var(--Base-Surface-Subtle-Hover); } -.main[data-open="true"] { - grid-template-rows: 2.4em 1fr; +.accordion[data-open="true"] { + grid-template-rows: var(--header-height) 1fr; gap: var(--Spacing-x3); } @@ -86,7 +89,7 @@ } @media screen and (min-width: 768px) { - .main { + .accordion { gap: var(--Spacing-x3); grid-template-areas: "circle header" "circle content"; } @@ -95,11 +98,11 @@ top: var(--Spacing-x1); } - .main:not(:last-child) .iconWrapper::after { + .accordion:not(:last-child) .iconWrapper::after { position: absolute; left: 12px; bottom: calc(0px - var(--Spacing-x7)); - top: 24px; + top: var(--circle-height); content: ""; border-left: 1px solid var(--Primary-Light-On-Surface-Divider-subtle); diff --git a/components/LoginButton/index.tsx b/components/LoginButton/index.tsx index 6258f6e88..89cc3a42a 100644 --- a/components/LoginButton/index.tsx +++ b/components/LoginButton/index.tsx @@ -31,13 +31,14 @@ export default function LoginButton({ : login[lang] useEffect(() => { - document - .getElementById(trackingId) - ?.addEventListener("click", () => trackLoginClick(position)) + function trackLogin() { + trackLoginClick(position) + } + document.getElementById(trackingId)?.addEventListener("click", trackLogin) return () => { document .getElementById(trackingId) - ?.removeEventListener("click", () => trackLoginClick(position)) + ?.removeEventListener("click", trackLogin) } }, [position, trackingId]) diff --git a/components/TempDesignSystem/Form/ChoiceCard/_Card/card.module.css b/components/TempDesignSystem/Form/ChoiceCard/_Card/card.module.css index 69044abb2..d50df8a15 100644 --- a/components/TempDesignSystem/Form/ChoiceCard/_Card/card.module.css +++ b/components/TempDesignSystem/Form/ChoiceCard/_Card/card.module.css @@ -1,5 +1,4 @@ .label { - align-self: flex-start; background-color: var(--Base-Surface-Primary-light-Normal); border: 1px solid var(--Base-Border-Subtle); border-radius: var(--Corner-radius-Large); @@ -9,7 +8,6 @@ padding: var(--Spacing-x-one-and-half) var(--Spacing-x2); transition: all 200ms ease; width: min(100%, 600px); - height: 100%; } .label:hover { diff --git a/components/TempDesignSystem/Link/link.module.css b/components/TempDesignSystem/Link/link.module.css index 948574ed2..3a997bddb 100644 --- a/components/TempDesignSystem/Link/link.module.css +++ b/components/TempDesignSystem/Link/link.module.css @@ -16,7 +16,7 @@ .breadcrumb { font-family: var(--typography-Footnote-Bold-fontFamily); font-size: var(--typography-Footnote-Bold-fontSize); - font-weight: 450; /* var(--typography-Footnote-Bold-fontWeight); */ + font-weight: 500; /* var(--typography-Footnote-Bold-fontWeight); */ letter-spacing: var(--typography-Footnote-Bold-letterSpacing); line-height: var(--typography-Footnote-Bold-lineHeight); } @@ -24,7 +24,7 @@ .link.breadcrumb { font-family: var(--typography-Footnote-Bold-fontFamily); font-size: var(--typography-Footnote-Bold-fontSize); - font-weight: 450; /* var(--typography-Footnote-Bold-fontWeight); */ + font-weight: 500; /* var(--typography-Footnote-Bold-fontWeight); */ letter-spacing: var(--typography-Footnote-Bold-letterSpacing); line-height: var(--typography-Footnote-Bold-lineHeight); } diff --git a/types/components/hotelReservation/enterDetails/details.ts b/types/components/hotelReservation/enterDetails/details.ts index 685fad18a..25004467a 100644 --- a/types/components/hotelReservation/enterDetails/details.ts +++ b/types/components/hotelReservation/enterDetails/details.ts @@ -6,6 +6,14 @@ import type { SafeUser } from "@/types/user" export type DetailsSchema = z.output +type MemberPrice = { price: number; currency: string } + export interface DetailsProps { user: SafeUser + memberPrice?: MemberPrice +} + +export type JoinScandicFriendsCardProps = { + name: string + memberPrice?: MemberPrice } From cc15dc2dc2d6f71726b3ffedd344d83ff104345b Mon Sep 17 00:00:00 2001 From: Pontus Dreij Date: Tue, 19 Nov 2024 09:17:58 +0100 Subject: [PATCH 06/31] fix(SW-917): fix correct link to map --- constants/routes/hotelReservation.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/constants/routes/hotelReservation.js b/constants/routes/hotelReservation.js index c7882f159..ed7ae99c5 100644 --- a/constants/routes/hotelReservation.js +++ b/constants/routes/hotelReservation.js @@ -48,7 +48,7 @@ export function selectHotel(lang) { * @param {Lang} lang */ export function selectHotelMap(lang) { - return `${base(lang)}/map` + return `${base(lang)}/select-hotel/map` } /** From 60f1d268a96945d4d9c20b698855c98687b20c14 Mon Sep 17 00:00:00 2001 From: Pontus Dreij Date: Tue, 19 Nov 2024 08:22:03 +0000 Subject: [PATCH 07/31] Merged in feat/SW-904-add-back-to-top-button (pull request #923) Feat/SW-904 add back to top button on hotel list page * feat(SW-904): Added back to top button * fix: removed alert on hotel listing page * Remove console.log Approved-by: Niclas Edenvin --- .../HotelReservation/HotelCard/index.tsx | 8 ---- .../HotelCardListing/index.tsx | 22 ++++++++- .../SelectHotel/SelectHotelMap/index.tsx | 15 ++----- .../SelectHotelMap/selectHotelMap.module.css | 11 +---- components/Icons/ArrowUp.tsx | 33 ++++++++++++++ components/Icons/index.tsx | 1 + .../backToTopButton.module.css | 45 +++++++++++++++++++ .../BackToTopButton/index.tsx | 20 +++++++++ 8 files changed, 124 insertions(+), 31 deletions(-) create mode 100644 components/Icons/ArrowUp.tsx create mode 100644 components/TempDesignSystem/BackToTopButton/backToTopButton.module.css create mode 100644 components/TempDesignSystem/BackToTopButton/index.tsx diff --git a/components/HotelReservation/HotelCard/index.tsx b/components/HotelReservation/HotelCard/index.tsx index 808e9ac9f..1836d4130 100644 --- a/components/HotelReservation/HotelCard/index.tsx +++ b/components/HotelReservation/HotelCard/index.tsx @@ -7,7 +7,6 @@ import { selectHotelMap, selectRate } from "@/constants/routes/hotelReservation" import { mapFacilityToIcon } from "@/components/ContentType/HotelPage/data" import ImageGallery from "@/components/ImageGallery" -import Alert from "@/components/TempDesignSystem/Alert" import Button from "@/components/TempDesignSystem/Button" import Divider from "@/components/TempDesignSystem/Divider" import Link from "@/components/TempDesignSystem/Link" @@ -133,13 +132,6 @@ export default function HotelCard({ hotel={hotelData} showCTA={true} /> - {hotelData.specialAlerts.length > 0 && ( -
- {hotelData.specialAlerts.map((alert) => ( - - ))} -
- )}
diff --git a/components/HotelReservation/HotelCardListing/index.tsx b/components/HotelReservation/HotelCardListing/index.tsx index ab2d45d3e..191c7acde 100644 --- a/components/HotelReservation/HotelCardListing/index.tsx +++ b/components/HotelReservation/HotelCardListing/index.tsx @@ -1,9 +1,12 @@ "use client" import { useSearchParams } from "next/navigation" -import { useMemo } from "react" +import { useEffect, useMemo, useState } from "react" +import { useIntl } from "react-intl" import { useHotelFilterStore } from "@/stores/hotel-filters" +import { BackToTopButton } from "@/components/TempDesignSystem/BackToTopButton" + import HotelCard from "../HotelCard" import { DEFAULT_SORT } from "../SelectHotel/HotelSorter" @@ -22,9 +25,11 @@ export default function HotelCardListing({ activeCard, onHotelCardHover, }: HotelCardListingProps) { + const intl = useIntl() const searchParams = useSearchParams() const activeFilters = useHotelFilterStore((state) => state.activeFilters) const setResultCount = useHotelFilterStore((state) => state.setResultCount) + const [showBackToTop, setShowBackToTop] = useState(false) const sortBy = useMemo( () => searchParams.get("sort") ?? DEFAULT_SORT, @@ -82,6 +87,20 @@ export default function HotelCardListing({ return filteredHotels }, [activeFilters, sortedHotels, setResultCount]) + useEffect(() => { + const handleScroll = () => { + const hasScrolledPast = window.scrollY > 490 + setShowBackToTop(hasScrolledPast) + } + + window.addEventListener("scroll", handleScroll, { passive: true }) + return () => window.removeEventListener("scroll", handleScroll) + }, []) + + function scrollToTop() { + window.scrollTo({ top: 0, behavior: "smooth" }) + } + return (
{hotels?.length @@ -95,6 +114,7 @@ export default function HotelCardListing({ /> )) : null} + {showBackToTop && }
) } diff --git a/components/HotelReservation/SelectHotel/SelectHotelMap/index.tsx b/components/HotelReservation/SelectHotel/SelectHotelMap/index.tsx index 215c7ae66..ce3d2173d 100644 --- a/components/HotelReservation/SelectHotel/SelectHotelMap/index.tsx +++ b/components/HotelReservation/SelectHotel/SelectHotelMap/index.tsx @@ -7,8 +7,9 @@ import { useMediaQuery } from "usehooks-ts" import { selectHotel } from "@/constants/routes/hotelReservation" -import { CloseIcon, CloseLargeIcon } from "@/components/Icons" +import { ArrowUpIcon, CloseIcon, CloseLargeIcon } from "@/components/Icons" import InteractiveMap from "@/components/Maps/InteractiveMap" +import { BackToTopButton } from "@/components/TempDesignSystem/BackToTopButton" import Button from "@/components/TempDesignSystem/Button" import useLang from "@/hooks/useLang" @@ -109,17 +110,7 @@ export default function SelectHotelMap({ activeHotelPin={activeHotelPin} setActiveHotelPin={setActiveHotelPin} /> - {showBackToTop && ( - - )} + {showBackToTop && }
+ + + + + + ) +} diff --git a/components/Icons/index.tsx b/components/Icons/index.tsx index 1bc195717..7ec502ca0 100644 --- a/components/Icons/index.tsx +++ b/components/Icons/index.tsx @@ -6,6 +6,7 @@ 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 ArrowUpIcon } from "./ArrowUp" export { default as BarIcon } from "./Bar" export { default as BathtubIcon } from "./Bathtub" export { default as BedDoubleIcon } from "./BedDouble" diff --git a/components/TempDesignSystem/BackToTopButton/backToTopButton.module.css b/components/TempDesignSystem/BackToTopButton/backToTopButton.module.css new file mode 100644 index 000000000..4ce70972b --- /dev/null +++ b/components/TempDesignSystem/BackToTopButton/backToTopButton.module.css @@ -0,0 +1,45 @@ +.backToTopButton { + border-radius: var(--Corner-radius-Rounded); + cursor: pointer; + display: flex; + align-items: flex-end; + position: fixed; + bottom: 20px; + right: 20px; + z-index: 1000; + background-color: var(--Base-Surface-Primary-light-Normal); + color: var(--Base-Button-Secondary-On-Fill-Normal); + border: 2px solid var(--Base-Button-Secondary-On-Fill-Normal); + gap: var(--Spacing-x-half); + padding: var(--Spacing-x1); + text-align: center; + transition: + background-color 300ms ease, + color 300ms ease; + font-family: var(--typography-Body-Bold-fontFamily); + font-weight: 500; + font-size: var(--typography-Caption-Bold-fontSize); + line-height: var(--typography-Caption-Bold-lineHeight); + letter-spacing: 0.6%; + text-decoration: none; +} + +.backToTopButtonText { + display: none; +} + +@media (min-width: 768px) { + .backToTopButtonText { + display: initial; + } + .backToTopButton:hover { + background-color: var(--Base-Button-Tertiary-Fill-Normal); + color: var(--Base-Button-Tertiary-On-Fill-Hover); + } + .backToTopButton:hover > svg * { + fill: var(--Base-Button-Tertiary-On-Fill-Hover); + } + .backToTopButton { + padding: calc(var(--Spacing-x1) + 2px) var(--Spacing-x2); + } +} diff --git a/components/TempDesignSystem/BackToTopButton/index.tsx b/components/TempDesignSystem/BackToTopButton/index.tsx new file mode 100644 index 000000000..a50f8329f --- /dev/null +++ b/components/TempDesignSystem/BackToTopButton/index.tsx @@ -0,0 +1,20 @@ +"use client" + +import { Button as ButtonRAC } from "react-aria-components" +import { useIntl } from "react-intl" + +import { ArrowUpIcon } from "@/components/Icons" + +import styles from "./backToTopButton.module.css" + +export function BackToTopButton({ onClick }: { onClick: () => void }) { + const intl = useIntl() + return ( + + + + {intl.formatMessage({ id: "Back to top" })} + + + ) +} From 94f97dffa994f916363e4d8309f7c4ab5d44e8db Mon Sep 17 00:00:00 2001 From: Pontus Dreij Date: Tue, 19 Nov 2024 09:50:08 +0100 Subject: [PATCH 08/31] fix(SW-565): Fixed that tooltip doesn't block anything. --- components/GuestsRoomsPicker/Form.tsx | 4 ++-- components/TempDesignSystem/Tooltip/index.tsx | 1 + components/TempDesignSystem/Tooltip/tooltip.module.css | 1 + 3 files changed, 4 insertions(+), 2 deletions(-) diff --git a/components/GuestsRoomsPicker/Form.tsx b/components/GuestsRoomsPicker/Form.tsx index 01ded876a..062347761 100644 --- a/components/GuestsRoomsPicker/Form.tsx +++ b/components/GuestsRoomsPicker/Form.tsx @@ -99,7 +99,7 @@ export default function GuestsRoomsPickerDialog({ {rooms.length < 4 ? ( @@ -124,7 +124,7 @@ export default function GuestsRoomsPickerDialog({ {rooms.length < 4 ? ( diff --git a/components/TempDesignSystem/Tooltip/index.tsx b/components/TempDesignSystem/Tooltip/index.tsx index 033fc9a04..d73252161 100644 --- a/components/TempDesignSystem/Tooltip/index.tsx +++ b/components/TempDesignSystem/Tooltip/index.tsx @@ -28,6 +28,7 @@ export function Tooltip

({ role="tooltip" aria-label={text} onClick={handleToggle} + onTouchStart={handleToggle} data-active={isActive} >

diff --git a/components/TempDesignSystem/Tooltip/tooltip.module.css b/components/TempDesignSystem/Tooltip/tooltip.module.css index e25433f7c..b0ae8cf4f 100644 --- a/components/TempDesignSystem/Tooltip/tooltip.module.css +++ b/components/TempDesignSystem/Tooltip/tooltip.module.css @@ -16,6 +16,7 @@ transition: opacity 0.3s; max-width: 200px; min-width: 150px; + height: fit-content; } .tooltipContainer:hover .tooltip { From ebbdecf8d8cb92c7d042d6c9b8d5d2dad001c993 Mon Sep 17 00:00:00 2001 From: Pontus Dreij Date: Tue, 19 Nov 2024 10:34:21 +0100 Subject: [PATCH 09/31] fix(SW-565) fix select hight to see its scrollable --- .../GuestsRoomsPicker/ChildSelector/ChildInfoSelector.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/components/GuestsRoomsPicker/ChildSelector/ChildInfoSelector.tsx b/components/GuestsRoomsPicker/ChildSelector/ChildInfoSelector.tsx index 8252f2d2a..b39821483 100644 --- a/components/GuestsRoomsPicker/ChildSelector/ChildInfoSelector.tsx +++ b/components/GuestsRoomsPicker/ChildSelector/ChildInfoSelector.tsx @@ -94,7 +94,7 @@ export default function ChildInfoSelector({ updateSelectedAge(key as number) }} placeholder={ageLabel} - maxHeight={150} + maxHeight={180} {...register(ageFieldName, { required: true, })} From 1b3999a05031e3906c2e662240917a85eef21a22 Mon Sep 17 00:00:00 2001 From: Erik Tiekstra Date: Tue, 19 Nov 2024 09:50:34 +0100 Subject: [PATCH 10/31] fix(SW-769): removed categoryname enum from pointOfInterestSchema and use z.string() instead --- components/Maps/Markers/utils.ts | 15 +++-------- server/routers/hotels/output.ts | 5 +--- server/routers/hotels/utils.ts | 43 +++++++++++++----------------- types/components/maps/poiMarker.ts | 7 ++--- types/hotel.ts | 20 -------------- 5 files changed, 26 insertions(+), 64 deletions(-) diff --git a/components/Maps/Markers/utils.ts b/components/Maps/Markers/utils.ts index fd574eb87..51a6ed9a9 100644 --- a/components/Maps/Markers/utils.ts +++ b/components/Maps/Markers/utils.ts @@ -1,22 +1,15 @@ import { IconName } from "@/types/components/icon" -import { - PointOfInterestCategoryNameEnum, - PointOfInterestGroupEnum, -} from "@/types/hotel" +import { PointOfInterestGroupEnum } from "@/types/hotel" export function getIconByPoiGroupAndCategory( group: PointOfInterestGroupEnum, - category?: PointOfInterestCategoryNameEnum + category?: string ) { switch (group) { case PointOfInterestGroupEnum.PUBLIC_TRANSPORT: - return category === PointOfInterestCategoryNameEnum.AIRPORT - ? IconName.Airplane - : IconName.Train + return category === "Airport" ? IconName.Airplane : IconName.Train case PointOfInterestGroupEnum.ATTRACTIONS: - return category === PointOfInterestCategoryNameEnum.MUSEUM - ? IconName.Museum - : IconName.Camera + return category === "Museum" ? IconName.Museum : IconName.Camera case PointOfInterestGroupEnum.BUSINESS: return IconName.Business case PointOfInterestGroupEnum.PARKING: diff --git a/server/routers/hotels/output.ts b/server/routers/hotels/output.ts index d4f3d89c9..1af9ba44c 100644 --- a/server/routers/hotels/output.ts +++ b/server/routers/hotels/output.ts @@ -13,7 +13,6 @@ import { AlertTypeEnum } from "@/types/enums/alert" import { CurrencyEnum } from "@/types/enums/currency" import { FacilityEnum } from "@/types/enums/facilities" import { PackageTypeEnum } from "@/types/enums/packages" -import { PointOfInterestCategoryNameEnum } from "@/types/hotel" const ratingsSchema = z .object({ @@ -199,14 +198,12 @@ const rewardNightSchema = z.object({ }), }) -const poiCategoryNames = z.nativeEnum(PointOfInterestCategoryNameEnum) - export const pointOfInterestSchema = z .object({ name: z.string(), distance: z.number(), category: z.object({ - name: poiCategoryNames, + name: z.string(), group: z.string(), }), location: locationSchema, diff --git a/server/routers/hotels/utils.ts b/server/routers/hotels/utils.ts index 6d384b3cf..c3e785a59 100644 --- a/server/routers/hotels/utils.ts +++ b/server/routers/hotels/utils.ts @@ -12,39 +12,34 @@ import { type Countries, } from "./output" -import type { RequestOptionsWithOutBody } from "@/types/fetch" -import { - PointOfInterestCategoryNameEnum, - PointOfInterestGroupEnum, -} from "@/types/hotel" -import { HotelLocation } from "@/types/trpc/routers/hotel/locations" import type { Lang } from "@/constants/languages" import type { Endpoint } from "@/lib/api/endpoints" +import type { RequestOptionsWithOutBody } from "@/types/fetch" +import { PointOfInterestGroupEnum } from "@/types/hotel" +import { HotelLocation } from "@/types/trpc/routers/hotel/locations" -export function getPoiGroupByCategoryName( - category: PointOfInterestCategoryNameEnum -) { +export function getPoiGroupByCategoryName(category: string) { switch (category) { - case PointOfInterestCategoryNameEnum.AIRPORT: - case PointOfInterestCategoryNameEnum.BUS_TERMINAL: - case PointOfInterestCategoryNameEnum.TRANSPORTATIONS: + case "Airport": + case "Bus terminal": + case "Transportations": return PointOfInterestGroupEnum.PUBLIC_TRANSPORT - case PointOfInterestCategoryNameEnum.AMUSEMENT_PARK: - case PointOfInterestCategoryNameEnum.MUSEUM: - case PointOfInterestCategoryNameEnum.SPORTS: - case PointOfInterestCategoryNameEnum.THEATRE: - case PointOfInterestCategoryNameEnum.TOURIST: - case PointOfInterestCategoryNameEnum.ZOO: + case "Amusement park": + case "Museum": + case "Sports": + case "Theatre": + case "Tourist": + case "Zoo": return PointOfInterestGroupEnum.ATTRACTIONS - case PointOfInterestCategoryNameEnum.NEARBY_COMPANIES: - case PointOfInterestCategoryNameEnum.FAIR: + case "Nearby companies": + case "Fair": return PointOfInterestGroupEnum.BUSINESS - case PointOfInterestCategoryNameEnum.PARKING_GARAGE: + case "Parking / Garage": return PointOfInterestGroupEnum.PARKING - case PointOfInterestCategoryNameEnum.SHOPPING: - case PointOfInterestCategoryNameEnum.RESTAURANT: + case "Shopping": + case "Restaurant": return PointOfInterestGroupEnum.SHOPPING_DINING - case PointOfInterestCategoryNameEnum.HOSPITAL: + case "Hospital": default: return PointOfInterestGroupEnum.LOCATION } diff --git a/types/components/maps/poiMarker.ts b/types/components/maps/poiMarker.ts index 34fad0e6f..abab6fc9d 100644 --- a/types/components/maps/poiMarker.ts +++ b/types/components/maps/poiMarker.ts @@ -2,14 +2,11 @@ import { poiVariants } from "@/components/Maps/Markers/Poi/variants" import type { VariantProps } from "class-variance-authority" -import { - PointOfInterestCategoryNameEnum, - PointOfInterestGroupEnum, -} from "@/types/hotel" +import type { PointOfInterestGroupEnum } from "@/types/hotel" export interface PoiMarkerProps extends VariantProps { group: PointOfInterestGroupEnum - categoryName?: PointOfInterestCategoryNameEnum + categoryName?: string size?: number className?: string } diff --git a/types/hotel.ts b/types/hotel.ts index 5052caded..aadaab740 100644 --- a/types/hotel.ts +++ b/types/hotel.ts @@ -26,26 +26,6 @@ export type GalleryImage = z.infer export type PointOfInterest = z.output -export enum PointOfInterestCategoryNameEnum { - AIRPORT = "Airport", - AMUSEMENT_PARK = "Amusement park", - BUS_TERMINAL = "Bus terminal", - FAIR = "Fair", - HOSPITAL = "Hospital", - HOTEL = "Hotel", - MARKETING_CITY = "Marketing city", - MUSEUM = "Museum", - NEARBY_COMPANIES = "Nearby companies", - PARKING_GARAGE = "Parking / Garage", - RESTAURANT = "Restaurant", - SHOPPING = "Shopping", - SPORTS = "Sports", - THEATRE = "Theatre", - TOURIST = "Tourist", - TRANSPORTATIONS = "Transportations", - ZOO = "Zoo", -} - export enum PointOfInterestGroupEnum { PUBLIC_TRANSPORT = "Public transport", ATTRACTIONS = "Attractions", From 744af22b08b2e524e6fa8fa2c66db81eec8b85da Mon Sep 17 00:00:00 2001 From: Simon Emanuelsson Date: Tue, 19 Nov 2024 11:24:17 +0100 Subject: [PATCH 11/31] fix: make sure all searchparams are used in redirect --- .../(standard)/step/@summary/page.tsx | 6 +- .../hotelreservation/(standard)/step/page.tsx | 5 +- .../EnterDetails/Summary/index.tsx | 58 +++++++++---------- .../SelectRate/Rooms/utils.ts | 7 ++- providers/StepsProvider.tsx | 7 ++- server/routers/hotels/output.ts | 11 +++- server/routers/hotels/query.ts | 2 +- stores/steps.ts | 17 +++--- types/providers/steps.ts | 1 + 9 files changed, 65 insertions(+), 49 deletions(-) diff --git a/app/[lang]/(live)/(public)/hotelreservation/(standard)/step/@summary/page.tsx b/app/[lang]/(live)/(public)/hotelreservation/(standard)/step/@summary/page.tsx index 337d501b3..0444913f1 100644 --- a/app/[lang]/(live)/(public)/hotelreservation/(standard)/step/@summary/page.tsx +++ b/app/[lang]/(live)/(public)/hotelreservation/(standard)/step/@summary/page.tsx @@ -83,10 +83,10 @@ export default async function SummaryPage({ price: availability.publicRate.localPrice.pricePerStay, currency: availability.publicRate.localPrice.currency, }, - euro: availability.publicRate.requestedPrice + euro: availability.publicRate?.requestedPrice ? { - price: availability.publicRate.requestedPrice.pricePerStay, - currency: availability.publicRate.requestedPrice.currency, + price: availability.publicRate?.requestedPrice.pricePerStay, + currency: availability.publicRate?.requestedPrice.currency, } : undefined, } diff --git a/app/[lang]/(live)/(public)/hotelreservation/(standard)/step/page.tsx b/app/[lang]/(live)/(public)/hotelreservation/(standard)/step/page.tsx index 7ffe57562..d175fc25f 100644 --- a/app/[lang]/(live)/(public)/hotelreservation/(standard)/step/page.tsx +++ b/app/[lang]/(live)/(public)/hotelreservation/(standard)/step/page.tsx @@ -1,6 +1,6 @@ import "./enterDetailsLayout.css" -import { notFound, redirect, RedirectType } from "next/navigation" +import { notFound } from "next/navigation" import { getBreakfastPackages, @@ -38,6 +38,8 @@ export default async function StepPage({ }: PageArgs) { const intl = await getIntl() const selectRoomParams = new URLSearchParams(searchParams) + selectRoomParams.delete("step") + const searchParamsString = selectRoomParams.toString() const { hotel: hotelId, rooms, @@ -123,6 +125,7 @@ export default async function StepPage({ bedTypes={roomAvailability.bedTypes} breakfastPackages={breakfastPackages} isMember={!!user} + searchParams={searchParamsString} step={searchParams.step} >
diff --git a/components/HotelReservation/EnterDetails/Summary/index.tsx b/components/HotelReservation/EnterDetails/Summary/index.tsx index 7507a6945..5b0a6a420 100644 --- a/components/HotelReservation/EnterDetails/Summary/index.tsx +++ b/components/HotelReservation/EnterDetails/Summary/index.tsx @@ -81,7 +81,6 @@ export default function Summary({ showMemberPrice, room }: SummaryProps) { useEffect(() => { setChosenBed(bedType) - setChosenBreakfast(breakfast) if (breakfast || breakfast === false) { setChosenBreakfast(breakfast) @@ -94,9 +93,9 @@ export default function Summary({ showMemberPrice, room }: SummaryProps) { euro: room.euroPrice && roomsPriceEuro ? { - price: roomsPriceEuro, - currency: room.euroPrice.currency, - } + price: roomsPriceEuro, + currency: room.euroPrice.currency, + } : undefined, }) } else { @@ -108,11 +107,11 @@ export default function Summary({ showMemberPrice, room }: SummaryProps) { euro: room.euroPrice && roomsPriceEuro ? { - price: - roomsPriceEuro + - parseInt(breakfast.requestedPrice.totalPrice), - currency: room.euroPrice.currency, - } + price: + roomsPriceEuro + + parseInt(breakfast.requestedPrice.totalPrice), + currency: room.euroPrice.currency, + } : undefined, }) } @@ -199,24 +198,24 @@ export default function Summary({ showMemberPrice, room }: SummaryProps) {
{room.packages ? room.packages.map((roomPackage) => ( -
-
- - {roomPackage.description} - -
+
+
+ + {roomPackage.description} + +
- - {intl.formatMessage( - { id: "{amount} {currency}" }, - { - amount: roomPackage.localPrice.price, - currency: roomPackage.localPrice.currency, - } - )} - -
- )) + + {intl.formatMessage( + { id: "{amount} {currency}" }, + { + amount: roomPackage.localPrice.price, + currency: roomPackage.localPrice.currency, + } + )} + +
+ )) : null} {chosenBed ? (
@@ -263,9 +262,8 @@ export default function Summary({ showMemberPrice, room }: SummaryProps) { )}
- ) : null - } -
+ ) : null} +
@@ -306,6 +304,6 @@ export default function Summary({ showMemberPrice, room }: SummaryProps) {
- + ) } diff --git a/components/HotelReservation/SelectRate/Rooms/utils.ts b/components/HotelReservation/SelectRate/Rooms/utils.ts index af80e1f4d..71b2149cc 100644 --- a/components/HotelReservation/SelectRate/Rooms/utils.ts +++ b/components/HotelReservation/SelectRate/Rooms/utils.ts @@ -31,7 +31,10 @@ export function filterDuplicateRoomTypesByLowestPrice( products.forEach((product) => { const { productType } = product - const publicProduct = productType.public + const publicProduct = productType.public || { + requestedPrice: null, + localPrice: null, + } const memberProduct = productType.member || { requestedPrice: null, localPrice: null, @@ -53,7 +56,7 @@ export function filterDuplicateRoomTypesByLowestPrice( Number(memberRequestedPrice?.pricePerNight) ?? Infinity ) const currentLocalPrice = Math.min( - Number(publicLocalPrice.pricePerNight) ?? Infinity, + Number(publicLocalPrice?.pricePerNight) ?? Infinity, Number(memberLocalPrice?.pricePerNight) ?? Infinity ) diff --git a/providers/StepsProvider.tsx b/providers/StepsProvider.tsx index 87594be02..9aaf6166f 100644 --- a/providers/StepsProvider.tsx +++ b/providers/StepsProvider.tsx @@ -1,4 +1,5 @@ "use client" +import { useRouter } from "next/navigation" import { useRef } from "react" import { useDetailsStore } from "@/stores/details" @@ -14,6 +15,7 @@ export default function StepsProvider({ breakfastPackages, children, isMember, + searchParams, step, }: StepsProviderProps) { const storeRef = useRef() @@ -21,6 +23,7 @@ export default function StepsProvider({ const updateBreakfast = useDetailsStore( (state) => state.actions.updateBreakfast ) + const router = useRouter() if (!storeRef.current) { const noBedChoices = bedTypes.length === 1 @@ -41,7 +44,9 @@ export default function StepsProvider({ step, isMember, noBedChoices, - noBreakfast + noBreakfast, + searchParams, + router.push ) } diff --git a/server/routers/hotels/output.ts b/server/routers/hotels/output.ts index 1af9ba44c..41f9a6b52 100644 --- a/server/routers/hotels/output.ts +++ b/server/routers/hotels/output.ts @@ -512,7 +512,16 @@ export const productTypePriceSchema = z.object({ const productSchema = z.object({ productType: z.object({ - public: productTypePriceSchema, + public: productTypePriceSchema.default({ + rateCode: "", + rateType: "", + localPrice: { + currency: "SEK", + pricePerNight: 0, + pricePerStay: 0, + }, + requestedPrice: undefined, + }), member: productTypePriceSchema.optional(), }), }) diff --git a/server/routers/hotels/query.ts b/server/routers/hotels/query.ts index 7f2ef84c0..34a1b3722 100644 --- a/server/routers/hotels/query.ts +++ b/server/routers/hotels/query.ts @@ -731,7 +731,7 @@ export const hotelQueryRouter = router({ const rateTypes = selectedRoom.products.find( (rate) => - rate.productType.public.rateCode === rateCode || + rate.productType.public?.rateCode === rateCode || rate.productType.member?.rateCode === rateCode ) diff --git a/stores/steps.ts b/stores/steps.ts index f1e456af2..cf14f6768 100644 --- a/stores/steps.ts +++ b/stores/steps.ts @@ -1,6 +1,7 @@ "use client" import merge from "deepmerge" import { produce } from "immer" +import { AppRouterInstance } from "next/dist/shared/lib/app-router-context.shared-runtime" import { useContext } from "react" import { create, useStore } from "zustand" @@ -18,17 +19,13 @@ import { StepEnum } from "@/types/enums/step" import type { DetailsState } from "@/types/stores/details" import type { StepState } from "@/types/stores/steps" -function push(data: Record, url: string) { - if (typeof window !== "undefined") { - window.history.pushState(data, "", url + window.location.search) - } -} - export function createStepsStore( currentStep: StepEnum, isMember: boolean, noBedChoices: boolean, - noBreakfast: boolean + noBreakfast: boolean, + searchParams: string, + push: AppRouterInstance["push"] ) { const isBrowser = typeof window !== "undefined" const steps = [ @@ -51,14 +48,14 @@ export function createStepsStore( steps.splice(1, 1) if (currentStep === StepEnum.breakfast) { currentStep = steps[1] - push({ step: currentStep }, currentStep) + push(`${currentStep}?${searchParams}`) } } if (noBedChoices) { if (currentStep === StepEnum.selectBed) { currentStep = steps[1] - push({ step: currentStep }, currentStep) + push(`${currentStep}?${searchParams}`) } } @@ -94,7 +91,7 @@ export function createStepsStore( if (!validPaths.includes(currentStep) && isBrowser) { // We will always have at least one valid path currentStep = validPaths.pop()! - push({ step: currentStep }, currentStep) + push(`${currentStep}?${searchParams}`) } } diff --git a/types/providers/steps.ts b/types/providers/steps.ts index 8c24fdc8f..9ba0361eb 100644 --- a/types/providers/steps.ts +++ b/types/providers/steps.ts @@ -6,5 +6,6 @@ export interface StepsProviderProps extends React.PropsWithChildren { bedTypes: BedTypeSelection[] breakfastPackages: BreakfastPackage[] | null isMember: boolean + searchParams: string step: StepEnum } From 11cff0ab11616762cbd0d78f2346cca3f2948c73 Mon Sep 17 00:00:00 2001 From: Pontus Dreij Date: Tue, 19 Nov 2024 13:03:42 +0100 Subject: [PATCH 12/31] feat(SW-913): Hide bookingwidget from cms --- components/BookingWidget/index.tsx | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/components/BookingWidget/index.tsx b/components/BookingWidget/index.tsx index c7674b9d7..ecff629c6 100644 --- a/components/BookingWidget/index.tsx +++ b/components/BookingWidget/index.tsx @@ -1,4 +1,4 @@ -import { getLocations } from "@/lib/trpc/memoizedRequests" +import { getLocations, getSiteConfig } from "@/lib/trpc/memoizedRequests" import BookingWidgetClient from "./Client" @@ -13,8 +13,9 @@ export default async function BookingWidget({ searchParams, }: BookingWidgetProps) { const locations = await getLocations() + const siteConfig = await getSiteConfig() - if (!locations || "error" in locations) { + if (!locations || "error" in locations || siteConfig?.bookingWidgetDisabled) { return null } From b7ffc8588d70e4748b76dec0d3bec3a3e2b447b2 Mon Sep 17 00:00:00 2001 From: Bianca Widstam Date: Tue, 19 Nov 2024 12:23:06 +0000 Subject: [PATCH 13/31] Merged in fix/small-UI-change-select-hotel (pull request #931) fix: small ui fix hotel card * fix: small ui fix hotel card Approved-by: Pontus Dreij --- .../HotelCard/HotelPriceList/hotelPriceList.module.css | 7 ++++++- components/HotelReservation/HotelCardListing/index.tsx | 2 -- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/components/HotelReservation/HotelCard/HotelPriceList/hotelPriceList.module.css b/components/HotelReservation/HotelCard/HotelPriceList/hotelPriceList.module.css index bd81f1170..fb67d45d9 100644 --- a/components/HotelReservation/HotelCard/HotelPriceList/hotelPriceList.module.css +++ b/components/HotelReservation/HotelCard/HotelPriceList/hotelPriceList.module.css @@ -15,7 +15,6 @@ display: flex; flex-direction: column; gap: var(--Spacing-x-one-and-half); - max-width: 260px; } .divider { @@ -38,3 +37,9 @@ font-weight: 400; font-size: var(--typography-Caption-Regular-fontSize); } + +@media screen and (min-width: 1367px) { + .prices { + max-width: 260px; + } +} diff --git a/components/HotelReservation/HotelCardListing/index.tsx b/components/HotelReservation/HotelCardListing/index.tsx index 191c7acde..6ebf3006e 100644 --- a/components/HotelReservation/HotelCardListing/index.tsx +++ b/components/HotelReservation/HotelCardListing/index.tsx @@ -1,7 +1,6 @@ "use client" import { useSearchParams } from "next/navigation" import { useEffect, useMemo, useState } from "react" -import { useIntl } from "react-intl" import { useHotelFilterStore } from "@/stores/hotel-filters" @@ -25,7 +24,6 @@ export default function HotelCardListing({ activeCard, onHotelCardHover, }: HotelCardListingProps) { - const intl = useIntl() const searchParams = useSearchParams() const activeFilters = useHotelFilterStore((state) => state.activeFilters) const setResultCount = useHotelFilterStore((state) => state.setResultCount) From 602e7ea2ce9b5beeadaa3126c67e528de5cba630 Mon Sep 17 00:00:00 2001 From: Christel Westerberg Date: Tue, 19 Nov 2024 13:25:47 +0100 Subject: [PATCH 14/31] fix: handle undefined pricing --- components/HotelReservation/SelectRate/Rooms/utils.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/components/HotelReservation/SelectRate/Rooms/utils.ts b/components/HotelReservation/SelectRate/Rooms/utils.ts index 71b2149cc..96d8a8488 100644 --- a/components/HotelReservation/SelectRate/Rooms/utils.ts +++ b/components/HotelReservation/SelectRate/Rooms/utils.ts @@ -66,7 +66,7 @@ export function filterDuplicateRoomTypesByLowestPrice( Math.min( Number( previousLowest.products[0].productType.public.requestedPrice - .pricePerNight + ?.pricePerNight ) ?? Infinity, Number( previousLowest.products[0].productType.member?.requestedPrice @@ -77,7 +77,7 @@ export function filterDuplicateRoomTypesByLowestPrice( Math.min( Number( previousLowest.products[0].productType.public.requestedPrice - .pricePerNight + ?.pricePerNight ) ?? Infinity, Number( previousLowest.products[0].productType.member?.requestedPrice From c2ef7f70746a8957045efb654a980ab267e6f47d Mon Sep 17 00:00:00 2001 From: Christel Westerberg Date: Tue, 19 Nov 2024 13:41:08 +0100 Subject: [PATCH 15/31] fix: padding issues --- .../EnterDetails/Details/details.module.css | 1 - .../SectionAccordion/sectionAccordion.module.css | 7 +++++-- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/components/HotelReservation/EnterDetails/Details/details.module.css b/components/HotelReservation/EnterDetails/Details/details.module.css index c6571ea9e..8781a9be2 100644 --- a/components/HotelReservation/EnterDetails/Details/details.module.css +++ b/components/HotelReservation/EnterDetails/Details/details.module.css @@ -1,7 +1,6 @@ .form { display: grid; gap: var(--Spacing-x3); - margin-bottom: var(--Spacing-x3); } .container { diff --git a/components/HotelReservation/EnterDetails/SectionAccordion/sectionAccordion.module.css b/components/HotelReservation/EnterDetails/SectionAccordion/sectionAccordion.module.css index 0bbcf851c..48d387add 100644 --- a/components/HotelReservation/EnterDetails/SectionAccordion/sectionAccordion.module.css +++ b/components/HotelReservation/EnterDetails/SectionAccordion/sectionAccordion.module.css @@ -79,7 +79,10 @@ .accordion[data-open="true"] { grid-template-rows: var(--header-height) 1fr; - gap: var(--Spacing-x3); +} + +.accordion[data-open="true"] .content { + padding-bottom: var(--Spacing-x3); } .content { @@ -90,7 +93,7 @@ @media screen and (min-width: 768px) { .accordion { - gap: var(--Spacing-x3); + column-gap: var(--Spacing-x3); grid-template-areas: "circle header" "circle content"; } From d43c29e394489329d80e43af35e17a8c52029090 Mon Sep 17 00:00:00 2001 From: Christel Westerberg Date: Tue, 19 Nov 2024 14:06:09 +0100 Subject: [PATCH 16/31] fix: disable button on not isComplete --- .../EnterDetails/SectionAccordion/index.tsx | 10 ++++++++-- .../SectionAccordion/sectionAccordion.module.css | 6 +++++- components/HotelReservation/SelectRate/Rooms/utils.ts | 2 +- 3 files changed, 14 insertions(+), 4 deletions(-) diff --git a/components/HotelReservation/EnterDetails/SectionAccordion/index.tsx b/components/HotelReservation/EnterDetails/SectionAccordion/index.tsx index b96ef8ef8..488d941b5 100644 --- a/components/HotelReservation/EnterDetails/SectionAccordion/index.tsx +++ b/components/HotelReservation/EnterDetails/SectionAccordion/index.tsx @@ -75,7 +75,11 @@ export default function SectionAccordion({
-
-
{children}
+
+
{children}
+
) } diff --git a/components/HotelReservation/EnterDetails/SectionAccordion/sectionAccordion.module.css b/components/HotelReservation/EnterDetails/SectionAccordion/sectionAccordion.module.css index 48d387add..125905317 100644 --- a/components/HotelReservation/EnterDetails/SectionAccordion/sectionAccordion.module.css +++ b/components/HotelReservation/EnterDetails/SectionAccordion/sectionAccordion.module.css @@ -33,6 +33,10 @@ padding: 0; } +.modifyButton:disabled { + cursor: default; +} + .title { grid-area: title; text-align: start; @@ -81,7 +85,7 @@ grid-template-rows: var(--header-height) 1fr; } -.accordion[data-open="true"] .content { +.contentWrapper { padding-bottom: var(--Spacing-x3); } diff --git a/components/HotelReservation/SelectRate/Rooms/utils.ts b/components/HotelReservation/SelectRate/Rooms/utils.ts index 96d8a8488..fbd353aa1 100644 --- a/components/HotelReservation/SelectRate/Rooms/utils.ts +++ b/components/HotelReservation/SelectRate/Rooms/utils.ts @@ -88,7 +88,7 @@ export function filterDuplicateRoomTypesByLowestPrice( Math.min( Number( previousLowest.products[0].productType.public.localPrice - .pricePerNight + ?.pricePerNight ) ?? Infinity, Number( previousLowest.products[0].productType.member?.localPrice From ef2860dd8ece3e4a4f0d66b400e50de697b75266 Mon Sep 17 00:00:00 2001 From: Pontus Dreij Date: Tue, 19 Nov 2024 15:11:13 +0100 Subject: [PATCH 17/31] fix: hide occupancy and roomSize if undefined --- .../RoomSelection/RoomCard/index.tsx | 97 ++++++++++--------- .../RoomCard/roomCard.module.css | 1 + 2 files changed, 52 insertions(+), 46 deletions(-) diff --git a/components/HotelReservation/SelectRate/RoomSelection/RoomCard/index.tsx b/components/HotelReservation/SelectRate/RoomSelection/RoomCard/index.tsx index d69391122..6c477d39f 100644 --- a/components/HotelReservation/SelectRate/RoomSelection/RoomCard/index.tsx +++ b/components/HotelReservation/SelectRate/RoomSelection/RoomCard/index.tsx @@ -112,56 +112,61 @@ export default function RoomCard({ : "default", }) + console.log(occupancy) + return (
- {mainImage && ( -
-
- {roomConfiguration.roomsLeft < 5 && ( - - {`${roomConfiguration.roomsLeft} ${intl.formatMessage({ id: "Left" })}`} - - )} - {roomConfiguration.features - .filter((feature) => selectedPackages.includes(feature.code)) - .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. */} - -
- )} -
- - {intl.formatMessage( - { - id: "booking.guests", - }, - { nrOfGuests: occupancy?.total } +
+
+ {roomConfiguration.roomsLeft < 5 && ( + + {`${roomConfiguration.roomsLeft} ${intl.formatMessage({ id: "Left" })}`} + )} - - - {roomSize?.min === roomSize?.max - ? roomSize?.min - : `${roomSize?.min}-${roomSize?.max}`} - m² - + {roomConfiguration.features + .filter((feature) => selectedPackages.includes(feature.code)) + .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. */} + +
+ +
+ {occupancy && ( + + {intl.formatMessage( + { + id: "booking.guests", + }, + { nrOfGuests: occupancy?.total } + )} + + )} + {roomSize && ( + + {roomSize.min === roomSize.max + ? roomSize.min + : `${roomSize.min}-${roomSize.max}`} + m² + + )}
{roomConfiguration.roomTypeCode && ( Date: Tue, 19 Nov 2024 15:11:55 +0100 Subject: [PATCH 18/31] fix: remove log --- .../SelectRate/RoomSelection/RoomCard/index.tsx | 2 -- 1 file changed, 2 deletions(-) diff --git a/components/HotelReservation/SelectRate/RoomSelection/RoomCard/index.tsx b/components/HotelReservation/SelectRate/RoomSelection/RoomCard/index.tsx index 6c477d39f..2a374b17c 100644 --- a/components/HotelReservation/SelectRate/RoomSelection/RoomCard/index.tsx +++ b/components/HotelReservation/SelectRate/RoomSelection/RoomCard/index.tsx @@ -112,8 +112,6 @@ export default function RoomCard({ : "default", }) - console.log(occupancy) - return (
From 14b1610d4c6108480b148879d32a86d69f7af01a Mon Sep 17 00:00:00 2001 From: Pontus Dreij Date: Tue, 19 Nov 2024 15:17:39 +0100 Subject: [PATCH 19/31] fix: removed unused code --- .../SelectRate/RoomSelection/RoomCard/index.tsx | 2 -- 1 file changed, 2 deletions(-) diff --git a/components/HotelReservation/SelectRate/RoomSelection/RoomCard/index.tsx b/components/HotelReservation/SelectRate/RoomSelection/RoomCard/index.tsx index 2a374b17c..d8a37179b 100644 --- a/components/HotelReservation/SelectRate/RoomSelection/RoomCard/index.tsx +++ b/components/HotelReservation/SelectRate/RoomSelection/RoomCard/index.tsx @@ -60,7 +60,6 @@ export default function RoomCard({ const getBreakfastMessage = (rate: RateDefinition | undefined) => { const breakfastIncluded = getRateDefinitionForRate(rate)?.breakfastIncluded - switch (breakfastIncluded) { case true: return intl.formatMessage({ id: "Breakfast is included." }) @@ -83,7 +82,6 @@ export default function RoomCard({ ) const { roomSize, occupancy, images } = selectedRoom || {} - const mainImage = images?.[0] const freeCancelation = intl.formatMessage({ id: "Free cancellation" }) const nonRefundable = intl.formatMessage({ id: "Non-refundable" }) From ea3aff5dcdc7254cc12a749cca8c5f27a779cc4f Mon Sep 17 00:00:00 2001 From: Bianca Widstam Date: Tue, 19 Nov 2024 14:34:45 +0000 Subject: [PATCH 20/31] Merged in fix/translation-hotelpage-header (pull request #934) Fix/SW-932-translation hotelpage header * fix: add translation for hotel page header * fix: add hotel translation * fix: add translation where to when loading * fix: update hotel(s) count if filtered * fix(SW-932): update hotel(s) count Approved-by: Pontus Dreij Approved-by: Niclas Edenvin --- .../(standard)/select-hotel/page.tsx | 4 ++-- .../FormContent/Search/index.tsx | 3 ++- .../SelectHotel/HotelCount/index.tsx | 22 +++++++++++++++++++ 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 + 9 files changed, 32 insertions(+), 3 deletions(-) create mode 100644 components/HotelReservation/SelectHotel/HotelCount/index.tsx diff --git a/app/[lang]/(live)/(public)/hotelreservation/(standard)/select-hotel/page.tsx b/app/[lang]/(live)/(public)/hotelreservation/(standard)/select-hotel/page.tsx index a12639acf..70696a689 100644 --- a/app/[lang]/(live)/(public)/hotelreservation/(standard)/select-hotel/page.tsx +++ b/app/[lang]/(live)/(public)/hotelreservation/(standard)/select-hotel/page.tsx @@ -8,6 +8,7 @@ import { getFiltersFromHotels, } from "@/app/[lang]/(live)/(public)/hotelreservation/(standard)/select-hotel/utils" import HotelCardListing from "@/components/HotelReservation/HotelCardListing" +import HotelCount from "@/components/HotelReservation/SelectHotel/HotelCount" import HotelFilter from "@/components/HotelReservation/SelectHotel/HotelFilter" import HotelSorter from "@/components/HotelReservation/SelectHotel/HotelSorter" import MobileMapButtonContainer from "@/components/HotelReservation/SelectHotel/MobileMapButtonContainer" @@ -20,7 +21,6 @@ import StaticMap from "@/components/Maps/StaticMap" import Alert from "@/components/TempDesignSystem/Alert" import Button from "@/components/TempDesignSystem/Button" import Link from "@/components/TempDesignSystem/Link" -import Preamble from "@/components/TempDesignSystem/Text/Preamble" import Subtitle from "@/components/TempDesignSystem/Text/Subtitle" import { getIntl } from "@/i18n" import { setLang } from "@/i18n/serverContext" @@ -72,7 +72,7 @@ export default async function SelectHotelPage({
{city.name} - {hotels.length} hotels +
diff --git a/components/Forms/BookingWidget/FormContent/Search/index.tsx b/components/Forms/BookingWidget/FormContent/Search/index.tsx index abbb28112..0d1ee36d4 100644 --- a/components/Forms/BookingWidget/FormContent/Search/index.tsx +++ b/components/Forms/BookingWidget/FormContent/Search/index.tsx @@ -206,11 +206,12 @@ export default function Search({ locations }: SearchProps) { } export function SearchSkeleton() { + const intl = useIntl() return (
- Where to + {intl.formatMessage({ id: "Where to" })}
diff --git a/components/HotelReservation/SelectHotel/HotelCount/index.tsx b/components/HotelReservation/SelectHotel/HotelCount/index.tsx new file mode 100644 index 000000000..1556dcf30 --- /dev/null +++ b/components/HotelReservation/SelectHotel/HotelCount/index.tsx @@ -0,0 +1,22 @@ +"use client" +import { useIntl } from "react-intl" + +import { useHotelFilterStore } from "@/stores/hotel-filters" + +import Preamble from "@/components/TempDesignSystem/Text/Preamble" + +export default function HotelCount() { + const intl = useIntl() + const resultCount = useHotelFilterStore((state) => state.resultCount) + + return ( + + {intl.formatMessage( + { + id: "Hotel(s)", + }, + { amount: resultCount } + )} + + ) +} diff --git a/i18n/dictionaries/da.json b/i18n/dictionaries/da.json index 7ec920d05..0ad5dce97 100644 --- a/i18n/dictionaries/da.json +++ b/i18n/dictionaries/da.json @@ -154,6 +154,7 @@ "Hotel": "Hotel", "Hotel facilities": "Hotel faciliteter", "Hotel surroundings": "Hotel omgivelser", + "Hotel(s)": "{amount} {amount, plural, one {hotel} other {hoteller}}", "Hotels": "Hoteller", "How do you want to sleep?": "Hvordan vil du sove?", "How it works": "Hvordan det virker", diff --git a/i18n/dictionaries/de.json b/i18n/dictionaries/de.json index 80b3b6116..bd094f4d5 100644 --- a/i18n/dictionaries/de.json +++ b/i18n/dictionaries/de.json @@ -154,6 +154,7 @@ "Hotel": "Hotel", "Hotel facilities": "Hotel-Infos", "Hotel surroundings": "Umgebung des Hotels", + "Hotel(s)": "{amount} {amount, plural, one {hotel} other {hotels}}", "Hotels": "Hotels", "How do you want to sleep?": "Wie möchtest du schlafen?", "How it works": "Wie es funktioniert", diff --git a/i18n/dictionaries/en.json b/i18n/dictionaries/en.json index 6080fc36b..afea03c06 100644 --- a/i18n/dictionaries/en.json +++ b/i18n/dictionaries/en.json @@ -166,6 +166,7 @@ "Hotel": "Hotel", "Hotel facilities": "Hotel facilities", "Hotel surroundings": "Hotel surroundings", + "Hotel(s)": "{amount} {amount, plural, one {hotel} other {hotels}}", "Hotels": "Hotels", "How do you want to sleep?": "How do you want to sleep?", "How it works": "How it works", diff --git a/i18n/dictionaries/fi.json b/i18n/dictionaries/fi.json index 0c1a08d35..57f137e94 100644 --- a/i18n/dictionaries/fi.json +++ b/i18n/dictionaries/fi.json @@ -154,6 +154,7 @@ "Hotel": "Hotelli", "Hotel facilities": "Hotellin palvelut", "Hotel surroundings": "Hotellin ympäristö", + "Hotel(s)": "{amount} {amount, plural, one {hotelli} other {hotellit}}", "Hotels": "Hotellit", "How do you want to sleep?": "Kuinka haluat nukkua?", "How it works": "Kuinka se toimii", diff --git a/i18n/dictionaries/no.json b/i18n/dictionaries/no.json index 2aca61a85..1ee68eb82 100644 --- a/i18n/dictionaries/no.json +++ b/i18n/dictionaries/no.json @@ -153,6 +153,7 @@ "Hotel": "Hotel", "Hotel facilities": "Hotelfaciliteter", "Hotel surroundings": "Hotellomgivelser", + "Hotel(s)": "{amount} {amount, plural, one {hotell} other {hoteller}}", "Hotels": "Hoteller", "How do you want to sleep?": "Hvordan vil du sove?", "How it works": "Hvordan det fungerer", diff --git a/i18n/dictionaries/sv.json b/i18n/dictionaries/sv.json index db300b7c2..3177903d2 100644 --- a/i18n/dictionaries/sv.json +++ b/i18n/dictionaries/sv.json @@ -153,6 +153,7 @@ "Hotel": "Hotell", "Hotel facilities": "Hotellfaciliteter", "Hotel surroundings": "Hotellomgivning", + "Hotel(s)": "{amount} hotell", "Hotels": "Hotell", "How do you want to sleep?": "Hur vill du sova?", "How it works": "Hur det fungerar", From ab9c8012c91670a2fa78ed6d2c82d19bdc06cdf2 Mon Sep 17 00:00:00 2001 From: Pontus Dreij Date: Tue, 19 Nov 2024 15:36:20 +0100 Subject: [PATCH 21/31] fix(SW-934): Change logic to isAllUnavailable instad of hotels.length --- .../hotelreservation/(standard)/select-hotel/page.tsx | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/app/[lang]/(live)/(public)/hotelreservation/(standard)/select-hotel/page.tsx b/app/[lang]/(live)/(public)/hotelreservation/(standard)/select-hotel/page.tsx index a12639acf..c76e6b7b6 100644 --- a/app/[lang]/(live)/(public)/hotelreservation/(standard)/select-hotel/page.tsx +++ b/app/[lang]/(live)/(public)/hotelreservation/(standard)/select-hotel/page.tsx @@ -66,6 +66,8 @@ export default async function SelectHotelPage({ const filterList = getFiltersFromHotels(hotels) + const isAllUnavailable = hotels.every((hotel) => hotel.price === undefined) + return ( <>
@@ -123,7 +125,7 @@ export default async function SelectHotelPage({
- {!hotels.length && ( + {isAllUnavailable && ( Date: Tue, 19 Nov 2024 14:39:25 +0000 Subject: [PATCH 22/31] Merged in feat/SW-863-remove-filters (pull request #935) feat(SW-863): remove filters without a type * feat(SW-863): remove filters without a type Approved-by: Pontus Dreij --- .../(standard)/select-hotel/utils.ts | 21 +++++++++++++++---- 1 file changed, 17 insertions(+), 4 deletions(-) diff --git a/app/[lang]/(live)/(public)/hotelreservation/(standard)/select-hotel/utils.ts b/app/[lang]/(live)/(public)/hotelreservation/(standard)/select-hotel/utils.ts index 58b18a25d..bd41510ce 100644 --- a/app/[lang]/(live)/(public)/hotelreservation/(standard)/select-hotel/utils.ts +++ b/app/[lang]/(live)/(public)/hotelreservation/(standard)/select-hotel/utils.ts @@ -19,6 +19,15 @@ const hotelSurroundingsFilterNames = [ "Omgivningar", ] +const hotelFacilitiesFilterNames = [ + "Hotel facilities", + "Hotellfaciliteter", + "Hotelfaciliteter", + "Hotel faciliteter", + "Hotel-Infos", + "Hotellin palvelut", +] + export async function fetchAvailableHotels( input: AvailabilityInput ): Promise { @@ -52,6 +61,7 @@ export function getFiltersFromHotels(hotels: HotelData[]): CategorizedFilters { const filterList: Filter[] = uniqueFilterIds .map((filterId) => filters.find((filter) => filter.id === filterId)) .filter((filter): filter is Filter => filter !== undefined) + .sort((a, b) => b.sortOrder - a.sortOrder) return filterList.reduce( (acc, filter) => { @@ -61,10 +71,13 @@ export function getFiltersFromHotels(hotels: HotelData[]): CategorizedFilters { surroundingsFilters: [...acc.surroundingsFilters, filter], } - return { - facilityFilters: [...acc.facilityFilters, filter], - surroundingsFilters: acc.surroundingsFilters, - } + if (filter.filter && hotelFacilitiesFilterNames.includes(filter.filter)) + return { + facilityFilters: [...acc.facilityFilters, filter], + surroundingsFilters: acc.surroundingsFilters, + } + + return acc }, { facilityFilters: [], surroundingsFilters: [] } ) From 6958db3ca576b4cc3360c4fdc8c938643cac2ee1 Mon Sep 17 00:00:00 2001 From: Chuma McPhoy Date: Fri, 15 Nov 2024 08:30:52 +0100 Subject: [PATCH 23/31] refactor(SW-898): replace signup server action with TRPC --- actions/registerUser.ts | 89 ------------------------------- components/Forms/Signup/index.tsx | 37 ++++++------- server/routers/user/input.ts | 8 +++ server/routers/user/mutation.ts | 72 ++++++++++++++++++++++++- server/routers/user/output.ts | 19 +++++++ server/trpc.ts | 2 +- 6 files changed, 116 insertions(+), 111 deletions(-) delete mode 100644 actions/registerUser.ts diff --git a/actions/registerUser.ts b/actions/registerUser.ts deleted file mode 100644 index 8def69ec3..000000000 --- a/actions/registerUser.ts +++ /dev/null @@ -1,89 +0,0 @@ -"use server" - -import { parsePhoneNumber } from "libphonenumber-js" -import { redirect } from "next/navigation" -import { z } from "zod" - -import { signupVerify } from "@/constants/routes/signup" -import * as api from "@/lib/api" -import { serviceServerActionProcedure } from "@/server/trpc" - -import { signUpSchema } from "@/components/Forms/Signup/schema" -import { passwordValidator } from "@/utils/passwordValidator" -import { phoneValidator } from "@/utils/phoneValidator" - -const registerUserPayload = z.object({ - language: z.string(), - firstName: z.string(), - lastName: z.string(), - email: z.string(), - phoneNumber: phoneValidator("Phone is required"), - dateOfBirth: z.string(), - address: z.object({ - city: z.string().default(""), - country: z.string().default(""), - countryCode: z.string().default(""), - zipCode: z.string().default(""), - streetAddress: z.string().default(""), - }), - password: passwordValidator("Password is required"), -}) - -export const registerUser = serviceServerActionProcedure - .input(signUpSchema) - .mutation(async function ({ ctx, input }) { - const payload = { - ...input, - language: ctx.lang, - phoneNumber: input.phoneNumber.replace(/\s+/g, ""), - } - - const parsedPayload = registerUserPayload.safeParse(payload) - if (!parsedPayload.success) { - console.error( - "registerUser payload validation error", - JSON.stringify({ - query: input, - error: parsedPayload.error, - }) - ) - - return { success: false, error: "Validation error" } - } - - let apiResponse - try { - apiResponse = await api.post(api.endpoints.v1.Profile.profile, { - body: parsedPayload.data, - headers: { - Authorization: `Bearer ${ctx.serviceToken}`, - }, - }) - } catch (error) { - console.error("Unexpected error", error) - return { success: false, error: "Unexpected error" } - } - - if (!apiResponse.ok) { - const text = await apiResponse.text() - console.error( - "registerUser api error", - JSON.stringify({ - query: input, - error: { - status: apiResponse.status, - statusText: apiResponse.statusText, - error: text, - }, - }) - ) - return { success: false, error: "API error" } - } - - const json = await apiResponse.json() - console.log("registerUser: json", json) - - // Note: The redirect needs to be called after the try/catch block. - // See: https://nextjs.org/docs/app/api-reference/functions/redirect - redirect(signupVerify[ctx.lang]) - }) diff --git a/components/Forms/Signup/index.tsx b/components/Forms/Signup/index.tsx index d6d5fc7da..d1e2727e0 100644 --- a/components/Forms/Signup/index.tsx +++ b/components/Forms/Signup/index.tsx @@ -1,12 +1,13 @@ "use client" import { zodResolver } from "@hookform/resolvers/zod" +import { useRouter } from "next/navigation" import { FormProvider, useForm } from "react-hook-form" import { useIntl } from "react-intl" import { privacyPolicy } from "@/constants/currentWebHrefs" +import { trpc } from "@/lib/trpc/client" -import { registerUser } from "@/actions/registerUser" import Button from "@/components/TempDesignSystem/Button" import Checkbox from "@/components/TempDesignSystem/Form/Checkbox" import CountrySelect from "@/components/TempDesignSystem/Form/Country" @@ -30,12 +31,25 @@ import type { SignUpFormProps } from "@/types/components/form/signupForm" export default function SignupForm({ link, subtitle, title }: SignUpFormProps) { const intl = useIntl() + const router = useRouter() const lang = useLang() const country = intl.formatMessage({ id: "Country" }) const email = intl.formatMessage({ id: "Email address" }) const phoneNumber = intl.formatMessage({ id: "Phone number" }) const zipCode = intl.formatMessage({ id: "Zip code" }) + const signup = trpc.user.signup.useMutation({ + onSuccess: (data) => { + if (data.success && data.redirectUrl) { + router.push(data.redirectUrl) + } + }, + onError: (error) => { + toast.error(intl.formatMessage({ id: "Something went wrong!" })) + console.error("Component Signup error:", error) + }, + }) + const methods = useForm({ defaultValues: { firstName: "", @@ -56,19 +70,7 @@ export default function SignupForm({ link, subtitle, title }: SignUpFormProps) { }) async function onSubmit(data: SignUpSchema) { - try { - const result = await registerUser(data) - if (result && !result.success) { - toast.error(intl.formatMessage({ id: "Something went wrong!" })) - } - } catch (error) { - // The server-side redirect will throw an error, which we can ignore - // as it's handled by Next.js. - if (error instanceof Error && error.message.includes("NEXT_REDIRECT")) { - return - } - toast.error(intl.formatMessage({ id: "Something went wrong!" })) - } + signup.mutate({ ...data, language: lang }) } return ( @@ -79,11 +81,6 @@ export default function SignupForm({ link, subtitle, title }: SignUpFormProps) { className={styles.form} id="register" onSubmit={methods.handleSubmit(onSubmit)} - /** - * Ignoring since ts doesn't recognize that tRPC - * parses FormData before reaching the route - * @ts-ignore */ - action={registerUser} >
@@ -194,7 +191,7 @@ export default function SignupForm({ link, subtitle, title }: SignUpFormProps) { type="submit" theme="base" intent="primary" - disabled={methods.formState.isSubmitting} + disabled={methods.formState.isSubmitting || signup.isPending} data-testid="submit" > {intl.formatMessage({ id: "Sign up to Scandic Friends" })} diff --git a/server/routers/user/input.ts b/server/routers/user/input.ts index d84875ea3..1d279c2ef 100644 --- a/server/routers/user/input.ts +++ b/server/routers/user/input.ts @@ -1,5 +1,9 @@ import { z } from "zod" +import { Lang } from "@/constants/languages" + +import { signUpSchema } from "@/components/Forms/Signup/schema" + // Query export const staysInput = z .object({ @@ -35,3 +39,7 @@ export const saveCreditCardInput = z.object({ transactionId: z.string(), merchantId: z.string().optional(), }) + +export const signupInput = signUpSchema.extend({ + language: z.nativeEnum(Lang), +}) diff --git a/server/routers/user/mutation.ts b/server/routers/user/mutation.ts index b03e6a68e..e233814ee 100644 --- a/server/routers/user/mutation.ts +++ b/server/routers/user/mutation.ts @@ -1,17 +1,21 @@ import { metrics } from "@opentelemetry/api" +import { signupVerify } from "@/constants/routes/signup" import { env } from "@/env/server" import * as api from "@/lib/api" +import { badRequestError, serverErrorByStatus } from "@/server/errors/trpc" import { initiateSaveCardSchema, + signupPayloadSchema, subscriberIdSchema, } from "@/server/routers/user/output" -import { protectedProcedure, router } from "@/server/trpc" +import { protectedProcedure, router,serviceProcedure } from "@/server/trpc" import { addCreditCardInput, deleteCreditCardInput, saveCreditCardInput, + signupInput, } from "./input" const meter = metrics.getMeter("trpc.user") @@ -24,6 +28,9 @@ const generatePreferencesLinkSuccessCounter = meter.createCounter( const generatePreferencesLinkFailCounter = meter.createCounter( "trpc.user.generatePreferencesLink-fail" ) +const signupCounter = meter.createCounter("trpc.user.signup") +const signupSuccessCounter = meter.createCounter("trpc.user.signup-success") +const signupFailCounter = meter.createCounter("trpc.user.signup-fail") export const userMutationRouter = router({ creditCard: router({ @@ -208,4 +215,67 @@ export const userMutationRouter = router({ generatePreferencesLinkSuccessCounter.add(1) return preferencesLink.toString() }), + signup: serviceProcedure.input(signupInput).mutation(async function ({ + ctx, + input, + }) { + const payload = { + ...input, + language: input.language, + phoneNumber: input.phoneNumber.replace(/\s+/g, ""), + } + signupCounter.add(1) + + const parsedPayload = signupPayloadSchema.safeParse(payload) + if (!parsedPayload.success) { + signupFailCounter.add(1, { + error_type: "validation_error", + error: JSON.stringify(parsedPayload.error), + }) + console.error( + "api.user.signup validation error", + JSON.stringify({ + query: input, + error: parsedPayload.error, + }) + ) + throw badRequestError(parsedPayload.error) + } + + const apiResponse = await api.post(api.endpoints.v1.Profile.profile, { + body: parsedPayload.data, + headers: { + Authorization: `Bearer ${ctx.serviceToken}`, + }, + }) + + if (!apiResponse.ok) { + const text = await apiResponse.text() + signupFailCounter.add(1, { + error_type: "http_error", + error: JSON.stringify({ + status: apiResponse.status, + statusText: apiResponse.statusText, + error: text, + }), + }) + console.error( + "api.user.signup api error", + JSON.stringify({ + error: { + status: apiResponse.status, + statusText: apiResponse.statusText, + error: text, + }, + }) + ) + throw serverErrorByStatus(apiResponse.status, text) + } + signupSuccessCounter.add(1) + console.info("api.user.signup success", JSON.stringify({})) + return { + success: true, + redirectUrl: signupVerify[input.language], + } + }), }) diff --git a/server/routers/user/output.ts b/server/routers/user/output.ts index a9e7a0416..4dadd467f 100644 --- a/server/routers/user/output.ts +++ b/server/routers/user/output.ts @@ -1,6 +1,8 @@ import { z } from "zod" import { countriesMap } from "@/components/TempDesignSystem/Form/Country/countries" +import { passwordValidator } from "@/utils/passwordValidator" +import { phoneValidator } from "@/utils/phoneValidator" import { getMembership } from "@/utils/user" export const membershipSchema = z.object({ @@ -244,3 +246,20 @@ export const initiateSaveCardSchema = z.object({ export const subscriberIdSchema = z.object({ subscriberId: z.string(), }) + +export const signupPayloadSchema = z.object({ + language: z.string(), + firstName: z.string(), + lastName: z.string(), + email: z.string(), + phoneNumber: phoneValidator("Phone is required"), + dateOfBirth: z.string(), + address: z.object({ + city: z.string().default(""), + country: z.string().default(""), + countryCode: z.string().default(""), + zipCode: z.string().default(""), + streetAddress: z.string().default(""), + }), + password: passwordValidator("Password is required"), +}) diff --git a/server/trpc.ts b/server/trpc.ts index 688ea01cf..3fc3a10a6 100644 --- a/server/trpc.ts +++ b/server/trpc.ts @@ -121,7 +121,7 @@ export const safeProtectedProcedure = t.procedure.use(async function (opts) { }) }) -export const serviceProcedure = t.procedure.use(async (opts) => { +export const serviceProcedure = t.procedure.use(async function (opts) { const { access_token } = await getServiceToken() if (!access_token) { throw internalServerError(`[serviceProcedure] No service token`) From bc5a01fdf4010f45bac5660224e080b6dca84378 Mon Sep 17 00:00:00 2001 From: Chuma McPhoy Date: Mon, 18 Nov 2024 10:13:05 +0100 Subject: [PATCH 24/31] fix(SW-898): formatting --- server/routers/user/mutation.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server/routers/user/mutation.ts b/server/routers/user/mutation.ts index e233814ee..fd8fe0d41 100644 --- a/server/routers/user/mutation.ts +++ b/server/routers/user/mutation.ts @@ -9,7 +9,7 @@ import { signupPayloadSchema, subscriberIdSchema, } from "@/server/routers/user/output" -import { protectedProcedure, router,serviceProcedure } from "@/server/trpc" +import { protectedProcedure, router, serviceProcedure } from "@/server/trpc" import { addCreditCardInput, From cfaa92260a3d31ccf7dd6f4e2324edf69098eff5 Mon Sep 17 00:00:00 2001 From: Chuma McPhoy Date: Mon, 18 Nov 2024 10:35:27 +0100 Subject: [PATCH 25/31] chore(SW-898): pr comments --- server/routers/user/mutation.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server/routers/user/mutation.ts b/server/routers/user/mutation.ts index fd8fe0d41..17fc39adf 100644 --- a/server/routers/user/mutation.ts +++ b/server/routers/user/mutation.ts @@ -272,7 +272,7 @@ export const userMutationRouter = router({ throw serverErrorByStatus(apiResponse.status, text) } signupSuccessCounter.add(1) - console.info("api.user.signup success", JSON.stringify({})) + console.info("api.user.signup success") return { success: true, redirectUrl: signupVerify[input.language], From 5c571c3c0c30b248d48b8eb9c45ffa2021cf1fa6 Mon Sep 17 00:00:00 2001 From: Chuma McPhoy Date: Mon, 18 Nov 2024 11:18:55 +0100 Subject: [PATCH 26/31] feat(SW-898): add pending ui text to signup button --- components/Forms/Signup/index.tsx | 10 ++++++++-- 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 + 7 files changed, 14 insertions(+), 2 deletions(-) diff --git a/components/Forms/Signup/index.tsx b/components/Forms/Signup/index.tsx index d1e2727e0..24fe2bd95 100644 --- a/components/Forms/Signup/index.tsx +++ b/components/Forms/Signup/index.tsx @@ -37,6 +37,10 @@ export default function SignupForm({ link, subtitle, title }: SignUpFormProps) { const email = intl.formatMessage({ id: "Email address" }) const phoneNumber = intl.formatMessage({ id: "Phone number" }) const zipCode = intl.formatMessage({ id: "Zip code" }) + const signupButtonText = intl.formatMessage({ + id: "Sign up to Scandic Friends", + }) + const signingUpPendingText = intl.formatMessage({ id: "Signing up..." }) const signup = trpc.user.signup.useMutation({ onSuccess: (data) => { @@ -183,7 +187,7 @@ export default function SignupForm({ link, subtitle, title }: SignUpFormProps) { onClick={() => methods.trigger()} data-testid="trigger-validation" > - {intl.formatMessage({ id: "Sign up to Scandic Friends" })} + {signupButtonText} ) : ( )} diff --git a/i18n/dictionaries/da.json b/i18n/dictionaries/da.json index 0ad5dce97..a9fdcfae4 100644 --- a/i18n/dictionaries/da.json +++ b/i18n/dictionaries/da.json @@ -337,6 +337,7 @@ "Show wellness & exercise": "Vis velvære og motion", "Sign up bonus": "Velkomstbonus", "Sign up to Scandic Friends": "Tilmeld dig Scandic Friends", + "Signing up...": "Tilmelder...", "Skip to main content": "Spring over og gå til hovedindhold", "Something went wrong and we couldn't add your card. Please try again later.": "Noget gik galt, og vi kunne ikke tilføje dit kort. Prøv venligst igen senere.", "Something went wrong and we couldn't remove your card. Please try again later.": "Noget gik galt, og vi kunne ikke fjerne dit kort. Prøv venligst igen senere.", diff --git a/i18n/dictionaries/de.json b/i18n/dictionaries/de.json index bd094f4d5..96bcef2de 100644 --- a/i18n/dictionaries/de.json +++ b/i18n/dictionaries/de.json @@ -336,6 +336,7 @@ "Show wellness & exercise": "Zeige Wellness und Bewegung", "Sign up bonus": "Anmelde-Bonus", "Sign up to Scandic Friends": "Treten Sie Scandic Friends bei", + "Signing up...": "Registrierung läuft...", "Skip to main content": "Direkt zum Inhalt", "Something went wrong and we couldn't add your card. Please try again later.": "Ein Fehler ist aufgetreten und wir konnten Ihre Karte nicht hinzufügen. Bitte versuchen Sie es später erneut.", "Something went wrong and we couldn't remove your card. Please try again later.": "Ein Fehler ist aufgetreten und wir konnten Ihre Karte nicht entfernen. Bitte versuchen Sie es später noch einmal.", diff --git a/i18n/dictionaries/en.json b/i18n/dictionaries/en.json index afea03c06..385861316 100644 --- a/i18n/dictionaries/en.json +++ b/i18n/dictionaries/en.json @@ -366,6 +366,7 @@ "Show wellness & exercise": "Show wellness & exercise", "Sign up bonus": "Sign up bonus", "Sign up to Scandic Friends": "Sign up to Scandic Friends", + "Signing up...": "Signing up...", "Skip to main content": "Skip to main content", "Something went wrong and we couldn't add your card. Please try again later.": "Something went wrong and we couldn't add your card. Please try again later.", "Something went wrong and we couldn't remove your card. Please try again later.": "Something went wrong and we couldn't remove your card. Please try again later.", diff --git a/i18n/dictionaries/fi.json b/i18n/dictionaries/fi.json index 57f137e94..09fb8a403 100644 --- a/i18n/dictionaries/fi.json +++ b/i18n/dictionaries/fi.json @@ -338,6 +338,7 @@ "Show wellness & exercise": "Näytä hyvinvointi ja liikunta", "Sign up bonus": "Liittymisbonus", "Sign up to Scandic Friends": "Liity Scandic Friends -jäseneksi", + "Signing up...": "Rekisteröidytään...", "Skip to main content": "Siirry pääsisältöön", "Something went wrong and we couldn't add your card. Please try again later.": "Jotain meni pieleen, emmekä voineet lisätä korttiasi. Yritä myöhemmin uudelleen.", "Something went wrong and we couldn't remove your card. Please try again later.": "Jotain meni pieleen, emmekä voineet poistaa korttiasi. Yritä myöhemmin uudelleen.", diff --git a/i18n/dictionaries/no.json b/i18n/dictionaries/no.json index 1ee68eb82..dcbcdddf4 100644 --- a/i18n/dictionaries/no.json +++ b/i18n/dictionaries/no.json @@ -335,6 +335,7 @@ "Show wellness & exercise": "Vis velvære og trening", "Sign up bonus": "Velkomstbonus", "Sign up to Scandic Friends": "Bli med i Scandic Friends", + "Signing up...": "Registrerer...", "Skip to main content": "Gå videre til hovedsiden", "Something went wrong and we couldn't add your card. Please try again later.": "Noe gikk galt, og vi kunne ikke legge til kortet ditt. Prøv igjen senere.", "Something went wrong and we couldn't remove your card. Please try again later.": "Noe gikk galt, og vi kunne ikke fjerne kortet ditt. Vennligst prøv igjen senere.", diff --git a/i18n/dictionaries/sv.json b/i18n/dictionaries/sv.json index 3177903d2..404896e15 100644 --- a/i18n/dictionaries/sv.json +++ b/i18n/dictionaries/sv.json @@ -335,6 +335,7 @@ "Show wellness & exercise": "Visa välbefinnande och träning", "Sign up bonus": "Välkomstbonus", "Sign up to Scandic Friends": "Bli medlem i Scandic Friends", + "Signing up...": "Registrerar...", "Skip to main content": "Fortsätt till huvudinnehåll", "Something went wrong and we couldn't add your card. Please try again later.": "Något gick fel och vi kunde inte lägga till ditt kort. Försök igen senare.", "Something went wrong and we couldn't remove your card. Please try again later.": "Något gick fel och vi kunde inte ta bort ditt kort. Försök igen senare.", From a68e37c26fc65c71c06ae6eda6032f297abbe16c Mon Sep 17 00:00:00 2001 From: Chuma McPhoy Date: Mon, 18 Nov 2024 15:47:13 +0100 Subject: [PATCH 27/31] fix(SW-898): remove redundant schema and add transform in signupInput --- server/routers/user/input.ts | 18 +++++++++++++++--- server/routers/user/mutation.ts | 26 ++------------------------ server/routers/user/output.ts | 17 ----------------- 3 files changed, 17 insertions(+), 44 deletions(-) diff --git a/server/routers/user/input.ts b/server/routers/user/input.ts index 1d279c2ef..1e9fee267 100644 --- a/server/routers/user/input.ts +++ b/server/routers/user/input.ts @@ -40,6 +40,18 @@ export const saveCreditCardInput = z.object({ merchantId: z.string().optional(), }) -export const signupInput = signUpSchema.extend({ - language: z.nativeEnum(Lang), -}) +export const signupInput = signUpSchema + .extend({ + language: z.nativeEnum(Lang), + }) + .omit({ termsAccepted: true }) + .transform((data) => ({ + ...data, + phoneNumber: data.phoneNumber.replace(/\s+/g, ""), + address: { + ...data.address, + city: "", + country: "", + streetAddress: "", + }, + })) diff --git a/server/routers/user/mutation.ts b/server/routers/user/mutation.ts index 17fc39adf..689c43a26 100644 --- a/server/routers/user/mutation.ts +++ b/server/routers/user/mutation.ts @@ -3,10 +3,9 @@ import { metrics } from "@opentelemetry/api" import { signupVerify } from "@/constants/routes/signup" import { env } from "@/env/server" import * as api from "@/lib/api" -import { badRequestError, serverErrorByStatus } from "@/server/errors/trpc" +import { serverErrorByStatus } from "@/server/errors/trpc" import { initiateSaveCardSchema, - signupPayloadSchema, subscriberIdSchema, } from "@/server/routers/user/output" import { protectedProcedure, router, serviceProcedure } from "@/server/trpc" @@ -219,31 +218,10 @@ export const userMutationRouter = router({ ctx, input, }) { - const payload = { - ...input, - language: input.language, - phoneNumber: input.phoneNumber.replace(/\s+/g, ""), - } signupCounter.add(1) - const parsedPayload = signupPayloadSchema.safeParse(payload) - if (!parsedPayload.success) { - signupFailCounter.add(1, { - error_type: "validation_error", - error: JSON.stringify(parsedPayload.error), - }) - console.error( - "api.user.signup validation error", - JSON.stringify({ - query: input, - error: parsedPayload.error, - }) - ) - throw badRequestError(parsedPayload.error) - } - const apiResponse = await api.post(api.endpoints.v1.Profile.profile, { - body: parsedPayload.data, + body: input, headers: { Authorization: `Bearer ${ctx.serviceToken}`, }, diff --git a/server/routers/user/output.ts b/server/routers/user/output.ts index 4dadd467f..616419047 100644 --- a/server/routers/user/output.ts +++ b/server/routers/user/output.ts @@ -246,20 +246,3 @@ export const initiateSaveCardSchema = z.object({ export const subscriberIdSchema = z.object({ subscriberId: z.string(), }) - -export const signupPayloadSchema = z.object({ - language: z.string(), - firstName: z.string(), - lastName: z.string(), - email: z.string(), - phoneNumber: phoneValidator("Phone is required"), - dateOfBirth: z.string(), - address: z.object({ - city: z.string().default(""), - country: z.string().default(""), - countryCode: z.string().default(""), - zipCode: z.string().default(""), - streetAddress: z.string().default(""), - }), - password: passwordValidator("Password is required"), -}) From bc344e64cf713041eb64e8234996e3143a9502ab Mon Sep 17 00:00:00 2001 From: Hrishikesh Vaipurkar Date: Tue, 19 Nov 2024 14:54:11 +0100 Subject: [PATCH 28/31] feat: SW-601 Implement filters and sort in map view --- .../select-hotel/@modal/(.)map/page.tsx | 4 +- .../filterAndSortModal.module.css | 70 +++++++++++++++++++ .../SelectHotel/FilterAndSortModal/index.tsx | 12 +++- .../HotelFilter/hotelFilter.module.css | 14 ++++ .../SelectHotel/HotelFilter/index.tsx | 10 ++- .../SelectHotel/SelectHotelMap/index.tsx | 5 +- .../SelectHotelMap/selectHotelMap.module.css | 5 ++ .../selectHotel/hotelFilters.ts | 5 ++ .../hotelReservation/selectHotel/map.ts | 3 +- 9 files changed, 120 insertions(+), 8 deletions(-) diff --git a/app/[lang]/(live)/(public)/hotelreservation/(standard)/select-hotel/@modal/(.)map/page.tsx b/app/[lang]/(live)/(public)/hotelreservation/(standard)/select-hotel/@modal/(.)map/page.tsx index fd9cc33c5..7c5573536 100644 --- a/app/[lang]/(live)/(public)/hotelreservation/(standard)/select-hotel/@modal/(.)map/page.tsx +++ b/app/[lang]/(live)/(public)/hotelreservation/(standard)/select-hotel/@modal/(.)map/page.tsx @@ -12,7 +12,7 @@ import { import { MapModal } from "@/components/MapModal" import { setLang } from "@/i18n/serverContext" -import { fetchAvailableHotels } from "../../utils" +import { fetchAvailableHotels, getFiltersFromHotels } from "../../utils" import type { SelectHotelSearchParams } from "@/types/components/hotelReservation/selectHotel/selectHotelSearchParams" import type { LangParams, PageArgs } from "@/types/params" @@ -57,6 +57,7 @@ export default async function SelectHotelMapPage({ }) const hotelPins = getHotelPins(hotels) + const filterList = getFiltersFromHotels(hotels) return ( @@ -65,6 +66,7 @@ export default async function SelectHotelMapPage({ hotelPins={hotelPins} mapId={googleMapId} hotels={hotels} + filterList={filterList} /> ) diff --git a/components/HotelReservation/SelectHotel/FilterAndSortModal/filterAndSortModal.module.css b/components/HotelReservation/SelectHotel/FilterAndSortModal/filterAndSortModal.module.css index 6768d2c56..299527ad0 100644 --- a/components/HotelReservation/SelectHotel/FilterAndSortModal/filterAndSortModal.module.css +++ b/components/HotelReservation/SelectHotel/FilterAndSortModal/filterAndSortModal.module.css @@ -81,6 +81,10 @@ flex: 0 0 auto; } +.title { + display: none; +} + .close { background: none; border: none; @@ -97,3 +101,69 @@ flex: 0 0 auto; border-top: 1px solid var(--Base-Border-Subtle); } + +@media screen and (min-width: 768px) { + .modal { + left: 50%; + bottom: 50%; + height: min(80dvh, 680px); + width: min(80dvw, 960px); + translate: -50% 50%; + overflow-y: auto; + } + + .header { + display: grid; + grid-template-columns: auto 1fr; + padding: var(--Spacing-x2) var(--Spacing-x3); + align-items: center; + border-bottom: 1px solid var(--Base-Border-Subtle); + position: sticky; + top: 0; + background: var(--Base-Surface-Primary-light-Normal); + z-index: 1; + border-top-left-radius: var(--Corner-radius-large); + border-top-right-radius: var(--Corner-radius-large); + } + + .title { + display: block; + } + + .content { + gap: var(--Spacing-x4); + height: auto; + } + + .filters { + overflow-y: unset; + } + + .sorter, + .filters, + .footer, + .divider { + padding: 0 var(--Spacing-x3); + } + + .footer { + flex-direction: row-reverse; + justify-content: space-between; + position: sticky; + bottom: 0; + background: var(--Base-Surface-Primary-light-Normal); + z-index: 1; + border-bottom-left-radius: var(--Corner-radius-large); + border-bottom-right-radius: var(--Corner-radius-large); + padding: var(--Spacing-x2) var(--Spacing-x3); + } + + .filters aside h1 { + margin-bottom: var(--Spacing-x2); + } + + .filters aside > div:last-child { + margin-top: var(--Spacing-x4); + padding-bottom: 0; + } +} diff --git a/components/HotelReservation/SelectHotel/FilterAndSortModal/index.tsx b/components/HotelReservation/SelectHotel/FilterAndSortModal/index.tsx index be1a1bc9c..40dd606fb 100644 --- a/components/HotelReservation/SelectHotel/FilterAndSortModal/index.tsx +++ b/components/HotelReservation/SelectHotel/FilterAndSortModal/index.tsx @@ -12,6 +12,8 @@ import { useHotelFilterStore } from "@/stores/hotel-filters" import { CloseLargeIcon, FilterIcon } from "@/components/Icons" import Button from "@/components/TempDesignSystem/Button" +import Divider from "@/components/TempDesignSystem/Divider" +import Subtitle from "@/components/TempDesignSystem/Text/Subtitle" import HotelFilter from "../HotelFilter" import HotelSorter from "../HotelSorter" @@ -47,12 +49,20 @@ export default function FilterAndSortModal({ > + + {intl.formatMessage({ id: "Filter and sort" })} +
+
- +
- Filter and sort - {/* TODO: Add filter and sort button */} +
From ebd60d9789b6924c0d51d49c14432e5e3b0b7e98 Mon Sep 17 00:00:00 2001 From: Hrishikesh Vaipurkar Date: Tue, 19 Nov 2024 17:18:12 +0100 Subject: [PATCH 29/31] feat: SW-601 Removed autoclose of map view in desktop --- components/MapModal/index.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/components/MapModal/index.tsx b/components/MapModal/index.tsx index 2c06a08cc..3f9e3b4b6 100644 --- a/components/MapModal/index.tsx +++ b/components/MapModal/index.tsx @@ -66,7 +66,7 @@ export function MapModal({ children }: { children: React.ReactNode }) { return (
- + Date: Tue, 19 Nov 2024 17:19:04 +0100 Subject: [PATCH 30/31] feat: SW-601 Optimized css --- .../filterAndSortModal.module.css | 11 +++++++++++ .../SelectHotel/HotelFilter/hotelFilter.module.css | 14 -------------- 2 files changed, 11 insertions(+), 14 deletions(-) diff --git a/components/HotelReservation/SelectHotel/FilterAndSortModal/filterAndSortModal.module.css b/components/HotelReservation/SelectHotel/FilterAndSortModal/filterAndSortModal.module.css index 299527ad0..eab6a1c1d 100644 --- a/components/HotelReservation/SelectHotel/FilterAndSortModal/filterAndSortModal.module.css +++ b/components/HotelReservation/SelectHotel/FilterAndSortModal/filterAndSortModal.module.css @@ -166,4 +166,15 @@ margin-top: var(--Spacing-x4); padding-bottom: 0; } + + .filters aside ul { + display: grid; + grid-template-columns: 1fr 1fr; + margin-top: var(--Spacing-x3); + } +} +@media screen and (min-width: 1024) { + .facilities ul { + grid-template-columns: 1fr 1fr 1fr; + } } diff --git a/components/HotelReservation/SelectHotel/HotelFilter/hotelFilter.module.css b/components/HotelReservation/SelectHotel/HotelFilter/hotelFilter.module.css index 0e204c144..8a4fcebff 100644 --- a/components/HotelReservation/SelectHotel/HotelFilter/hotelFilter.module.css +++ b/components/HotelReservation/SelectHotel/HotelFilter/hotelFilter.module.css @@ -38,17 +38,3 @@ height: 1.25rem; margin: 0; } - -@media screen and (min-width: 768px) { - .facilities ul.modal { - display: grid; - grid-template-columns: auto auto auto; - margin-top: var(--Spacing-x3); - } -} - -@media screen and (min-width: 768px) and (max-width: 1023) { - .facilities ul.modal { - grid-template-columns: auto auto; - } -} From 1330c8b537adb4424b54169d32bfebcaed5dbfe2 Mon Sep 17 00:00:00 2001 From: Hrishikesh Vaipurkar Date: Tue, 19 Nov 2024 17:31:34 +0100 Subject: [PATCH 31/31] feat: SW-601 Optimized code --- .../SelectHotel/FilterAndSortModal/index.tsx | 2 +- .../HotelReservation/SelectHotel/HotelFilter/index.tsx | 10 +++------- .../hotelReservation/selectHotel/hotelFilters.ts | 1 - 3 files changed, 4 insertions(+), 9 deletions(-) diff --git a/components/HotelReservation/SelectHotel/FilterAndSortModal/index.tsx b/components/HotelReservation/SelectHotel/FilterAndSortModal/index.tsx index 40dd606fb..286042ca0 100644 --- a/components/HotelReservation/SelectHotel/FilterAndSortModal/index.tsx +++ b/components/HotelReservation/SelectHotel/FilterAndSortModal/index.tsx @@ -62,7 +62,7 @@ export default function FilterAndSortModal({
- +