diff --git a/components/HotelReservation/EnterDetails/Summary/Mobile/index.tsx b/components/HotelReservation/EnterDetails/Summary/Mobile/index.tsx index 07f18807e..844a20485 100644 --- a/components/HotelReservation/EnterDetails/Summary/Mobile/index.tsx +++ b/components/HotelReservation/EnterDetails/Summary/Mobile/index.tsx @@ -1,13 +1,30 @@ +"use client" + +import { useEnterDetailsStore } from "@/stores/enter-details" + +import SignupPromoMobile from "@/components/HotelReservation/SignupPromo/Mobile" + import SummaryUI from "../UI" import SummaryBottomSheet from "./BottomSheet" import styles from "./mobile.module.css" import type { SummaryProps } from "@/types/components/hotelReservation/summary" +import { DetailsState } from "@/types/stores/enter-details" + +function storeSelector(state: DetailsState) { + return { + join: state.guest.join, + membershipNo: state.guest.membershipNo, + } +} export default function MobileSummary(props: SummaryProps) { + const { join, membershipNo } = useEnterDetailsStore(storeSelector) + const showPromo = !props.isMember && !join && !membershipNo return (
+ {showPromo ? : null}
diff --git a/components/HotelReservation/EnterDetails/Summary/UI/index.tsx b/components/HotelReservation/EnterDetails/Summary/UI/index.tsx index 2a33812f0..455692248 100644 --- a/components/HotelReservation/EnterDetails/Summary/UI/index.tsx +++ b/components/HotelReservation/EnterDetails/Summary/UI/index.tsx @@ -4,6 +4,7 @@ import { useIntl } from "react-intl" import { dt } from "@/lib/dt" import { useEnterDetailsStore } from "@/stores/enter-details" +import SignupPromoDesktop from "@/components/HotelReservation/SignupPromo/Desktop" import { ArrowRightIcon, ChevronDownSmallIcon } from "@/components/Icons" import Button from "@/components/TempDesignSystem/Button" import Divider from "@/components/TempDesignSystem/Divider" @@ -17,7 +18,6 @@ import useLang from "@/hooks/useLang" import styles from "./ui.module.css" import type { SummaryProps } from "@/types/components/hotelReservation/summary" -import { CurrencyEnum } from "@/types/enums/currency" import type { DetailsState } from "@/types/stores/enter-details" export function storeSelector(state: DetailsState) { @@ -60,10 +60,14 @@ export default function SummaryUI({ const adults = booking.rooms[0].adults const children = booking.rooms[0].children - const showMemberPrice = !!( - (isMember || join || membershipNo) && - roomRate.memberRate - ) + const memberPrice = roomRate.memberRate + ? { + currency: roomRate.memberRate.localPrice.currency, + amount: roomRate.memberRate.localPrice.pricePerStay, + } + : null + + const showMemberPrice = !!(isMember || join || membershipNo) const diff = dt(booking.toDate).diff(booking.fromDate, "days") @@ -240,6 +244,9 @@ export default function SummaryUI({
+ {!showMemberPrice && memberPrice ? ( + + ) : null} ) } diff --git a/components/HotelReservation/SelectRate/RoomSelection/RateSummary/index.tsx b/components/HotelReservation/SelectRate/RoomSelection/RateSummary/index.tsx index 4322872dc..34e78e572 100644 --- a/components/HotelReservation/SelectRate/RoomSelection/RateSummary/index.tsx +++ b/components/HotelReservation/SelectRate/RoomSelection/RateSummary/index.tsx @@ -3,6 +3,8 @@ import { useIntl } from "react-intl" import { dt } from "@/lib/dt" +import SignupPromoDesktop from "@/components/HotelReservation/SignupPromo/Desktop" +import SignupPromoMobile from "@/components/HotelReservation/SignupPromo/Mobile" import Button from "@/components/TempDesignSystem/Button" import Body from "@/components/TempDesignSystem/Text/Body" import Caption from "@/components/TempDesignSystem/Text/Caption" @@ -72,15 +74,7 @@ export default function RateSummary({ return (
- {showMemberDiscountBanner && ( -
- - {intl.formatMessage({ - id: "Join or log in while booking for member pricing.", - })} - -
- )} + {showMemberDiscountBanner && }
{roomType} @@ -88,23 +82,13 @@ export default function RateSummary({
{showMemberDiscountBanner && ( -
- - {intl.formatMessage( - { - id: "To get the member price {amount} {currency}, log in or join when completing the booking.", - }, - { - span: (str) => ( - - {str} - - ), - amount: member.localPrice.pricePerStay, - currency: member.localPrice.currency, - } - )} - +
+
)}
diff --git a/components/HotelReservation/SelectRate/RoomSelection/RateSummary/rateSummary.module.css b/components/HotelReservation/SelectRate/RoomSelection/RateSummary/rateSummary.module.css index 3271969ae..ed90225b6 100644 --- a/components/HotelReservation/SelectRate/RoomSelection/RateSummary/rateSummary.module.css +++ b/components/HotelReservation/SelectRate/RoomSelection/RateSummary/rateSummary.module.css @@ -33,7 +33,12 @@ width: 100%; } +.promoContainer { + display: none; + max-width: 264px; +} .summaryPrice { + align-self: center; display: flex; width: 100%; gap: var(--Spacing-x4); @@ -50,6 +55,7 @@ } .summaryPriceTextDesktop { + align-self: center; display: none; } @@ -64,27 +70,6 @@ white-space: nowrap; } -.memberDiscountBannerDesktop { - display: none; - background: var(--Primary-Light-Surface-Normal); - border-radius: var(--Corner-radius-xLarge) var(--Corner-radius-xLarge) 0px - var(--Corner-radius-xLarge); - flex-direction: row; - align-items: center; - padding: var(--Spacing-x1) var(--Spacing-x-one-and-half); - gap: var(--Spacing-x2); - max-width: 264px; -} - -.memberDiscountBannerMobile { - width: 100%; - background: var(--Primary-Light-Surface-Normal); - padding: var(--Spacing-x-one-and-half); - display: flex; - align-items: center; - justify-content: center; -} - @media (min-width: 768px) { .summary { padding: var(--Spacing-x3) var(--Spacing-x7) var(--Spacing-x5); @@ -93,15 +78,12 @@ flex-direction: row; } .petInfo, + .promoContainer, .summaryText, .summaryPriceTextDesktop { display: block; } - .memberDiscountBannerDesktop { - display: flex; - } - .summaryPriceTextMobile, - .memberDiscountBannerMobile { + .summaryPriceTextMobile { display: none; } .summaryPrice, diff --git a/components/HotelReservation/SignupPromo/Desktop.tsx b/components/HotelReservation/SignupPromo/Desktop.tsx new file mode 100644 index 000000000..4d11c7cdb --- /dev/null +++ b/components/HotelReservation/SignupPromo/Desktop.tsx @@ -0,0 +1,43 @@ +"use client" + +import { useIntl } from "react-intl" + +import Caption from "@/components/TempDesignSystem/Text/Caption" +import Footnote from "@/components/TempDesignSystem/Text/Footnote" + +import styles from "./signupPromo.module.css" + +import { SignupPromoProps } from "@/types/components/hotelReservation/signupPromo" + +export default function SignupPromoDesktop({ + memberPrice, + badgeContent, +}: SignupPromoProps) { + const intl = useIntl() + if (!memberPrice) { + return null + } + const { amount, currency } = memberPrice + const price = intl.formatNumber(amount, { currency, style: "currency" }) + + return memberPrice ? ( +
+ {badgeContent && {badgeContent}} + + {intl.formatMessage( + { + id: "To get the member price {price}, log in or join when completing the booking.", + }, + { + span: (str) => ( + + {str} + + ), + price, + } + )} + +
+ ) : null +} diff --git a/components/HotelReservation/SignupPromo/Mobile.tsx b/components/HotelReservation/SignupPromo/Mobile.tsx new file mode 100644 index 000000000..60baf0a4a --- /dev/null +++ b/components/HotelReservation/SignupPromo/Mobile.tsx @@ -0,0 +1,20 @@ +"use client" + +import { useIntl } from "react-intl" + +import Footnote from "@/components/TempDesignSystem/Text/Footnote" + +import styles from "./signupPromo.module.css" + +export default function SignupPromoMobile() { + const intl = useIntl() + return ( +
+ + {intl.formatMessage({ + id: "Join or log in while booking for member pricing.", + })} + +
+ ) +} diff --git a/components/HotelReservation/SignupPromo/signupPromo.module.css b/components/HotelReservation/SignupPromo/signupPromo.module.css new file mode 100644 index 000000000..7dedb7205 --- /dev/null +++ b/components/HotelReservation/SignupPromo/signupPromo.module.css @@ -0,0 +1,43 @@ +.memberDiscountBannerMobile { + width: 100%; + background: var(--Primary-Light-Surface-Normal); + padding: var(--Spacing-x-one-and-half); + display: flex; + align-items: center; + justify-content: center; +} +.memberDiscountBannerDesktop { + display: none; + background: var(--Primary-Light-Surface-Normal); + border-radius: var(--Corner-radius-xLarge) var(--Corner-radius-xLarge) 0px + var(--Corner-radius-xLarge); + align-items: center; + padding: var(--Spacing-x-one-and-half) var(--Spacing-x2); + gap: var(--Spacing-x2); + position: relative; +} + +.badge { + display: flex; + align-items: center; + justify-content: center; + position: absolute; + top: -12px; + left: -12px; + height: 30px; + width: 30px; + background-color: var(--Main-Grey-White); + border-radius: 50%; + font-size: 24px; + overflow: hidden; +} + +@media (min-width: 768px) { + .memberDiscountBannerMobile { + display: none; + } + + .memberDiscountBannerDesktop { + display: flex; + } +} diff --git a/i18n/dictionaries/da.json b/i18n/dictionaries/da.json index 3a66fc700..7f2f2c2e1 100644 --- a/i18n/dictionaries/da.json +++ b/i18n/dictionaries/da.json @@ -387,7 +387,7 @@ "Things nearby HOTEL_NAME": "Ting i nærheden af {hotelName}", "This room is not available": "Dette værelse er ikke tilgængeligt", "Times": "Tider", - "To get the member price {amount} {currency}, log in or join when completing the booking.": "For at få medlemsprisen {amount} {currency}, log ind eller tilmeld dig, når du udfylder bookingen.", + "To get the member price {price}, log in or join when completing the booking.": "For at få medlemsprisen {price}, log ind eller tilmeld dig, når du udfylder bookingen.", "To secure your reservation, we kindly ask you to provide your payment card details. Rest assured, no charges will be made at this time.": "For at sikre din reservation, beder vi om at du giver os dine betalingsoplysninger. Du kan så være sikker på, at ingen gebyrer vil blive opkrævet på dette tidspunkt.", "Total": "Total", "Total Points": "Samlet antal point", diff --git a/i18n/dictionaries/de.json b/i18n/dictionaries/de.json index 8768310d2..cc302974d 100644 --- a/i18n/dictionaries/de.json +++ b/i18n/dictionaries/de.json @@ -386,7 +386,7 @@ "Things nearby HOTEL_NAME": "Dinge in der Nähe von {hotelName}", "This room is not available": "Dieses Zimmer ist nicht verfügbar", "Times": "Zeiten", - "To get the member price {amount} {currency}, log in or join when completing the booking.": "Um den Mitgliederpreis von {amount} {currency} zu erhalten, loggen Sie sich ein oder treten Sie Scandic Friends bei, wenn Sie die Buchung abschließen.", + "To get the member price {price}, log in or join when completing the booking.": "Um den Mitgliederpreis von {price} zu erhalten, loggen Sie sich ein oder treten Sie Scandic Friends bei, wenn Sie die Buchung abschließen.", "To secure your reservation, we kindly ask you to provide your payment card details. Rest assured, no charges will be made at this time.": "Um Ihre Reservierung zu sichern, bitten wir Sie, Ihre Zahlungskarteninformationen zu geben. Sie können sicher sein, dass keine Gebühren zu diesem Zeitpunkt erhoben werden.", "Total": "Gesamt", "Total Points": "Gesamtpunktzahl", diff --git a/i18n/dictionaries/en.json b/i18n/dictionaries/en.json index 4193038a4..ec64caa3a 100644 --- a/i18n/dictionaries/en.json +++ b/i18n/dictionaries/en.json @@ -426,7 +426,7 @@ "Things nearby HOTEL_NAME": "Things nearby {hotelName}", "This room is not available": "This room is not available", "Times": "Times", - "To get the member price {amount} {currency}, log in or join when completing the booking.": "To get the member price {amount} {currency}, log in or join when completing the booking.", + "To get the member price {price}, log in or join when completing the booking.": "To get the member price {price}, log in or join when completing the booking.", "To secure your reservation, we kindly ask you to provide your payment card details. Rest assured, no charges will be made at this time.": "To secure your reservation, we kindly ask you to provide your payment card details. Rest assured, no charges will be made at this time.", "Total": "Total", "Total Points": "Total Points", diff --git a/i18n/dictionaries/fi.json b/i18n/dictionaries/fi.json index 4a0ca7811..79d7ecfb2 100644 --- a/i18n/dictionaries/fi.json +++ b/i18n/dictionaries/fi.json @@ -387,7 +387,7 @@ "Things nearby HOTEL_NAME": "Lähellä olevia asioita {hotelName}", "This room is not available": "Tämä huone ei ole käytettävissä", "Times": "Ajat", - "To get the member price {amount} {currency}, log in or join when completing the booking.": "Jäsenhintaan saavat sisäänkirjautuneet tai liittyneet jäsenet.", + "To get the member price {price}, log in or join when completing the booking.": "Jäsenhintaan saavat sisäänkirjautuneet tai liittyneet jäsenet.", "To secure your reservation, we kindly ask you to provide your payment card details. Rest assured, no charges will be made at this time.": "Varmistaaksesi varauksen, pyydämme sinua antamaan meille maksukortin tiedot. Varmista, että ei veloiteta maksusi tällä hetkellä.", "Total": "Kokonais", "Total Points": "Kokonaispisteet", diff --git a/i18n/dictionaries/no.json b/i18n/dictionaries/no.json index 8f849219f..fa8b3e79a 100644 --- a/i18n/dictionaries/no.json +++ b/i18n/dictionaries/no.json @@ -384,7 +384,7 @@ "Things nearby HOTEL_NAME": "Ting i nærheten av {hotelName}", "This room is not available": "Dette rommet er ikke tilgjengelig", "Times": "Tider", - "To get the member price {amount} {currency}, log in or join when completing the booking.": "For å få medlemsprisen {amount} {currency}, logg inn eller bli med når du fullfører bestillingen.", + "To get the member price {price}, log in or join when completing the booking.": "For å få medlemsprisen {price}, logg inn eller bli med når du fullfører bestillingen.", "To secure your reservation, we kindly ask you to provide your payment card details. Rest assured, no charges will be made at this time.": "For å sikre din reservasjon, ber vi om at du gir oss dine betalingskortdetaljer. Vær sikker på at ingen gebyrer vil bli belastet på dette tidspunktet.", "Total": "Total", "Total Points": "Totale poeng", diff --git a/i18n/dictionaries/sv.json b/i18n/dictionaries/sv.json index 620c4c747..ea2e8e6b5 100644 --- a/i18n/dictionaries/sv.json +++ b/i18n/dictionaries/sv.json @@ -385,7 +385,7 @@ "Things nearby HOTEL_NAME": "Saker i närheten av {hotelName}", "This room is not available": "Detta rum är inte tillgängligt", "Times": "Tider", - "To get the member price {amount} {currency}, log in or join when completing the booking.": "För att få medlemsprisen {amount} {currency}, logga in eller bli medlem när du slutför bokningen.", + "To get the member price {price}, log in or join when completing the booking.": "För att få medlemsprisen {price}, logga in eller bli medlem när du slutför bokningen.", "To secure your reservation, we kindly ask you to provide your payment card details. Rest assured, no charges will be made at this time.": "För att säkra din bokning ber vi om att du ger oss dina betalkortdetaljer. Välj säker på att ingen avgifter kommer att debiteras just nu.", "Total": "Totalt", "Total Points": "Poäng totalt", diff --git a/server/routers/hotels/output.ts b/server/routers/hotels/output.ts index 0b5cfb726..17c7eb263 100644 --- a/server/routers/hotels/output.ts +++ b/server/routers/hotels/output.ts @@ -514,7 +514,7 @@ const linksSchema = z.object({ export const priceSchema = z.object({ pricePerNight: z.coerce.number(), pricePerStay: z.coerce.number(), - currency: z.string(), + currency: z.nativeEnum(CurrencyEnum), }) export const productTypePriceSchema = z.object({ @@ -530,7 +530,7 @@ const productSchema = z.object({ rateCode: "", rateType: "", localPrice: { - currency: "SEK", + currency: CurrencyEnum.SEK, pricePerNight: 0, pricePerStay: 0, }, diff --git a/types/components/hotelReservation/signupPromo.ts b/types/components/hotelReservation/signupPromo.ts new file mode 100644 index 000000000..8b3c5e812 --- /dev/null +++ b/types/components/hotelReservation/signupPromo.ts @@ -0,0 +1,9 @@ +import { CurrencyEnum } from "@/types/enums/currency" + +export interface SignupPromoProps { + memberPrice: { + amount: number + currency: CurrencyEnum + } + badgeContent?: string +}