From 0824f7ce26e1a914bc19ffc36b5e431fbe386825 Mon Sep 17 00:00:00 2001 From: Christian Andolf Date: Thu, 7 Nov 2024 11:51:21 +0100 Subject: [PATCH 01/21] fix(SW-696): add unwrap to surprises add animations to sliding cards various minor fixes --- .../(live)/(protected)/my-pages/layout.tsx | 6 +- .../Surprises/SurprisesNotification.tsx | 227 ++++++++++++------ components/MyPages/Surprises/index.tsx | 5 + .../MyPages/Surprises/surprises.module.css | 21 +- i18n/dictionaries/da.json | 2 +- i18n/dictionaries/de.json | 2 +- i18n/dictionaries/en.json | 2 +- i18n/dictionaries/fi.json | 2 +- i18n/dictionaries/no.json | 2 +- i18n/dictionaries/sv.json | 2 +- public/_static/img/confetti.svg | 1 + server/routers/contentstack/reward/input.ts | 9 +- server/routers/contentstack/reward/query.ts | 102 +++++--- types/components/blocks/surprises.ts | 9 +- 14 files changed, 264 insertions(+), 128 deletions(-) create mode 100644 public/_static/img/confetti.svg diff --git a/app/[lang]/(live)/(protected)/my-pages/layout.tsx b/app/[lang]/(live)/(protected)/my-pages/layout.tsx index 43268adca..80a038b9a 100644 --- a/app/[lang]/(live)/(protected)/my-pages/layout.tsx +++ b/app/[lang]/(live)/(protected)/my-pages/layout.tsx @@ -2,8 +2,8 @@ import { Suspense } from "react" import LoadingSpinner from "@/components/LoadingSpinner" import Sidebar from "@/components/MyPages/Sidebar" +import Surprises from "@/components/MyPages/Surprises" -// import Surprises from "@/components/MyPages/Surprises" import styles from "./layout.module.css" export default async function MyPagesLayout({ @@ -24,9 +24,7 @@ export default async function MyPagesLayout({ - {/* TODO: Waiting on new API stuff - - */} + ) } diff --git a/components/MyPages/Surprises/SurprisesNotification.tsx b/components/MyPages/Surprises/SurprisesNotification.tsx index 6b71c6703..f69269c8c 100644 --- a/components/MyPages/Surprises/SurprisesNotification.tsx +++ b/components/MyPages/Surprises/SurprisesNotification.tsx @@ -1,5 +1,6 @@ "use client" +import { AnimatePresence, motion } from "framer-motion" import { usePathname } from "next/navigation" import React, { useState } from "react" import { Dialog, Modal, ModalOverlay } from "react-aria-components" @@ -21,7 +22,29 @@ import useLang from "@/hooks/useLang" import styles from "./surprises.module.css" -import type { SurprisesProps } from "@/types/components/blocks/surprises" +import type { + Surprise, + SurprisesProps, +} from "@/types/components/blocks/surprises" + +const variants = { + enter: (direction: number) => { + return { + x: direction > 0 ? 1000 : -1000, + opacity: 0, + } + }, + center: { + x: 0, + opacity: 1, + }, + exit: (direction: number) => { + return { + x: direction < 0 ? 1000 : -1000, + opacity: 0, + } + }, +} export default function SurprisesNotification({ surprises, @@ -30,9 +53,29 @@ export default function SurprisesNotification({ const lang = useLang() const pathname = usePathname() const [open, setOpen] = useState(true) - const [selectedSurprise, setSelectedSurprise] = useState(0) + const [[selectedSurprise, direction], setSelectedSurprise] = useState([0, 0]) const [showSurprises, setShowSurprises] = useState(false) - const update = trpc.contentstack.rewards.update.useMutation() + const unwrap = trpc.contentstack.rewards.unwrap.useMutation({ + onSuccess: () => { + if (pathname.indexOf(benefits[lang]) !== 0) { + toast.success( + <> + {intl.formatMessage( + { id: "Gift(s) added to your benefits" }, + { amount: surprises.length } + )} +
+ + {intl.formatMessage({ id: "Go to My Benefits" })} + + + ) + } + }, + onError: (error) => { + console.error("Error", error) + }, + }) const intl = useIntl() if (!surprises.length) { @@ -41,36 +84,48 @@ export default function SurprisesNotification({ const surprise = surprises[selectedSurprise] - function showSurprise(n: number) { - setSelectedSurprise((surprise) => surprise + n) + function showSurprise(newDirection: number) { + setSelectedSurprise(([currentIndex]) => [ + currentIndex + newDirection, + newDirection, + ]) } - function viewRewards() { - if (surprise.reward_id) { - update.mutate({ id: surprise.reward_id }) - } - } + async function viewRewards() { + const updates = surprises + .map((surprise) => { + const coupons = surprise.coupons + ?.map((coupon) => { + if (coupon?.couponCode) { + return { + rewardId: surprise.id, + couponCode: coupon.couponCode, + } + } + }) + .filter( + (coupon): coupon is { rewardId: string; couponCode: string } => + !!coupon + ) - function closeModal(close: VoidFunction) { - viewRewards() - close() - - if (pathname.indexOf(benefits[lang]) !== 0) { - toast.success( - <> - {intl.formatMessage( - { id: "Gift(s) added to your benefits" }, - { amount: surprises.length } - )} -
- - {intl.formatMessage({ id: "Go to My Benefits" })} - - + return coupons + }) + .flat() + .filter( + (coupon): coupon is { rewardId: string; couponCode: string } => !!coupon ) - } + + unwrap.mutate(updates) } + const earliestExpirationDate = surprise.coupons?.reduce( + (earliestDate, coupon) => { + const expiresAt = dt(coupon.expiresAt) + return earliestDate.isBefore(expiresAt) ? earliestDate : expiresAt + }, + dt() + ) + return ( )} - + )} ) @@ -230,18 +301,20 @@ function Surprise({ children?: React.ReactNode }) { return ( - <> +
Gift - - {title} - +
+ + {title} + +
{children} - +
) } diff --git a/components/MyPages/Surprises/index.tsx b/components/MyPages/Surprises/index.tsx index 131ae2b28..4a0eae273 100644 --- a/components/MyPages/Surprises/index.tsx +++ b/components/MyPages/Surprises/index.tsx @@ -1,9 +1,14 @@ +import { env } from "@/env/server" import { getProfile } from "@/lib/trpc/memoizedRequests" import { serverClient } from "@/lib/trpc/server" import SurprisesNotification from "./SurprisesNotification" export default async function Surprises() { + if (env.HIDE_FOR_NEXT_RELEASE) { + return null + } + const user = await getProfile() if (!user || "error" in user) { diff --git a/components/MyPages/Surprises/surprises.module.css b/components/MyPages/Surprises/surprises.module.css index 4bde7c46d..c752fece3 100644 --- a/components/MyPages/Surprises/surprises.module.css +++ b/components/MyPages/Surprises/surprises.module.css @@ -28,7 +28,7 @@ z-index: 100; &[data-entering] { - animation: modal-fade 200ms; + animation: modal-fade 200ms ease-in; } &[data-exiting] { animation: modal-fade 200ms reverse ease-in; @@ -40,6 +40,17 @@ display: flex; justify-content: center; align-items: center; + + &:before { + background-image: url("/_static/img/confetti.svg"); + background-repeat: no-repeat; + background-position: center 40%; + content: ""; + width: 100%; + height: 100%; + animation: modal-fade 200ms ease-in; + display: block; + } } } @@ -65,6 +76,10 @@ flex-direction: column; gap: var(--Spacing-x2); padding-bottom: var(--Spacing-x2); + + /* to hide sliding cards */ + position: relative; + overflow: hidden; } @media screen and (min-width: 768px) { @@ -90,8 +105,10 @@ display: flex; flex-direction: column; align-items: center; + justify-content: center; padding: 0 var(--Spacing-x3); gap: var(--Spacing-x2); + min-height: 350px; } .nav { @@ -103,6 +120,8 @@ } .nav button { + user-select: none; + &:nth-child(1) { padding-left: 0; } diff --git a/i18n/dictionaries/da.json b/i18n/dictionaries/da.json index 9bea132a9..8a5d64ac4 100644 --- a/i18n/dictionaries/da.json +++ b/i18n/dictionaries/da.json @@ -118,6 +118,7 @@ "Enter destination or hotel": "Indtast destination eller hotel", "Enter your details": "Indtast dine oplysninger", "Events that make an impression": "Events that make an impression", + "Expires at the earliest": "Udløber tidligst {date}", "Explore all levels and benefits": "Udforsk alle niveauer og fordele", "Explore nearby": "Udforsk i nærheden", "Extras to your booking": "Tillæg til din booking", @@ -380,7 +381,6 @@ "Use bonus cheque": "Brug Bonus Cheque", "Use code/voucher": "Brug kode/voucher", "User information": "Brugeroplysninger", - "Valid through": "Gyldig igennem", "View as list": "Vis som liste", "View as map": "Vis som kort", "View your booking": "Se din booking", diff --git a/i18n/dictionaries/de.json b/i18n/dictionaries/de.json index d63fe2700..7e35d2aab 100644 --- a/i18n/dictionaries/de.json +++ b/i18n/dictionaries/de.json @@ -118,6 +118,7 @@ "Enter destination or hotel": "Reiseziel oder Hotel eingeben", "Enter your details": "Geben Sie Ihre Daten ein", "Events that make an impression": "Events that make an impression", + "Expires at the earliest": "Läuft frühestens am {date} ab", "Explore all levels and benefits": "Entdecken Sie alle Levels und Vorteile", "Explore nearby": "Erkunden Sie die Umgebung", "Extras to your booking": "Extras zu Ihrer Buchung", @@ -378,7 +379,6 @@ "Use bonus cheque": "Bonusscheck nutzen", "Use code/voucher": "Code/Gutschein nutzen", "User information": "Nutzerinformation", - "Valid through": "Gültig bis", "View as list": "Als Liste anzeigen", "View as map": "Als Karte anzeigen", "View your booking": "Ihre Buchung ansehen", diff --git a/i18n/dictionaries/en.json b/i18n/dictionaries/en.json index 4abc07ea7..42b4b9316 100644 --- a/i18n/dictionaries/en.json +++ b/i18n/dictionaries/en.json @@ -127,6 +127,7 @@ "Enter destination or hotel": "Enter destination or hotel", "Enter your details": "Enter your details", "Events that make an impression": "Events that make an impression", + "Expires at the earliest": "Expires at the earliest {date}", "Explore all levels and benefits": "Explore all levels and benefits", "Explore nearby": "Explore nearby", "Extras to your booking": "Extras to your booking", @@ -410,7 +411,6 @@ "User information": "User information", "VAT": "VAT", "VAT amount": "VAT amount", - "Valid through": "Valid through", "View as list": "View as list", "View as map": "View as map", "View terms": "View terms", diff --git a/i18n/dictionaries/fi.json b/i18n/dictionaries/fi.json index f179163ee..94d09060c 100644 --- a/i18n/dictionaries/fi.json +++ b/i18n/dictionaries/fi.json @@ -118,6 +118,7 @@ "Enter destination or hotel": "Anna kohde tai hotelli", "Enter your details": "Anna tietosi", "Events that make an impression": "Events that make an impression", + "Expires at the earliest": "Päättyy aikaisintaan {date}", "Explore all levels and benefits": "Tutustu kaikkiin tasoihin ja etuihin", "Explore nearby": "Tutustu lähialueeseen", "Extras to your booking": "Varauksessa lisäpalveluita", @@ -380,7 +381,6 @@ "Use bonus cheque": "Käytä bonussekkiä", "Use code/voucher": "Käytä koodia/voucheria", "User information": "Käyttäjän tiedot", - "Valid through": "Voimassa läpi", "View as list": "Näytä listana", "View as map": "Näytä kartalla", "View your booking": "Näytä varauksesi", diff --git a/i18n/dictionaries/no.json b/i18n/dictionaries/no.json index 2ed440701..bffe5c2ac 100644 --- a/i18n/dictionaries/no.json +++ b/i18n/dictionaries/no.json @@ -117,6 +117,7 @@ "Enter destination or hotel": "Skriv inn destinasjon eller hotell", "Enter your details": "Skriv inn detaljene dine", "Events that make an impression": "Events that make an impression", + "Expires at the earliest": "Utløper tidligst {date}", "Explore all levels and benefits": "Utforsk alle nivåer og fordeler", "Explore nearby": "Utforsk i nærheten", "Extras to your booking": "Tilvalg til bestillingen din", @@ -377,7 +378,6 @@ "Use bonus cheque": "Bruk bonussjekk", "Use code/voucher": "Bruk kode/voucher", "User information": "Brukerinformasjon", - "Valid through": "Gyldig gjennom", "View as list": "Vis som liste", "View as map": "Vis som kart", "View your booking": "Se din bestilling", diff --git a/i18n/dictionaries/sv.json b/i18n/dictionaries/sv.json index ee77155fe..883ee8e07 100644 --- a/i18n/dictionaries/sv.json +++ b/i18n/dictionaries/sv.json @@ -117,6 +117,7 @@ "Enter destination or hotel": "Ange destination eller hotell", "Enter your details": "Ange dina uppgifter", "Events that make an impression": "Events that make an impression", + "Expires at the earliest": "Löper ut tidigast {date}", "Explore all levels and benefits": "Utforska alla nivåer och fördelar", "Explore nearby": "Utforska i närheten", "Extras to your booking": "Extra tillval till din bokning", @@ -377,7 +378,6 @@ "Use bonus cheque": "Använd bonuscheck", "Use code/voucher": "Använd kod/voucher", "User information": "Användarinformation", - "Valid through": "Giltig t.o.m.", "View as list": "Visa som lista", "View as map": "Visa som karta", "View your booking": "Visa din bokning", diff --git a/public/_static/img/confetti.svg b/public/_static/img/confetti.svg new file mode 100644 index 000000000..54f4306bd --- /dev/null +++ b/public/_static/img/confetti.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/server/routers/contentstack/reward/input.ts b/server/routers/contentstack/reward/input.ts index acf4a67f8..50add5aa4 100644 --- a/server/routers/contentstack/reward/input.ts +++ b/server/routers/contentstack/reward/input.ts @@ -18,6 +18,9 @@ export const rewardsCurrentInput = z.object({ lang: z.nativeEnum(Lang).optional(), }) -export const rewardsUpdateInput = z.object({ - id: z.string(), -}) +export const rewardsUpdateInput = z.array( + z.object({ + rewardId: z.string(), + couponCode: z.string(), + }) +) diff --git a/server/routers/contentstack/reward/query.ts b/server/routers/contentstack/reward/query.ts index b1f5ccb8c..0deede637 100644 --- a/server/routers/contentstack/reward/query.ts +++ b/server/routers/contentstack/reward/query.ts @@ -4,6 +4,7 @@ import { notFound } from "@/server/errors/trpc" import { contentStackBaseWithProtectedProcedure, contentStackBaseWithServiceProcedure, + protectedProcedure, router, } from "@/server/trpc" @@ -16,7 +17,6 @@ import { } from "./input" import { Reward, - SurpriseReward, validateApiRewardSchema, validateCategorizedRewardsSchema, } from "./output" @@ -36,8 +36,6 @@ import { getUniqueRewardIds, } from "./utils" -import { Surprise } from "@/types/components/blocks/surprises" - const ONE_HOUR = 60 * 60 export const rewardQueryRouter = router({ @@ -327,42 +325,84 @@ export const rewardQueryRouter = router({ getCurrentRewardSuccessCounter.add(1) - const surprises = - validatedApiRewards.data - .filter( - (reward): reward is SurpriseReward => - reward?.type === "coupon" && reward?.rewardType === "Surprise" + const surprises = validatedApiRewards.data + // TODO: Add predicates once legacy endpoints are removed + .filter((reward) => { + if (reward?.rewardType !== "Surprise") { + return false + } + + if (!("coupon" in reward)) { + return false + } + + const unwrappedCoupons = + reward.coupon?.filter((coupon) => !coupon.unwrapped) || [] + if (unwrappedCoupons.length === 0) { + return false + } + + return true + }) + .map((surprise) => { + const reward = cmsRewards.find( + ({ reward_id }) => surprise.rewardId === reward_id ) - .map((surprise) => { - const reward = cmsRewards.find( - ({ reward_id }) => surprise.rewardId === reward_id - ) - if (!reward) { - return null - } + if (!reward) { + return null + } - return { - ...reward, - id: surprise.id, - endsAt: surprise.endsAt, - } - }) - .filter((surprise): surprise is Surprise => !!surprise) ?? [] + return { + ...reward, + id: surprise.id, + coupons: "coupon" in surprise ? surprise.coupon || [] : [], + } + }) + .flatMap((surprises) => (surprises ? [surprises] : [])) return surprises }), - update: contentStackBaseWithProtectedProcedure + unwrap: protectedProcedure .input(rewardsUpdateInput) .mutation(async ({ input, ctx }) => { - const response = await Promise.resolve({ ok: true }) - // const response = await api.post(api.endpoints.v1.rewards, { - // body: { - // ids: [input.id], - // }, - // }) - if (!response.ok) { - return false + const promises = input.map(({ rewardId, couponCode }) => { + return api.post(api.endpoints.v1.Profile.Reward.unwrap, { + body: { + rewardId, + couponCode, + }, + headers: { + Authorization: `Bearer ${ctx.session.token.access_token}`, + }, + }) + }) + + const responses = await Promise.all(promises) + + const errors = await Promise.all( + responses.map(async (apiResponse) => { + if (!apiResponse.ok) { + const text = await apiResponse.text() + console.error( + "contentstack.unwrap validation error", + JSON.stringify({ + error: { + status: apiResponse.status, + statusText: apiResponse.statusText, + text, + }, + query: {}, + }) + ) + return false + } + return true + }) + ) + + if (errors.filter((ok) => !ok).length > 0) { + return null } return true diff --git a/types/components/blocks/surprises.ts b/types/components/blocks/surprises.ts index 00c8fadfa..da4a8730c 100644 --- a/types/components/blocks/surprises.ts +++ b/types/components/blocks/surprises.ts @@ -1,11 +1,8 @@ -import { - Reward, - SurpriseReward, -} from "@/server/routers/contentstack/reward/output" +import { Reward } from "@/server/routers/contentstack/reward/output" export interface Surprise extends Reward { - endsAt: SurpriseReward["endsAt"] - id: SurpriseReward["id"] + coupons: { couponCode?: string; expiresAt?: string }[] + id?: string } export interface SurprisesProps { From 3aedc4ff25839d6ac67626dc67dae9a126cb55e9 Mon Sep 17 00:00:00 2001 From: Christian Andolf Date: Thu, 14 Nov 2024 17:08:55 +0100 Subject: [PATCH 02/21] fix(SW-696): split up surprises into more components for readability add tsparticles for confetti --- components/MyPages/Surprises/Card.tsx | 26 ++ components/MyPages/Surprises/Client.tsx | 217 ++++++++++++ components/MyPages/Surprises/Header.tsx | 16 + components/MyPages/Surprises/Initial.tsx | 62 ++++ components/MyPages/Surprises/Navigation.tsx | 45 +++ components/MyPages/Surprises/Slide.tsx | 49 +++ .../Surprises/SurprisesNotification.tsx | 320 ------------------ components/MyPages/Surprises/confetti.ts | 13 + components/MyPages/Surprises/index.tsx | 4 +- .../MyPages/Surprises/surprises.module.css | 67 ++-- package-lock.json | 274 +++++++++++++++ package.json | 1 + types/components/blocks/surprises.ts | 24 ++ 13 files changed, 760 insertions(+), 358 deletions(-) create mode 100644 components/MyPages/Surprises/Card.tsx create mode 100644 components/MyPages/Surprises/Client.tsx create mode 100644 components/MyPages/Surprises/Header.tsx create mode 100644 components/MyPages/Surprises/Initial.tsx create mode 100644 components/MyPages/Surprises/Navigation.tsx create mode 100644 components/MyPages/Surprises/Slide.tsx delete mode 100644 components/MyPages/Surprises/SurprisesNotification.tsx create mode 100644 components/MyPages/Surprises/confetti.ts diff --git a/components/MyPages/Surprises/Card.tsx b/components/MyPages/Surprises/Card.tsx new file mode 100644 index 000000000..932f0ecc9 --- /dev/null +++ b/components/MyPages/Surprises/Card.tsx @@ -0,0 +1,26 @@ +import Image from "@/components/Image" +import Title from "@/components/TempDesignSystem/Text/Title" + +import styles from "./surprises.module.css" + +import type { CardProps } from "@/types/components/blocks/surprises" + +export default function Card({ title, children }: CardProps) { + return ( +
+ Gift +
+ + {title} + +
+ + {children} +
+ ) +} diff --git a/components/MyPages/Surprises/Client.tsx b/components/MyPages/Surprises/Client.tsx new file mode 100644 index 000000000..2e4202103 --- /dev/null +++ b/components/MyPages/Surprises/Client.tsx @@ -0,0 +1,217 @@ +"use client" + +import { AnimatePresence, motion } from "framer-motion" +import { usePathname } from "next/navigation" +import React, { useState } from "react" +import { Dialog, Modal, ModalOverlay } from "react-aria-components" +import { useIntl } from "react-intl" + +import { benefits } from "@/constants/routes/myPages" +import { trpc } from "@/lib/trpc/client" + +import Link from "@/components/TempDesignSystem/Link" +import Caption from "@/components/TempDesignSystem/Text/Caption" +import { toast } from "@/components/TempDesignSystem/Toasts" +import useLang from "@/hooks/useLang" + +import confetti from "./confetti" +import Header from "./Header" +import Initial from "./Initial" +import Navigation from "./Navigation" +import Slide from "./Slide" + +import styles from "./surprises.module.css" + +import type { SurprisesProps } from "@/types/components/blocks/surprises" + +const MotionModal = motion(Modal) + +export default function SurprisesNotification({ + surprises, + membershipNumber, +}: SurprisesProps) { + const lang = useLang() + const intl = useIntl() + const pathname = usePathname() + const [open, setOpen] = useState(true) + const [[selectedSurprise, direction], setSelectedSurprise] = useState([0, 0]) + const [showSurprises, setShowSurprises] = useState(false) + const unwrap = trpc.contentstack.rewards.unwrap.useMutation({ + onSuccess: () => { + if (pathname.indexOf(benefits[lang]) !== 0) { + toast.success( + <> + {intl.formatMessage( + { id: "Gift(s) added to your benefits" }, + { amount: surprises.length } + )} +
+ + {intl.formatMessage({ id: "Go to My Benefits" })} + + + ) + } + }, + onError: (error) => { + console.error("Failed to unwrap surprise", error) + }, + }) + + const totalSurprises = surprises.length + if (!totalSurprises) { + return null + } + + const surprise = surprises[selectedSurprise] + + function showSurprise(newDirection: number) { + setSelectedSurprise(([currentIndex]) => [ + currentIndex + newDirection, + newDirection, + ]) + } + + async function viewRewards() { + const updates = surprises + .map((surprise) => { + const coupons = surprise.coupons + ?.map((coupon) => { + if (coupon?.couponCode) { + return { + rewardId: surprise.id, + couponCode: coupon.couponCode, + } + } + }) + .filter( + (coupon): coupon is { rewardId: string; couponCode: string } => + !!coupon + ) + + return coupons + }) + .flat() + .filter( + (coupon): coupon is { rewardId: string; couponCode: string } => !!coupon + ) + + unwrap.mutate(updates) + } + + return ( + + + + {open && ( + + + {({ close }) => { + return ( + <> +
{ + viewRewards() + close() + }} + > + {showSurprises && totalSurprises > 1 && ( + + {intl.formatMessage( + { id: "{amount} out of {total}" }, + { + amount: selectedSurprise + 1, + total: totalSurprises, + } + )} + + )} +
+ + {showSurprises ? ( + <> + + + + + + + {totalSurprises > 1 && ( + + )} + + ) : ( + { + setShowSurprises(true) + }} + /> + )} + + ) + }} +
+
+ )} +
+
+ ) +} + +const variants = { + enter: (direction: number) => { + return { + x: direction > 0 ? 1000 : -1000, + opacity: 0, + } + }, + center: { + x: 0, + opacity: 1, + }, + exit: (direction: number) => { + return { + x: direction < 0 ? 1000 : -1000, + opacity: 0, + } + }, +} diff --git a/components/MyPages/Surprises/Header.tsx b/components/MyPages/Surprises/Header.tsx new file mode 100644 index 000000000..3a25400a9 --- /dev/null +++ b/components/MyPages/Surprises/Header.tsx @@ -0,0 +1,16 @@ +import { CloseLargeIcon } from "@/components/Icons" + +import styles from "./surprises.module.css" + +import type { HeaderProps } from "@/types/components/blocks/surprises" + +export default function Header({ onClose, children }: HeaderProps) { + return ( +
+ {children} + +
+ ) +} diff --git a/components/MyPages/Surprises/Initial.tsx b/components/MyPages/Surprises/Initial.tsx new file mode 100644 index 000000000..8e3a60b9c --- /dev/null +++ b/components/MyPages/Surprises/Initial.tsx @@ -0,0 +1,62 @@ +import { useIntl } from "react-intl" + +import Button from "@/components/TempDesignSystem/Button" +import Body from "@/components/TempDesignSystem/Text/Body" +import Caption from "@/components/TempDesignSystem/Text/Caption" + +import Card from "./Card" + +import type { InitialProps } from "@/types/components/blocks/surprises" + +export default function Initial({ totalSurprises, onOpen }: InitialProps) { + const intl = useIntl() + + return ( + + + {totalSurprises > 1 ? ( + <> + {intl.formatMessage( + { + id: "You have # gifts waiting for you!", + }, + { + amount: totalSurprises, + b: (str) => {str}, + } + )} +
+ {intl.formatMessage({ + id: "Hurry up and use them before they expire!", + })} + + ) : ( + intl.formatMessage({ + id: "We have a special gift waiting for you!", + }) + )} + + + {intl.formatMessage({ + id: "You'll find all your gifts in 'My benefits'", + })} + + + +
+ ) +} diff --git a/components/MyPages/Surprises/Navigation.tsx b/components/MyPages/Surprises/Navigation.tsx new file mode 100644 index 000000000..2a1296f01 --- /dev/null +++ b/components/MyPages/Surprises/Navigation.tsx @@ -0,0 +1,45 @@ +import { useIntl } from "react-intl" + +import { ChevronRightSmallIcon } from "@/components/Icons" +import Button from "@/components/TempDesignSystem/Button" + +import styles from "./surprises.module.css" + +import type { NavigationProps } from "@/types/components/blocks/surprises" + +export default function Navigation({ + selectedSurprise, + totalSurprises, + showSurprise, +}: NavigationProps) { + const intl = useIntl() + + return ( + + ) +} diff --git a/components/MyPages/Surprises/Slide.tsx b/components/MyPages/Surprises/Slide.tsx new file mode 100644 index 000000000..2a4173fca --- /dev/null +++ b/components/MyPages/Surprises/Slide.tsx @@ -0,0 +1,49 @@ +import { useIntl } from "react-intl" + +import { dt } from "@/lib/dt" + +import Body from "@/components/TempDesignSystem/Text/Body" +import Caption from "@/components/TempDesignSystem/Text/Caption" +import useLang from "@/hooks/useLang" + +import Card from "./Card" + +import styles from "./surprises.module.css" + +import type { SlideProps } from "@/types/components/blocks/surprises" + +export default function Slide({ surprise, membershipNumber }: SlideProps) { + const lang = useLang() + const intl = useIntl() + + const earliestExpirationDate = surprise.coupons?.reduce( + (earliestDate, coupon) => { + const expiresAt = dt(coupon.expiresAt) + return earliestDate.isBefore(expiresAt) ? earliestDate : expiresAt + }, + dt() + ) + return ( + + {surprise.description} +
+ + {intl.formatMessage( + { id: "Expires at the earliest" }, + { + date: dt(earliestExpirationDate) + .locale(lang) + .format("D MMM YYYY"), + } + )} + + + {intl.formatMessage({ + id: "Membership ID", + })}{" "} + {membershipNumber} + +
+
+ ) +} diff --git a/components/MyPages/Surprises/SurprisesNotification.tsx b/components/MyPages/Surprises/SurprisesNotification.tsx deleted file mode 100644 index f69269c8c..000000000 --- a/components/MyPages/Surprises/SurprisesNotification.tsx +++ /dev/null @@ -1,320 +0,0 @@ -"use client" - -import { AnimatePresence, motion } from "framer-motion" -import { usePathname } from "next/navigation" -import React, { useState } from "react" -import { Dialog, Modal, ModalOverlay } from "react-aria-components" -import { useIntl } from "react-intl" - -import { benefits } from "@/constants/routes/myPages" -import { dt } from "@/lib/dt" -import { trpc } from "@/lib/trpc/client" - -import { ChevronRightSmallIcon, CloseLargeIcon } from "@/components/Icons" -import Image from "@/components/Image" -import Button from "@/components/TempDesignSystem/Button" -import Link from "@/components/TempDesignSystem/Link" -import Body from "@/components/TempDesignSystem/Text/Body" -import Caption from "@/components/TempDesignSystem/Text/Caption" -import Title from "@/components/TempDesignSystem/Text/Title" -import { toast } from "@/components/TempDesignSystem/Toasts" -import useLang from "@/hooks/useLang" - -import styles from "./surprises.module.css" - -import type { - Surprise, - SurprisesProps, -} from "@/types/components/blocks/surprises" - -const variants = { - enter: (direction: number) => { - return { - x: direction > 0 ? 1000 : -1000, - opacity: 0, - } - }, - center: { - x: 0, - opacity: 1, - }, - exit: (direction: number) => { - return { - x: direction < 0 ? 1000 : -1000, - opacity: 0, - } - }, -} - -export default function SurprisesNotification({ - surprises, - membershipNumber, -}: SurprisesProps) { - const lang = useLang() - const pathname = usePathname() - const [open, setOpen] = useState(true) - const [[selectedSurprise, direction], setSelectedSurprise] = useState([0, 0]) - const [showSurprises, setShowSurprises] = useState(false) - const unwrap = trpc.contentstack.rewards.unwrap.useMutation({ - onSuccess: () => { - if (pathname.indexOf(benefits[lang]) !== 0) { - toast.success( - <> - {intl.formatMessage( - { id: "Gift(s) added to your benefits" }, - { amount: surprises.length } - )} -
- - {intl.formatMessage({ id: "Go to My Benefits" })} - - - ) - } - }, - onError: (error) => { - console.error("Error", error) - }, - }) - const intl = useIntl() - - if (!surprises.length) { - return null - } - - const surprise = surprises[selectedSurprise] - - function showSurprise(newDirection: number) { - setSelectedSurprise(([currentIndex]) => [ - currentIndex + newDirection, - newDirection, - ]) - } - - async function viewRewards() { - const updates = surprises - .map((surprise) => { - const coupons = surprise.coupons - ?.map((coupon) => { - if (coupon?.couponCode) { - return { - rewardId: surprise.id, - couponCode: coupon.couponCode, - } - } - }) - .filter( - (coupon): coupon is { rewardId: string; couponCode: string } => - !!coupon - ) - - return coupons - }) - .flat() - .filter( - (coupon): coupon is { rewardId: string; couponCode: string } => !!coupon - ) - - unwrap.mutate(updates) - } - - const earliestExpirationDate = surprise.coupons?.reduce( - (earliestDate, coupon) => { - const expiresAt = dt(coupon.expiresAt) - return earliestDate.isBefore(expiresAt) ? earliestDate : expiresAt - }, - dt() - ) - - return ( - - - - {({ close }) => { - return ( - <> -
- {surprises.length > 1 && showSurprises && ( - - {intl.formatMessage( - { id: "{amount} out of {total}" }, - { - amount: selectedSurprise + 1, - total: surprises.length, - } - )} - - )} - -
- {showSurprises ? ( - <> - - - - {surprise.description} -
- - {intl.formatMessage( - { id: "Expires at the earliest" }, - { - date: dt(earliestExpirationDate) - .locale(lang) - .format("D MMM YYYY"), - } - )} - - - {intl.formatMessage({ - id: "Membership ID", - })}{" "} - {membershipNumber} - -
-
-
-
- {surprises.length > 1 && ( - <> - - - )} - - ) : ( - - - {surprises.length > 1 ? ( - <> - {intl.formatMessage( - { - id: "You have # gifts waiting for you!", - }, - { - amount: surprises.length, - b: (str) => {str}, - } - )} -
- {intl.formatMessage({ - id: "Hurry up and use them before they expire!", - })} - - ) : ( - intl.formatMessage({ - id: "We have a special gift waiting for you!", - }) - )} - - - {intl.formatMessage({ - id: "You'll find all your gifts in 'My benefits'", - })} - - - -
- )} - - ) - }} -
-
-
- ) -} - -function Surprise({ - title, - children, -}: { - title?: string - children?: React.ReactNode -}) { - return ( -
- Gift -
- - {title} - -
- - {children} -
- ) -} diff --git a/components/MyPages/Surprises/confetti.ts b/components/MyPages/Surprises/confetti.ts new file mode 100644 index 000000000..2990fade4 --- /dev/null +++ b/components/MyPages/Surprises/confetti.ts @@ -0,0 +1,13 @@ +import { confetti as particlesConfetti } from "@tsparticles/confetti" + +export default function confetti() { + particlesConfetti("surprise-confetti", { + count: 300, + spread: 150, + position: { + y: 60, + }, + colors: ["#cd0921", "#4d001b", "#fff"], + shapes: ["star", "square", "circle", "polygon"], + }) +} diff --git a/components/MyPages/Surprises/index.tsx b/components/MyPages/Surprises/index.tsx index 4a0eae273..2ae414a3a 100644 --- a/components/MyPages/Surprises/index.tsx +++ b/components/MyPages/Surprises/index.tsx @@ -2,7 +2,7 @@ import { env } from "@/env/server" import { getProfile } from "@/lib/trpc/memoizedRequests" import { serverClient } from "@/lib/trpc/server" -import SurprisesNotification from "./SurprisesNotification" +import SurprisesClient from "./Client" export default async function Surprises() { if (env.HIDE_FOR_NEXT_RELEASE) { @@ -22,7 +22,7 @@ export default async function Surprises() { } return ( - diff --git a/components/MyPages/Surprises/surprises.module.css b/components/MyPages/Surprises/surprises.module.css index c752fece3..9a80761f4 100644 --- a/components/MyPages/Surprises/surprises.module.css +++ b/components/MyPages/Surprises/surprises.module.css @@ -1,4 +1,4 @@ -@keyframes modal-fade { +@keyframes fade { from { opacity: 0; } @@ -8,16 +8,6 @@ } } -@keyframes slide-up { - from { - transform: translateY(100%); - } - - to { - transform: translateY(0); - } -} - .overlay { background: rgba(0, 0, 0, 0.5); height: var(--visual-viewport-height); @@ -28,10 +18,10 @@ z-index: 100; &[data-entering] { - animation: modal-fade 200ms ease-in; + animation: fade 400ms ease-in; } &[data-exiting] { - animation: modal-fade 200ms reverse ease-in; + animation: fade 400ms reverse ease-in; } } @@ -40,17 +30,19 @@ display: flex; justify-content: center; align-items: center; + } +} - &:before { - background-image: url("/_static/img/confetti.svg"); - background-repeat: no-repeat; - background-position: center 40%; - content: ""; - width: 100%; - height: 100%; - animation: modal-fade 200ms ease-in; - display: block; - } +@media screen and (min-width: 768px) and (prefers-reduced-motion) { + .overlay:before { + background-image: url("/_static/img/confetti.svg"); + background-repeat: no-repeat; + background-position: center 40%; + content: ""; + width: 100%; + height: 100%; + animation: fade 400ms ease-in; + display: block; } } @@ -62,12 +54,14 @@ position: absolute; left: 0; bottom: 0; + z-index: 102; +} - &[data-entering] { - animation: slide-up 200ms; - } - &[data-exiting] { - animation: slide-up 200ms reverse ease-in-out; +@media screen and (min-width: 768px) { + .modal { + left: auto; + bottom: auto; + width: 400px; } } @@ -82,14 +76,6 @@ overflow: hidden; } -@media screen and (min-width: 768px) { - .modal { - left: auto; - bottom: auto; - width: 400px; - } -} - .top { --button-height: 32px; box-sizing: content-box; @@ -160,3 +146,12 @@ display: flex; align-items: center; } + +/* + * temporary fix until next version of tsparticles is released + * https://github.com/tsparticles/tsparticles/issues/5375 + */ +.confetti { + position: relative; + z-index: 101; +} diff --git a/package-lock.json b/package-lock.json index e611379b4..d1a2b5f1f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -27,6 +27,7 @@ "@trpc/client": "^11.0.0-rc.467", "@trpc/react-query": "^11.0.0-rc.467", "@trpc/server": "^11.0.0-rc.467", + "@tsparticles/confetti": "^3.5.0", "@vercel/otel": "^1.9.1", "@vis.gl/react-google-maps": "^1.2.0", "class-variance-authority": "^0.7.0", @@ -6402,6 +6403,279 @@ "integrity": "sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA==", "dev": true }, + "node_modules/@tsparticles/basic": { + "version": "3.5.0", + "resolved": "https://registry.npmjs.org/@tsparticles/basic/-/basic-3.5.0.tgz", + "integrity": "sha512-oty33TxM2aHWrzcwWRic1bQ04KBCdpnvzv8JXEkx5Uyp70vgVegUbtKmwGki3shqKZIt3v2qE4I8NsK6onhLrA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/matteobruni" + }, + { + "type": "github", + "url": "https://github.com/sponsors/tsparticles" + }, + { + "type": "buymeacoffee", + "url": "https://www.buymeacoffee.com/matteobruni" + } + ], + "dependencies": { + "@tsparticles/engine": "^3.5.0", + "@tsparticles/move-base": "^3.5.0", + "@tsparticles/shape-circle": "^3.5.0", + "@tsparticles/updater-color": "^3.5.0", + "@tsparticles/updater-opacity": "^3.5.0", + "@tsparticles/updater-out-modes": "^3.5.0", + "@tsparticles/updater-size": "^3.5.0" + } + }, + "node_modules/@tsparticles/confetti": { + "version": "3.5.0", + "resolved": "https://registry.npmjs.org/@tsparticles/confetti/-/confetti-3.5.0.tgz", + "integrity": "sha512-wS3nqtanbCvAbNlyAffKJq6lgIPzHFljEOO3JSCDgRD6rG5X/jvidhw2vR3kLrjBTV40c+Xv6MpJgSgTRWkogg==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/matteobruni" + }, + { + "type": "github", + "url": "https://github.com/sponsors/tsparticles" + }, + { + "type": "buymeacoffee", + "url": "https://www.buymeacoffee.com/matteobruni" + } + ], + "dependencies": { + "@tsparticles/basic": "^3.5.0", + "@tsparticles/engine": "^3.5.0", + "@tsparticles/plugin-emitters": "^3.5.0", + "@tsparticles/plugin-motion": "^3.5.0", + "@tsparticles/shape-cards": "^3.5.0", + "@tsparticles/shape-emoji": "^3.5.0", + "@tsparticles/shape-heart": "^3.5.0", + "@tsparticles/shape-image": "^3.5.0", + "@tsparticles/shape-polygon": "^3.5.0", + "@tsparticles/shape-square": "^3.5.0", + "@tsparticles/shape-star": "^3.5.0", + "@tsparticles/updater-life": "^3.5.0", + "@tsparticles/updater-roll": "^3.5.0", + "@tsparticles/updater-rotate": "^3.5.0", + "@tsparticles/updater-tilt": "^3.5.0", + "@tsparticles/updater-wobble": "^3.5.0" + } + }, + "node_modules/@tsparticles/engine": { + "version": "3.5.0", + "resolved": "https://registry.npmjs.org/@tsparticles/engine/-/engine-3.5.0.tgz", + "integrity": "sha512-RCwrJ2SvSYdhXJ24oUCjSUKEZQ9lXwObOWMvfMC9vS6/bk+Qo0N7Xx8AfumqzP/LebB1YJdlCvuoJMauAon0Pg==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/matteobruni" + }, + { + "type": "github", + "url": "https://github.com/sponsors/tsparticles" + }, + { + "type": "buymeacoffee", + "url": "https://www.buymeacoffee.com/matteobruni" + } + ], + "hasInstallScript": true + }, + "node_modules/@tsparticles/move-base": { + "version": "3.5.0", + "resolved": "https://registry.npmjs.org/@tsparticles/move-base/-/move-base-3.5.0.tgz", + "integrity": "sha512-9oDk7zTxyhUCstj3lHTNTiWAgqIBzWa2o1tVQFK63Qwq+/WxzJCSwZOocC9PAHGM1IP6nA4zYJSfjbMBTrUocA==", + "dependencies": { + "@tsparticles/engine": "^3.5.0" + } + }, + "node_modules/@tsparticles/plugin-emitters": { + "version": "3.5.0", + "resolved": "https://registry.npmjs.org/@tsparticles/plugin-emitters/-/plugin-emitters-3.5.0.tgz", + "integrity": "sha512-8Vg6wAPS75ibkukqtTM7yoC+8NnfXBl8xVUUbTaoeQCE0WDWwztboMf5L4pUgWe9WA52ZgFkWtT/mFH5wk5T9g==", + "dependencies": { + "@tsparticles/engine": "^3.5.0" + } + }, + "node_modules/@tsparticles/plugin-motion": { + "version": "3.5.0", + "resolved": "https://registry.npmjs.org/@tsparticles/plugin-motion/-/plugin-motion-3.5.0.tgz", + "integrity": "sha512-juP8f9ABjlhQmg4SO+tTofLYJwvwLPfKWJYvG8c6HU2rlJxJ/6eeWe9kDpv/T8nun3kXYHtrLhcJAmvWg/b5qA==", + "dependencies": { + "@tsparticles/engine": "^3.5.0" + } + }, + "node_modules/@tsparticles/shape-cards": { + "version": "3.5.0", + "resolved": "https://registry.npmjs.org/@tsparticles/shape-cards/-/shape-cards-3.5.0.tgz", + "integrity": "sha512-rU7rp1Yn1leHpCNA/7vrfY6tcLjvrG6A6sOT11dSanIj2J8zgLNXnbVtRJPtU13x+masft9Ta1tpw3dFRdtHcA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/matteobruni" + }, + { + "type": "github", + "url": "https://github.com/sponsors/tsparticles" + }, + { + "type": "buymeacoffee", + "url": "https://www.buymeacoffee.com/matteobruni" + } + ], + "dependencies": { + "@tsparticles/engine": "^3.5.0" + } + }, + "node_modules/@tsparticles/shape-circle": { + "version": "3.5.0", + "resolved": "https://registry.npmjs.org/@tsparticles/shape-circle/-/shape-circle-3.5.0.tgz", + "integrity": "sha512-59TmXkeeI6Jzv5vt/D3TkclglabaoEXQi2kbDjSCBK68SXRHzlQu29mSAL41Y5S0Ft5ZJKkAQHX1IqEnm8Hyjg==", + "dependencies": { + "@tsparticles/engine": "^3.5.0" + } + }, + "node_modules/@tsparticles/shape-emoji": { + "version": "3.5.0", + "resolved": "https://registry.npmjs.org/@tsparticles/shape-emoji/-/shape-emoji-3.5.0.tgz", + "integrity": "sha512-cxWHxQxnG5vLDltkoxdo7KS87uKPwQgf4SDWy/WCxW4Psm1TEeeSGYMJPVed+wWPspOKmLb7u8OaEexgE2pHHQ==", + "dependencies": { + "@tsparticles/engine": "^3.5.0" + } + }, + "node_modules/@tsparticles/shape-heart": { + "version": "3.5.0", + "resolved": "https://registry.npmjs.org/@tsparticles/shape-heart/-/shape-heart-3.5.0.tgz", + "integrity": "sha512-MvOxW6X7w1jHH+KRJShvHMDhRZ+bpei2mAqQOFR5HY+2D6KFzaDVgtfGFwoiaX8Pm6oP6OQssQ3QnDtrywLRFw==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/matteobruni" + }, + { + "type": "github", + "url": "https://github.com/sponsors/tsparticles" + }, + { + "type": "buymeacoffee", + "url": "https://www.buymeacoffee.com/matteobruni" + } + ], + "dependencies": { + "@tsparticles/engine": "^3.5.0" + } + }, + "node_modules/@tsparticles/shape-image": { + "version": "3.5.0", + "resolved": "https://registry.npmjs.org/@tsparticles/shape-image/-/shape-image-3.5.0.tgz", + "integrity": "sha512-lWYg7DTv74dSOnXy+4dr7t1/OSuUmxDpIo12Lbxgx/QBN7A5I/HoqbKcs13TSA0RS1hcuMgtti07BcDTEYW3Dw==", + "dependencies": { + "@tsparticles/engine": "^3.5.0" + } + }, + "node_modules/@tsparticles/shape-polygon": { + "version": "3.5.0", + "resolved": "https://registry.npmjs.org/@tsparticles/shape-polygon/-/shape-polygon-3.5.0.tgz", + "integrity": "sha512-sqYL+YXpnq3nSWcOEGZaJ4Z7Cb7x8M0iORSLpPdNEIvwDKdPczYyQM95D8ep19Pv1CV5L0uRthV36wg7UpnJ9Q==", + "dependencies": { + "@tsparticles/engine": "^3.5.0" + } + }, + "node_modules/@tsparticles/shape-square": { + "version": "3.5.0", + "resolved": "https://registry.npmjs.org/@tsparticles/shape-square/-/shape-square-3.5.0.tgz", + "integrity": "sha512-rPHpA4Pzm1W5DIIow+lQS+VS7D2thSBQQbV9eHxb933Wh0/QC3me3w4vovuq7hdtVANhsUVO04n44Gc/2TgHkw==", + "dependencies": { + "@tsparticles/engine": "^3.5.0" + } + }, + "node_modules/@tsparticles/shape-star": { + "version": "3.5.0", + "resolved": "https://registry.npmjs.org/@tsparticles/shape-star/-/shape-star-3.5.0.tgz", + "integrity": "sha512-EDEJc4MYv3UbOeA3wrZjuJVtZ08PdCzzBij3T/7Tp3HUCf/p9XnfHBd/CPR5Mo6X0xpGfrein8UQN9CjGLHwUA==", + "dependencies": { + "@tsparticles/engine": "^3.5.0" + } + }, + "node_modules/@tsparticles/updater-color": { + "version": "3.5.0", + "resolved": "https://registry.npmjs.org/@tsparticles/updater-color/-/updater-color-3.5.0.tgz", + "integrity": "sha512-TGGgiLixIg37sst2Fj9IV4XbdMwkT6PYanM7qEqyfmv4hJ/RHMQlCznEe6b7OhChQVBg5ov5EMl/BT4/fIWEYw==", + "dependencies": { + "@tsparticles/engine": "^3.5.0" + } + }, + "node_modules/@tsparticles/updater-life": { + "version": "3.5.0", + "resolved": "https://registry.npmjs.org/@tsparticles/updater-life/-/updater-life-3.5.0.tgz", + "integrity": "sha512-jlMEq16dwN+rZmW/UmLdqaCe4W0NFrVdmXkZV8QWYgu06a+Ucslz337nHYaP89/9rZWpNua/uq1JDjDzaVD5Jg==", + "dependencies": { + "@tsparticles/engine": "^3.5.0" + } + }, + "node_modules/@tsparticles/updater-opacity": { + "version": "3.5.0", + "resolved": "https://registry.npmjs.org/@tsparticles/updater-opacity/-/updater-opacity-3.5.0.tgz", + "integrity": "sha512-T2YfqdIFV/f5VOg1JQsXu6/owdi9g9K2wrJlBfgteo+IboVp6Lcuo4PGAfilWVkWrTdp1Nz4mz39NrLHfOce2g==", + "dependencies": { + "@tsparticles/engine": "^3.5.0" + } + }, + "node_modules/@tsparticles/updater-out-modes": { + "version": "3.5.0", + "resolved": "https://registry.npmjs.org/@tsparticles/updater-out-modes/-/updater-out-modes-3.5.0.tgz", + "integrity": "sha512-y6NZe2OSk5SrYdaLwUIQnHICsNEDIdPPJHQ2nAWSvAuPJphlSKjUknc7OaGiFwle6l0OkhWoZZe1rV1ktbw/lA==", + "dependencies": { + "@tsparticles/engine": "^3.5.0" + } + }, + "node_modules/@tsparticles/updater-roll": { + "version": "3.5.0", + "resolved": "https://registry.npmjs.org/@tsparticles/updater-roll/-/updater-roll-3.5.0.tgz", + "integrity": "sha512-K3NfBGqVIu2zyJv72oNPlYLMDQKmUXTaCvnxUjzBEJJCYRdx7KhZPQVjAsfVYLHd7m7D7/+wKlkXmdYYAd67bg==", + "dependencies": { + "@tsparticles/engine": "^3.5.0" + } + }, + "node_modules/@tsparticles/updater-rotate": { + "version": "3.5.0", + "resolved": "https://registry.npmjs.org/@tsparticles/updater-rotate/-/updater-rotate-3.5.0.tgz", + "integrity": "sha512-j4qPHQd1eUmDoGnIJOsVswHLqtTof1je+b2GTOLB3WIoKmlyUpzQYjVc7PNfLMuCEUubwpZCfcd/vC80VZeWkg==", + "dependencies": { + "@tsparticles/engine": "^3.5.0" + } + }, + "node_modules/@tsparticles/updater-size": { + "version": "3.5.0", + "resolved": "https://registry.npmjs.org/@tsparticles/updater-size/-/updater-size-3.5.0.tgz", + "integrity": "sha512-TnWlOChBsVZffT2uO0S4ALGSzxT6UAMIVlhGMGFgSeIlktKMqM+dxDGAPrYa1n8IS2dkVGisiXzsV0Ss6Ceu1A==", + "dependencies": { + "@tsparticles/engine": "^3.5.0" + } + }, + "node_modules/@tsparticles/updater-tilt": { + "version": "3.5.0", + "resolved": "https://registry.npmjs.org/@tsparticles/updater-tilt/-/updater-tilt-3.5.0.tgz", + "integrity": "sha512-ovK6jH4fAmTav1kCC5Z1FW/pPjKxtK+X+w9BZJEddpS5cyBEdWD4FgvNgLnmZYpK0xad/nb+xxqeDkpSu/O51Q==", + "dependencies": { + "@tsparticles/engine": "^3.5.0" + } + }, + "node_modules/@tsparticles/updater-wobble": { + "version": "3.5.0", + "resolved": "https://registry.npmjs.org/@tsparticles/updater-wobble/-/updater-wobble-3.5.0.tgz", + "integrity": "sha512-fpN0XPvAf3dJ5UU++C+ETVDLurpnkzje02w865Ar4ubPBgGpMhowr6AbtFUe37Zl8rFUTYntBOSEoxqNYJAUgQ==", + "dependencies": { + "@tsparticles/engine": "^3.5.0" + } + }, "node_modules/@types/aria-query": { "version": "5.0.4", "resolved": "https://registry.npmjs.org/@types/aria-query/-/aria-query-5.0.4.tgz", diff --git a/package.json b/package.json index 5871b48b4..7baa98fe1 100644 --- a/package.json +++ b/package.json @@ -42,6 +42,7 @@ "@trpc/client": "^11.0.0-rc.467", "@trpc/react-query": "^11.0.0-rc.467", "@trpc/server": "^11.0.0-rc.467", + "@tsparticles/confetti": "^3.5.0", "@vercel/otel": "^1.9.1", "@vis.gl/react-google-maps": "^1.2.0", "class-variance-authority": "^0.7.0", diff --git a/types/components/blocks/surprises.ts b/types/components/blocks/surprises.ts index da4a8730c..674c52d39 100644 --- a/types/components/blocks/surprises.ts +++ b/types/components/blocks/surprises.ts @@ -9,3 +9,27 @@ export interface SurprisesProps { surprises: Surprise[] membershipNumber?: string } + +export interface NavigationProps { + selectedSurprise: number + totalSurprises: number + showSurprise: (direction: number) => void +} + +export interface CardProps extends React.PropsWithChildren { + title?: string +} + +export interface InitialProps { + totalSurprises: number + onOpen: VoidFunction +} + +export interface SlideProps { + surprise: Surprise + membershipNumber?: string +} + +export interface HeaderProps extends React.PropsWithChildren { + onClose: VoidFunction +} From e294d39fdf12e9696fdb437c14490bca1d32ce5c Mon Sep 17 00:00:00 2001 From: Christian Andolf Date: Tue, 19 Nov 2024 09:09:50 +0100 Subject: [PATCH 03/21] fix(SW-696): add unwrap metrics --- server/routers/contentstack/reward/query.ts | 19 ++++++++++++++++++- server/routers/contentstack/reward/utils.ts | 9 +++++++++ 2 files changed, 27 insertions(+), 1 deletion(-) diff --git a/server/routers/contentstack/reward/query.ts b/server/routers/contentstack/reward/query.ts index 0deede637..5fdf7da82 100644 --- a/server/routers/contentstack/reward/query.ts +++ b/server/routers/contentstack/reward/query.ts @@ -34,6 +34,9 @@ import { getCurrentRewardFailCounter, getCurrentRewardSuccessCounter, getUniqueRewardIds, + getUnwrapSurpriseCounter, + getUnwrapSurpriseFailCounter, + getUnwrapSurpriseSuccessCounter, } from "./utils" const ONE_HOUR = 60 * 60 @@ -366,6 +369,8 @@ export const rewardQueryRouter = router({ unwrap: protectedProcedure .input(rewardsUpdateInput) .mutation(async ({ input, ctx }) => { + getUnwrapSurpriseCounter.add(1) + const promises = input.map(({ rewardId, couponCode }) => { return api.post(api.endpoints.v1.Profile.Reward.unwrap, { body: { @@ -384,8 +389,18 @@ export const rewardQueryRouter = router({ responses.map(async (apiResponse) => { if (!apiResponse.ok) { const text = await apiResponse.text() + + getUnwrapSurpriseFailCounter.add(1, { + error_type: "http_error", + error: JSON.stringify({ + status: apiResponse.status, + statusText: apiResponse.statusText, + text, + }), + }) + console.error( - "contentstack.unwrap validation error", + "contentstack.unwrap API error", JSON.stringify({ error: { status: apiResponse.status, @@ -405,6 +420,8 @@ export const rewardQueryRouter = router({ return null } + getUnwrapSurpriseSuccessCounter.add(1) + return true }), }) diff --git a/server/routers/contentstack/reward/utils.ts b/server/routers/contentstack/reward/utils.ts index d06f2666b..d0f2e73c9 100644 --- a/server/routers/contentstack/reward/utils.ts +++ b/server/routers/contentstack/reward/utils.ts @@ -44,6 +44,15 @@ export const getByLevelRewardFailCounter = meter.createCounter( export const getByLevelRewardSuccessCounter = meter.createCounter( "trpc.contentstack.reward.byLevel-success" ) +export const getUnwrapSurpriseCounter = meter.createCounter( + "trpc.contentstack.reward.unwrap" +) +export const getUnwrapSurpriseFailCounter = meter.createCounter( + "trpc.contentstack.reward.unwrap-fail" +) +export const getUnwrapSurpriseSuccessCounter = meter.createCounter( + "trpc.contentstack.reward.unwrap-success" +) const ONE_HOUR = 60 * 60 From 848338248fc381299a507821a9937c85d1cf1036 Mon Sep 17 00:00:00 2001 From: Christian Andolf Date: Tue, 19 Nov 2024 09:12:54 +0100 Subject: [PATCH 04/21] fix(SW-696): remove redundant filter, add translation --- components/MyPages/Surprises/Card.tsx | 6 +++++- components/MyPages/Surprises/Client.tsx | 3 --- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/components/MyPages/Surprises/Card.tsx b/components/MyPages/Surprises/Card.tsx index 932f0ecc9..d3b01a270 100644 --- a/components/MyPages/Surprises/Card.tsx +++ b/components/MyPages/Surprises/Card.tsx @@ -1,3 +1,5 @@ +import { useIntl } from "react-intl" + import Image from "@/components/Image" import Title from "@/components/TempDesignSystem/Text/Title" @@ -6,13 +8,15 @@ import styles from "./surprises.module.css" import type { CardProps } from "@/types/components/blocks/surprises" export default function Card({ title, children }: CardProps) { + const intl = useIntl() + return (
Gift
diff --git a/components/MyPages/Surprises/Client.tsx b/components/MyPages/Surprises/Client.tsx index 2e4202103..f2adf4186 100644 --- a/components/MyPages/Surprises/Client.tsx +++ b/components/MyPages/Surprises/Client.tsx @@ -92,9 +92,6 @@ export default function SurprisesNotification({ return coupons }) .flat() - .filter( - (coupon): coupon is { rewardId: string; couponCode: string } => !!coupon - ) unwrap.mutate(updates) } From 08e71a8dc69e67ddaaf97c216fc8ead2b133b7bd Mon Sep 17 00:00:00 2001 From: Pontus Dreij <pontus.dreij@scandichotels.com> Date: Thu, 21 Nov 2024 14:15:51 +0100 Subject: [PATCH 05/21] fix(946) Center from town name instead of calculate center --- .../select-hotel/@modal/(.)map/page.tsx | 4 +++- .../Map/DynamicMap/Sidebar/index.tsx | 11 ++++++++-- .../HotelPage/Map/DynamicMap/index.tsx | 4 ++-- .../SelectHotel/SelectHotelMap/index.tsx | 14 ++++++------- .../SelectHotel/SelectHotelMap/utils.ts | 17 ---------------- .../InteractiveMap/HotelMapContent/index.tsx | 4 ++-- lib/trpc/memoizedRequests/index.ts | 6 ++++++ server/routers/hotels/input.ts | 3 +++ server/routers/hotels/output.ts | 20 ++++++++++--------- server/routers/hotels/query.ts | 17 ++++++++++++++++ server/routers/hotels/utils.ts | 7 ++++--- .../hotelReservation/selectHotel/map.ts | 1 + 12 files changed, 64 insertions(+), 44 deletions(-) delete mode 100644 components/HotelReservation/SelectHotel/SelectHotelMap/utils.ts 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 7c5573536..cdf236184 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 @@ -1,7 +1,7 @@ import { notFound } from "next/navigation" import { env } from "@/env/server" -import { getLocations } from "@/lib/trpc/memoizedRequests" +import { getCityCoordinates, getLocations } from "@/lib/trpc/memoizedRequests" import { getHotelPins } from "@/components/HotelReservation/HotelCardDialogListing/utils" import SelectHotelMap from "@/components/HotelReservation/SelectHotel/SelectHotelMap" @@ -58,6 +58,7 @@ export default async function SelectHotelMapPage({ const hotelPins = getHotelPins(hotels) const filterList = getFiltersFromHotels(hotels) + const cityCoordinates = await getCityCoordinates({ city: city.name }) return ( <MapModal> @@ -67,6 +68,7 @@ export default async function SelectHotelMapPage({ mapId={googleMapId} hotels={hotels} filterList={filterList} + cityCoordinates={cityCoordinates} /> </MapModal> ) diff --git a/components/ContentType/HotelPage/Map/DynamicMap/Sidebar/index.tsx b/components/ContentType/HotelPage/Map/DynamicMap/Sidebar/index.tsx index db979bf47..d58cb10fc 100644 --- a/components/ContentType/HotelPage/Map/DynamicMap/Sidebar/index.tsx +++ b/components/ContentType/HotelPage/Map/DynamicMap/Sidebar/index.tsx @@ -60,13 +60,20 @@ export default function Sidebar({ } } - function handleMouseEnter(poiName: string) { + function handleMouseEnter(poiName: string | undefined) { + if (!poiName) return + if (!isClicking) { onActivePoiChange(poiName) } } - function handlePoiClick(poiName: string, poiCoordinates: Coordinates) { + function handlePoiClick( + poiName: string | undefined, + poiCoordinates: Coordinates + ) { + if (!poiName || !poiCoordinates) return + setIsClicking(true) toggleFullScreenSidebar() onActivePoiChange(poiName) diff --git a/components/ContentType/HotelPage/Map/DynamicMap/index.tsx b/components/ContentType/HotelPage/Map/DynamicMap/index.tsx index 969b24588..6af68db43 100644 --- a/components/ContentType/HotelPage/Map/DynamicMap/index.tsx +++ b/components/ContentType/HotelPage/Map/DynamicMap/index.tsx @@ -113,7 +113,7 @@ export default function DynamicMap({ activePoi={activePoi} hotelName={hotelName} pointsOfInterest={pointsOfInterest} - onActivePoiChange={setActivePoi} + onActivePoiChange={(poi) => setActivePoi(poi ?? null)} coordinates={coordinates} /> <InteractiveMap @@ -121,7 +121,7 @@ export default function DynamicMap({ coordinates={coordinates} pointsOfInterest={pointsOfInterest} activePoi={activePoi} - onActivePoiChange={setActivePoi} + onActivePoiChange={(poi) => setActivePoi(poi ?? null)} mapId={mapId} /> </Dialog> diff --git a/components/HotelReservation/SelectHotel/SelectHotelMap/index.tsx b/components/HotelReservation/SelectHotel/SelectHotelMap/index.tsx index 4fe186988..0d1d58eee 100644 --- a/components/HotelReservation/SelectHotel/SelectHotelMap/index.tsx +++ b/components/HotelReservation/SelectHotel/SelectHotelMap/index.tsx @@ -7,7 +7,7 @@ import { useMediaQuery } from "usehooks-ts" import { selectHotel } from "@/constants/routes/hotelReservation" -import { ArrowUpIcon, CloseIcon, CloseLargeIcon } from "@/components/Icons" +import { CloseIcon, CloseLargeIcon } from "@/components/Icons" import InteractiveMap from "@/components/Maps/InteractiveMap" import { BackToTopButton } from "@/components/TempDesignSystem/BackToTopButton" import Button from "@/components/TempDesignSystem/Button" @@ -15,7 +15,6 @@ import useLang from "@/hooks/useLang" import FilterAndSortModal from "../FilterAndSortModal" import HotelListing from "./HotelListing" -import { getCentralCoordinates } from "./utils" import styles from "./selectHotelMap.module.css" @@ -27,6 +26,7 @@ export default function SelectHotelMap({ mapId, hotels, filterList, + cityCoordinates, }: SelectHotelMapProps) { const searchParams = useSearchParams() const router = useRouter() @@ -36,15 +36,13 @@ export default function SelectHotelMap({ const [activeHotelPin, setActiveHotelPin] = useState<string | null>(null) const [showBackToTop, setShowBackToTop] = useState<boolean>(false) - const centralCoordinates = getCentralCoordinates(hotelPins) - - const coordinates = isAboveMobile - ? centralCoordinates - : { ...centralCoordinates, lat: centralCoordinates.lat - 0.006 } - const selectHotelParams = new URLSearchParams(searchParams.toString()) const selectedHotel = selectHotelParams.get("selectedHotel") + const coordinates = isAboveMobile + ? cityCoordinates + : { ...cityCoordinates, lat: cityCoordinates.lat - 0.006 } + useEffect(() => { if (selectedHotel) { setActiveHotelPin(selectedHotel) diff --git a/components/HotelReservation/SelectHotel/SelectHotelMap/utils.ts b/components/HotelReservation/SelectHotel/SelectHotelMap/utils.ts deleted file mode 100644 index 7a4c32ecc..000000000 --- a/components/HotelReservation/SelectHotel/SelectHotelMap/utils.ts +++ /dev/null @@ -1,17 +0,0 @@ -import { HotelPin } from "@/types/components/hotelReservation/selectHotel/map" - -export function getCentralCoordinates(hotels: HotelPin[]) { - const centralCoordinates = hotels.reduce( - (acc, pin) => { - acc.lat += pin.coordinates.lat - acc.lng += pin.coordinates.lng - return acc - }, - { lat: 0, lng: 0 } - ) - - centralCoordinates.lat /= hotels.length - centralCoordinates.lng /= hotels.length - - return centralCoordinates -} diff --git a/components/Maps/InteractiveMap/HotelMapContent/index.tsx b/components/Maps/InteractiveMap/HotelMapContent/index.tsx index 04c796c2c..662c66667 100644 --- a/components/Maps/InteractiveMap/HotelMapContent/index.tsx +++ b/components/Maps/InteractiveMap/HotelMapContent/index.tsx @@ -35,9 +35,9 @@ export default function HotelMapContent({ position={poi.coordinates} anchorPoint={AdvancedMarkerAnchorPoint.CENTER} zIndex={activePoi === poi.name ? 2 : 0} - onMouseEnter={() => onActivePoiChange?.(poi.name)} + onMouseEnter={() => onActivePoiChange?.(poi.name ?? null)} onMouseLeave={() => onActivePoiChange?.(null)} - onClick={() => toggleActivePoi(poi.name)} + onClick={() => toggleActivePoi(poi.name ?? "")} > <span className={`${styles.poi} ${activePoi === poi.name ? styles.active : ""}`} diff --git a/lib/trpc/memoizedRequests/index.ts b/lib/trpc/memoizedRequests/index.ts index 4ab1aa5ea..2a97f791c 100644 --- a/lib/trpc/memoizedRequests/index.ts +++ b/lib/trpc/memoizedRequests/index.ts @@ -158,3 +158,9 @@ export const getBookingConfirmation = cache( return serverClient().booking.confirmation({ confirmationNumber }) } ) + +export const getCityCoordinates = cache( + async function getMemoizedCityCoordinates(input: { city: string }) { + return serverClient().hotel.map.get(input) + } +) diff --git a/server/routers/hotels/input.ts b/server/routers/hotels/input.ts index 9bfecaf6a..7fe4c02fd 100644 --- a/server/routers/hotels/input.ts +++ b/server/routers/hotels/input.ts @@ -74,3 +74,6 @@ export const getRoomPackagesInputSchema = z.object({ children: z.number().optional().default(0), packageCodes: z.array(z.string()).optional().default([]), }) +export const getCityCoordinatesInputSchema = z.object({ + city: z.string(), +}) diff --git a/server/routers/hotels/output.ts b/server/routers/hotels/output.ts index f9a5710de..c843a4e84 100644 --- a/server/routers/hotels/output.ts +++ b/server/routers/hotels/output.ts @@ -200,14 +200,14 @@ const rewardNightSchema = z.object({ export const pointOfInterestSchema = z .object({ - name: z.string(), - distance: z.number(), + name: z.string().optional(), + distance: z.number().optional(), category: z.object({ - name: z.string(), - group: z.string(), + name: z.string().optional(), + group: z.string().optional(), }), - location: locationSchema, - isHighlighted: z.boolean(), + location: locationSchema.optional(), + isHighlighted: z.boolean().optional(), }) .transform((poi) => ({ name: poi.name, @@ -215,8 +215,8 @@ export const pointOfInterestSchema = z categoryName: poi.category.name, group: getPoiGroupByCategoryName(poi.category.name), coordinates: { - lat: poi.location.latitude, - lng: poi.location.longitude, + lat: poi.location?.latitude ?? 0, + lng: poi.location?.longitude ?? 0, }, })) @@ -463,7 +463,9 @@ export const getHotelDataSchema = z.object({ parking: z.array(parkingSchema), pointsOfInterest: z .array(pointOfInterestSchema) - .transform((pois) => pois.sort((a, b) => a.distance - b.distance)), + .transform((pois) => + pois.sort((a, b) => (a.distance ?? 0) - (b.distance ?? 0)) + ), ratings: ratingsSchema, rewardNight: rewardNightSchema, restaurantImages: facilitySchema.optional(), diff --git a/server/routers/hotels/query.ts b/server/routers/hotels/query.ts index eb31aacb6..31ffd63f1 100644 --- a/server/routers/hotels/query.ts +++ b/server/routers/hotels/query.ts @@ -30,6 +30,7 @@ import { import { getVerifiedUser, parsedUser } from "../user/query" import { getBreakfastPackageInputSchema, + getCityCoordinatesInputSchema, getHotelDataInputSchema, getHotelsAvailabilityInputSchema, getRatesInputSchema, @@ -1078,4 +1079,20 @@ export const hotelQueryRouter = router({ ) }), }), + map: router({ + get: serviceProcedure + .input(getCityCoordinatesInputSchema) + .query(async function ({ input }) { + const apiKey = process.env.GOOGLE_STATIC_MAP_KEY + const { city } = input + const url = `https://maps.googleapis.com/maps/api/geocode/json?address=${encodeURIComponent(city)}&key=${apiKey}` + + const response = await fetch(url) + const data = await response.json() + console.log("DATA_RESPONSE", data) + const { lat, lng } = data.results[0].geometry.location + + return { lat, lng } + }), + }), }) diff --git a/server/routers/hotels/utils.ts b/server/routers/hotels/utils.ts index c3e785a59..3e5b5f5e4 100644 --- a/server/routers/hotels/utils.ts +++ b/server/routers/hotels/utils.ts @@ -12,13 +12,14 @@ import { type Countries, } from "./output" -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" +import type { Lang } from "@/constants/languages" +import type { Endpoint } from "@/lib/api/endpoints" -export function getPoiGroupByCategoryName(category: string) { +export function getPoiGroupByCategoryName(category: string | undefined) { + if (!category) return PointOfInterestGroupEnum.LOCATION switch (category) { case "Airport": case "Bus terminal": diff --git a/types/components/hotelReservation/selectHotel/map.ts b/types/components/hotelReservation/selectHotel/map.ts index 490f8d06e..4ec21f86f 100644 --- a/types/components/hotelReservation/selectHotel/map.ts +++ b/types/components/hotelReservation/selectHotel/map.ts @@ -22,6 +22,7 @@ export interface SelectHotelMapProps { mapId: string hotels: HotelData[] filterList: CategorizedFilters + cityCoordinates: Coordinates } type ImageSizes = z.infer<typeof imageSizesSchema> From f725c46dcfce9281a7d8629a2748c8c1e19fc2f1 Mon Sep 17 00:00:00 2001 From: Pontus Dreij <pontus.dreij@scandichotels.com> Date: Thu, 21 Nov 2024 14:25:47 +0100 Subject: [PATCH 06/21] fix(SW-946) Removed log --- server/routers/hotels/query.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/server/routers/hotels/query.ts b/server/routers/hotels/query.ts index 31ffd63f1..e45ced06f 100644 --- a/server/routers/hotels/query.ts +++ b/server/routers/hotels/query.ts @@ -1089,7 +1089,6 @@ export const hotelQueryRouter = router({ const response = await fetch(url) const data = await response.json() - console.log("DATA_RESPONSE", data) const { lat, lng } = data.results[0].geometry.location return { lat, lng } From 87c12f67095d2bf8bf0ddcd8676ca4b1e306b1d5 Mon Sep 17 00:00:00 2001 From: Pontus Dreij <pontus.dreij@scandichotels.com> Date: Thu, 21 Nov 2024 14:26:37 +0100 Subject: [PATCH 07/21] fix(SW-946) renamde query --- lib/trpc/memoizedRequests/index.ts | 2 +- server/routers/hotels/query.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/trpc/memoizedRequests/index.ts b/lib/trpc/memoizedRequests/index.ts index 2a97f791c..75e58765f 100644 --- a/lib/trpc/memoizedRequests/index.ts +++ b/lib/trpc/memoizedRequests/index.ts @@ -161,6 +161,6 @@ export const getBookingConfirmation = cache( export const getCityCoordinates = cache( async function getMemoizedCityCoordinates(input: { city: string }) { - return serverClient().hotel.map.get(input) + return serverClient().hotel.map.city(input) } ) diff --git a/server/routers/hotels/query.ts b/server/routers/hotels/query.ts index e45ced06f..73f617fc4 100644 --- a/server/routers/hotels/query.ts +++ b/server/routers/hotels/query.ts @@ -1080,7 +1080,7 @@ export const hotelQueryRouter = router({ }), }), map: router({ - get: serviceProcedure + city: serviceProcedure .input(getCityCoordinatesInputSchema) .query(async function ({ input }) { const apiKey = process.env.GOOGLE_STATIC_MAP_KEY From 1aeed2e9ca4d2eade5acfec66251ccf79d804155 Mon Sep 17 00:00:00 2001 From: Simon Emanuelsson <simon.emanuelsson@scandichotels.com> Date: Tue, 19 Nov 2024 14:25:55 +0100 Subject: [PATCH 08/21] fix: make sure session storage is cleaner whenever user is no longer in the booking flow --- app/[lang]/(live)/layout.tsx | 2 ++ .../EnterDetails/StorageCleaner.tsx | 26 +++++++++++++++++++ constants/routes/hotelReservation.js | 16 ++++++------ next-env.d.ts | 2 +- stores/details.ts | 7 ----- types/stores/details.ts | 1 - 6 files changed, 37 insertions(+), 17 deletions(-) create mode 100644 components/HotelReservation/EnterDetails/StorageCleaner.tsx diff --git a/app/[lang]/(live)/layout.tsx b/app/[lang]/(live)/layout.tsx index e1c63bbbc..037f2d714 100644 --- a/app/[lang]/(live)/layout.tsx +++ b/app/[lang]/(live)/layout.tsx @@ -9,6 +9,7 @@ import TrpcProvider from "@/lib/trpc/Provider" import TokenRefresher from "@/components/Auth/TokenRefresher" import AdobeSDKScript from "@/components/Current/AdobeSDKScript" import VwoScript from "@/components/Current/VwoScript" +import StorageCleaner from "@/components/HotelReservation/EnterDetails/StorageCleaner" import { ToastHandler } from "@/components/TempDesignSystem/Toasts" import { preloadUserTracking } from "@/components/TrackingSDK" import { getIntl } from "@/i18n" @@ -64,6 +65,7 @@ export default async function RootLayout({ {footer} <ToastHandler /> <TokenRefresher /> + <StorageCleaner /> </TrpcProvider> </ServerIntlProvider> </body> diff --git a/components/HotelReservation/EnterDetails/StorageCleaner.tsx b/components/HotelReservation/EnterDetails/StorageCleaner.tsx new file mode 100644 index 000000000..96fdf3105 --- /dev/null +++ b/components/HotelReservation/EnterDetails/StorageCleaner.tsx @@ -0,0 +1,26 @@ +"use client" +import { usePathname } from "next/navigation" +import { useEffect } from "react" + +import { hotelreservation } from "@/constants/routes/hotelReservation" +import { detailsStorageName } from "@/stores/details" + +import useLang from "@/hooks/useLang" + +/** + * Cleanup component to make sure no stale data is left + * from previous booking when user is not in the booking + * flow anymore + */ +export default function StorageCleaner() { + const lang = useLang() + const pathname = usePathname() + + useEffect(() => { + if (!pathname.startsWith(hotelreservation(lang))) { + sessionStorage.removeItem(detailsStorageName) + } + }, [lang, pathname]) + + return null +} diff --git a/constants/routes/hotelReservation.js b/constants/routes/hotelReservation.js index ed7ae99c5..dffc0acb2 100644 --- a/constants/routes/hotelReservation.js +++ b/constants/routes/hotelReservation.js @@ -5,7 +5,7 @@ /** * @param {Lang} lang */ -function base(lang) { +export function hotelreservation(lang) { return `/${lang}/hotelreservation` } @@ -13,47 +13,47 @@ function base(lang) { * @param {Lang} lang */ export function bookingConfirmation(lang) { - return `${base(lang)}/booking-confirmation` + return `${hotelreservation(lang)}/booking-confirmation` } /** * @param {Lang} lang */ export function details(lang) { - return `${base(lang)}/details` + return `${hotelreservation(lang)}/details` } /** * @param {Lang} lang */ export function payment(lang) { - return `${base(lang)}/payment` + return `${hotelreservation(lang)}/payment` } /** * @param {Lang} lang */ export function selectBed(lang) { - return `${base(lang)}/select-bed` + return `${hotelreservation(lang)}/select-bed` } /** * @param {Lang} lang */ export function selectHotel(lang) { - return `${base(lang)}/select-hotel` + return `${hotelreservation(lang)}/select-hotel` } /** * @param {Lang} lang */ export function selectHotelMap(lang) { - return `${base(lang)}/select-hotel/map` + return `${hotelreservation(lang)}/select-hotel/map` } /** * @param {Lang} lang */ export function selectRate(lang) { - return `${base(lang)}/select-rate` + return `${hotelreservation(lang)}/select-rate` } diff --git a/next-env.d.ts b/next-env.d.ts index 4f11a03dc..40c3d6809 100644 --- a/next-env.d.ts +++ b/next-env.d.ts @@ -2,4 +2,4 @@ /// <reference types="next/image-types/global" /> // NOTE: This file should not be edited -// see https://nextjs.org/docs/basic-features/typescript for more information. +// see https://nextjs.org/docs/app/building-your-application/configuring/typescript for more information. diff --git a/stores/details.ts b/stores/details.ts index 30bf1ed81..250262ade 100644 --- a/stores/details.ts +++ b/stores/details.ts @@ -101,13 +101,6 @@ export function createDetailsStore( }) ) }, - updateValidity(property, isValid) { - return set( - produce((state: DetailsState) => { - state.isValid[property] = isValid - }) - ) - }, }, data: merge( diff --git a/types/stores/details.ts b/types/stores/details.ts index d10bb0d61..72b7f490b 100644 --- a/types/stores/details.ts +++ b/types/stores/details.ts @@ -12,7 +12,6 @@ export interface DetailsState { updateBedType: (data: BedTypeSchema) => void updateBreakfast: (data: BreakfastPackage | false) => void updateDetails: (data: DetailsSchema) => void - updateValidity: (property: StepEnum, isValid: boolean) => void } data: DetailsSchema & { bedType: BedTypeSchema | undefined From 9344db7876c925f5317a5bd856fa9bbb7fff8efe Mon Sep 17 00:00:00 2001 From: Fredrik Thorsson <fredrik.thorsson@scandichotels.com> Date: Mon, 18 Nov 2024 13:15:25 +0100 Subject: [PATCH 09/21] feat(SW-750): add about the hotel sidepeek --- .../contactInformation.module.css | 47 +++++++ .../ContactInformation/index.tsx | 116 ++++++++++++++++++ .../AboutTheHotel/aboutTheHotel.module.css | 5 + .../SidePeeks/AboutTheHotel/index.tsx | 53 ++++++++ components/ContentType/HotelPage/index.tsx | 20 +-- .../Divider/divider.module.css | 4 + .../TempDesignSystem/Divider/variants.ts | 1 + server/routers/hotels/query.ts | 5 +- .../hotelPage/sidepeek/aboutTheHotel.ts | 13 ++ .../hotelPage/sidepeek/contactInformation.ts | 12 ++ 10 files changed, 268 insertions(+), 8 deletions(-) create mode 100644 components/ContentType/HotelPage/SidePeeks/AboutTheHotel/ContactInformation/contactInformation.module.css create mode 100644 components/ContentType/HotelPage/SidePeeks/AboutTheHotel/ContactInformation/index.tsx create mode 100644 components/ContentType/HotelPage/SidePeeks/AboutTheHotel/aboutTheHotel.module.css create mode 100644 components/ContentType/HotelPage/SidePeeks/AboutTheHotel/index.tsx create mode 100644 types/components/hotelPage/sidepeek/aboutTheHotel.ts create mode 100644 types/components/hotelPage/sidepeek/contactInformation.ts diff --git a/components/ContentType/HotelPage/SidePeeks/AboutTheHotel/ContactInformation/contactInformation.module.css b/components/ContentType/HotelPage/SidePeeks/AboutTheHotel/ContactInformation/contactInformation.module.css new file mode 100644 index 000000000..ac5ce5db3 --- /dev/null +++ b/components/ContentType/HotelPage/SidePeeks/AboutTheHotel/ContactInformation/contactInformation.module.css @@ -0,0 +1,47 @@ +.wrapper { + display: flex; + flex-direction: column; + gap: var(--Spacing-x-one-and-half); +} + +.placeholder { + display: grid; + grid-template-columns: 1fr 1fr; + gap: var(--Spacing-x2); + grid-template-areas: + "address drivingDirections" + "contact socials" + "email email" + "ecoLabel ecoLabel"; +} + +.address { + grid-area: address; +} + +.drivingDirections { + grid-area: drivingDirections; +} + +.contact { + grid-area: contact; +} + +.socials { + grid-area: socials; +} + +.socialIcons { + display: flex; + gap: var(--Spacing-x1); +} + +.email { + grid-area: email; +} + +.ecoLabel { + grid-area: ecoLabel; + display: flex; + gap: var(--Spacing-x-one-and-half); +} diff --git a/components/ContentType/HotelPage/SidePeeks/AboutTheHotel/ContactInformation/index.tsx b/components/ContentType/HotelPage/SidePeeks/AboutTheHotel/ContactInformation/index.tsx new file mode 100644 index 000000000..c2b041b23 --- /dev/null +++ b/components/ContentType/HotelPage/SidePeeks/AboutTheHotel/ContactInformation/index.tsx @@ -0,0 +1,116 @@ +import { InstagramIcon } from "@/components/Icons" +import FacebookIcon from "@/components/Icons/Facebook" +import Image from "@/components/Image" +import Link from "@/components/TempDesignSystem/Link" +import Body from "@/components/TempDesignSystem/Text/Body" +import Caption from "@/components/TempDesignSystem/Text/Caption" +import Subtitle from "@/components/TempDesignSystem/Text/Subtitle" +import { getIntl } from "@/i18n" +import { getLang } from "@/i18n/serverContext" + +import styles from "./contactInformation.module.css" + +import type { ContactInformationProps } from "@/types/components/hotelPage/sidepeek/contactInformation" + +export default async function ContactInformation({ + adress, + coordinates, + contact, + socials, + ecoLabels, +}: ContactInformationProps) { + const intl = await getIntl() + const lang = getLang() + return ( + <div className={styles.wrapper}> + <Subtitle color="burgundy"> + {intl.formatMessage({ id: "Practical information" })} + </Subtitle> + <div className={styles.placeholder}> + <div className={styles.address}> + <Body textTransform="bold"> + {intl.formatMessage({ id: "Address" })} + </Body> + <Body color="uiTextHighContrast">{adress.streetAddress}</Body> + <Body color="uiTextHighContrast">{adress.city}</Body> + </div> + <div className={styles.drivingDirections}> + <Body textTransform="bold"> + {intl.formatMessage({ id: "Driving directions" })} + </Body> + <Link + href={`https://www.google.com/maps/dir/?api=1&destination=${coordinates.lat},${coordinates.lng}`} + target="_blank" + color="peach80" + textDecoration="underline" + > + Google Maps + </Link> + </div> + <div className={styles.contact}> + <Body textTransform="bold"> + {intl.formatMessage({ id: "Contact us" })} + </Body> + <Body> + <Link + href={`tel:+${contact.phoneNumber}`} + color="peach80" + textDecoration="underline" + > + {contact.phoneNumber} + </Link> + </Body> + </div> + <div className={styles.socials}> + <Body textTransform="bold"> + {intl.formatMessage({ id: "Follow us" })} + </Body> + <div className={styles.socialIcons}> + <Link + href={ + socials.instagram || "https://www.instagram.com/scandichotels/" + } + > + <InstagramIcon color="burgundy" /> + </Link> + <Link + href={socials.facebook || "https://www.facebook.com/Scandic/"} + > + <FacebookIcon color="burgundy" /> + </Link> + </div> + </div> + <div className={styles.email}> + <Body textTransform="bold"> + {intl.formatMessage({ id: "Email" })} + </Body> + <Link + href={`mailto:${contact.email}`} + color="peach80" + textDecoration="underline" + > + {contact.email} + </Link> + </div> + {ecoLabels.nordicEcoLabel ? ( + <div className={styles.ecoLabel}> + <Image + height={38} + width={38} + alt={intl.formatMessage({ id: "Nordic Swan Ecolabel" })} + src={`/_static/img/icons/swan-eco/swan_eco_dark_${lang}.png`} + /> + <div> + <Caption color="uiTextPlaceholder"> + {intl.formatMessage({ id: "Nordic Swan Ecolabel" })} + </Caption> + <Caption color="uiTextPlaceholder"> + {ecoLabels.svanenEcoLabelCertificateNumber} + </Caption> + </div> + </div> + ) : null} + </div> + </div> + ) +} diff --git a/components/ContentType/HotelPage/SidePeeks/AboutTheHotel/aboutTheHotel.module.css b/components/ContentType/HotelPage/SidePeeks/AboutTheHotel/aboutTheHotel.module.css new file mode 100644 index 000000000..00ab8aebe --- /dev/null +++ b/components/ContentType/HotelPage/SidePeeks/AboutTheHotel/aboutTheHotel.module.css @@ -0,0 +1,5 @@ +.wrapper { + display: flex; + flex-direction: column; + gap: var(--Spacing-x3); +} diff --git a/components/ContentType/HotelPage/SidePeeks/AboutTheHotel/index.tsx b/components/ContentType/HotelPage/SidePeeks/AboutTheHotel/index.tsx new file mode 100644 index 000000000..c61809406 --- /dev/null +++ b/components/ContentType/HotelPage/SidePeeks/AboutTheHotel/index.tsx @@ -0,0 +1,53 @@ +import { about } from "@/constants/routes/hotelPageParams" + +import Button from "@/components/TempDesignSystem/Button" +import Divider from "@/components/TempDesignSystem/Divider" +import Link from "@/components/TempDesignSystem/Link" +import SidePeek from "@/components/TempDesignSystem/SidePeek" +import Body from "@/components/TempDesignSystem/Text/Body" +import Preamble from "@/components/TempDesignSystem/Text/Preamble" +import { getIntl } from "@/i18n" +import { getLang } from "@/i18n/serverContext" + +import ContactInformation from "./ContactInformation" + +import styles from "./aboutTheHotel.module.css" + +import type { AboutTheHotelSidePeekProps } from "@/types/components/hotelPage/sidepeek/aboutTheHotel" + +export default async function AboutTheHotelSidePeek({ + hotelAdress, + coordinates, + contact, + socials, + ecoLabels, + descriptions, +}: AboutTheHotelSidePeekProps) { + const lang = getLang() + const intl = await getIntl() + + return ( + <SidePeek + contentKey={about[lang]} + title={intl.formatMessage({ id: "About the hotel" })} + > + <section className={styles.wrapper}> + <ContactInformation + adress={hotelAdress} + coordinates={coordinates} + contact={contact} + socials={socials} + ecoLabels={ecoLabels} + /> + <Divider color="baseSurfaceSutbleHover" /> + <Preamble>{descriptions.short}</Preamble> + <Body>{descriptions.medium}</Body> + <Button fullWidth theme="base" intent="secondary" asChild> + <Link href="#" color="burgundy" weight="bold"> + {intl.formatMessage({ id: "Read more about the hotel" })} + </Link> + </Button> + </section> + </SidePeek> + ) +} diff --git a/components/ContentType/HotelPage/index.tsx b/components/ContentType/HotelPage/index.tsx index a565ea117..5f6c6a0e7 100644 --- a/components/ContentType/HotelPage/index.tsx +++ b/components/ContentType/HotelPage/index.tsx @@ -16,6 +16,7 @@ import MapCard from "./Map/MapCard" import MapWithCardWrapper from "./Map/MapWithCard" import MobileMapToggle from "./Map/MobileMapToggle" import StaticMap from "./Map/StaticMap" +import AboutTheHotelSidePeek from "./SidePeeks/AboutTheHotel" import WellnessAndExerciseSidePeek from "./SidePeeks/WellnessAndExercise" import AmenitiesList from "./AmenitiesList" import Facilities from "./Facilities" @@ -54,6 +55,9 @@ export default async function HotelPage() { faq, alerts, healthFacilities, + contact, + socials, + ecoLabels, } = hotelData const topThreePois = pointsOfInterest.slice(0, 3) @@ -80,7 +84,7 @@ export default async function HotelPage() { <div className={styles.introContainer}> <IntroSection hotelName={hotelName} - hotelDescription={hotelDescription} + hotelDescription={hotelDescription.short} location={hotelLocation} address={hotelAddress} tripAdvisor={hotelRatings?.tripAdvisor} @@ -134,12 +138,14 @@ export default async function HotelPage() { {/* TODO: Render amenities as per the design. */} Read more about the amenities here </SidePeek> - <SidePeek - contentKey={hotelPageParams.about[lang]} - title={intl.formatMessage({ id: "Read more about the hotel" })} - > - Some additional information about the hotel - </SidePeek> + <AboutTheHotelSidePeek + hotelAdress={hotelAddress} + coordinates={coordinates} + contact={contact} + socials={socials} + ecoLabels={ecoLabels} + descriptions={hotelDescription} + /> <SidePeek contentKey={hotelPageParams.restaurantAndBar[lang]} title={intl.formatMessage({ id: "Restaurant & Bar" })} diff --git a/components/TempDesignSystem/Divider/divider.module.css b/components/TempDesignSystem/Divider/divider.module.css index 6753e7533..c992bc096 100644 --- a/components/TempDesignSystem/Divider/divider.module.css +++ b/components/TempDesignSystem/Divider/divider.module.css @@ -51,3 +51,7 @@ .opacity8 { opacity: 0.08; } + +.baseSurfaceSubtleHover { + background-color: var(--Base-Surface-Subtle-Hover); +} diff --git a/components/TempDesignSystem/Divider/variants.ts b/components/TempDesignSystem/Divider/variants.ts index 43f7899e0..a9f25b044 100644 --- a/components/TempDesignSystem/Divider/variants.ts +++ b/components/TempDesignSystem/Divider/variants.ts @@ -13,6 +13,7 @@ export const dividerVariants = cva(styles.divider, { primaryLightSubtle: styles.primaryLightSubtle, subtle: styles.subtle, white: styles.white, + baseSurfaceSutbleHover: styles.baseSurfaceSubtleHover, }, opacity: { 100: styles.opacity100, diff --git a/server/routers/hotels/query.ts b/server/routers/hotels/query.ts index eb31aacb6..3d2bde8ce 100644 --- a/server/routers/hotels/query.ts +++ b/server/routers/hotels/query.ts @@ -342,7 +342,7 @@ export const hotelQueryRouter = router({ return { hotelId, hotelName: hotelAttributes.name, - hotelDescription: hotelAttributes.hotelContent.texts.descriptions.short, + hotelDescription: hotelAttributes.hotelContent.texts.descriptions, hotelLocation: hotelAttributes.location, hotelAddress: hotelAttributes.address, hotelRatings: hotelAttributes.ratings, @@ -355,6 +355,9 @@ export const hotelQueryRouter = router({ alerts: hotelAlerts, faq: contentstackData?.faq, healthFacilities: hotelAttributes.healthFacilities, + contact: hotelAttributes.contactInformation, + socials: hotelAttributes.socialMedia, + ecoLabels: hotelAttributes.hotelFacts.ecoLabels, } }), availability: router({ diff --git a/types/components/hotelPage/sidepeek/aboutTheHotel.ts b/types/components/hotelPage/sidepeek/aboutTheHotel.ts new file mode 100644 index 000000000..2062cf6e4 --- /dev/null +++ b/types/components/hotelPage/sidepeek/aboutTheHotel.ts @@ -0,0 +1,13 @@ +import type { Hotel, HotelAddress } from "@/types/hotel" + +export type AboutTheHotelSidePeekProps = { + hotelAdress: HotelAddress + coordinates: { + lat: number + lng: number + } + contact: Hotel["contactInformation"] + socials: Hotel["socialMedia"] + ecoLabels: Hotel["hotelFacts"]["ecoLabels"] + descriptions: Hotel["hotelContent"]["texts"]["descriptions"] +} diff --git a/types/components/hotelPage/sidepeek/contactInformation.ts b/types/components/hotelPage/sidepeek/contactInformation.ts new file mode 100644 index 000000000..836265a7a --- /dev/null +++ b/types/components/hotelPage/sidepeek/contactInformation.ts @@ -0,0 +1,12 @@ +import type { Hotel, HotelAddress } from "@/types/hotel" + +export type ContactInformationProps = { + adress: HotelAddress + coordinates: { + lat: number + lng: number + } + contact: Hotel["contactInformation"] + socials: Hotel["socialMedia"] + ecoLabels: Hotel["hotelFacts"]["ecoLabels"] +} From fc27339aff740269524134b2cb7e8b8600897908 Mon Sep 17 00:00:00 2001 From: Fredrik Thorsson <fredrik.thorsson@scandichotels.com> Date: Mon, 18 Nov 2024 13:43:34 +0100 Subject: [PATCH 10/21] feat(SW-750): refactor types --- .../AboutTheHotel/ContactInformation/index.tsx | 6 +++--- .../HotelPage/SidePeeks/AboutTheHotel/index.tsx | 2 +- .../hotelPage/sidepeek/contactInformation.ts | 16 +++++----------- 3 files changed, 9 insertions(+), 15 deletions(-) diff --git a/components/ContentType/HotelPage/SidePeeks/AboutTheHotel/ContactInformation/index.tsx b/components/ContentType/HotelPage/SidePeeks/AboutTheHotel/ContactInformation/index.tsx index c2b041b23..a906bc95f 100644 --- a/components/ContentType/HotelPage/SidePeeks/AboutTheHotel/ContactInformation/index.tsx +++ b/components/ContentType/HotelPage/SidePeeks/AboutTheHotel/ContactInformation/index.tsx @@ -13,7 +13,7 @@ import styles from "./contactInformation.module.css" import type { ContactInformationProps } from "@/types/components/hotelPage/sidepeek/contactInformation" export default async function ContactInformation({ - adress, + hotelAdress, coordinates, contact, socials, @@ -31,8 +31,8 @@ export default async function ContactInformation({ <Body textTransform="bold"> {intl.formatMessage({ id: "Address" })} </Body> - <Body color="uiTextHighContrast">{adress.streetAddress}</Body> - <Body color="uiTextHighContrast">{adress.city}</Body> + <Body color="uiTextHighContrast">{hotelAdress.streetAddress}</Body> + <Body color="uiTextHighContrast">{hotelAdress.city}</Body> </div> <div className={styles.drivingDirections}> <Body textTransform="bold"> diff --git a/components/ContentType/HotelPage/SidePeeks/AboutTheHotel/index.tsx b/components/ContentType/HotelPage/SidePeeks/AboutTheHotel/index.tsx index c61809406..d709c4a7c 100644 --- a/components/ContentType/HotelPage/SidePeeks/AboutTheHotel/index.tsx +++ b/components/ContentType/HotelPage/SidePeeks/AboutTheHotel/index.tsx @@ -33,7 +33,7 @@ export default async function AboutTheHotelSidePeek({ > <section className={styles.wrapper}> <ContactInformation - adress={hotelAdress} + hotelAdress={hotelAdress} coordinates={coordinates} contact={contact} socials={socials} diff --git a/types/components/hotelPage/sidepeek/contactInformation.ts b/types/components/hotelPage/sidepeek/contactInformation.ts index 836265a7a..f1eb73196 100644 --- a/types/components/hotelPage/sidepeek/contactInformation.ts +++ b/types/components/hotelPage/sidepeek/contactInformation.ts @@ -1,12 +1,6 @@ -import type { Hotel, HotelAddress } from "@/types/hotel" +import type { AboutTheHotelSidePeekProps } from "./aboutTheHotel" -export type ContactInformationProps = { - adress: HotelAddress - coordinates: { - lat: number - lng: number - } - contact: Hotel["contactInformation"] - socials: Hotel["socialMedia"] - ecoLabels: Hotel["hotelFacts"]["ecoLabels"] -} +export type ContactInformationProps = Omit< + AboutTheHotelSidePeekProps, + "descriptions" +> From d5cdbf7e82a94477d6292f4695ad9f26b4d98248 Mon Sep 17 00:00:00 2001 From: Fredrik Thorsson <fredrik.thorsson@scandichotels.com> Date: Mon, 18 Nov 2024 14:23:37 +0100 Subject: [PATCH 11/21] feat(SW-750): update icon import --- .../HotelPage/IntroSection/index.tsx | 3 +-- .../contactInformation.module.css | 2 +- .../ContactInformation/index.tsx | 27 +++++++++---------- .../SidePeeks/AboutTheHotel/index.tsx | 13 +++++---- components/ContentType/HotelPage/index.tsx | 1 + components/HotelReservation/Contact/index.tsx | 3 +-- .../HotelCardDialog/index.tsx | 3 +-- .../TripAdvisorChip/index.tsx | 2 +- components/Icons/get-icon-by-icon-name.ts | 6 ++--- components/Icons/index.tsx | 2 ++ .../hotelPage/sidepeek/aboutTheHotel.ts | 1 + .../hotelPage/sidepeek/contactInformation.ts | 2 +- 12 files changed, 33 insertions(+), 32 deletions(-) diff --git a/components/ContentType/HotelPage/IntroSection/index.tsx b/components/ContentType/HotelPage/IntroSection/index.tsx index fe2db96c0..453f1e192 100644 --- a/components/ContentType/HotelPage/IntroSection/index.tsx +++ b/components/ContentType/HotelPage/IntroSection/index.tsx @@ -1,7 +1,6 @@ import { about } from "@/constants/routes/hotelPageParams" -import { ChevronRightSmallIcon } from "@/components/Icons" -import TripAdvisorIcon from "@/components/Icons/TripAdvisor" +import { ChevronRightSmallIcon, TripAdvisorIcon } from "@/components/Icons" import Link from "@/components/TempDesignSystem/Link" import BiroScript from "@/components/TempDesignSystem/Text/BiroScript" import Body from "@/components/TempDesignSystem/Text/Body" diff --git a/components/ContentType/HotelPage/SidePeeks/AboutTheHotel/ContactInformation/contactInformation.module.css b/components/ContentType/HotelPage/SidePeeks/AboutTheHotel/ContactInformation/contactInformation.module.css index ac5ce5db3..56e92dba3 100644 --- a/components/ContentType/HotelPage/SidePeeks/AboutTheHotel/ContactInformation/contactInformation.module.css +++ b/components/ContentType/HotelPage/SidePeeks/AboutTheHotel/ContactInformation/contactInformation.module.css @@ -4,7 +4,7 @@ gap: var(--Spacing-x-one-and-half); } -.placeholder { +.information { display: grid; grid-template-columns: 1fr 1fr; gap: var(--Spacing-x2); diff --git a/components/ContentType/HotelPage/SidePeeks/AboutTheHotel/ContactInformation/index.tsx b/components/ContentType/HotelPage/SidePeeks/AboutTheHotel/ContactInformation/index.tsx index a906bc95f..2caf0f3ad 100644 --- a/components/ContentType/HotelPage/SidePeeks/AboutTheHotel/ContactInformation/index.tsx +++ b/components/ContentType/HotelPage/SidePeeks/AboutTheHotel/ContactInformation/index.tsx @@ -1,5 +1,4 @@ -import { InstagramIcon } from "@/components/Icons" -import FacebookIcon from "@/components/Icons/Facebook" +import { FacebookIcon, InstagramIcon } from "@/components/Icons" import Image from "@/components/Image" import Link from "@/components/TempDesignSystem/Link" import Body from "@/components/TempDesignSystem/Text/Body" @@ -26,7 +25,7 @@ export default async function ContactInformation({ <Subtitle color="burgundy"> {intl.formatMessage({ id: "Practical information" })} </Subtitle> - <div className={styles.placeholder}> + <div className={styles.information}> <div className={styles.address}> <Body textTransform="bold"> {intl.formatMessage({ id: "Address" })} @@ -66,18 +65,16 @@ export default async function ContactInformation({ {intl.formatMessage({ id: "Follow us" })} </Body> <div className={styles.socialIcons}> - <Link - href={ - socials.instagram || "https://www.instagram.com/scandichotels/" - } - > - <InstagramIcon color="burgundy" /> - </Link> - <Link - href={socials.facebook || "https://www.facebook.com/Scandic/"} - > - <FacebookIcon color="burgundy" /> - </Link> + {socials.instagram && ( + <Link href={socials.instagram}> + <InstagramIcon color="burgundy" /> + </Link> + )} + {socials.facebook && ( + <Link href={socials.facebook}> + <FacebookIcon color="burgundy" /> + </Link> + )} </div> </div> <div className={styles.email}> diff --git a/components/ContentType/HotelPage/SidePeeks/AboutTheHotel/index.tsx b/components/ContentType/HotelPage/SidePeeks/AboutTheHotel/index.tsx index d709c4a7c..f0737a09a 100644 --- a/components/ContentType/HotelPage/SidePeeks/AboutTheHotel/index.tsx +++ b/components/ContentType/HotelPage/SidePeeks/AboutTheHotel/index.tsx @@ -22,6 +22,7 @@ export default async function AboutTheHotelSidePeek({ socials, ecoLabels, descriptions, + buttonUrl, }: AboutTheHotelSidePeekProps) { const lang = getLang() const intl = await getIntl() @@ -42,11 +43,13 @@ export default async function AboutTheHotelSidePeek({ <Divider color="baseSurfaceSutbleHover" /> <Preamble>{descriptions.short}</Preamble> <Body>{descriptions.medium}</Body> - <Button fullWidth theme="base" intent="secondary" asChild> - <Link href="#" color="burgundy" weight="bold"> - {intl.formatMessage({ id: "Read more about the hotel" })} - </Link> - </Button> + {buttonUrl && ( + <Button fullWidth theme="base" intent="secondary" asChild> + <Link href={buttonUrl} color="burgundy" weight="bold"> + {intl.formatMessage({ id: "Read more about the hotel" })} + </Link> + </Button> + )} </section> </SidePeek> ) diff --git a/components/ContentType/HotelPage/index.tsx b/components/ContentType/HotelPage/index.tsx index 5f6c6a0e7..40264d7d8 100644 --- a/components/ContentType/HotelPage/index.tsx +++ b/components/ContentType/HotelPage/index.tsx @@ -145,6 +145,7 @@ export default async function HotelPage() { socials={socials} ecoLabels={ecoLabels} descriptions={hotelDescription} + buttonUrl="#" /> <SidePeek contentKey={hotelPageParams.restaurantAndBar[lang]} diff --git a/components/HotelReservation/Contact/index.tsx b/components/HotelReservation/Contact/index.tsx index 8eea2e95a..95fcb114a 100644 --- a/components/HotelReservation/Contact/index.tsx +++ b/components/HotelReservation/Contact/index.tsx @@ -2,8 +2,7 @@ import { useIntl } from "react-intl" -import FacebookIcon from "@/components/Icons/Facebook" -import InstagramIcon from "@/components/Icons/Instagram" +import { FacebookIcon, InstagramIcon } from "@/components/Icons" import Image from "@/components/Image" import Link from "@/components/TempDesignSystem/Link" import useLang from "@/hooks/useLang" diff --git a/components/HotelReservation/HotelCardDialog/index.tsx b/components/HotelReservation/HotelCardDialog/index.tsx index d444a1083..32f745620 100644 --- a/components/HotelReservation/HotelCardDialog/index.tsx +++ b/components/HotelReservation/HotelCardDialog/index.tsx @@ -7,8 +7,7 @@ import { Lang } from "@/constants/languages" import { selectRate } from "@/constants/routes/hotelReservation" import { mapFacilityToIcon } from "@/components/ContentType/HotelPage/data" -import { CloseLargeIcon } from "@/components/Icons" -import TripAdvisorIcon from "@/components/Icons/TripAdvisor" +import { CloseLargeIcon, TripAdvisorIcon } from "@/components/Icons" import Image from "@/components/Image" import Button from "@/components/TempDesignSystem/Button" import Chip from "@/components/TempDesignSystem/Chip" diff --git a/components/HotelReservation/TripAdvisorChip/index.tsx b/components/HotelReservation/TripAdvisorChip/index.tsx index 96005bd60..05e03cd02 100644 --- a/components/HotelReservation/TripAdvisorChip/index.tsx +++ b/components/HotelReservation/TripAdvisorChip/index.tsx @@ -1,4 +1,4 @@ -import TripAdvisorIcon from "@/components/Icons/TripAdvisor" +import { TripAdvisorIcon } from "@/components/Icons" import Caption from "@/components/TempDesignSystem/Text/Caption" import styles from "./tripAdvisorChip.module.css" diff --git a/components/Icons/get-icon-by-icon-name.ts b/components/Icons/get-icon-by-icon-name.ts index 989941db7..7ba60f321 100644 --- a/components/Icons/get-icon-by-icon-name.ts +++ b/components/Icons/get-icon-by-icon-name.ts @@ -1,8 +1,5 @@ import { FC } from "react" -import FacebookIcon from "./Facebook" -import InstagramIcon from "./Instagram" -import TripAdvisorIcon from "./TripAdvisor" import { AccesoriesIcon, AccessibilityIcon, @@ -41,6 +38,7 @@ import { EmailIcon, EyeHideIcon, EyeShowIcon, + FacebookIcon, FanIcon, FitnessIcon, FootstoolIcon, @@ -56,6 +54,7 @@ import { HouseIcon, ImageIcon, InfoCircleIcon, + InstagramIcon, KayakingIcon, KettleIcon, LampIcon, @@ -93,6 +92,7 @@ import { SwimIcon, ThermostatIcon, TrainIcon, + TripAdvisorIcon, TshirtIcon, TshirtWashIcon, TvCastingIcon, diff --git a/components/Icons/index.tsx b/components/Icons/index.tsx index 7ec502ca0..d252c8755 100644 --- a/components/Icons/index.tsx +++ b/components/Icons/index.tsx @@ -55,6 +55,7 @@ export { default as EmailIcon } from "./Email" export { default as ErrorCircleIcon } from "./ErrorCircle" export { default as EyeHideIcon } from "./EyeHide" export { default as EyeShowIcon } from "./EyeShow" +export { default as FacebookIcon } from "./Facebook" export { default as FanIcon } from "./Fan" export { default as FilterIcon } from "./Filter" export { default as FitnessIcon } from "./Fitness" @@ -127,6 +128,7 @@ export { default as StreetIcon } from "./Street" export { default as SwimIcon } from "./Swim" export { default as ThermostatIcon } from "./Thermostat" export { default as TrainIcon } from "./Train" +export { default as TripAdvisorIcon } from "./TripAdvisor" export { default as TshirtIcon } from "./Tshirt" export { default as TshirtWashIcon } from "./TshirtWash" export { default as TvCastingIcon } from "./TvCasting" diff --git a/types/components/hotelPage/sidepeek/aboutTheHotel.ts b/types/components/hotelPage/sidepeek/aboutTheHotel.ts index 2062cf6e4..016dd0ae4 100644 --- a/types/components/hotelPage/sidepeek/aboutTheHotel.ts +++ b/types/components/hotelPage/sidepeek/aboutTheHotel.ts @@ -10,4 +10,5 @@ export type AboutTheHotelSidePeekProps = { socials: Hotel["socialMedia"] ecoLabels: Hotel["hotelFacts"]["ecoLabels"] descriptions: Hotel["hotelContent"]["texts"]["descriptions"] + buttonUrl?: string } diff --git a/types/components/hotelPage/sidepeek/contactInformation.ts b/types/components/hotelPage/sidepeek/contactInformation.ts index f1eb73196..7d8d17327 100644 --- a/types/components/hotelPage/sidepeek/contactInformation.ts +++ b/types/components/hotelPage/sidepeek/contactInformation.ts @@ -2,5 +2,5 @@ import type { AboutTheHotelSidePeekProps } from "./aboutTheHotel" export type ContactInformationProps = Omit< AboutTheHotelSidePeekProps, - "descriptions" + "descriptions" | "buttonUrl" > From 32bd5b8b6fc20d32206f04093657fd1b267a2b23 Mon Sep 17 00:00:00 2001 From: Fredrik Thorsson <fredrik.thorsson@scandichotels.com> Date: Mon, 18 Nov 2024 14:53:11 +0100 Subject: [PATCH 12/21] feat(SW-750): subtitle as child --- .../SidePeeks/AboutTheHotel/ContactInformation/index.tsx | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/components/ContentType/HotelPage/SidePeeks/AboutTheHotel/ContactInformation/index.tsx b/components/ContentType/HotelPage/SidePeeks/AboutTheHotel/ContactInformation/index.tsx index 2caf0f3ad..ed9e55b76 100644 --- a/components/ContentType/HotelPage/SidePeeks/AboutTheHotel/ContactInformation/index.tsx +++ b/components/ContentType/HotelPage/SidePeeks/AboutTheHotel/ContactInformation/index.tsx @@ -4,6 +4,7 @@ import Link from "@/components/TempDesignSystem/Link" import Body from "@/components/TempDesignSystem/Text/Body" import Caption from "@/components/TempDesignSystem/Text/Caption" import Subtitle from "@/components/TempDesignSystem/Text/Subtitle" +import Title from "@/components/TempDesignSystem/Text/Title" import { getIntl } from "@/i18n" import { getLang } from "@/i18n/serverContext" @@ -22,8 +23,10 @@ export default async function ContactInformation({ const lang = getLang() return ( <div className={styles.wrapper}> - <Subtitle color="burgundy"> - {intl.formatMessage({ id: "Practical information" })} + <Subtitle color="burgundy" asChild> + <Title level="h3"> + {intl.formatMessage({ id: "Practical information" })} +
From acd165688589d4435de92bfe3389de250690a798 Mon Sep 17 00:00:00 2001 From: Fredrik Thorsson Date: Mon, 18 Nov 2024 15:30:25 +0100 Subject: [PATCH 13/21] feat(SW-750): add base url --- .../AboutTheHotel/ContactInformation/index.tsx | 11 +++++++---- .../SidePeeks/WellnessAndExercise/Facility/index.tsx | 2 +- .../hotelPage/sidepeek/contactInformation.ts | 4 ++++ 3 files changed, 12 insertions(+), 5 deletions(-) diff --git a/components/ContentType/HotelPage/SidePeeks/AboutTheHotel/ContactInformation/index.tsx b/components/ContentType/HotelPage/SidePeeks/AboutTheHotel/ContactInformation/index.tsx index ed9e55b76..09824d10a 100644 --- a/components/ContentType/HotelPage/SidePeeks/AboutTheHotel/ContactInformation/index.tsx +++ b/components/ContentType/HotelPage/SidePeeks/AboutTheHotel/ContactInformation/index.tsx @@ -10,7 +10,10 @@ import { getLang } from "@/i18n/serverContext" import styles from "./contactInformation.module.css" -import type { ContactInformationProps } from "@/types/components/hotelPage/sidepeek/contactInformation" +import { + type ContactInformationProps, + GoogleMapsDirections, +} from "@/types/components/hotelPage/sidepeek/contactInformation" export default async function ContactInformation({ hotelAdress, @@ -41,7 +44,7 @@ export default async function ContactInformation({ {intl.formatMessage({ id: "Driving directions" })}
- {ecoLabels.nordicEcoLabel ? ( + {ecoLabels.nordicEcoLabel && (
- ) : null} + )}
) diff --git a/components/ContentType/HotelPage/SidePeeks/WellnessAndExercise/Facility/index.tsx b/components/ContentType/HotelPage/SidePeeks/WellnessAndExercise/Facility/index.tsx index e00ed5964..3313a109b 100644 --- a/components/ContentType/HotelPage/SidePeeks/WellnessAndExercise/Facility/index.tsx +++ b/components/ContentType/HotelPage/SidePeeks/WellnessAndExercise/Facility/index.tsx @@ -16,7 +16,7 @@ export default async function Facility({ data }: FacilityProps) { return (
- {image.imageSizes.medium && ( + {image?.imageSizes.medium && ( {image.metaData.altText Date: Tue, 19 Nov 2024 14:21:46 +0100 Subject: [PATCH 14/21] feat(SW-750): remove button --- .../HotelPage/SidePeeks/AboutTheHotel/index.tsx | 10 ---------- components/ContentType/HotelPage/index.tsx | 1 - types/components/hotelPage/sidepeek/aboutTheHotel.ts | 1 - .../hotelPage/sidepeek/contactInformation.ts | 2 +- 4 files changed, 1 insertion(+), 13 deletions(-) diff --git a/components/ContentType/HotelPage/SidePeeks/AboutTheHotel/index.tsx b/components/ContentType/HotelPage/SidePeeks/AboutTheHotel/index.tsx index f0737a09a..318f195bc 100644 --- a/components/ContentType/HotelPage/SidePeeks/AboutTheHotel/index.tsx +++ b/components/ContentType/HotelPage/SidePeeks/AboutTheHotel/index.tsx @@ -1,8 +1,6 @@ import { about } from "@/constants/routes/hotelPageParams" -import Button from "@/components/TempDesignSystem/Button" import Divider from "@/components/TempDesignSystem/Divider" -import Link from "@/components/TempDesignSystem/Link" import SidePeek from "@/components/TempDesignSystem/SidePeek" import Body from "@/components/TempDesignSystem/Text/Body" import Preamble from "@/components/TempDesignSystem/Text/Preamble" @@ -22,7 +20,6 @@ export default async function AboutTheHotelSidePeek({ socials, ecoLabels, descriptions, - buttonUrl, }: AboutTheHotelSidePeekProps) { const lang = getLang() const intl = await getIntl() @@ -43,13 +40,6 @@ export default async function AboutTheHotelSidePeek({ {descriptions.short} {descriptions.medium} - {buttonUrl && ( - - )} ) diff --git a/components/ContentType/HotelPage/index.tsx b/components/ContentType/HotelPage/index.tsx index 40264d7d8..5f6c6a0e7 100644 --- a/components/ContentType/HotelPage/index.tsx +++ b/components/ContentType/HotelPage/index.tsx @@ -145,7 +145,6 @@ export default async function HotelPage() { socials={socials} ecoLabels={ecoLabels} descriptions={hotelDescription} - buttonUrl="#" /> From 1881f6646d10d4659293e1593b46425d9465d7a0 Mon Sep 17 00:00:00 2001 From: Fredrik Thorsson Date: Tue, 19 Nov 2024 16:43:53 +0100 Subject: [PATCH 15/21] feat(SW-750): fix typo --- .../SidePeeks/AboutTheHotel/ContactInformation/index.tsx | 6 +++--- .../ContentType/HotelPage/SidePeeks/AboutTheHotel/index.tsx | 4 ++-- components/ContentType/HotelPage/index.tsx | 2 +- types/components/hotelPage/sidepeek/aboutTheHotel.ts | 2 +- 4 files changed, 7 insertions(+), 7 deletions(-) diff --git a/components/ContentType/HotelPage/SidePeeks/AboutTheHotel/ContactInformation/index.tsx b/components/ContentType/HotelPage/SidePeeks/AboutTheHotel/ContactInformation/index.tsx index 09824d10a..58dd360c6 100644 --- a/components/ContentType/HotelPage/SidePeeks/AboutTheHotel/ContactInformation/index.tsx +++ b/components/ContentType/HotelPage/SidePeeks/AboutTheHotel/ContactInformation/index.tsx @@ -16,7 +16,7 @@ import { } from "@/types/components/hotelPage/sidepeek/contactInformation" export default async function ContactInformation({ - hotelAdress, + hotelAddress, coordinates, contact, socials, @@ -36,8 +36,8 @@ export default async function ContactInformation({ {intl.formatMessage({ id: "Address" })} - {hotelAdress.streetAddress} - {hotelAdress.city} + {hotelAddress.streetAddress} + {hotelAddress.city}
diff --git a/components/ContentType/HotelPage/SidePeeks/AboutTheHotel/index.tsx b/components/ContentType/HotelPage/SidePeeks/AboutTheHotel/index.tsx index 318f195bc..14175b231 100644 --- a/components/ContentType/HotelPage/SidePeeks/AboutTheHotel/index.tsx +++ b/components/ContentType/HotelPage/SidePeeks/AboutTheHotel/index.tsx @@ -14,7 +14,7 @@ import styles from "./aboutTheHotel.module.css" import type { AboutTheHotelSidePeekProps } from "@/types/components/hotelPage/sidepeek/aboutTheHotel" export default async function AboutTheHotelSidePeek({ - hotelAdress, + hotelAddress, coordinates, contact, socials, @@ -31,7 +31,7 @@ export default async function AboutTheHotelSidePeek({ >
Date: Wed, 20 Nov 2024 13:19:59 +0100 Subject: [PATCH 16/21] feat(SW-750): add HotelLocation type --- .../ContactInformation/contactInformation.module.css | 1 + .../AboutTheHotel/ContactInformation/index.tsx | 10 +++++----- components/ContentType/HotelPage/SidePeeks/index.ts | 2 ++ components/ContentType/HotelPage/index.tsx | 5 ++--- types/components/hotelPage/sidepeek/aboutTheHotel.ts | 7 ++----- .../hotelPage/sidepeek/contactInformation.ts | 4 ---- 6 files changed, 12 insertions(+), 17 deletions(-) create mode 100644 components/ContentType/HotelPage/SidePeeks/index.ts diff --git a/components/ContentType/HotelPage/SidePeeks/AboutTheHotel/ContactInformation/contactInformation.module.css b/components/ContentType/HotelPage/SidePeeks/AboutTheHotel/ContactInformation/contactInformation.module.css index 56e92dba3..b616382ab 100644 --- a/components/ContentType/HotelPage/SidePeeks/AboutTheHotel/ContactInformation/contactInformation.module.css +++ b/components/ContentType/HotelPage/SidePeeks/AboutTheHotel/ContactInformation/contactInformation.module.css @@ -34,6 +34,7 @@ .socialIcons { display: flex; gap: var(--Spacing-x1); + align-items: center; } .email { diff --git a/components/ContentType/HotelPage/SidePeeks/AboutTheHotel/ContactInformation/index.tsx b/components/ContentType/HotelPage/SidePeeks/AboutTheHotel/ContactInformation/index.tsx index 58dd360c6..7ac140336 100644 --- a/components/ContentType/HotelPage/SidePeeks/AboutTheHotel/ContactInformation/index.tsx +++ b/components/ContentType/HotelPage/SidePeeks/AboutTheHotel/ContactInformation/index.tsx @@ -10,10 +10,7 @@ import { getLang } from "@/i18n/serverContext" import styles from "./contactInformation.module.css" -import { - type ContactInformationProps, - GoogleMapsDirections, -} from "@/types/components/hotelPage/sidepeek/contactInformation" +import type { ContactInformationProps } from "@/types/components/hotelPage/sidepeek/contactInformation" export default async function ContactInformation({ hotelAddress, @@ -24,6 +21,9 @@ export default async function ContactInformation({ }: ContactInformationProps) { const intl = await getIntl() const lang = getLang() + const { latitude, longitude } = coordinates + const directionsUrl = `https://www.google.com/maps/dir/?api=1&destination=${latitude},${longitude}` + return (
@@ -44,7 +44,7 @@ export default async function ContactInformation({ {intl.formatMessage({ id: "Driving directions" })} Date: Wed, 20 Nov 2024 13:35:21 +0100 Subject: [PATCH 17/21] feat(SW-750): add facility information text --- .../ContentType/HotelPage/SidePeeks/AboutTheHotel/index.tsx | 4 ++-- components/ContentType/HotelPage/index.tsx | 2 +- server/routers/hotels/query.ts | 2 +- types/components/hotelPage/sidepeek/aboutTheHotel.ts | 2 +- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/components/ContentType/HotelPage/SidePeeks/AboutTheHotel/index.tsx b/components/ContentType/HotelPage/SidePeeks/AboutTheHotel/index.tsx index 14175b231..436147a50 100644 --- a/components/ContentType/HotelPage/SidePeeks/AboutTheHotel/index.tsx +++ b/components/ContentType/HotelPage/SidePeeks/AboutTheHotel/index.tsx @@ -38,8 +38,8 @@ export default async function AboutTheHotelSidePeek({ ecoLabels={ecoLabels} /> - {descriptions.short} - {descriptions.medium} + {descriptions.descriptions.medium} + {descriptions.facilityInformation}
) diff --git a/components/ContentType/HotelPage/index.tsx b/components/ContentType/HotelPage/index.tsx index 03e4c550f..57e8f741d 100644 --- a/components/ContentType/HotelPage/index.tsx +++ b/components/ContentType/HotelPage/index.tsx @@ -83,7 +83,7 @@ export default async function HotelPage() {
Date: Thu, 21 Nov 2024 13:41:08 +0100 Subject: [PATCH 18/21] feat(SW-750): change key name --- components/ContentType/HotelPage/index.tsx | 6 +++--- server/routers/hotels/query.ts | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/components/ContentType/HotelPage/index.tsx b/components/ContentType/HotelPage/index.tsx index 57e8f741d..3e4893065 100644 --- a/components/ContentType/HotelPage/index.tsx +++ b/components/ContentType/HotelPage/index.tsx @@ -41,7 +41,7 @@ export default async function HotelPage() { const { hotelId, hotelName, - hotelDescription, + hotelDescriptions, hotelLocation, hotelAddress, hotelRatings, @@ -83,7 +83,7 @@ export default async function HotelPage() {
Date: Thu, 21 Nov 2024 14:39:45 +0100 Subject: [PATCH 19/21] feat(SW-750): restore comment --- .../SidePeeks/AboutTheHotel/ContactInformation/index.tsx | 1 + 1 file changed, 1 insertion(+) diff --git a/components/ContentType/HotelPage/SidePeeks/AboutTheHotel/ContactInformation/index.tsx b/components/ContentType/HotelPage/SidePeeks/AboutTheHotel/ContactInformation/index.tsx index 7ac140336..f4bf137a4 100644 --- a/components/ContentType/HotelPage/SidePeeks/AboutTheHotel/ContactInformation/index.tsx +++ b/components/ContentType/HotelPage/SidePeeks/AboutTheHotel/ContactInformation/index.tsx @@ -21,6 +21,7 @@ export default async function ContactInformation({ }: ContactInformationProps) { const intl = await getIntl() const lang = getLang() + const { latitude, longitude } = coordinates const directionsUrl = `https://www.google.com/maps/dir/?api=1&destination=${latitude},${longitude}` From ac0d67181139946e44d667c72163aec8814d5ceb Mon Sep 17 00:00:00 2001 From: Arvid Norlin Date: Thu, 21 Nov 2024 14:48:18 +0100 Subject: [PATCH 20/21] fix(SW-957 SW-958): Make selected EnterDetailsCards clickable --- .../EnterDetails/BedType/index.tsx | 3 +++ .../EnterDetails/Breakfast/index.tsx | 6 ++++++ .../TempDesignSystem/Form/ChoiceCard/_Card/card.ts | 1 + .../Form/ChoiceCard/_Card/index.tsx | 14 +++++++++++++- 4 files changed, 23 insertions(+), 1 deletion(-) diff --git a/components/HotelReservation/EnterDetails/BedType/index.tsx b/components/HotelReservation/EnterDetails/BedType/index.tsx index eeb8237a0..a7290e40b 100644 --- a/components/HotelReservation/EnterDetails/BedType/index.tsx +++ b/components/HotelReservation/EnterDetails/BedType/index.tsx @@ -76,6 +76,9 @@ export default function BedType({ bedTypes }: BedTypeProps) { subtitle={width} title={roomType.description} value={roomType.value} + handleSelectedOnClick={ + bedType === roomType.value ? completeStep : undefined + } /> ) })} diff --git a/components/HotelReservation/EnterDetails/Breakfast/index.tsx b/components/HotelReservation/EnterDetails/Breakfast/index.tsx index fdaec3a84..a3be32d65 100644 --- a/components/HotelReservation/EnterDetails/Breakfast/index.tsx +++ b/components/HotelReservation/EnterDetails/Breakfast/index.tsx @@ -97,6 +97,9 @@ export default function Breakfast({ packages }: BreakfastProps) { })} title={intl.formatMessage({ id: "Breakfast buffet" })} value={pkg.code} + handleSelectedOnClick={ + breakfast === pkg.code ? completeStep : undefined + } /> ))} diff --git a/components/TempDesignSystem/Form/ChoiceCard/_Card/card.ts b/components/TempDesignSystem/Form/ChoiceCard/_Card/card.ts index 7d24e46d7..c733dac50 100644 --- a/components/TempDesignSystem/Form/ChoiceCard/_Card/card.ts +++ b/components/TempDesignSystem/Form/ChoiceCard/_Card/card.ts @@ -12,6 +12,7 @@ interface BaseCardProps title: React.ReactNode type: "checkbox" | "radio" value?: string + handleSelectedOnClick?: () => void } interface ListCardProps extends BaseCardProps { diff --git a/components/TempDesignSystem/Form/ChoiceCard/_Card/index.tsx b/components/TempDesignSystem/Form/ChoiceCard/_Card/index.tsx index 7d6ef8105..29ce8e531 100644 --- a/components/TempDesignSystem/Form/ChoiceCard/_Card/index.tsx +++ b/components/TempDesignSystem/Form/ChoiceCard/_Card/index.tsx @@ -25,10 +25,22 @@ export default function Card({ title, type, value, + handleSelectedOnClick, }: CardProps) { const { register } = useFormContext() + + function onLabelClick(event: React.MouseEvent) { + // Preventing click event on label elements firing twice: https://github.com/facebook/react/issues/14295 + event.preventDefault() + handleSelectedOnClick?.() + } return ( -