From a3602b285bf9a04a90bacc71e68c532c5397156f Mon Sep 17 00:00:00 2001 From: Fredrik Thorsson Date: Tue, 13 Aug 2024 09:09:33 +0200 Subject: [PATCH 01/45] feat(SW-240): add translations --- .../BookingWidget/bookingWidget.module.css | 36 +++++++++++++------ components/BookingWidget/index.tsx | 20 ++++++++--- components/Current/Header/index.tsx | 2 +- i18n/dictionaries/da.json | 6 +++- i18n/dictionaries/de.json | 6 +++- i18n/dictionaries/en.json | 6 +++- i18n/dictionaries/fi.json | 6 +++- i18n/dictionaries/no.json | 6 +++- i18n/dictionaries/sv.json | 6 +++- 9 files changed, 71 insertions(+), 23 deletions(-) diff --git a/components/BookingWidget/bookingWidget.module.css b/components/BookingWidget/bookingWidget.module.css index 0437c2c48..f642b37aa 100644 --- a/components/BookingWidget/bookingWidget.module.css +++ b/components/BookingWidget/bookingWidget.module.css @@ -1,21 +1,35 @@ .container { - display: none; + border-top: 1px solid var(--Base-Border-Subtle); + /* display: none; */ +} + +.form { + display: flex; + padding: var(--Spacing-x2) var(--Spacing-x5); +} + +.input { + display: flex; + width: 100%; + max-width: 1250px; +} + +.where { + width: 100%; + max-width: 280px; +} + +.when { + width: 100%; + max-width: 240px; } /** * Update the styles after mobile UX is ready */ -@media screen and (min-width: 1367px) { +@media screen and (max-width: 1367px) { .container { - display: grid; - padding: 0 var(--Spacing-x5); - gap: var(--Spacing-x3); - } - .form { - display: grid; - gap: var(--Spacing-x5); - grid-template-columns: repeat(6, auto); - align-items: center; + display: none; } } diff --git a/components/BookingWidget/index.tsx b/components/BookingWidget/index.tsx index c3c32437b..c8d60fe91 100644 --- a/components/BookingWidget/index.tsx +++ b/components/BookingWidget/index.tsx @@ -1,6 +1,7 @@ "use client" import { zodResolver } from "@hookform/resolvers/zod" import { FormProvider, useForm } from "react-hook-form" +import { useIntl } from "react-intl" import { dt } from "@/lib/dt" @@ -12,6 +13,8 @@ import styles from "./bookingWidget.module.css" import { type BookingWidgetSchema } from "@/types/components/bookingWidget" export function BookingWidget() { + const { formatMessage } = useIntl() + const methods = useForm({ defaultValues: { search: { @@ -49,11 +52,18 @@ export function BookingWidget() {
-
Search
-
Nights
-
Rooms
-
Bonus code
-
Bonus cheque or reward nights
+
+
+ {formatMessage({ id: "Where to" })} +
+
{formatMessage({ id: "When" })}
+
+ {formatMessage({ id: "Rooms & Guests" })} +
+
+ {formatMessage({ id: "Booking codes and vouchers" })} +
+
diff --git a/components/Current/Header/index.tsx b/components/Current/Header/index.tsx index 0f932cfc8..e590e3f65 100644 --- a/components/Current/Header/index.tsx +++ b/components/Current/Header/index.tsx @@ -28,7 +28,7 @@ export default async function Header({ /** * ToDo: Create logic to get this info from ContentStack based on page * */ - const hideBookingWidget = true + const hideBookingWidget = false if (!data) { return null diff --git a/i18n/dictionaries/da.json b/i18n/dictionaries/da.json index b1382e9a7..0c767e51d 100644 --- a/i18n/dictionaries/da.json +++ b/i18n/dictionaries/da.json @@ -134,5 +134,9 @@ "Room facilities": "Værelsesfaciliteter", "Hotel facilities": "Hotel faciliteter", "Hotel surroundings": "Hotel omgivelser", - "Show map": "Vis kort" + "Show map": "Vis kort", + "Where to": "Hvorhen", + "When": "Hvornår", + "Rooms & Guests": "Værelser & gæster", + "Booking codes and vouchers": "Bestillingskoder og værdibeviser" } diff --git a/i18n/dictionaries/de.json b/i18n/dictionaries/de.json index 56d36585f..a29347278 100644 --- a/i18n/dictionaries/de.json +++ b/i18n/dictionaries/de.json @@ -128,5 +128,9 @@ "Room facilities": "Zimmerausstattung", "Hotel facilities": "Hotel-Infos", "Hotel surroundings": "Umgebung des Hotels", - "Show map": "Karte anzeigen" + "Show map": "Karte anzeigen", + "Where to": "Wohin", + "When": "Wann", + "Rooms & Guests": "Zimmer & Gäste", + "Booking codes and vouchers": "Buchungscodes und Gutscheine" } diff --git a/i18n/dictionaries/en.json b/i18n/dictionaries/en.json index 7e46b4e0c..365535e30 100644 --- a/i18n/dictionaries/en.json +++ b/i18n/dictionaries/en.json @@ -139,5 +139,9 @@ "Room facilities": "Room facilities", "Hotel facilities": "Hotel facilities", "Hotel surroundings": "Hotel surroundings", - "Show map": "Show map" + "Show map": "Show map", + "Where to": "Where to", + "When": "When", + "Rooms & Guests": "Rooms & Guests", + "Booking codes and vouchers": "Booking codes and vouchers" } diff --git a/i18n/dictionaries/fi.json b/i18n/dictionaries/fi.json index 56093a1e4..5b516890b 100644 --- a/i18n/dictionaries/fi.json +++ b/i18n/dictionaries/fi.json @@ -134,5 +134,9 @@ "Room facilities": "Huoneen varustelu", "Hotel facilities": "Hotellin palvelut", "Hotel surroundings": "Hotellin ympäristö", - "Show map": "Näytä kartta" + "Show map": "Näytä kartta", + "Where to": "Minne", + "When": "Kun", + "Rooms & Guestss": "Huoneet & Vieraat", + "Booking codes and vouchers": "Varauskoodit ja kupongit" } diff --git a/i18n/dictionaries/no.json b/i18n/dictionaries/no.json index c1c219e0b..87993b450 100644 --- a/i18n/dictionaries/no.json +++ b/i18n/dictionaries/no.json @@ -134,5 +134,9 @@ "Room facilities": "Romfasiliteter", "Hotel facilities": "Hotelfaciliteter", "Hotel surroundings": "Hotellomgivelser", - "Show map": "Vis kart" + "Show map": "Vis kart", + "Where to": "Hvor skal du", + "When": "Når", + "Rooms & Guests": "Rom og gjester", + "Booking codes and vouchers": "Bestillingskoder og kuponger" } diff --git a/i18n/dictionaries/sv.json b/i18n/dictionaries/sv.json index 2038735f9..1641d8fb2 100644 --- a/i18n/dictionaries/sv.json +++ b/i18n/dictionaries/sv.json @@ -136,5 +136,9 @@ "Room facilities": "Rumlfaciliteter", "Hotel facilities": "Hotellfaciliteter", "Hotel surroundings": "Hotellomgivning", - "Show map": "Visa karta" + "Show map": "Visa karta", + "Where to": "Vart", + "When": "När", + "Rooms & Guests": "Rum och gäster", + "Booking codes and vouchers": "Bokningskoder och kuponger" } From 8c339fa5936b1db39cd1990413edcd52adb64583 Mon Sep 17 00:00:00 2001 From: Fredrik Thorsson Date: Tue, 13 Aug 2024 14:13:02 +0200 Subject: [PATCH 02/45] feat(SW-240): add basic styling --- .../hotelreservation/select-hotel/page.tsx | 7 +- .../BookingWidget/bookingWidget.module.css | 30 +++++++- components/BookingWidget/index.tsx | 71 +++++++++++++++++-- .../Text/Body/body.module.css | 2 +- i18n/dictionaries/da.json | 6 +- i18n/dictionaries/de.json | 6 +- i18n/dictionaries/en.json | 6 +- i18n/dictionaries/fi.json | 6 +- i18n/dictionaries/no.json | 6 +- i18n/dictionaries/sv.json | 6 +- 10 files changed, 125 insertions(+), 21 deletions(-) diff --git a/app/[lang]/(live)/(public)/hotelreservation/select-hotel/page.tsx b/app/[lang]/(live)/(public)/hotelreservation/select-hotel/page.tsx index c28f415bb..b75ba0aaf 100644 --- a/app/[lang]/(live)/(public)/hotelreservation/select-hotel/page.tsx +++ b/app/[lang]/(live)/(public)/hotelreservation/select-hotel/page.tsx @@ -40,12 +40,7 @@ export default async function SelectHotelPage({ zoomLevel={11} mapType="roadmap" /> - + {intl.formatMessage({ id: "Show map" })} diff --git a/components/BookingWidget/bookingWidget.module.css b/components/BookingWidget/bookingWidget.module.css index f642b37aa..dee9aff98 100644 --- a/components/BookingWidget/bookingWidget.module.css +++ b/components/BookingWidget/bookingWidget.module.css @@ -1,29 +1,55 @@ .container { border-top: 1px solid var(--Base-Border-Subtle); - /* display: none; */ } .form { display: flex; + justify-content: space-between; + max-width: 1432px; padding: var(--Spacing-x2) var(--Spacing-x5); } .input { display: flex; + gap: var(--Spacing-x2); width: 100%; max-width: 1250px; } +.button { + width: 118px; + justify-content: center; +} + +.bodyFontSize { + font-size: var(--typography-Caption-Bold-Desktop-fontSize); +} + .where { width: 100%; max-width: 280px; + height: 100%; + max-height: 46px; } -.when { +.when, +.rooms { width: 100%; max-width: 240px; } +.vouchers { + width: 100%; + max-width: 200px; +} + +.options { + display: flex; + flex-direction: column; + width: 100%; + max-width: 158px; +} + /** * Update the styles after mobile UX is ready */ diff --git a/components/BookingWidget/index.tsx b/components/BookingWidget/index.tsx index c8d60fe91..b51e5d51a 100644 --- a/components/BookingWidget/index.tsx +++ b/components/BookingWidget/index.tsx @@ -6,6 +6,7 @@ import { useIntl } from "react-intl" import { dt } from "@/lib/dt" import Button from "../TempDesignSystem/Button" +import Body from "../TempDesignSystem/Text/Body" import { bookingWidgetSchema } from "./schema" import styles from "./bookingWidget.module.css" @@ -13,7 +14,7 @@ import styles from "./bookingWidget.module.css" import { type BookingWidgetSchema } from "@/types/components/bookingWidget" export function BookingWidget() { - const { formatMessage } = useIntl() + const intl = useIntl() const methods = useForm({ defaultValues: { @@ -54,17 +55,75 @@ export function BookingWidget() {
- {formatMessage({ id: "Where to" })} + + {intl.formatMessage({ id: "Where to" })} + +
+
+ + {intl.formatMessage({ id: "When" })} +
-
{formatMessage({ id: "When" })}
- {formatMessage({ id: "Rooms & Guests" })} + + {intl.formatMessage({ id: "Rooms & Guests" })} +
- {formatMessage({ id: "Booking codes and vouchers" })} + + {intl.formatMessage({ id: "Booking codes and vouchers" })} + +
+
+
+ + {intl.formatMessage({ id: "Use bonus cheque" })} + +
+
+ + {intl.formatMessage({ id: "Book reward night" })} + +
- +
diff --git a/components/TempDesignSystem/Text/Body/body.module.css b/components/TempDesignSystem/Text/Body/body.module.css index d25e80ea2..045e0886d 100644 --- a/components/TempDesignSystem/Text/Body/body.module.css +++ b/components/TempDesignSystem/Text/Body/body.module.css @@ -63,7 +63,7 @@ } .textMediumContrast { - color: var(--Base-Text-UI-Medium-contrast); + color: var(--UI-Text-Medium-contrast); } .white { diff --git a/i18n/dictionaries/da.json b/i18n/dictionaries/da.json index 0c767e51d..d61a32605 100644 --- a/i18n/dictionaries/da.json +++ b/i18n/dictionaries/da.json @@ -138,5 +138,9 @@ "Where to": "Hvorhen", "When": "Hvornår", "Rooms & Guests": "Værelser & gæster", - "Booking codes and vouchers": "Bestillingskoder og værdibeviser" + "Booking codes and vouchers": "Bestillingskoder og værdibeviser", + "Add code": "Tilføj kode", + "Use bonus cheque": "Brug bonuscheck", + "Book reward night": "Book belønningsaften", + "Find hotels": "Find hoteller" } diff --git a/i18n/dictionaries/de.json b/i18n/dictionaries/de.json index a29347278..6f3363fff 100644 --- a/i18n/dictionaries/de.json +++ b/i18n/dictionaries/de.json @@ -132,5 +132,9 @@ "Where to": "Wohin", "When": "Wann", "Rooms & Guests": "Zimmer & Gäste", - "Booking codes and vouchers": "Buchungscodes und Gutscheine" + "Booking codes and vouchers": "Buchungscodes und Gutscheine", + "Add code": "Code hinzufügen", + "Use bonus cheque": "Bonusscheck nutzen", + "Book reward night": "Bonusnacht buchen", + "Find hotels": "Hotels finden" } diff --git a/i18n/dictionaries/en.json b/i18n/dictionaries/en.json index 365535e30..28af28045 100644 --- a/i18n/dictionaries/en.json +++ b/i18n/dictionaries/en.json @@ -143,5 +143,9 @@ "Where to": "Where to", "When": "When", "Rooms & Guests": "Rooms & Guests", - "Booking codes and vouchers": "Booking codes and vouchers" + "Booking codes and vouchers": "Booking codes and vouchers", + "Add code": "Add code", + "Use bonus cheque": "Use bonus cheque", + "Book reward night": "Book reward night", + "Find hotels": "Find hotels" } diff --git a/i18n/dictionaries/fi.json b/i18n/dictionaries/fi.json index 5b516890b..3e84c0e6a 100644 --- a/i18n/dictionaries/fi.json +++ b/i18n/dictionaries/fi.json @@ -138,5 +138,9 @@ "Where to": "Minne", "When": "Kun", "Rooms & Guestss": "Huoneet & Vieraat", - "Booking codes and vouchers": "Varauskoodit ja kupongit" + "Booking codes and vouchers": "Varauskoodit ja kupongit", + "Add code": "Lisää koodi", + "Use bonus cheque": "Käytä bonussekkiä", + "Book reward night": "Kirjapalkinto-ilta", + "Find hotels": "Etsi hotelleja" } diff --git a/i18n/dictionaries/no.json b/i18n/dictionaries/no.json index 87993b450..d903be4f4 100644 --- a/i18n/dictionaries/no.json +++ b/i18n/dictionaries/no.json @@ -138,5 +138,9 @@ "Where to": "Hvor skal du", "When": "Når", "Rooms & Guests": "Rom og gjester", - "Booking codes and vouchers": "Bestillingskoder og kuponger" + "Booking codes and vouchers": "Bestillingskoder og kuponger", + "Add code": "Legg til kode", + "Use bonus cheque": "Bruk bonussjekk", + "Book reward night": "Bestill belønningskveld", + "Find hotels": "Finn hotell" } diff --git a/i18n/dictionaries/sv.json b/i18n/dictionaries/sv.json index 1641d8fb2..0f3a4e9e1 100644 --- a/i18n/dictionaries/sv.json +++ b/i18n/dictionaries/sv.json @@ -140,5 +140,9 @@ "Where to": "Vart", "When": "När", "Rooms & Guests": "Rum och gäster", - "Booking codes and vouchers": "Bokningskoder och kuponger" + "Booking codes and vouchers": "Bokningskoder och kuponger", + "Add code": "Add code", + "Use bonus cheque": "Use bonus cheque", + "Book reward night": "Book reward night", + "Find hotels": "Hitta hotell" } From 5a0d1c6d7bcbc7eef1a0dc31acb37787c0a9c909 Mon Sep 17 00:00:00 2001 From: Fredrik Thorsson Date: Tue, 13 Aug 2024 15:09:03 +0200 Subject: [PATCH 03/45] feat(SW-240): add borders --- components/BookingWidget/bookingWidget.module.css | 4 ++++ components/BookingWidget/index.tsx | 8 ++++---- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/components/BookingWidget/bookingWidget.module.css b/components/BookingWidget/bookingWidget.module.css index dee9aff98..d256d451a 100644 --- a/components/BookingWidget/bookingWidget.module.css +++ b/components/BookingWidget/bookingWidget.module.css @@ -25,6 +25,10 @@ font-size: var(--typography-Caption-Bold-Desktop-fontSize); } +.border { + border-right: 1px solid var(--Base-Surface-Subtle-Normal); +} + .where { width: 100%; max-width: 280px; diff --git a/components/BookingWidget/index.tsx b/components/BookingWidget/index.tsx index b51e5d51a..b9efb5e69 100644 --- a/components/BookingWidget/index.tsx +++ b/components/BookingWidget/index.tsx @@ -54,7 +54,7 @@ export function BookingWidget() {
-
+
-
+
-
+
-
+
Date: Wed, 14 Aug 2024 18:46:07 +0200 Subject: [PATCH 04/45] feat(SW-240): refactor booking widget --- .../BookingWidget/bookingWidget.module.css | 57 +------- components/BookingWidget/index.tsx | 128 +----------------- .../FormContent/formContent.module.css | 45 ++++++ .../Forms/BookingWidget/FormContent/index.tsx | 90 ++++++++++++ .../Forms/BookingWidget/form.module.css | 7 + components/Forms/BookingWidget/index.tsx | 61 +++++++++ .../{ => Forms}/BookingWidget/schema.ts | 0 types/components/bookingWidget/index.ts | 2 +- 8 files changed, 209 insertions(+), 181 deletions(-) create mode 100644 components/Forms/BookingWidget/FormContent/formContent.module.css create mode 100644 components/Forms/BookingWidget/FormContent/index.tsx create mode 100644 components/Forms/BookingWidget/form.module.css create mode 100644 components/Forms/BookingWidget/index.tsx rename components/{ => Forms}/BookingWidget/schema.ts (100%) diff --git a/components/BookingWidget/bookingWidget.module.css b/components/BookingWidget/bookingWidget.module.css index d256d451a..629a9523e 100644 --- a/components/BookingWidget/bookingWidget.module.css +++ b/components/BookingWidget/bookingWidget.module.css @@ -1,63 +1,8 @@ .container { border-top: 1px solid var(--Base-Border-Subtle); + box-shadow: 0px 16px 24px 0px #00000014; } -.form { - display: flex; - justify-content: space-between; - max-width: 1432px; - padding: var(--Spacing-x2) var(--Spacing-x5); -} - -.input { - display: flex; - gap: var(--Spacing-x2); - width: 100%; - max-width: 1250px; -} - -.button { - width: 118px; - justify-content: center; -} - -.bodyFontSize { - font-size: var(--typography-Caption-Bold-Desktop-fontSize); -} - -.border { - border-right: 1px solid var(--Base-Surface-Subtle-Normal); -} - -.where { - width: 100%; - max-width: 280px; - height: 100%; - max-height: 46px; -} - -.when, -.rooms { - width: 100%; - max-width: 240px; -} - -.vouchers { - width: 100%; - max-width: 200px; -} - -.options { - display: flex; - flex-direction: column; - width: 100%; - max-width: 158px; -} - -/** -* Update the styles after mobile UX is ready -*/ - @media screen and (max-width: 1367px) { .container { display: none; diff --git a/components/BookingWidget/index.tsx b/components/BookingWidget/index.tsx index b9efb5e69..5d4bc4dbd 100644 --- a/components/BookingWidget/index.tsx +++ b/components/BookingWidget/index.tsx @@ -1,131 +1,11 @@ -"use client" -import { zodResolver } from "@hookform/resolvers/zod" -import { FormProvider, useForm } from "react-hook-form" -import { useIntl } from "react-intl" - -import { dt } from "@/lib/dt" - -import Button from "../TempDesignSystem/Button" -import Body from "../TempDesignSystem/Text/Body" -import { bookingWidgetSchema } from "./schema" +import Form from "../Forms/BookingWidget" import styles from "./bookingWidget.module.css" -import { type BookingWidgetSchema } from "@/types/components/bookingWidget" - export function BookingWidget() { - const intl = useIntl() - - const methods = useForm({ - defaultValues: { - search: { - stayType: "", - stayValue: "", - }, - nights: { - // UTC is required to handle requests from far away timezones https://scandichotels.atlassian.net/browse/SWAP-6375 & PET-507 - // This is specifically to handle timezones falling in different dates. - fromDate: dt().utc().format("DD/MM/YYYY"), - toDate: dt().utc().add(1, "day").format("DD/MM/YYYY"), - }, - bookingCode: "", - redemption: false, - voucher: false, - rooms: [ - { - adults: 1, - childs: [], - }, - ], - }, - mode: "all", - resolver: zodResolver(bookingWidgetSchema), - reValidateMode: "onChange", - }) - - function onSubmit(data: BookingWidgetSchema) { - console.log(data) - // Parse data and route accordignly to Select hotel or select room-rate page - console.log("to be routing") - } - return ( -
- - -
-
- - {intl.formatMessage({ id: "Where to" })} - -
-
- - {intl.formatMessage({ id: "When" })} - -
-
- - {intl.formatMessage({ id: "Rooms & Guests" })} - -
-
- - {intl.formatMessage({ id: "Booking codes and vouchers" })} - -
-
-
- - {intl.formatMessage({ id: "Use bonus cheque" })} - -
-
- - {intl.formatMessage({ id: "Book reward night" })} - -
-
-
- -
- -
+
+
+
) } diff --git a/components/Forms/BookingWidget/FormContent/formContent.module.css b/components/Forms/BookingWidget/FormContent/formContent.module.css new file mode 100644 index 000000000..5bcbc15c8 --- /dev/null +++ b/components/Forms/BookingWidget/FormContent/formContent.module.css @@ -0,0 +1,45 @@ +.input { + display: flex; + gap: var(--Spacing-x2); + width: 100%; + max-width: 1250px; +} + +.button { + width: 118px; + justify-content: center; +} + +.bodyFontSize { + font-size: var(--typography-Caption-Bold-Desktop-fontSize); +} + +.border { + border-right: 1px solid var(--Base-Surface-Subtle-Normal); +} + +.where { + display: flex; + gap: var(--Spacing-x-quarter); + flex-direction: column; + width: 100%; + max-width: 280px; +} + +.when, +.rooms { + width: 100%; + max-width: 240px; +} + +.vouchers { + width: 100%; + max-width: 200px; +} + +.options { + display: flex; + flex-direction: column; + width: 100%; + max-width: 158px; +} diff --git a/components/Forms/BookingWidget/FormContent/index.tsx b/components/Forms/BookingWidget/FormContent/index.tsx new file mode 100644 index 000000000..8ae011a21 --- /dev/null +++ b/components/Forms/BookingWidget/FormContent/index.tsx @@ -0,0 +1,90 @@ +"use client" + +import { useIntl } from "react-intl" + +import Button from "@/components/TempDesignSystem/Button" +import Body from "@/components/TempDesignSystem/Text/Body" + +import styles from "./formContent.module.css" + +export default function FormContent() { + const intl = useIntl() + + const where = intl.formatMessage({ id: "Where to" }) + const when = intl.formatMessage({ id: "When" }) + const rooms = intl.formatMessage({ id: "Rooms & Guests" }) + const vouchers = intl.formatMessage({ id: "Booking codes and vouchers" }) + const bonus = intl.formatMessage({ id: "Use bonus cheque" }) + const reward = intl.formatMessage({ id: "Book reward night" }) + const search = intl.formatMessage({ id: "Find hotels" }) + + return ( + <> +
+
+ + {where} + +
+
+ + {when} + +
+
+ + {rooms} + +
+
+ + {vouchers} + +
+
+
+ + {bonus} + +
+
+ + {reward} + +
+
+
+ + + ) +} diff --git a/components/Forms/BookingWidget/form.module.css b/components/Forms/BookingWidget/form.module.css new file mode 100644 index 000000000..67a91b3c0 --- /dev/null +++ b/components/Forms/BookingWidget/form.module.css @@ -0,0 +1,7 @@ +.form { + display: flex; + justify-content: space-between; + width: 100%; + max-width: 1432px; + padding: var(--Spacing-x2) var(--Spacing-x5); +} diff --git a/components/Forms/BookingWidget/index.tsx b/components/Forms/BookingWidget/index.tsx new file mode 100644 index 000000000..6d1c43cd9 --- /dev/null +++ b/components/Forms/BookingWidget/index.tsx @@ -0,0 +1,61 @@ +"use client" +import { zodResolver } from "@hookform/resolvers/zod" +import { FormProvider, useForm } from "react-hook-form" + +import { dt } from "@/lib/dt" + +import FormContent from "./FormContent" +import { bookingWidgetSchema } from "./schema" + +import styles from "./form.module.css" + +import { BookingWidgetSchema } from "@/types/components/bookingWidget" + +const formId = "booking-widget" + +export default function Form() { + const methods = useForm({ + defaultValues: { + search: { + stayType: "", + stayValue: "", + }, + nights: { + // UTC is required to handle requests from far away timezones https://scandichotels.atlassian.net/browse/SWAP-6375 & PET-507 + // This is specifically to handle timezones falling in different dates. + fromDate: dt().utc().format("DD/MM/YYYY"), + toDate: dt().utc().add(1, "day").format("DD/MM/YYYY"), + }, + bookingCode: "", + redemption: false, + voucher: false, + rooms: [ + { + adults: 1, + childs: [], + }, + ], + }, + mode: "all", + resolver: zodResolver(bookingWidgetSchema), + reValidateMode: "onChange", + }) + + function onSubmit(data: BookingWidgetSchema) { + console.log(data) + // Parse data and route accordignly to Select hotel or select room-rate page + console.log("to be routing") + } + + return ( + + + + + + ) +} diff --git a/components/BookingWidget/schema.ts b/components/Forms/BookingWidget/schema.ts similarity index 100% rename from components/BookingWidget/schema.ts rename to components/Forms/BookingWidget/schema.ts diff --git a/types/components/bookingWidget/index.ts b/types/components/bookingWidget/index.ts index d6032dc3b..1998e3778 100644 --- a/types/components/bookingWidget/index.ts +++ b/types/components/bookingWidget/index.ts @@ -1,5 +1,5 @@ import { z } from "zod" -import { bookingWidgetSchema } from "@/components/BookingWidget/schema" +import { bookingWidgetSchema } from "@/components/Forms/BookingWidget/schema" export type BookingWidgetSchema = z.output From 608fe13004f9aa2542215e6a2f146d42ccc358eb Mon Sep 17 00:00:00 2001 From: Fredrik Thorsson Date: Thu, 15 Aug 2024 11:38:38 +0200 Subject: [PATCH 05/45] feat(SW-240): add simple form --- .../FormContent/formContent.module.css | 12 +-- .../Forms/BookingWidget/FormContent/index.tsx | 101 +++++++----------- .../Forms/BookingWidget/form.module.css | 17 ++- components/Forms/BookingWidget/index.tsx | 40 +++++-- 4 files changed, 87 insertions(+), 83 deletions(-) diff --git a/components/Forms/BookingWidget/FormContent/formContent.module.css b/components/Forms/BookingWidget/FormContent/formContent.module.css index 5bcbc15c8..1d7e96672 100644 --- a/components/Forms/BookingWidget/FormContent/formContent.module.css +++ b/components/Forms/BookingWidget/FormContent/formContent.module.css @@ -5,11 +5,6 @@ max-width: 1250px; } -.button { - width: 118px; - justify-content: center; -} - .bodyFontSize { font-size: var(--typography-Caption-Bold-Desktop-fontSize); } @@ -19,9 +14,6 @@ } .where { - display: flex; - gap: var(--Spacing-x-quarter); - flex-direction: column; width: 100%; max-width: 280px; } @@ -43,3 +35,7 @@ width: 100%; max-width: 158px; } + +.option { + display: flex; +} diff --git a/components/Forms/BookingWidget/FormContent/index.tsx b/components/Forms/BookingWidget/FormContent/index.tsx index 8ae011a21..74d8fc4ba 100644 --- a/components/Forms/BookingWidget/FormContent/index.tsx +++ b/components/Forms/BookingWidget/FormContent/index.tsx @@ -2,7 +2,6 @@ import { useIntl } from "react-intl" -import Button from "@/components/TempDesignSystem/Button" import Body from "@/components/TempDesignSystem/Text/Body" import styles from "./formContent.module.css" @@ -16,75 +15,51 @@ export default function FormContent() { const vouchers = intl.formatMessage({ id: "Booking codes and vouchers" }) const bonus = intl.formatMessage({ id: "Use bonus cheque" }) const reward = intl.formatMessage({ id: "Book reward night" }) - const search = intl.formatMessage({ id: "Find hotels" }) return ( - <> -
-
- - {where} - -
-
- - {when} - -
-
- - {rooms} - -
-
- - {vouchers} - -
-
-
- - {bonus} - -
-
- - {reward} - -
-
+
+
+ + {where} + +
- - + +
+
+
+ + + {bonus} + +
+
+ + + {reward} + +
+
+
) } diff --git a/components/Forms/BookingWidget/form.module.css b/components/Forms/BookingWidget/form.module.css index 67a91b3c0..962eb3fec 100644 --- a/components/Forms/BookingWidget/form.module.css +++ b/components/Forms/BookingWidget/form.module.css @@ -1,7 +1,18 @@ -.form { +.section { display: flex; - justify-content: space-between; - width: 100%; max-width: 1432px; padding: var(--Spacing-x2) var(--Spacing-x5); } + +.form { + width: 100%; +} + +.button { + width: 118px; + justify-content: center; +} + +.bodyFontSize { + font-size: var(--typography-Caption-Bold-Desktop-fontSize); +} diff --git a/components/Forms/BookingWidget/index.tsx b/components/Forms/BookingWidget/index.tsx index 6d1c43cd9..2e40003cb 100644 --- a/components/Forms/BookingWidget/index.tsx +++ b/components/Forms/BookingWidget/index.tsx @@ -1,9 +1,13 @@ "use client" import { zodResolver } from "@hookform/resolvers/zod" import { FormProvider, useForm } from "react-hook-form" +import { useIntl } from "react-intl" import { dt } from "@/lib/dt" +import Button from "@/components/TempDesignSystem/Button" +import Body from "@/components/TempDesignSystem/Text/Body" + import FormContent from "./FormContent" import { bookingWidgetSchema } from "./schema" @@ -14,6 +18,7 @@ import { BookingWidgetSchema } from "@/types/components/bookingWidget" const formId = "booking-widget" export default function Form() { + const intl = useIntl() const methods = useForm({ defaultValues: { search: { @@ -48,14 +53,31 @@ export default function Form() { } return ( -
- - - -
+
+
+ + + +
+ +
) } From 2d45a637e586bfaa1ddede092198b18da03f89d7 Mon Sep 17 00:00:00 2001 From: Fredrik Thorsson Date: Thu, 15 Aug 2024 11:49:34 +0200 Subject: [PATCH 06/45] feat(SW-240): remove width from input class --- .../Forms/BookingWidget/FormContent/formContent.module.css | 2 -- 1 file changed, 2 deletions(-) diff --git a/components/Forms/BookingWidget/FormContent/formContent.module.css b/components/Forms/BookingWidget/FormContent/formContent.module.css index 1d7e96672..767fd6797 100644 --- a/components/Forms/BookingWidget/FormContent/formContent.module.css +++ b/components/Forms/BookingWidget/FormContent/formContent.module.css @@ -1,8 +1,6 @@ .input { display: flex; gap: var(--Spacing-x2); - width: 100%; - max-width: 1250px; } .bodyFontSize { From 3eaaa9aca2abdb763c004fb02910de351a0d3f35 Mon Sep 17 00:00:00 2001 From: Fredrik Thorsson Date: Thu, 15 Aug 2024 13:40:53 +0200 Subject: [PATCH 07/45] feat(SW-240): basic styling forms --- .../BookingWidget/FormContent/formContent.module.css | 5 +++++ components/Forms/BookingWidget/FormContent/index.tsx | 8 ++++---- 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/components/Forms/BookingWidget/FormContent/formContent.module.css b/components/Forms/BookingWidget/FormContent/formContent.module.css index 767fd6797..59d478236 100644 --- a/components/Forms/BookingWidget/FormContent/formContent.module.css +++ b/components/Forms/BookingWidget/FormContent/formContent.module.css @@ -3,6 +3,11 @@ gap: var(--Spacing-x2); } +.input input[type="text"] { + border: none; + padding: var(--Spacing-x1) var(--Spacing-x0); +} + .bodyFontSize { font-size: var(--typography-Caption-Bold-Desktop-fontSize); } diff --git a/components/Forms/BookingWidget/FormContent/index.tsx b/components/Forms/BookingWidget/FormContent/index.tsx index 74d8fc4ba..21120b34a 100644 --- a/components/Forms/BookingWidget/FormContent/index.tsx +++ b/components/Forms/BookingWidget/FormContent/index.tsx @@ -22,19 +22,19 @@ export default function FormContent() { {where} - +
{when} - +
{rooms} - +
{vouchers} - +
From 66f2b202c9f847d6d66c8cfe30c82061ac069838 Mon Sep 17 00:00:00 2001 From: Fredrik Thorsson Date: Thu, 15 Aug 2024 14:09:09 +0200 Subject: [PATCH 08/45] feat(SW-240): change class name --- .../FormContent/formContent.module.css | 2 +- .../Forms/BookingWidget/FormContent/index.tsx | 13 ++++++------- 2 files changed, 7 insertions(+), 8 deletions(-) diff --git a/components/Forms/BookingWidget/FormContent/formContent.module.css b/components/Forms/BookingWidget/FormContent/formContent.module.css index 59d478236..d60dcb72b 100644 --- a/components/Forms/BookingWidget/FormContent/formContent.module.css +++ b/components/Forms/BookingWidget/FormContent/formContent.module.css @@ -8,7 +8,7 @@ padding: var(--Spacing-x1) var(--Spacing-x0); } -.bodyFontSize { +.fontSize { font-size: var(--typography-Caption-Bold-Desktop-fontSize); } diff --git a/components/Forms/BookingWidget/FormContent/index.tsx b/components/Forms/BookingWidget/FormContent/index.tsx index 21120b34a..156578aae 100644 --- a/components/Forms/BookingWidget/FormContent/index.tsx +++ b/components/Forms/BookingWidget/FormContent/index.tsx @@ -1,5 +1,4 @@ "use client" - import { useIntl } from "react-intl" import Body from "@/components/TempDesignSystem/Text/Body" @@ -19,19 +18,19 @@ export default function FormContent() { return (
- + {where}
- + {when}
- + {rooms} @@ -40,7 +39,7 @@ export default function FormContent() { {vouchers} @@ -49,13 +48,13 @@ export default function FormContent() {
- + {bonus}
- + {reward}
From 4a469243e41bccb0ffc51740bad3a947ac4d5bc8 Mon Sep 17 00:00:00 2001 From: Fredrik Thorsson Date: Thu, 15 Aug 2024 14:51:31 +0200 Subject: [PATCH 09/45] feat(SW-240): name change --- components/Forms/BookingWidget/form.module.css | 2 +- components/Forms/BookingWidget/index.tsx | 6 +----- 2 files changed, 2 insertions(+), 6 deletions(-) diff --git a/components/Forms/BookingWidget/form.module.css b/components/Forms/BookingWidget/form.module.css index 962eb3fec..6a4248169 100644 --- a/components/Forms/BookingWidget/form.module.css +++ b/components/Forms/BookingWidget/form.module.css @@ -13,6 +13,6 @@ justify-content: center; } -.bodyFontSize { +.fontSize { font-size: var(--typography-Caption-Bold-Desktop-fontSize); } diff --git a/components/Forms/BookingWidget/index.tsx b/components/Forms/BookingWidget/index.tsx index 2e40003cb..f0ca1de1c 100644 --- a/components/Forms/BookingWidget/index.tsx +++ b/components/Forms/BookingWidget/index.tsx @@ -70,11 +70,7 @@ export default function Form() { intent="primary" className={styles.button} > - + {intl.formatMessage({ id: "Find hotels" })} From 0e084538b335c0c0b41b8a26aa9514baf2a8ad06 Mon Sep 17 00:00:00 2001 From: Fredrik Thorsson Date: Mon, 19 Aug 2024 14:56:38 +0200 Subject: [PATCH 10/45] feat(SW-240): remove box shadow --- components/BookingWidget/bookingWidget.module.css | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/components/BookingWidget/bookingWidget.module.css b/components/BookingWidget/bookingWidget.module.css index 629a9523e..27e9b2279 100644 --- a/components/BookingWidget/bookingWidget.module.css +++ b/components/BookingWidget/bookingWidget.module.css @@ -1,6 +1,6 @@ .container { border-top: 1px solid var(--Base-Border-Subtle); - box-shadow: 0px 16px 24px 0px #00000014; + border-bottom: 1px solid var(--Base-Border-Subtle); } @media screen and (max-width: 1367px) { From 0bf2927e7945801c0aa1feb973e31b463e9e68bb Mon Sep 17 00:00:00 2001 From: Fredrik Thorsson Date: Mon, 19 Aug 2024 14:59:08 +0200 Subject: [PATCH 11/45] feat(SW-240): add form id to button --- components/Forms/BookingWidget/index.tsx | 1 + 1 file changed, 1 insertion(+) diff --git a/components/Forms/BookingWidget/index.tsx b/components/Forms/BookingWidget/index.tsx index f0ca1de1c..309f1931c 100644 --- a/components/Forms/BookingWidget/index.tsx +++ b/components/Forms/BookingWidget/index.tsx @@ -65,6 +65,7 @@ export default function Form() { ) diff --git a/components/TempDesignSystem/Text/Caption/caption.module.css b/components/TempDesignSystem/Text/Caption/caption.module.css index 536fed0a8..b27906dbf 100644 --- a/components/TempDesignSystem/Text/Caption/caption.module.css +++ b/components/TempDesignSystem/Text/Caption/caption.module.css @@ -41,6 +41,14 @@ color: var(--UI-Text-Medium-contrast); } +.red { + color: var(--Scandic-Brand-Scandic-Red); +} + +.white { + color: var(--UI-Opacity-White-100); +} + .center { text-align: center; } diff --git a/components/TempDesignSystem/Text/Caption/variants.ts b/components/TempDesignSystem/Text/Caption/variants.ts index a0188ffee..f1dbafa37 100644 --- a/components/TempDesignSystem/Text/Caption/variants.ts +++ b/components/TempDesignSystem/Text/Caption/variants.ts @@ -9,6 +9,8 @@ const config = { burgundy: styles.burgundy, pale: styles.pale, textMediumContrast: styles.textMediumContrast, + red: styles.red, + white: styles.white, }, textTransform: { bold: styles.bold, From aa9e723cb54aa97eca8769b30bf7a143140c9e77 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matilda=20Landstr=C3=B6m?= Date: Tue, 20 Aug 2024 10:10:18 +0200 Subject: [PATCH 13/45] feat(SW-288): update translations --- .../DynamicContent/LoyaltyLevels/data/SV.json | 2 +- i18n/dictionaries/da.json | 14 +++++++------- i18n/dictionaries/de.json | 10 +++++----- i18n/dictionaries/fi.json | 8 ++++---- i18n/dictionaries/no.json | 4 ++-- i18n/dictionaries/sv.json | 10 +++++----- 6 files changed, 24 insertions(+), 24 deletions(-) diff --git a/components/Loyalty/Blocks/DynamicContent/LoyaltyLevels/data/SV.json b/components/Loyalty/Blocks/DynamicContent/LoyaltyLevels/data/SV.json index 91483535c..c543b884a 100644 --- a/components/Loyalty/Blocks/DynamicContent/LoyaltyLevels/data/SV.json +++ b/components/Loyalty/Blocks/DynamicContent/LoyaltyLevels/data/SV.json @@ -13,7 +13,7 @@ "title": "10 % rabatt på mat under helger" }, { - "title": "Kostnadsfri mocktail för barn under vistelse" + "title": "Fri mocktail för barn under vistelse" } ] }, diff --git a/i18n/dictionaries/da.json b/i18n/dictionaries/da.json index d61a32605..5e98028b6 100644 --- a/i18n/dictionaries/da.json +++ b/i18n/dictionaries/da.json @@ -12,8 +12,8 @@ "As our": "Som vores", "As our Close Friend": "Som vores nære ven", "At the hotel": "På hotellet", - "Book": "Bestil", - "Booking number": "Bestillingsnummer", + "Book": "Book", + "Booking number": "Bookingnummer", "Breakfast": "Morgenmad", "by": "inden", "Cancel": "Afbestille", @@ -24,7 +24,7 @@ "City/State": "By/Stat", "Click here to log in": "Klik her for at logge ind", "Close": "Tæt", - "Coming up": "Kommer op", + "Coming up": "Er lige om hjørnet", "Compare all levels": "Sammenlign alle niveauer", "Contact us": "Kontakt os", "Continue": "Blive ved", @@ -47,7 +47,7 @@ "Find booking": "Find booking", "Flexibility": "Fleksibilitet", "From": "Fra", - "Get inspired": "Blive inspireret", + "Get inspired": "Bliv inspireret", "Go back to overview": "Gå tilbage til oversigten", "Highest level": "Højeste niveau", "How do you want to sleep?": "Hvordan vil du sove?", @@ -55,7 +55,7 @@ "Join Scandic Friends": "Tilmeld dig Scandic Friends", "Language": "Sprog", "Level": "Niveau", - "Level up to unlock": "Niveau op for at låse op", + "Level up to unlock": "Stig i niveau for at låse op", "Log in": "Log på", "Log in here": "Log ind her", "Log out": "Log ud", @@ -88,7 +88,7 @@ "Phone is required": "Telefonnummer er påkrævet", "Phone number": "Telefonnummer", "Please enter a valid phone number": "Indtast venligst et gyldigt telefonnummer", - "Points": "Points", + "Points": "Point", "Points may take up to 10 days to be displayed.": "Det kan tage op til 10 dage at få vist point.", "Points needed to level up": "Point nødvendige for at komme i niveau", "Points needed to stay on level": "Point nødvendige for at holde sig på niveau", @@ -123,7 +123,7 @@ "Welcome": "Velkommen", "Welcome to": "Velkommen til", "Wellness & Exercise": "Velvære & Motion", - "Where should you go next?": "Hvor skal du tage hen næste gang?", + "Where should you go next?": "Find inspiration til dit næste ophold", "Which room class suits you the best?": "Hvilken rumklasse passer bedst til dig", "Year": "År", "You have no previous stays.": "Du har ingen tidligere ophold.", diff --git a/i18n/dictionaries/de.json b/i18n/dictionaries/de.json index 6f3363fff..f3c64bab7 100644 --- a/i18n/dictionaries/de.json +++ b/i18n/dictionaries/de.json @@ -8,10 +8,10 @@ "Amenities": "Annehmlichkeiten", "Arrival date": "Ankunftsdatum", "as of today": "Ab heute", - "As our": "Als unsere", + "As our": "Als unser", "As our Close Friend": "Als unser enger Freund", "At the hotel": "Im Hotel", - "Book": "Buch", + "Book": "Buchen", "Booking number": "Buchungsnummer", "Breakfast": "Frühstück", "by": "bis", @@ -46,7 +46,7 @@ "Find booking": "Buchung finden", "Flexibility": "Flexibilität", "From": "Fromm", - "Get inspired": "Lass dich inspirieren", + "Get inspired": "Lassen Sie sich inspieren", "Go back to overview": "Zurück zur Übersicht", "Highest level": "Höchstes Level", "How do you want to sleep?": "Wie möchtest du schlafen?", @@ -69,7 +69,7 @@ "My wishes": "Meine Wünsche", "New password": "Neues Kennwort", "Next": "Nächste", - "next level:": "Nächste Ebene:", + "next level:": "Nächstes Level:", "No content published": "Kein Inhalt veröffentlicht", "No transactions available": "Keine Transaktionen verfügbar", "Not found": "Nicht gefunden", @@ -117,7 +117,7 @@ "Visiting address": "Besuchsadresse", "Welcome to": "Willkommen zu", "Welcome": "Willkommen", - "Where should you go next?": "Wohin soll es als nächstes gehen?", + "Where should you go next?": "Wo geht es als Nächstes hin?", "Which room class suits you the best?": "Welche Zimmerklasse passt am besten zu Ihnen?", "Year": "Jahr", "You have no previous stays.": "Sie haben keine vorherigen Aufenthalte.", diff --git a/i18n/dictionaries/fi.json b/i18n/dictionaries/fi.json index 3e84c0e6a..e1581623e 100644 --- a/i18n/dictionaries/fi.json +++ b/i18n/dictionaries/fi.json @@ -12,7 +12,7 @@ "As our": "Kuin meidän", "As our Close Friend": "Läheisenä ystävänämme", "At the hotel": "Hotellissa", - "Book": "Kirja", + "Book": "Varaa", "Booking number": "Varausnumero", "Breakfast": "Aamiainen", "by": "mennessä", @@ -58,7 +58,7 @@ "Level up to unlock": "Nosta taso avataksesi lukituksen", "Log in": "Kirjaudu sisään", "Log in here": "Kirjaudu sisään", - "Log out": "Kirjautua ulos", + "Log out": "Kirjaudu ulos", "Meetings & Conferences": "Kokoukset & Konferenssit", "Members": "Jäsenet", "Membership cards": "Jäsenkortit", @@ -123,11 +123,11 @@ "Welcome": "Tervetuloa", "Welcome to": "Tervetuloa", "Wellness & Exercise": "Hyvinvointi & Liikunta", - "Where should you go next?": "Minne sinun pitäisi mennä seuraavaksi?", + "Where should you go next?": "Mihin menisit seuraavaksi?", "Which room class suits you the best?": "Mikä huoneluokka sopii sinulle parhaiten?", "Year": "Vuosi", "You have no previous stays.": "Sinulla ei ole aiempaa oleskelua.", - "You have no upcoming stays.": "Sinulla ei ole tulevia oleskeluja.", + "You have no upcoming stays.": "Sinulla ei ole tulevia majoituksia.", "Your Challenges Conquer & Earn!": "Voita ja ansaitse haasteesi!", "Your level": "Tasosi", "Zip code": "Postinumero", diff --git a/i18n/dictionaries/no.json b/i18n/dictionaries/no.json index d903be4f4..d39a1b64d 100644 --- a/i18n/dictionaries/no.json +++ b/i18n/dictionaries/no.json @@ -5,7 +5,7 @@ "Address": "Adresse", "All our beds are from Bliss, allowing you to adjust the firmness for your perfect comfort.": "Alle sengene våre er fra Bliss, slik at du kan justere fastheten for din perfekte komfort.", "All rooms comes with standard amenities": "Alle rommene har standard fasiliteter", - "Already a friend?": "Allerede en venn?", + "Already a friend?": "Allerede Friend?", "Amenities": "Fasiliteter", "Arrival date": "Ankomstdato", "as of today": "per idag", @@ -123,7 +123,7 @@ "Welcome": "Velkommen", "Welcome to": "Velkommen til", "Wellness & Exercise": "Velvære & Trening", - "Where should you go next?": "Hvor bør du gå videre?", + "Where should you go next?": "Hvor ønsker du å reise neste gang?", "Which room class suits you the best?": "Hvilken romklasse passer deg best?", "Year": "År", "You have no previous stays.": "Du har ingen tidligere opphold.", diff --git a/i18n/dictionaries/sv.json b/i18n/dictionaries/sv.json index 0f3a4e9e1..9be17ce5c 100644 --- a/i18n/dictionaries/sv.json +++ b/i18n/dictionaries/sv.json @@ -58,7 +58,7 @@ "Join Scandic Friends": "Gå med i Scandic Friends", "Language": "Språk", "Level": "Nivå", - "Level up to unlock": "Nivå upp för att låsa upp", + "Level up to unlock": "Levla upp för att låsa upp", "Log in": "Logga in", "Log in here": "Logga in här", "Log out": "Logga ut", @@ -114,7 +114,7 @@ "Something went wrong!": "Något gick fel!", "Street": "Gata", "special character": "speciell karaktär", - "Total Points": "Total poäng", + "Total Points": "Poäng totalt", "Your points to spend": "Dina spenderbara poäng", "Transaction date": "Transaktionsdatum", "Transactions": "Transaktioner", @@ -125,15 +125,15 @@ "Visiting address": "Besöksadress", "Welcome": "Välkommen", "Wellness & Exercise": "Hälsa & Träning", - "Where should you go next?": "Vart ska du gå härnäst?", + "Where should you go next?": "Låter inte en spontanweekend härligt?", "Which room class suits you the best?": "Vilken rumsklass passar dig bäst?", "Year": "År", "You have no previous stays.": "Du har inga tidigare vistelser.", - "You have no upcoming stays.": "Du har inga kommande vistelser.", + "You have no upcoming stays.": "Du har inga planerade resor.", "Your Challenges Conquer & Earn!": "Dina utmaningar Erövra och tjäna!", "Your level": "Din nivå", "Zip code": "Postnummer", - "Room facilities": "Rumlfaciliteter", + "Room facilities": "Rumfaciliteter", "Hotel facilities": "Hotellfaciliteter", "Hotel surroundings": "Hotellomgivning", "Show map": "Visa karta", From 32aa6cf273dcbb2e9ab179e2adc73fe1f0ce75ed Mon Sep 17 00:00:00 2001 From: Michael Zetterberg Date: Tue, 20 Aug 2024 15:02:02 +0200 Subject: [PATCH 14/45] fix(SW-236): add returnUrl to refresh --- app/[lang]/webview/[contentType]/[uid]/page.tsx | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/app/[lang]/webview/[contentType]/[uid]/page.tsx b/app/[lang]/webview/[contentType]/[uid]/page.tsx index 8c6de3a0d..0efdea462 100644 --- a/app/[lang]/webview/[contentType]/[uid]/page.tsx +++ b/app/[lang]/webview/[contentType]/[uid]/page.tsx @@ -1,3 +1,4 @@ +import { headers } from "next/headers" import { notFound, redirect } from "next/navigation" import { serverClient } from "@/lib/trpc/server" @@ -28,7 +29,11 @@ export default async function ContentTypePage({ case "unauthorized": // fall through case "forbidden": // fall through case "token_expired": - redirect(`/${getLang()}/webview/refresh`) + const h = headers() + const returnURL = `/${getLang()}/webview${h.get("x-pathname")!}` + redirect( + `/${getLang()}/webview/refresh?returnUrl=${encodeURIComponent(returnURL)}` + ) } } From 84f5e74f00135b2dd0dddfa2070bf7bc03abd82b Mon Sep 17 00:00:00 2001 From: Tobias Johansson Date: Tue, 20 Aug 2024 15:04:02 +0000 Subject: [PATCH 15/45] Merged in feat/SW-104-add-card (pull request #410) Feat/SW-104 add card * feat: add api endpoints for adding and removing credit card * feat(SW-104): Added Sonner toast lib * feat(SW-104): Add route handlers for add card flow * feat(SW-104): Added link to route handler and trigger toast when query params from callback is set * feat(SW-104): Added translations for add card success toast * feat(SW-104): Refactored to use client request for initiate save card * fix(SW-104): Return proper status codes when initiating save card fails * fix(SW-104): remove delete card endpoint because it was added in SW-245 * fix(SW-104): remove console.log * fix(SW-104): Use api.post for save card request * fix(SW-104): move function declaration above export * fix(SW-104): handle response of save card and use Lang enum * fix(SW-104): added comment for why setTimeout is needed for toast and also removed lang prop * fix(SW-104): added type for AddCreditCardButton props * feat: add toasts * fix(SW-104): start using toasts from ToastHandler and fix problem with duplicate toasts * fix(SW-104): remove unnecessary wrapping div Approved-by: Michael Zetterberg --- .../my-pages/profile/@creditCards/page.tsx | 7 +- app/[lang]/(live)/layout.tsx | 2 + app/api/web/add-card-callback/[lang]/route.ts | 47 +++++++++ components/Icons/CloseLarge.tsx | 40 ++++++++ components/Icons/CrossCircle.tsx | 40 ++++++++ components/Icons/WarningTriangle.tsx | 40 ++++++++ components/Icons/get-icon-by-icon-name.ts | 9 ++ components/Icons/icon.module.css | 2 +- components/Icons/index.tsx | 3 + .../addCreditCardButton.module.css | 3 + .../Profile/AddCreditCardButton/index.tsx | 72 ++++++++++++-- components/TempDesignSystem/Toasts/index.tsx | 96 +++++++++++++++++++ .../TempDesignSystem/Toasts/toasts.module.css | 38 ++++++++ components/TempDesignSystem/Toasts/toasts.ts | 10 ++ .../TempDesignSystem/Toasts/variants.ts | 14 +++ i18n/dictionaries/da.json | 1 + i18n/dictionaries/de.json | 1 + i18n/dictionaries/en.json | 1 + i18n/dictionaries/fi.json | 3 +- i18n/dictionaries/no.json | 1 + i18n/dictionaries/sv.json | 1 + lib/api/endpoints.ts | 1 + lib/api/index.ts | 2 +- package-lock.json | 10 ++ package.json | 1 + server/routers/user/input.ts | 11 +++ server/routers/user/output.ts | 13 +++ server/routers/user/query.ts | 77 ++++++++++++++- types/components/icon.ts | 3 + .../myPages/myProfile/addCreditCardButton.ts | 3 + 30 files changed, 537 insertions(+), 15 deletions(-) create mode 100644 app/api/web/add-card-callback/[lang]/route.ts create mode 100644 components/Icons/CloseLarge.tsx create mode 100644 components/Icons/CrossCircle.tsx create mode 100644 components/Icons/WarningTriangle.tsx create mode 100644 components/Profile/AddCreditCardButton/addCreditCardButton.module.css create mode 100644 components/TempDesignSystem/Toasts/index.tsx create mode 100644 components/TempDesignSystem/Toasts/toasts.module.css create mode 100644 components/TempDesignSystem/Toasts/toasts.ts create mode 100644 components/TempDesignSystem/Toasts/variants.ts create mode 100644 types/components/myPages/myProfile/addCreditCardButton.ts diff --git a/app/[lang]/(live)/(protected)/my-pages/profile/@creditCards/page.tsx b/app/[lang]/(live)/(protected)/my-pages/profile/@creditCards/page.tsx index 6fe1f7e01..a3cff5783 100644 --- a/app/[lang]/(live)/(protected)/my-pages/profile/@creditCards/page.tsx +++ b/app/[lang]/(live)/(protected)/my-pages/profile/@creditCards/page.tsx @@ -1,3 +1,4 @@ +import { env } from "@/env/server" import { serverClient } from "@/lib/trpc/server" import { CreditCard, Delete } from "@/components/Icons" @@ -18,6 +19,8 @@ export default async function CreditCardSlot({ params }: PageArgs) { const { formatMessage } = await getIntl() const creditCards = await serverClient().user.creditCards() + const { lang } = params + return (
@@ -41,7 +44,9 @@ export default async function CreditCardSlot({ params }: PageArgs) { ))}
) : null} - + ) } diff --git a/app/[lang]/(live)/layout.tsx b/app/[lang]/(live)/layout.tsx index 35144b8d9..fe70a0371 100644 --- a/app/[lang]/(live)/layout.tsx +++ b/app/[lang]/(live)/layout.tsx @@ -9,6 +9,7 @@ import TokenRefresher from "@/components/Auth/TokenRefresher" import AdobeSDKScript from "@/components/Current/AdobeSDKScript" import Footer from "@/components/Current/Footer" import VwoScript from "@/components/Current/VwoScript" +import { ToastHandler } from "@/components/TempDesignSystem/Toasts" import { preloadUserTracking } from "@/components/TrackingSDK" import { getIntl } from "@/i18n" import ServerIntlProvider from "@/i18n/Provider" @@ -59,6 +60,7 @@ export default async function RootLayout({ {header} {children} +
diff --git a/app/api/web/add-card-callback/[lang]/route.ts b/app/api/web/add-card-callback/[lang]/route.ts new file mode 100644 index 000000000..17527a66e --- /dev/null +++ b/app/api/web/add-card-callback/[lang]/route.ts @@ -0,0 +1,47 @@ +import { NextRequest } from "next/server" +import { env } from "process" + +import { Lang } from "@/constants/languages" +import { serverClient } from "@/lib/trpc/server" +import { badRequest, internalServerError } from "@/server/errors/next" + +export async function GET( + request: NextRequest, + { params }: { params: { lang: string } } +) { + try { + const lang = params.lang as Lang + + const searchParams = request.nextUrl.searchParams + const success = searchParams.get("success") + const failure = searchParams.get("failure") + const trxId = searchParams.get("datatransTrxId") + + const returnUrl = new URL( + `${env.PUBLIC_URL}/${lang ?? Lang.en}/scandic-friends/my-pages/profile` + ) + + if (success) { + if (!trxId) { + return badRequest("Missing datatransTrxId param") + } + + const saveCardSuccess = await serverClient().user.saveCard({ + transactionId: trxId, + }) + + if (saveCardSuccess) { + returnUrl.searchParams.set("success", "true") + } else { + returnUrl.searchParams.set("failure", "true") + } + } else if (failure) { + returnUrl.searchParams.set("failure", "true") + } + + return Response.redirect(returnUrl, 307) + } catch (error) { + console.error(error) + return internalServerError() + } +} diff --git a/components/Icons/CloseLarge.tsx b/components/Icons/CloseLarge.tsx new file mode 100644 index 000000000..e6692f914 --- /dev/null +++ b/components/Icons/CloseLarge.tsx @@ -0,0 +1,40 @@ +import { iconVariants } from "./variants" + +import type { IconProps } from "@/types/components/icon" + +export default function CloseLargeIcon({ + className, + color, + ...props +}: IconProps) { + const classNames = iconVariants({ className, color }) + return ( + + + + + + + + + ) +} diff --git a/components/Icons/CrossCircle.tsx b/components/Icons/CrossCircle.tsx new file mode 100644 index 000000000..f4c4c258a --- /dev/null +++ b/components/Icons/CrossCircle.tsx @@ -0,0 +1,40 @@ +import { iconVariants } from "./variants" + +import type { IconProps } from "@/types/components/icon" + +export default function CrossCircleIcon({ + className, + color, + ...props +}: IconProps) { + const classNames = iconVariants({ className, color }) + return ( + + + + + + + + + ) +} diff --git a/components/Icons/WarningTriangle.tsx b/components/Icons/WarningTriangle.tsx new file mode 100644 index 000000000..78d47d60a --- /dev/null +++ b/components/Icons/WarningTriangle.tsx @@ -0,0 +1,40 @@ +import { iconVariants } from "./variants" + +import type { IconProps } from "@/types/components/icon" + +export default function WarningTriangleIcon({ + className, + color, + ...props +}: IconProps) { + const classNames = iconVariants({ className, color }) + return ( + + + + + + + + + ) +} diff --git a/components/Icons/get-icon-by-icon-name.ts b/components/Icons/get-icon-by-icon-name.ts index 897ca8b28..aa20d00ab 100644 --- a/components/Icons/get-icon-by-icon-name.ts +++ b/components/Icons/get-icon-by-icon-name.ts @@ -14,8 +14,10 @@ import { ChevronDownIcon, ChevronRightIcon, CloseIcon, + CloseLarge, CoffeeIcon, ConciergeIcon, + CrossCircle, DoorOpenIcon, ElectricBikeIcon, EmailIcon, @@ -34,6 +36,7 @@ import { PlusCircleIcon, RestaurantIcon, TshirtWashIcon, + WarningTriangle, WifiIcon, } from "." @@ -59,6 +62,8 @@ export function getIconByIconName(icon?: IconName): FC | null { return CellphoneIcon case IconName.Check: return CheckIcon + case IconName.CrossCircle: + return CrossCircle case IconName.CheckCircle: return CheckCircleIcon case IconName.ChevronDown: @@ -67,6 +72,8 @@ export function getIconByIconName(icon?: IconName): FC | null { return ChevronRightIcon case IconName.Close: return CloseIcon + case IconName.CloseLarge: + return CloseLarge case IconName.Coffee: return CoffeeIcon case IconName.Concierge: @@ -107,6 +114,8 @@ export function getIconByIconName(icon?: IconName): FC | null { return RestaurantIcon case IconName.TshirtWash: return TshirtWashIcon + case IconName.WarningTriangle: + return WarningTriangle case IconName.Wifi: return WifiIcon default: diff --git a/components/Icons/icon.module.css b/components/Icons/icon.module.css index bc8715040..ddab46d97 100644 --- a/components/Icons/icon.module.css +++ b/components/Icons/icon.module.css @@ -44,5 +44,5 @@ .white, .white * { - fill: var(--Scandic-Opacity-White-100); + fill: var(--UI-Opacity-White-100); } diff --git a/components/Icons/index.tsx b/components/Icons/index.tsx index 848e701fe..cb797f6dd 100644 --- a/components/Icons/index.tsx +++ b/components/Icons/index.tsx @@ -11,9 +11,11 @@ export { default as CheckCircleIcon } from "./CheckCircle" export { default as ChevronDownIcon } from "./ChevronDown" export { default as ChevronRightIcon } from "./ChevronRight" export { default as CloseIcon } from "./Close" +export { default as CloseLarge } from "./CloseLarge" export { default as CoffeeIcon } from "./Coffee" export { default as ConciergeIcon } from "./Concierge" export { default as CreditCard } from "./CreditCard" +export { default as CrossCircle } from "./CrossCircle" export { default as Delete } from "./Delete" export { default as DoorOpenIcon } from "./DoorOpen" export { default as ElectricBikeIcon } from "./ElectricBike" @@ -34,4 +36,5 @@ export { default as PlusCircleIcon } from "./PlusCircle" export { default as RestaurantIcon } from "./Restaurant" export { default as ScandicLogoIcon } from "./ScandicLogo" export { default as TshirtWashIcon } from "./TshirtWash" +export { default as WarningTriangle } from "./WarningTriangle" export { default as WifiIcon } from "./Wifi" diff --git a/components/Profile/AddCreditCardButton/addCreditCardButton.module.css b/components/Profile/AddCreditCardButton/addCreditCardButton.module.css new file mode 100644 index 000000000..3f0f74203 --- /dev/null +++ b/components/Profile/AddCreditCardButton/addCreditCardButton.module.css @@ -0,0 +1,3 @@ +.addCreditCardButton { + justify-self: flex-start; +} diff --git a/components/Profile/AddCreditCardButton/index.tsx b/components/Profile/AddCreditCardButton/index.tsx index a85ef1cf6..3c6286a7d 100644 --- a/components/Profile/AddCreditCardButton/index.tsx +++ b/components/Profile/AddCreditCardButton/index.tsx @@ -1,31 +1,83 @@ "use client" +import { usePathname, useRouter, useSearchParams } from "next/navigation" +import { useEffect } from "react" import { useIntl } from "react-intl" +import { toast } from "sonner" import { trpc } from "@/lib/trpc/client" import { PlusCircleIcon } from "@/components/Icons" import Button from "@/components/TempDesignSystem/Button" +import useLang from "@/hooks/useLang" -export default function AddCreditCardButton() { - const { formatMessage } = useIntl() +import styles from "./addCreditCardButton.module.css" + +import { type AddCreditCardButtonProps } from "@/types/components/myPages/myProfile/addCreditCardButton" + +let hasRunOnce = false + +function useAddCardResultToast() { + const intl = useIntl() + const router = useRouter() + const pathname = usePathname() + const searchParams = useSearchParams() + + useEffect(() => { + if (hasRunOnce) return + + const success = searchParams.get("success") + const failure = searchParams.get("failure") + + if (success) { + // setTimeout is used to make sure DOM is loaded before triggering toast. See documentation for more info: https://sonner.emilkowal.ski/toast#render-toast-on-page-load + setTimeout(() => { + toast.success( + intl.formatMessage({ id: "Your card was successfully saved!" }) + ) + }) + } else if (failure) { + setTimeout(() => { + toast.error(intl.formatMessage({ id: "Something went wrong!" })) + }) + } + + router.replace(pathname) + hasRunOnce = true + }, [intl, pathname, router, searchParams]) +} + +export default function AddCreditCardButton({ + redirectUrl, +}: AddCreditCardButtonProps) { + const intl = useIntl() + const router = useRouter() + const lang = useLang() + useAddCardResultToast() + + const initiateAddCard = trpc.user.initiateSaveCard.useMutation({ + onSuccess: (result) => (result ? router.push(result.attribute.link) : null), + onError: () => + toast.error(intl.formatMessage({ id: "Something went wrong!" })), + }) - async function handleAddCreditCard() { - // TODO: initiate add credit card flow and redirect user to planet: - // const { url } = trpc.user.creditCard.add.useMutation() - // router.redirect(url) - console.log("Credit card added!") - } return ( ) } diff --git a/components/TempDesignSystem/Toasts/index.tsx b/components/TempDesignSystem/Toasts/index.tsx new file mode 100644 index 000000000..5e3ad1922 --- /dev/null +++ b/components/TempDesignSystem/Toasts/index.tsx @@ -0,0 +1,96 @@ +import { ExternalToast, toast as sonnerToast, Toaster } from "sonner" + +import { + CheckCircleIcon, + CloseLarge, + CrossCircle, + InfoCircleIcon, + WarningTriangle, +} from "@/components/Icons" + +import Button from "../Button" +import Body from "../Text/Body" +import { ToastsProps } from "./toasts" +import { toastVariants } from "./variants" + +import styles from "./toasts.module.css" + +export function ToastHandler() { + return +} + +function getIcon(variant: ToastsProps["variant"]) { + switch (variant) { + case "error": + return CrossCircle + case "info": + return InfoCircleIcon + case "success": + return CheckCircleIcon + case "warning": + return WarningTriangle + } +} + +export function Toast({ message, onClose, variant }: ToastsProps) { + const className = toastVariants({ variant }) + const Icon = getIcon(variant) + return ( +
+
+ {Icon && } +
+ {message} + +
+ ) +} + +export const toast = { + success: (message: string, options?: ExternalToast) => + sonnerToast.custom( + (t) => ( + sonnerToast.dismiss(t)} + /> + ), + options + ), + info: (message: string, options?: ExternalToast) => + sonnerToast.custom( + (t) => ( + sonnerToast.dismiss(t)} + /> + ), + options + ), + error: (message: string, options?: ExternalToast) => + sonnerToast.custom( + (t) => ( + sonnerToast.dismiss(t)} + /> + ), + options + ), + warning: (message: string, options?: ExternalToast) => + sonnerToast.custom( + (t) => ( + sonnerToast.dismiss(t)} + /> + ), + options + ), +} diff --git a/components/TempDesignSystem/Toasts/toasts.module.css b/components/TempDesignSystem/Toasts/toasts.module.css new file mode 100644 index 000000000..cc7b305fb --- /dev/null +++ b/components/TempDesignSystem/Toasts/toasts.module.css @@ -0,0 +1,38 @@ +.toast { + display: grid; + grid-template-columns: auto 1fr auto; + border-radius: var(--Corner-radius-Large); + overflow: hidden; + background: var(--Base-Surface-Primary-light-Normal); + box-shadow: 0px 0px 8px 2px rgba(0, 0, 0, 0.08); + align-items: center; + width: var(--width); +} + +.message { + padding: var(--Spacing-x2) var(--Spacing-x-one-and-half); +} + +.success { + --icon-background-color: var(--UI-Semantic-Success); +} + +.error { + --icon-background-color: var(--UI-Semantic-Error); +} + +.warning { + --icon-background-color: var(--UI-Semantic-Warning); +} + +.info { + --icon-background-color: var(--UI-Semantic-Information); +} + +.iconContainer { + display: flex; + background-color: var(--icon-background-color); + padding: var(--Spacing-x2); + align-items: center; + justify-content: center; +} diff --git a/components/TempDesignSystem/Toasts/toasts.ts b/components/TempDesignSystem/Toasts/toasts.ts new file mode 100644 index 000000000..457ab67ac --- /dev/null +++ b/components/TempDesignSystem/Toasts/toasts.ts @@ -0,0 +1,10 @@ +import { toastVariants } from "./variants" + +import type { VariantProps } from "class-variance-authority" + +export interface ToastsProps + extends Omit, "color">, + VariantProps { + message: string + onClose: () => void +} diff --git a/components/TempDesignSystem/Toasts/variants.ts b/components/TempDesignSystem/Toasts/variants.ts new file mode 100644 index 000000000..a215edc77 --- /dev/null +++ b/components/TempDesignSystem/Toasts/variants.ts @@ -0,0 +1,14 @@ +import { cva } from "class-variance-authority" + +import styles from "./toasts.module.css" + +export const toastVariants = cva(styles.toast, { + variants: { + variant: { + success: styles.success, + info: styles.info, + warning: styles.warning, + error: styles.error, + }, + }, +}) diff --git a/i18n/dictionaries/da.json b/i18n/dictionaries/da.json index 5e98028b6..4af82935b 100644 --- a/i18n/dictionaries/da.json +++ b/i18n/dictionaries/da.json @@ -128,6 +128,7 @@ "Year": "År", "You have no previous stays.": "Du har ingen tidligere ophold.", "You have no upcoming stays.": "Du har ingen kommende ophold.", + "Your card was successfully saved!": "Dit kort blev gemt!", "Your Challenges Conquer & Earn!": "Dine udfordringer Overvind og tjen!", "Your level": "Dit niveau", "Zip code": "Postnummer", diff --git a/i18n/dictionaries/de.json b/i18n/dictionaries/de.json index f3c64bab7..ca524c900 100644 --- a/i18n/dictionaries/de.json +++ b/i18n/dictionaries/de.json @@ -122,6 +122,7 @@ "Year": "Jahr", "You have no previous stays.": "Sie haben keine vorherigen Aufenthalte.", "You have no upcoming stays.": "Sie haben keine bevorstehenden Aufenthalte.", + "Your card was successfully saved!": "Ihre Karte wurde erfolgreich gespeichert!", "Your Challenges Conquer & Earn!": "Meistern Sie Ihre Herausforderungen und verdienen Sie Geld!", "Your level": "Dein level", "Zip code": "PLZ", diff --git a/i18n/dictionaries/en.json b/i18n/dictionaries/en.json index 28af28045..57f57bfd7 100644 --- a/i18n/dictionaries/en.json +++ b/i18n/dictionaries/en.json @@ -133,6 +133,7 @@ "Year": "Year", "You have no previous stays.": "You have no previous stays.", "You have no upcoming stays.": "You have no upcoming stays.", + "Your card was successfully saved!": "Your card was successfully saved!", "Your Challenges Conquer & Earn!": "Your Challenges Conquer & Earn!", "Your level": "Your level", "Zip code": "Zip code", diff --git a/i18n/dictionaries/fi.json b/i18n/dictionaries/fi.json index e1581623e..452693de1 100644 --- a/i18n/dictionaries/fi.json +++ b/i18n/dictionaries/fi.json @@ -127,7 +127,8 @@ "Which room class suits you the best?": "Mikä huoneluokka sopii sinulle parhaiten?", "Year": "Vuosi", "You have no previous stays.": "Sinulla ei ole aiempaa oleskelua.", - "You have no upcoming stays.": "Sinulla ei ole tulevia majoituksia.", + "You have no upcoming stays.": "Sinulla ei ole tulevia oleskeluja.", + "Your card was successfully saved!": "Korttisi tallennettu onnistuneesti!", "Your Challenges Conquer & Earn!": "Voita ja ansaitse haasteesi!", "Your level": "Tasosi", "Zip code": "Postinumero", diff --git a/i18n/dictionaries/no.json b/i18n/dictionaries/no.json index d39a1b64d..a3e2ce356 100644 --- a/i18n/dictionaries/no.json +++ b/i18n/dictionaries/no.json @@ -128,6 +128,7 @@ "Year": "År", "You have no previous stays.": "Du har ingen tidligere opphold.", "You have no upcoming stays.": "Du har ingen kommende opphold.", + "Your card was successfully saved!": "Kortet ditt ble lagret!", "Your Challenges Conquer & Earn!": "Dine utfordringer Erobre og tjen!", "Your level": "Ditt nivå", "Zip code": "Post kode", diff --git a/i18n/dictionaries/sv.json b/i18n/dictionaries/sv.json index 9be17ce5c..e0ec7b543 100644 --- a/i18n/dictionaries/sv.json +++ b/i18n/dictionaries/sv.json @@ -130,6 +130,7 @@ "Year": "År", "You have no previous stays.": "Du har inga tidigare vistelser.", "You have no upcoming stays.": "Du har inga planerade resor.", + "Your card was successfully saved!": "Ditt kort har sparats!", "Your Challenges Conquer & Earn!": "Dina utmaningar Erövra och tjäna!", "Your level": "Din nivå", "Zip code": "Postnummer", diff --git a/lib/api/endpoints.ts b/lib/api/endpoints.ts index 1c1570277..739029fe7 100644 --- a/lib/api/endpoints.ts +++ b/lib/api/endpoints.ts @@ -8,6 +8,7 @@ export namespace endpoints { export const enum v1 { profile = "profile/v1/Profile", creditCards = `${profile}/creditCards`, + initiateSaveCard = `${creditCards}/initiateSaveCard`, friendTransactions = "profile/v1/Transaction/friendTransactions", upcomingStays = "booking/v1/Stays/future", previousStays = "booking/v1/Stays/past", diff --git a/lib/api/index.ts b/lib/api/index.ts index 9455a85ed..db751428d 100644 --- a/lib/api/index.ts +++ b/lib/api/index.ts @@ -53,7 +53,7 @@ export async function patch( } export async function post( - endpoint: Endpoint, + endpoint: Endpoint | `${Endpoint}/${string}`, options: RequestOptionsWithJSONBody ) { const { body, ...requestOptions } = options diff --git a/package-lock.json b/package-lock.json index 67df4a0a0..95c925618 100644 --- a/package-lock.json +++ b/package-lock.json @@ -41,6 +41,7 @@ "react-international-phone": "^4.2.6", "react-intl": "^6.6.8", "server-only": "^0.0.1", + "sonner": "^1.5.0", "superjson": "^2.2.1", "zod": "^3.22.4", "zustand": "^4.5.2" @@ -16538,6 +16539,15 @@ "tslib": "^2.0.3" } }, + "node_modules/sonner": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/sonner/-/sonner-1.5.0.tgz", + "integrity": "sha512-FBjhG/gnnbN6FY0jaNnqZOMmB73R+5IiyYAw8yBj7L54ER7HB3fOSE5OFiQiE2iXWxeXKvg6fIP4LtVppHEdJA==", + "peerDependencies": { + "react": "^18.0.0", + "react-dom": "^18.0.0" + } + }, "node_modules/source-map": { "version": "0.6.1", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", diff --git a/package.json b/package.json index ee6cb94e2..5a3130158 100644 --- a/package.json +++ b/package.json @@ -57,6 +57,7 @@ "react-international-phone": "^4.2.6", "react-intl": "^6.6.8", "server-only": "^0.0.1", + "sonner": "^1.5.0", "superjson": "^2.2.1", "zod": "^3.22.4", "zustand": "^4.5.2" diff --git a/server/routers/user/input.ts b/server/routers/user/input.ts index 56a8fe385..c7aa4102b 100644 --- a/server/routers/user/input.ts +++ b/server/routers/user/input.ts @@ -18,3 +18,14 @@ export const soonestUpcomingStaysInput = z limit: z.number().int().positive(), }) .default({ limit: 3 }) + +export const initiateSaveCardInput = z.object({ + language: z.string(), + mobileToken: z.boolean(), + redirectUrl: z.string(), +}) + +export const saveCardInput = z.object({ + transactionId: z.string(), + merchantId: z.string().optional(), +}) diff --git a/server/routers/user/output.ts b/server/routers/user/output.ts index ea156f7bf..77f8e992a 100644 --- a/server/routers/user/output.ts +++ b/server/routers/user/output.ts @@ -180,6 +180,8 @@ export const getCreditCardsSchema = z.object({ expirationDate: z.string(), cardType: z.string(), }), + id: z.string(), + type: z.string(), }) ), }) @@ -193,3 +195,14 @@ export const getMembershipCardsSchema = z.array( membershipType: z.string(), }) ) + +export const initiateSaveCardSchema = z.object({ + data: z.object({ + attribute: z.object({ + transactionId: z.string(), + link: z.string(), + mobileToken: z.string().optional(), + }), + type: z.string(), + }), +}) diff --git a/server/routers/user/query.ts b/server/routers/user/query.ts index 069270bea..055c09766 100644 --- a/server/routers/user/query.ts +++ b/server/routers/user/query.ts @@ -1,6 +1,12 @@ import { Lang } from "@/constants/languages" import { env } from "@/env/server" import * as api from "@/lib/api" +import { internalServerError } from "@/server/errors/next" +import { + badRequestError, + forbiddenError, + unauthorizedError, +} from "@/server/errors/trpc" import { protectedProcedure, router, @@ -12,13 +18,19 @@ import * as maskValue from "@/utils/maskValue" import { getMembership, getMembershipCards } from "@/utils/user" import encryptValue from "../utils/encryptValue" -import { getUserInputSchema, staysInput } from "./input" +import { + getUserInputSchema, + initiateSaveCardInput, + saveCardInput, + staysInput, +} from "./input" import { getCreditCardsSchema, getFriendTransactionsSchema, getMembershipCardsSchema, getStaysSchema, getUserSchema, + initiateSaveCardSchema, Stay, } from "./output" import { benefits, extendedUser, nextLevelPerks } from "./temp" @@ -517,6 +529,69 @@ export const userQueryRouter = router({ return verifiedData.data.data }), + initiateSaveCard: protectedProcedure + .input(initiateSaveCardInput) + .mutation(async function ({ ctx, input }) { + const apiResponse = await api.post(api.endpoints.v1.initiateSaveCard, { + headers: { + Authorization: `Bearer ${ctx.session.token.access_token}`, + }, + body: { + language: input.language, + mobileToken: input.mobileToken, + redirectUrl: input.redirectUrl, + }, + }) + + if (!apiResponse.ok) { + switch (apiResponse.status) { + case 400: + throw badRequestError(apiResponse) + case 401: + throw unauthorizedError(apiResponse) + case 403: + throw forbiddenError(apiResponse) + default: + throw internalServerError(apiResponse) + } + } + + const apiJson = await apiResponse.json() + const verifiedData = initiateSaveCardSchema.safeParse(apiJson) + if (!verifiedData.success) { + console.error(`Failed to initiate save card data`) + console.error(`User: (${JSON.stringify(ctx.session.user)})`) + console.error(verifiedData.error) + return null + } + + return verifiedData.data.data + }), + + saveCard: protectedProcedure.input(saveCardInput).mutation(async function ({ + ctx, + input, + }) { + const apiResponse = await api.post( + `${api.endpoints.v1.creditCards}/${input.transactionId}`, + { + headers: { + Authorization: `Bearer ${ctx.session.token.access_token}`, + }, + body: {}, + } + ) + + if (!apiResponse.ok) { + console.error(`API Response Failed - Save card`) + console.error(`User: (${JSON.stringify(ctx.session.user)})`) + console.error(apiResponse) + return null + } + + return true + }), + membershipCards: protectedProcedure.query(async function ({ ctx }) { const apiResponse = await api.get(api.endpoints.v1.profile, { cache: "no-store", diff --git a/types/components/icon.ts b/types/components/icon.ts index ff76c28eb..8838658e1 100644 --- a/types/components/icon.ts +++ b/types/components/icon.ts @@ -16,10 +16,12 @@ export enum IconName { Camera = "Camera", Cellphone = "Cellphone", Check = "Check", + CrossCircle = "CrossCircle", CheckCircle = "CheckCircle", ChevronDown = "ChevronDown", ChevronRight = "ChevronRight", Close = "Close", + CloseLarge = "CloseLarge", Coffee = "Coffee", Concierge = "Concierge", DoorOpen = "DoorOpen", @@ -41,4 +43,5 @@ export enum IconName { Restaurant = "Restaurant", TshirtWash = "TshirtWash", Wifi = "Wifi", + WarningTriangle = "WarningTriangle", } diff --git a/types/components/myPages/myProfile/addCreditCardButton.ts b/types/components/myPages/myProfile/addCreditCardButton.ts new file mode 100644 index 000000000..61a8e3aef --- /dev/null +++ b/types/components/myPages/myProfile/addCreditCardButton.ts @@ -0,0 +1,3 @@ +export type AddCreditCardButtonProps = { + redirectUrl: string +} From 86c4059c16593ccb0a0199134962932d5f9002a2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matilda=20Landstr=C3=B6m?= Date: Wed, 21 Aug 2024 08:29:52 +0200 Subject: [PATCH 16/45] feat(SW-288): Update Finnish logout url --- constants/routes/handleAuth.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/constants/routes/handleAuth.js b/constants/routes/handleAuth.js index a695dfa6d..46d890133 100644 --- a/constants/routes/handleAuth.js +++ b/constants/routes/handleAuth.js @@ -17,7 +17,7 @@ export const logout = { da: "/da/log-ud", de: "/de/ausloggen", en: "/en/logout", - fi: "/fi/kirjautua-ulos", + fi: "/fi/kirjaudu-ulos", no: "/no/logg-ut", sv: "/sv/logga-ut", } From 6f293be3a7b608578c32eb0578110eea8fa916f8 Mon Sep 17 00:00:00 2001 From: Arvid Norlin Date: Tue, 13 Aug 2024 11:33:06 +0200 Subject: [PATCH 17/45] feat(SW-164): Add pagination for friendship transactions --- .../Points/EarnAndBurn/Desktop/index.tsx | 76 ---------------- .../Desktop/Row/AwardPoints.tsx | 0 .../Desktop/Row/awardPointsVariants.ts | 0 .../Desktop/Row/index.tsx | 9 +- .../Desktop/Row/row.module.css | 0 .../Desktop/desktop.module.css | 19 ++++ .../TransactionTable/Desktop/index.tsx | 91 +++++++++++++++++++ .../{ => TransactionTable}/Mobile/index.tsx | 38 ++++++-- .../Mobile/mobile.module.css | 10 ++ .../EarnAndBurn/TransactionTable/index.tsx | 34 +++++++ .../Blocks/Points/EarnAndBurn/index.tsx | 8 +- server/routers/user/query.ts | 1 + .../components/myPages/myPage/earnAndBurn.ts | 7 +- 13 files changed, 202 insertions(+), 91 deletions(-) delete mode 100644 components/MyPages/Blocks/Points/EarnAndBurn/Desktop/index.tsx rename components/MyPages/Blocks/Points/EarnAndBurn/{ => TransactionTable}/Desktop/Row/AwardPoints.tsx (100%) rename components/MyPages/Blocks/Points/EarnAndBurn/{ => TransactionTable}/Desktop/Row/awardPointsVariants.ts (100%) rename components/MyPages/Blocks/Points/EarnAndBurn/{ => TransactionTable}/Desktop/Row/index.tsx (87%) rename components/MyPages/Blocks/Points/EarnAndBurn/{ => TransactionTable}/Desktop/Row/row.module.css (100%) rename components/MyPages/Blocks/Points/EarnAndBurn/{ => TransactionTable}/Desktop/desktop.module.css (57%) create mode 100644 components/MyPages/Blocks/Points/EarnAndBurn/TransactionTable/Desktop/index.tsx rename components/MyPages/Blocks/Points/EarnAndBurn/{ => TransactionTable}/Mobile/index.tsx (67%) rename components/MyPages/Blocks/Points/EarnAndBurn/{ => TransactionTable}/Mobile/mobile.module.css (73%) create mode 100644 components/MyPages/Blocks/Points/EarnAndBurn/TransactionTable/index.tsx diff --git a/components/MyPages/Blocks/Points/EarnAndBurn/Desktop/index.tsx b/components/MyPages/Blocks/Points/EarnAndBurn/Desktop/index.tsx deleted file mode 100644 index 3046e44e2..000000000 --- a/components/MyPages/Blocks/Points/EarnAndBurn/Desktop/index.tsx +++ /dev/null @@ -1,76 +0,0 @@ -import Body from "@/components/TempDesignSystem/Text/Body" -import { getIntl } from "@/i18n" - -import Row from "./Row" - -import styles from "./desktop.module.css" - -import type { TableProps } from "@/types/components/myPages/myPage/earnAndBurn" - -const tableHeadings = [ - "Arrival date", - "Description", - "Booking number", - "Transaction date", - "Points", -] - -export default async function DesktopTable({ transactions }: TableProps) { - const { formatMessage } = await getIntl() - return ( -
- {transactions.length ? ( - - - - {tableHeadings.map((heading) => ( - - ))} - - - - {transactions.map((transaction) => ( - - ))} - -
- - {formatMessage({ id: heading })} - -
- ) : ( - // TODO: add once pagination is available through API - // - - - - {tableHeadings.map((heading) => ( - - ))} - - - - - - - -
- {heading} -
- {formatMessage({ id: "No transactions available" })} -
- )} -
- ) -} diff --git a/components/MyPages/Blocks/Points/EarnAndBurn/Desktop/Row/AwardPoints.tsx b/components/MyPages/Blocks/Points/EarnAndBurn/TransactionTable/Desktop/Row/AwardPoints.tsx similarity index 100% rename from components/MyPages/Blocks/Points/EarnAndBurn/Desktop/Row/AwardPoints.tsx rename to components/MyPages/Blocks/Points/EarnAndBurn/TransactionTable/Desktop/Row/AwardPoints.tsx diff --git a/components/MyPages/Blocks/Points/EarnAndBurn/Desktop/Row/awardPointsVariants.ts b/components/MyPages/Blocks/Points/EarnAndBurn/TransactionTable/Desktop/Row/awardPointsVariants.ts similarity index 100% rename from components/MyPages/Blocks/Points/EarnAndBurn/Desktop/Row/awardPointsVariants.ts rename to components/MyPages/Blocks/Points/EarnAndBurn/TransactionTable/Desktop/Row/awardPointsVariants.ts diff --git a/components/MyPages/Blocks/Points/EarnAndBurn/Desktop/Row/index.tsx b/components/MyPages/Blocks/Points/EarnAndBurn/TransactionTable/Desktop/Row/index.tsx similarity index 87% rename from components/MyPages/Blocks/Points/EarnAndBurn/Desktop/Row/index.tsx rename to components/MyPages/Blocks/Points/EarnAndBurn/TransactionTable/Desktop/Row/index.tsx index 661a476d7..540b68891 100644 --- a/components/MyPages/Blocks/Points/EarnAndBurn/Desktop/Row/index.tsx +++ b/components/MyPages/Blocks/Points/EarnAndBurn/TransactionTable/Desktop/Row/index.tsx @@ -1,6 +1,9 @@ +"use client" + +import { useIntl } from "react-intl" + import { dt } from "@/lib/dt" -import { getIntl } from "@/i18n" import { getLang } from "@/i18n/serverContext" import AwardPoints from "./AwardPoints" @@ -9,8 +12,8 @@ import styles from "./row.module.css" import type { RowProps } from "@/types/components/myPages/myPage/earnAndBurn" -export default async function Row({ transaction }: RowProps) { - const { formatMessage } = await getIntl() +export default function Row({ transaction }: RowProps) { + const { formatMessage } = useIntl() const description = transaction.hotelName && transaction.city ? `${transaction.hotelName}, ${transaction.city} ${transaction.nights} ${formatMessage({ id: "nights" })}` diff --git a/components/MyPages/Blocks/Points/EarnAndBurn/Desktop/Row/row.module.css b/components/MyPages/Blocks/Points/EarnAndBurn/TransactionTable/Desktop/Row/row.module.css similarity index 100% rename from components/MyPages/Blocks/Points/EarnAndBurn/Desktop/Row/row.module.css rename to components/MyPages/Blocks/Points/EarnAndBurn/TransactionTable/Desktop/Row/row.module.css diff --git a/components/MyPages/Blocks/Points/EarnAndBurn/Desktop/desktop.module.css b/components/MyPages/Blocks/Points/EarnAndBurn/TransactionTable/Desktop/desktop.module.css similarity index 57% rename from components/MyPages/Blocks/Points/EarnAndBurn/Desktop/desktop.module.css rename to components/MyPages/Blocks/Points/EarnAndBurn/TransactionTable/Desktop/desktop.module.css index 101d99cec..113c23aba 100644 --- a/components/MyPages/Blocks/Points/EarnAndBurn/Desktop/desktop.module.css +++ b/components/MyPages/Blocks/Points/EarnAndBurn/TransactionTable/Desktop/desktop.module.css @@ -28,6 +28,25 @@ background-color: #fff; } +.footer { + background-color: var(--Scandic-Brand-Pale-Peach); + border-left: 1px solid var(--Scandic-Brand-Pale-Peach); + border-right: 1px solid var(--Scandic-Brand-Pale-Peach); + display: flex; + padding: 20px 32px; + justify-content: center; +} + +.loadMoreButton { + border: none; + background-color: transparent; + color: var(--Main-Brand-Burgundy); + font-size: var(--typography-Caption-Bold-Desktop-fontSize); + display: flex; + align-items: center; + gap: var(--Spacing-x-half); + cursor: pointer; +} @media screen and (min-width: 768px) { .container { display: flex; diff --git a/components/MyPages/Blocks/Points/EarnAndBurn/TransactionTable/Desktop/index.tsx b/components/MyPages/Blocks/Points/EarnAndBurn/TransactionTable/Desktop/index.tsx new file mode 100644 index 000000000..fc12a4a00 --- /dev/null +++ b/components/MyPages/Blocks/Points/EarnAndBurn/TransactionTable/Desktop/index.tsx @@ -0,0 +1,91 @@ +"use client" + + +import { useIntl } from "react-intl" + +import { ChevronDownIcon } from "@/components/Icons" +import Body from "@/components/TempDesignSystem/Text/Body" + +import Row from "./Row" + +import styles from "./desktop.module.css" + +import type { TablePropsPagination } from "@/types/components/myPages/myPage/earnAndBurn" + +const tableHeadings = [ + "Arrival date", + "Description", + "Booking number", + "Transaction date", + "Points", +] + +export default function DesktopTable({ + transactions, + showMore, + hasMore, +}: TablePropsPagination) { + const { formatMessage } = useIntl() + + return ( +
+ {transactions.length ? ( +
+ + + + {tableHeadings.map((heading) => ( + + ))} + + + + {transactions.map((transaction, idx) => ( + + ))} + +
+ + {formatMessage({ id: heading })} + +
+ {hasMore ? ( +
+ +
+ ) : null} +
+ ) : ( + + + + {tableHeadings.map((heading) => ( + + ))} + + + + + + + +
+ {heading} +
+ {formatMessage({ id: "No transactions available" })} +
+ )} +
+ ) +} diff --git a/components/MyPages/Blocks/Points/EarnAndBurn/Mobile/index.tsx b/components/MyPages/Blocks/Points/EarnAndBurn/TransactionTable/Mobile/index.tsx similarity index 67% rename from components/MyPages/Blocks/Points/EarnAndBurn/Mobile/index.tsx rename to components/MyPages/Blocks/Points/EarnAndBurn/TransactionTable/Mobile/index.tsx index ec8f62412..9a00e1eef 100644 --- a/components/MyPages/Blocks/Points/EarnAndBurn/Mobile/index.tsx +++ b/components/MyPages/Blocks/Points/EarnAndBurn/TransactionTable/Mobile/index.tsx @@ -1,16 +1,25 @@ +"use client" + +import { useIntl } from "react-intl" + import { dt } from "@/lib/dt" -import AwardPoints from "@/components/MyPages/Blocks/Points/EarnAndBurn/Desktop/Row/AwardPoints" +import { ChevronDownIcon } from "@/components/Icons" +import AwardPoints from "@/components/MyPages/Blocks/Points/EarnAndBurn/TransactionTable/Desktop/Row/AwardPoints" import Body from "@/components/TempDesignSystem/Text/Body" -import { getIntl } from "@/i18n" import { getLang } from "@/i18n/serverContext" import styles from "./mobile.module.css" -import type { TableProps } from "@/types/components/myPages/myPage/earnAndBurn" +import type { TablePropsPagination } from "@/types/components/myPages/myPage/earnAndBurn" + +export default function MobileTable({ + transactions, + showMore, + hasMore, +}: TablePropsPagination) { + const { formatMessage } = useIntl() -export default async function MobileTable({ transactions }: TableProps) { - const { formatMessage } = await getIntl() return (
@@ -28,8 +37,11 @@ export default async function MobileTable({ transactions }: TableProps) { {transactions.length ? ( - transactions.map((transaction) => ( - + transactions.map((transaction, idx) => ( +
{dt(transaction.checkinDate) @@ -55,6 +67,18 @@ export default async function MobileTable({ transactions }: TableProps) { )}
+ + {hasMore ? ( + + ) : null}
) } diff --git a/components/MyPages/Blocks/Points/EarnAndBurn/Mobile/mobile.module.css b/components/MyPages/Blocks/Points/EarnAndBurn/TransactionTable/Mobile/mobile.module.css similarity index 73% rename from components/MyPages/Blocks/Points/EarnAndBurn/Mobile/mobile.module.css rename to components/MyPages/Blocks/Points/EarnAndBurn/TransactionTable/Mobile/mobile.module.css index 3b40d8c69..ac7e30bda 100644 --- a/components/MyPages/Blocks/Points/EarnAndBurn/Mobile/mobile.module.css +++ b/components/MyPages/Blocks/Points/EarnAndBurn/TransactionTable/Mobile/mobile.module.css @@ -34,6 +34,16 @@ padding: var(--Spacing-x4); border: 1px solid var(--Main-Grey-10); } +.loadMoreButton { + background-color: var(--Main-Grey-10); + border: none; + display: flex; + align-items: center; + justify-content: center; + gap: var(--Spacing-x-half); + padding: var(--Spacing-x2); + width: 100%; +} @media screen and (min-width: 768px) { .container { diff --git a/components/MyPages/Blocks/Points/EarnAndBurn/TransactionTable/index.tsx b/components/MyPages/Blocks/Points/EarnAndBurn/TransactionTable/index.tsx new file mode 100644 index 000000000..8a2c0050a --- /dev/null +++ b/components/MyPages/Blocks/Points/EarnAndBurn/TransactionTable/index.tsx @@ -0,0 +1,34 @@ +"use client" + +import { useState } from "react" + +import DesktopTable from "./Desktop" +import MobileTable from "./Mobile" + +import { TableProps } from "@/types/components/myPages/myPage/earnAndBurn" + +export function TransactionTable({ transactions }: TableProps) { + const [transactionDisplayCount, setTransactionDisplayCount] = useState(5) + + const showMoreTransactions = () => { + setTransactionDisplayCount((count) => count + 5) + } + + const displayedTransactions = transactions.slice(0, transactionDisplayCount) + const hasMoreTransactions = transactions.length > transactionDisplayCount + + return ( + <> + + + + ) +} diff --git a/components/MyPages/Blocks/Points/EarnAndBurn/index.tsx b/components/MyPages/Blocks/Points/EarnAndBurn/index.tsx index a989e6429..521e8d954 100644 --- a/components/MyPages/Blocks/Points/EarnAndBurn/index.tsx +++ b/components/MyPages/Blocks/Points/EarnAndBurn/index.tsx @@ -4,8 +4,7 @@ import SectionContainer from "@/components/Section/Container" import SectionHeader from "@/components/Section/Header" import SectionLink from "@/components/Section/Link" -import DesktopTable from "./Desktop" -import MobileTable from "./Mobile" +import { TransactionTable } from "./TransactionTable" import type { AccountPageComponentProps } from "@/types/components/myPages/myPage/accountPage" @@ -22,8 +21,9 @@ export default async function EarnAndBurn({ return ( - - + + + ) diff --git a/server/routers/user/query.ts b/server/routers/user/query.ts index 055c09766..946ec5945 100644 --- a/server/routers/user/query.ts +++ b/server/routers/user/query.ts @@ -455,6 +455,7 @@ export const userQueryRouter = router({ transaction: router({ friendTransactions: protectedProcedure.query(async (opts) => { const apiResponse = await api.get(api.endpoints.v1.friendTransactions, { + cache: "no-store", headers: { Authorization: `Bearer ${opts.ctx.session.token.access_token}`, }, diff --git a/types/components/myPages/myPage/earnAndBurn.ts b/types/components/myPages/myPage/earnAndBurn.ts index 42d51d123..7791aea22 100644 --- a/types/components/myPages/myPage/earnAndBurn.ts +++ b/types/components/myPages/myPage/earnAndBurn.ts @@ -1,4 +1,4 @@ -import { awardPointsVariants } from "@/components/MyPages/Blocks/Points/EarnAndBurn/Desktop/Row/awardPointsVariants" +import { awardPointsVariants } from "@/components/MyPages/Blocks/Points/EarnAndBurn/TransactionTable/Desktop/Row/awardPointsVariants" import type { VariantProps } from "class-variance-authority" @@ -27,6 +27,11 @@ export interface TableProps { transactions: Transactions } +export interface TablePropsPagination extends TableProps { + showMore: () => void + hasMore: boolean +} + export interface RowProps { transaction: Transaction } From 6c15f1ae3a0aabf3b6458752f9574b39a396b0c9 Mon Sep 17 00:00:00 2001 From: Arvid Norlin Date: Wed, 14 Aug 2024 09:39:26 +0200 Subject: [PATCH 18/45] chore: remove destructuring of intl.formatMessage --- .../TransactionTable/Desktop/Row/index.tsx | 6 +++--- .../TransactionTable/Desktop/index.tsx | 9 ++++----- .../TransactionTable/Mobile/index.tsx | 16 ++++++++++------ 3 files changed, 17 insertions(+), 14 deletions(-) diff --git a/components/MyPages/Blocks/Points/EarnAndBurn/TransactionTable/Desktop/Row/index.tsx b/components/MyPages/Blocks/Points/EarnAndBurn/TransactionTable/Desktop/Row/index.tsx index 540b68891..6028db7f5 100644 --- a/components/MyPages/Blocks/Points/EarnAndBurn/TransactionTable/Desktop/Row/index.tsx +++ b/components/MyPages/Blocks/Points/EarnAndBurn/TransactionTable/Desktop/Row/index.tsx @@ -13,11 +13,11 @@ import styles from "./row.module.css" import type { RowProps } from "@/types/components/myPages/myPage/earnAndBurn" export default function Row({ transaction }: RowProps) { - const { formatMessage } = useIntl() + const intl = useIntl() const description = transaction.hotelName && transaction.city - ? `${transaction.hotelName}, ${transaction.city} ${transaction.nights} ${formatMessage({ id: "nights" })}` - : `${transaction.nights} ${formatMessage({ id: "nights" })}` + ? `${transaction.hotelName}, ${transaction.city} ${transaction.nights} ${intl.formatMessage({ id: "nights" })}` + : `${transaction.nights} ${intl.formatMessage({ id: "nights" })}` const arrival = dt(transaction.checkinDate) .locale(getLang()) .format("DD MMM YYYY") diff --git a/components/MyPages/Blocks/Points/EarnAndBurn/TransactionTable/Desktop/index.tsx b/components/MyPages/Blocks/Points/EarnAndBurn/TransactionTable/Desktop/index.tsx index fc12a4a00..8b244f67b 100644 --- a/components/MyPages/Blocks/Points/EarnAndBurn/TransactionTable/Desktop/index.tsx +++ b/components/MyPages/Blocks/Points/EarnAndBurn/TransactionTable/Desktop/index.tsx @@ -1,6 +1,5 @@ "use client" - import { useIntl } from "react-intl" import { ChevronDownIcon } from "@/components/Icons" @@ -25,7 +24,7 @@ export default function DesktopTable({ showMore, hasMore, }: TablePropsPagination) { - const { formatMessage } = useIntl() + const intl = useIntl() return (
@@ -37,7 +36,7 @@ export default function DesktopTable({ {tableHeadings.map((heading) => ( - {formatMessage({ id: heading })} + {intl.formatMessage({ id: heading })} ))} @@ -61,7 +60,7 @@ export default function DesktopTable({ }} > - {formatMessage({ id: "Show more" })} + {intl.formatMessage({ id: "Show more" })}
) : null} @@ -80,7 +79,7 @@ export default function DesktopTable({ - {formatMessage({ id: "No transactions available" })} + {intl.formatMessage({ id: "No transactions available" })} diff --git a/components/MyPages/Blocks/Points/EarnAndBurn/TransactionTable/Mobile/index.tsx b/components/MyPages/Blocks/Points/EarnAndBurn/TransactionTable/Mobile/index.tsx index 9a00e1eef..f77e12d6f 100644 --- a/components/MyPages/Blocks/Points/EarnAndBurn/TransactionTable/Mobile/index.tsx +++ b/components/MyPages/Blocks/Points/EarnAndBurn/TransactionTable/Mobile/index.tsx @@ -18,7 +18,7 @@ export default function MobileTable({ showMore, hasMore, }: TablePropsPagination) { - const { formatMessage } = useIntl() + const intl = useIntl() return (
@@ -27,11 +27,13 @@ export default function MobileTable({ - {formatMessage({ id: "Transactions" })} + {intl.formatMessage({ id: "Transactions" })} - {formatMessage({ id: "Points" })} + + {intl.formatMessage({ id: "Points" })} + @@ -52,7 +54,7 @@ export default function MobileTable({ {`${transaction.hotelName}, ${transaction.city}`} ) : null} - {`${transaction.nights} ${formatMessage({ id: transaction.nights === 1 ? "night" : "nights" })}`} + {`${transaction.nights} ${intl.formatMessage({ id: transaction.nights === 1 ? "night" : "nights" })}`} @@ -61,7 +63,9 @@ export default function MobileTable({ ) : ( - {formatMessage({ id: "There are no transactions to display" })} + {intl.formatMessage({ + id: "There are no transactions to display", + })} )} @@ -76,7 +80,7 @@ export default function MobileTable({ }} > - {formatMessage({ id: "Show more" })} + {intl.formatMessage({ id: "Show more" })} ) : null}
From 8c75b9bcd7b59ab586aec3935fe8bf4cc8204fda Mon Sep 17 00:00:00 2001 From: Arvid Norlin Date: Fri, 16 Aug 2024 13:27:52 +0200 Subject: [PATCH 19/45] wip: initial stab trpc pagination --- .../Desktop/Row/AwardPoints.tsx | 0 .../Desktop/Row/awardPointsVariants.ts | 0 .../Desktop/Row/index.tsx | 0 .../Desktop/Row/row.module.css | 0 .../Desktop/desktop.module.css | 0 .../Desktop/index.tsx | 24 +--- .../Mobile/index.tsx | 25 +--- .../Mobile/mobile.module.css | 0 .../Points/EarnAndBurn/JourneyTable/index.tsx | 126 ++++++++++++++++++ .../EarnAndBurn/TransactionTable/index.tsx | 34 ----- .../Points/EarnAndBurn/earnAndBurn.module.css | 34 +++++ .../Blocks/Points/EarnAndBurn/index.tsx | 15 ++- server/routers/user/input.ts | 6 + server/routers/user/query.ts | 106 ++++++++------- .../components/myPages/myPage/earnAndBurn.ts | 11 +- 15 files changed, 243 insertions(+), 138 deletions(-) rename components/MyPages/Blocks/Points/EarnAndBurn/{TransactionTable => JourneyTable}/Desktop/Row/AwardPoints.tsx (100%) rename components/MyPages/Blocks/Points/EarnAndBurn/{TransactionTable => JourneyTable}/Desktop/Row/awardPointsVariants.ts (100%) rename components/MyPages/Blocks/Points/EarnAndBurn/{TransactionTable => JourneyTable}/Desktop/Row/index.tsx (100%) rename components/MyPages/Blocks/Points/EarnAndBurn/{TransactionTable => JourneyTable}/Desktop/Row/row.module.css (100%) rename components/MyPages/Blocks/Points/EarnAndBurn/{TransactionTable => JourneyTable}/Desktop/desktop.module.css (100%) rename components/MyPages/Blocks/Points/EarnAndBurn/{TransactionTable => JourneyTable}/Desktop/index.tsx (71%) rename components/MyPages/Blocks/Points/EarnAndBurn/{TransactionTable => JourneyTable}/Mobile/index.tsx (78%) rename components/MyPages/Blocks/Points/EarnAndBurn/{TransactionTable => JourneyTable}/Mobile/mobile.module.css (100%) create mode 100644 components/MyPages/Blocks/Points/EarnAndBurn/JourneyTable/index.tsx delete mode 100644 components/MyPages/Blocks/Points/EarnAndBurn/TransactionTable/index.tsx diff --git a/components/MyPages/Blocks/Points/EarnAndBurn/TransactionTable/Desktop/Row/AwardPoints.tsx b/components/MyPages/Blocks/Points/EarnAndBurn/JourneyTable/Desktop/Row/AwardPoints.tsx similarity index 100% rename from components/MyPages/Blocks/Points/EarnAndBurn/TransactionTable/Desktop/Row/AwardPoints.tsx rename to components/MyPages/Blocks/Points/EarnAndBurn/JourneyTable/Desktop/Row/AwardPoints.tsx diff --git a/components/MyPages/Blocks/Points/EarnAndBurn/TransactionTable/Desktop/Row/awardPointsVariants.ts b/components/MyPages/Blocks/Points/EarnAndBurn/JourneyTable/Desktop/Row/awardPointsVariants.ts similarity index 100% rename from components/MyPages/Blocks/Points/EarnAndBurn/TransactionTable/Desktop/Row/awardPointsVariants.ts rename to components/MyPages/Blocks/Points/EarnAndBurn/JourneyTable/Desktop/Row/awardPointsVariants.ts diff --git a/components/MyPages/Blocks/Points/EarnAndBurn/TransactionTable/Desktop/Row/index.tsx b/components/MyPages/Blocks/Points/EarnAndBurn/JourneyTable/Desktop/Row/index.tsx similarity index 100% rename from components/MyPages/Blocks/Points/EarnAndBurn/TransactionTable/Desktop/Row/index.tsx rename to components/MyPages/Blocks/Points/EarnAndBurn/JourneyTable/Desktop/Row/index.tsx diff --git a/components/MyPages/Blocks/Points/EarnAndBurn/TransactionTable/Desktop/Row/row.module.css b/components/MyPages/Blocks/Points/EarnAndBurn/JourneyTable/Desktop/Row/row.module.css similarity index 100% rename from components/MyPages/Blocks/Points/EarnAndBurn/TransactionTable/Desktop/Row/row.module.css rename to components/MyPages/Blocks/Points/EarnAndBurn/JourneyTable/Desktop/Row/row.module.css diff --git a/components/MyPages/Blocks/Points/EarnAndBurn/TransactionTable/Desktop/desktop.module.css b/components/MyPages/Blocks/Points/EarnAndBurn/JourneyTable/Desktop/desktop.module.css similarity index 100% rename from components/MyPages/Blocks/Points/EarnAndBurn/TransactionTable/Desktop/desktop.module.css rename to components/MyPages/Blocks/Points/EarnAndBurn/JourneyTable/Desktop/desktop.module.css diff --git a/components/MyPages/Blocks/Points/EarnAndBurn/TransactionTable/Desktop/index.tsx b/components/MyPages/Blocks/Points/EarnAndBurn/JourneyTable/Desktop/index.tsx similarity index 71% rename from components/MyPages/Blocks/Points/EarnAndBurn/TransactionTable/Desktop/index.tsx rename to components/MyPages/Blocks/Points/EarnAndBurn/JourneyTable/Desktop/index.tsx index 8b244f67b..3d50eb609 100644 --- a/components/MyPages/Blocks/Points/EarnAndBurn/TransactionTable/Desktop/index.tsx +++ b/components/MyPages/Blocks/Points/EarnAndBurn/JourneyTable/Desktop/index.tsx @@ -1,15 +1,12 @@ -"use client" - import { useIntl } from "react-intl" -import { ChevronDownIcon } from "@/components/Icons" import Body from "@/components/TempDesignSystem/Text/Body" import Row from "./Row" import styles from "./desktop.module.css" -import type { TablePropsPagination } from "@/types/components/myPages/myPage/earnAndBurn" +import type { TableProps } from "@/types/components/myPages/myPage/earnAndBurn" const tableHeadings = [ "Arrival date", @@ -19,11 +16,7 @@ const tableHeadings = [ "Points", ] -export default function DesktopTable({ - transactions, - showMore, - hasMore, -}: TablePropsPagination) { +export default function DesktopTable({ transactions }: TableProps) { const intl = useIntl() return ( @@ -51,19 +44,6 @@ export default function DesktopTable({ ))} - {hasMore ? ( -
- -
- ) : null}
) : ( diff --git a/components/MyPages/Blocks/Points/EarnAndBurn/TransactionTable/Mobile/index.tsx b/components/MyPages/Blocks/Points/EarnAndBurn/JourneyTable/Mobile/index.tsx similarity index 78% rename from components/MyPages/Blocks/Points/EarnAndBurn/TransactionTable/Mobile/index.tsx rename to components/MyPages/Blocks/Points/EarnAndBurn/JourneyTable/Mobile/index.tsx index f77e12d6f..639f73f9b 100644 --- a/components/MyPages/Blocks/Points/EarnAndBurn/TransactionTable/Mobile/index.tsx +++ b/components/MyPages/Blocks/Points/EarnAndBurn/JourneyTable/Mobile/index.tsx @@ -1,23 +1,16 @@ -"use client" - import { useIntl } from "react-intl" import { dt } from "@/lib/dt" -import { ChevronDownIcon } from "@/components/Icons" -import AwardPoints from "@/components/MyPages/Blocks/Points/EarnAndBurn/TransactionTable/Desktop/Row/AwardPoints" +import AwardPoints from "@/components/MyPages/Blocks/Points/EarnAndBurn/JourneyTable/Desktop/Row/AwardPoints" import Body from "@/components/TempDesignSystem/Text/Body" import { getLang } from "@/i18n/serverContext" import styles from "./mobile.module.css" -import type { TablePropsPagination } from "@/types/components/myPages/myPage/earnAndBurn" +import type { TableProps } from "@/types/components/myPages/myPage/earnAndBurn" -export default function MobileTable({ - transactions, - showMore, - hasMore, -}: TablePropsPagination) { +export default function MobileTable({ transactions }: TableProps) { const intl = useIntl() return ( @@ -71,18 +64,6 @@ export default function MobileTable({ )}
- - {hasMore ? ( - - ) : null}
) } diff --git a/components/MyPages/Blocks/Points/EarnAndBurn/TransactionTable/Mobile/mobile.module.css b/components/MyPages/Blocks/Points/EarnAndBurn/JourneyTable/Mobile/mobile.module.css similarity index 100% rename from components/MyPages/Blocks/Points/EarnAndBurn/TransactionTable/Mobile/mobile.module.css rename to components/MyPages/Blocks/Points/EarnAndBurn/JourneyTable/Mobile/mobile.module.css diff --git a/components/MyPages/Blocks/Points/EarnAndBurn/JourneyTable/index.tsx b/components/MyPages/Blocks/Points/EarnAndBurn/JourneyTable/index.tsx new file mode 100644 index 000000000..c8f6ff6c9 --- /dev/null +++ b/components/MyPages/Blocks/Points/EarnAndBurn/JourneyTable/index.tsx @@ -0,0 +1,126 @@ +"use client" + +import { useEffect, useState } from "react" + +import { trpc } from "@/lib/trpc/client" + +import { ChevronRightIcon } from "@/components/Icons" + +import DesktopTable from "./Desktop" +import MobileTable from "./Mobile" + +import styles from "../earnAndBurn.module.css" + +import { Transactions } from "@/types/components/myPages/myPage/earnAndBurn" + +function PaginationButton({ + children, + isActive, + handleClick, + disabled, +}: React.PropsWithChildren<{ + disabled: boolean + isActive?: boolean + handleClick: () => void +}>) { + return ( + + ) +} + +function Pagination({ + pageCount, + isFetching, + handlePageChange, + currentPage, +}: { + pageCount: number + isFetching: boolean + handlePageChange: (page: number) => void + currentPage: number +}) { + const isOnFirstPage = currentPage === 1 + const isOnLastPage = currentPage === pageCount + return ( +
+ { + handlePageChange(currentPage - 1) + }} + > + + + {[...Array(pageCount)].map((_, idx) => ( + { + handlePageChange(idx + 1) + }} + > + {idx + 1} + + ))} + { + handlePageChange(currentPage + 1) + }} + > + + +
+ ) +} + +export default function TransactionTable() { + const limit = 5 + const [page, setPage] = useState(1) + const [totalPages, setTotalPages] = useState(0) + const [currentTransactions, setCurrentTransactions] = useState( + [] + ) + const { data, isFetching, isLoading } = + trpc.user.transaction.friendTransactions.useQuery({ + limit, + page, + }) + // Should the active page be mirroried in the URL with params? + // That way the actual fetch could be moved up and Mobile/Desktop can be strictly server side + useEffect(() => { + if (typeof data?.data.pages === "number") { + setTotalPages(data?.data.pages) + } + }, [data?.data.pages]) + + useEffect(() => { + if (data?.data.transactions) { + setCurrentTransactions(data.data.transactions) + } + }, [data?.data.transactions]) + + return !currentTransactions.length ? ( + "Loading..." // Add loading state table + ) : ( + <> + + + {totalPages > 1 ? ( + + ) : null} + + ) +} diff --git a/components/MyPages/Blocks/Points/EarnAndBurn/TransactionTable/index.tsx b/components/MyPages/Blocks/Points/EarnAndBurn/TransactionTable/index.tsx deleted file mode 100644 index 8a2c0050a..000000000 --- a/components/MyPages/Blocks/Points/EarnAndBurn/TransactionTable/index.tsx +++ /dev/null @@ -1,34 +0,0 @@ -"use client" - -import { useState } from "react" - -import DesktopTable from "./Desktop" -import MobileTable from "./Mobile" - -import { TableProps } from "@/types/components/myPages/myPage/earnAndBurn" - -export function TransactionTable({ transactions }: TableProps) { - const [transactionDisplayCount, setTransactionDisplayCount] = useState(5) - - const showMoreTransactions = () => { - setTransactionDisplayCount((count) => count + 5) - } - - const displayedTransactions = transactions.slice(0, transactionDisplayCount) - const hasMoreTransactions = transactions.length > transactionDisplayCount - - return ( - <> - - - - ) -} diff --git a/components/MyPages/Blocks/Points/EarnAndBurn/earnAndBurn.module.css b/components/MyPages/Blocks/Points/EarnAndBurn/earnAndBurn.module.css index 230868385..6dc2aa8e8 100644 --- a/components/MyPages/Blocks/Points/EarnAndBurn/earnAndBurn.module.css +++ b/components/MyPages/Blocks/Points/EarnAndBurn/earnAndBurn.module.css @@ -2,3 +2,37 @@ display: grid; gap: var(--Spacing-x3); } + +.pagination { + display: flex; + justify-content: center; + padding: var(--Spacing-x2); + background-color: var(--Base-Surface-Primary-light-Normal); + border-radius: var(--Corner-radius-Rounded); + margin: auto; + gap: var(--Spacing-x5); +} + +.paginationButton { + background-color: transparent; + border: none; + height: 32px; + width: 32px; + font-size: var(--typography-Body-Bold-fontSize); + font-weight: var(--typography-Body-Bold-fontWeight); + padding: 0; + display: flex; + align-items: center; + justify-content: center; +} + +.chevronLeft { + transform: rotate(180deg); + height: 100%; +} + +.paginationButtonActive { + color: var(--WHITE); + background-color: var(--Base-Text-Accent); + border-radius: var(--Corner-radius-Rounded); +} diff --git a/components/MyPages/Blocks/Points/EarnAndBurn/index.tsx b/components/MyPages/Blocks/Points/EarnAndBurn/index.tsx index 521e8d954..3eb3d5496 100644 --- a/components/MyPages/Blocks/Points/EarnAndBurn/index.tsx +++ b/components/MyPages/Blocks/Points/EarnAndBurn/index.tsx @@ -4,7 +4,7 @@ import SectionContainer from "@/components/Section/Container" import SectionHeader from "@/components/Section/Header" import SectionLink from "@/components/Section/Link" -import { TransactionTable } from "./TransactionTable" +import JourneyTable from "./JourneyTable" import type { AccountPageComponentProps } from "@/types/components/myPages/myPage/accountPage" @@ -13,17 +13,18 @@ export default async function EarnAndBurn({ subtitle, title, }: AccountPageComponentProps) { - const transactions = - await serverClient().user.transaction.friendTransactions() - if (!transactions) { + const transactionsData = + await serverClient().user.transaction.friendTransactions({ + limit: 10, + page: 1, + }) + if (!transactionsData) { return null } return ( - - - + ) diff --git a/server/routers/user/input.ts b/server/routers/user/input.ts index c7aa4102b..822b1d46d 100644 --- a/server/routers/user/input.ts +++ b/server/routers/user/input.ts @@ -29,3 +29,9 @@ export const saveCardInput = z.object({ transactionId: z.string(), merchantId: z.string().optional(), }) +export const friendTransactionsInput = z + .object({ + limit: z.number().int().positive(), + page: z.number().int().positive(), + }) + .default({ limit: 5, page: 1 }) diff --git a/server/routers/user/query.ts b/server/routers/user/query.ts index 946ec5945..a01ee15f7 100644 --- a/server/routers/user/query.ts +++ b/server/routers/user/query.ts @@ -19,6 +19,7 @@ import { getMembership, getMembershipCards } from "@/utils/user" import encryptValue from "../utils/encryptValue" import { + friendTransactionsInput, getUserInputSchema, initiateSaveCardInput, saveCardInput, @@ -453,54 +454,69 @@ export const userQueryRouter = router({ }), }), transaction: router({ - friendTransactions: protectedProcedure.query(async (opts) => { - const apiResponse = await api.get(api.endpoints.v1.friendTransactions, { - cache: "no-store", - headers: { - Authorization: `Bearer ${opts.ctx.session.token.access_token}`, - }, - }) + friendTransactions: protectedProcedure + .input(friendTransactionsInput) + .query(async ({ ctx, input }) => { + const { limit, page } = input + const apiResponse = await api.get(api.endpoints.v1.friendTransactions, { + headers: { + Authorization: `Bearer ${ctx.session.token.access_token}`, + }, + next: { revalidate: 30 * 60 * 1000 }, + }) - if (!apiResponse.ok) { - // switch (apiResponse.status) { - // case 400: - // throw badRequestError() - // case 401: - // throw unauthorizedError() - // case 403: - // throw forbiddenError() - // default: - // throw internalServerError() - // } - console.error(`API Response Failed - Getting Friend Transactions`) - console.error(`User: (${JSON.stringify(opts.ctx.session.user)})`) - console.error(apiResponse) - return null - } + if (!apiResponse.ok) { + // switch (apiResponse.status) { + // case 400: + // throw badRequestError() + // case 401: + // throw unauthorizedError() + // case 403: + // throw forbiddenError() + // default: + // throw internalServerError() + // } + console.error(`API Response Failed - Getting Friend Transactions`) + console.error(`User: (${JSON.stringify(ctx.session.user)})`) + console.error(apiResponse) + return null + } - const apiJson = await apiResponse.json() - const verifiedData = getFriendTransactionsSchema.safeParse(apiJson) - if (!verifiedData.success) { - console.error(`Failed to validate Friend Transactions Data`) - console.error(`User: (${JSON.stringify(opts.ctx.session.user)})`) - console.error(verifiedData.error) - return null - } + const apiJson = await apiResponse.json() + const verifiedData = getFriendTransactionsSchema.safeParse(apiJson) + if (!verifiedData.success) { + console.error(`Failed to validate Friend Transactions Data`) + console.error(`User: (${JSON.stringify(ctx.session.user)})`) + console.error(verifiedData.error) + return null + } - return { - data: verifiedData.data.data.map(({ attributes }) => { - return { - awardPoints: attributes.awardPoints, - checkinDate: attributes.checkinDate, - checkoutDate: attributes.checkoutDate, - city: attributes.hotelInformation?.city, - confirmationNumber: attributes.confirmationNumber, - hotelName: attributes.hotelInformation?.name, - nights: attributes.nights, - } - }), - } - }), + const pageData = verifiedData.data.data.slice( + limit * (page - 1), + limit * page + ) + + return { + data: { + transactions: pageData.map(({ attributes }) => { + return { + awardPoints: attributes.awardPoints, + checkinDate: attributes.checkinDate, + checkoutDate: attributes.checkoutDate, + city: attributes.hotelInformation?.city, + confirmationNumber: attributes.confirmationNumber, + hotelName: attributes.hotelInformation?.name, + nights: attributes.nights, + } + }), + + pages: Math.ceil(verifiedData.data.data.length / limit), + }, + meta: { + totalPages: Math.ceil(verifiedData.data.data.length / limit), + }, + } + }), }), creditCards: protectedProcedure.query(async function ({ ctx }) { diff --git a/types/components/myPages/myPage/earnAndBurn.ts b/types/components/myPages/myPage/earnAndBurn.ts index 7791aea22..06effe106 100644 --- a/types/components/myPages/myPage/earnAndBurn.ts +++ b/types/components/myPages/myPage/earnAndBurn.ts @@ -1,4 +1,4 @@ -import { awardPointsVariants } from "@/components/MyPages/Blocks/Points/EarnAndBurn/TransactionTable/Desktop/Row/awardPointsVariants" +import { awardPointsVariants } from "@/components/MyPages/Blocks/Points/EarnAndBurn/JourneyTable/Desktop/Row/awardPointsVariants" import type { VariantProps } from "class-variance-authority" @@ -10,9 +10,9 @@ export type TransactionResponse = Awaited< > export type TransactionsNonNullResponseObject = NonNullable export type Transactions = - NonNullable["data"] + NonNullable["data"]["transactions"] export type Transaction = - NonNullable["data"][number] + NonNullable["data"]["transactions"][number] export type ClientEarnAndBurnProps = { initialData: TransactionsNonNullResponseObject @@ -27,11 +27,6 @@ export interface TableProps { transactions: Transactions } -export interface TablePropsPagination extends TableProps { - showMore: () => void - hasMore: boolean -} - export interface RowProps { transaction: Transaction } From 1bcd3b81e1612844bab533304a0cc111dd25365b Mon Sep 17 00:00:00 2001 From: Arvid Norlin Date: Fri, 16 Aug 2024 14:34:25 +0200 Subject: [PATCH 20/45] chore: move totalPages to meta --- .../EarnAndBurn/JourneyTable/Desktop/Row/index.tsx | 9 ++++----- .../Points/EarnAndBurn/JourneyTable/Mobile/index.tsx | 6 +++--- .../Blocks/Points/EarnAndBurn/JourneyTable/index.tsx | 8 +++----- server/routers/user/query.ts | 2 -- 4 files changed, 10 insertions(+), 15 deletions(-) diff --git a/components/MyPages/Blocks/Points/EarnAndBurn/JourneyTable/Desktop/Row/index.tsx b/components/MyPages/Blocks/Points/EarnAndBurn/JourneyTable/Desktop/Row/index.tsx index 6028db7f5..297aa39cf 100644 --- a/components/MyPages/Blocks/Points/EarnAndBurn/JourneyTable/Desktop/Row/index.tsx +++ b/components/MyPages/Blocks/Points/EarnAndBurn/JourneyTable/Desktop/Row/index.tsx @@ -4,7 +4,7 @@ import { useIntl } from "react-intl" import { dt } from "@/lib/dt" -import { getLang } from "@/i18n/serverContext" +import useLang from "@/hooks/useLang" import AwardPoints from "./AwardPoints" @@ -14,15 +14,14 @@ import type { RowProps } from "@/types/components/myPages/myPage/earnAndBurn" export default function Row({ transaction }: RowProps) { const intl = useIntl() + const lang = useLang() const description = transaction.hotelName && transaction.city ? `${transaction.hotelName}, ${transaction.city} ${transaction.nights} ${intl.formatMessage({ id: "nights" })}` : `${transaction.nights} ${intl.formatMessage({ id: "nights" })}` - const arrival = dt(transaction.checkinDate) - .locale(getLang()) - .format("DD MMM YYYY") + const arrival = dt(transaction.checkinDate).locale(lang).format("DD MMM YYYY") const departure = dt(transaction.checkoutDate) - .locale(getLang()) + .locale(lang) .format("DD MMM YYYY") return ( diff --git a/components/MyPages/Blocks/Points/EarnAndBurn/JourneyTable/Mobile/index.tsx b/components/MyPages/Blocks/Points/EarnAndBurn/JourneyTable/Mobile/index.tsx index 639f73f9b..d4de69ac4 100644 --- a/components/MyPages/Blocks/Points/EarnAndBurn/JourneyTable/Mobile/index.tsx +++ b/components/MyPages/Blocks/Points/EarnAndBurn/JourneyTable/Mobile/index.tsx @@ -4,7 +4,7 @@ import { dt } from "@/lib/dt" import AwardPoints from "@/components/MyPages/Blocks/Points/EarnAndBurn/JourneyTable/Desktop/Row/AwardPoints" import Body from "@/components/TempDesignSystem/Text/Body" -import { getLang } from "@/i18n/serverContext" +import useLang from "@/hooks/useLang" import styles from "./mobile.module.css" @@ -12,7 +12,7 @@ import type { TableProps } from "@/types/components/myPages/myPage/earnAndBurn" export default function MobileTable({ transactions }: TableProps) { const intl = useIntl() - + const lang = useLang() return (
@@ -40,7 +40,7 @@ export default function MobileTable({ transactions }: TableProps) {
{dt(transaction.checkinDate) - .locale(getLang()) + .locale(lang) .format("DD MMM YYYY")} {transaction.hotelName && transaction.city ? ( diff --git a/components/MyPages/Blocks/Points/EarnAndBurn/JourneyTable/index.tsx b/components/MyPages/Blocks/Points/EarnAndBurn/JourneyTable/index.tsx index c8f6ff6c9..71c624dd6 100644 --- a/components/MyPages/Blocks/Points/EarnAndBurn/JourneyTable/index.tsx +++ b/components/MyPages/Blocks/Points/EarnAndBurn/JourneyTable/index.tsx @@ -93,13 +93,11 @@ export default function TransactionTable() { limit, page, }) - // Should the active page be mirroried in the URL with params? - // That way the actual fetch could be moved up and Mobile/Desktop can be strictly server side useEffect(() => { - if (typeof data?.data.pages === "number") { - setTotalPages(data?.data.pages) + if (typeof data?.meta.totalPages === "number") { + setTotalPages(data?.meta.totalPages) } - }, [data?.data.pages]) + }, [data?.meta.totalPages]) useEffect(() => { if (data?.data.transactions) { diff --git a/server/routers/user/query.ts b/server/routers/user/query.ts index a01ee15f7..a42b8ea1f 100644 --- a/server/routers/user/query.ts +++ b/server/routers/user/query.ts @@ -509,8 +509,6 @@ export const userQueryRouter = router({ nights: attributes.nights, } }), - - pages: Math.ceil(verifiedData.data.data.length / limit), }, meta: { totalPages: Math.ceil(verifiedData.data.data.length / limit), From f96712538e42c152189f0d505c53c523f866f246 Mon Sep 17 00:00:00 2001 From: Arvid Norlin Date: Fri, 16 Aug 2024 16:21:45 +0200 Subject: [PATCH 21/45] refactor: implement trpc client pattern for EarnAndBurn Journey --- .../EarnAndBurn/JourneyTable/Client.tsx | 132 ++++++++++++++++++ .../Points/EarnAndBurn/JourneyTable/index.tsx | 130 ++--------------- .../Blocks/Points/EarnAndBurn/index.tsx | 10 -- server/routers/user/query.ts | 1 + 4 files changed, 145 insertions(+), 128 deletions(-) create mode 100644 components/MyPages/Blocks/Points/EarnAndBurn/JourneyTable/Client.tsx diff --git a/components/MyPages/Blocks/Points/EarnAndBurn/JourneyTable/Client.tsx b/components/MyPages/Blocks/Points/EarnAndBurn/JourneyTable/Client.tsx new file mode 100644 index 000000000..e4905270f --- /dev/null +++ b/components/MyPages/Blocks/Points/EarnAndBurn/JourneyTable/Client.tsx @@ -0,0 +1,132 @@ +"use client" + +import { useEffect, useState } from "react" + +import { trpc } from "@/lib/trpc/client" + +import { ChevronRightIcon } from "@/components/Icons" +import LoadingSpinner from "@/components/LoadingSpinner" + +import DesktopTable from "./Desktop" +import MobileTable from "./Mobile" + +import styles from "../earnAndBurn.module.css" + +import { Transactions } from "@/types/components/myPages/myPage/earnAndBurn" + +function PaginationButton({ + children, + isActive, + handleClick, + disabled, +}: React.PropsWithChildren<{ + disabled: boolean + isActive?: boolean + handleClick: () => void +}>) { + return ( + + ) +} + +function Pagination({ + pageCount, + isFetching, + handlePageChange, + currentPage, +}: { + pageCount: number + isFetching: boolean + handlePageChange: (page: number) => void + currentPage: number +}) { + const isOnFirstPage = currentPage === 1 + const isOnLastPage = currentPage === pageCount + return ( +
+ { + handlePageChange(currentPage - 1) + }} + > + + + {[...Array(pageCount)].map((_, idx) => ( + { + handlePageChange(idx + 1) + }} + > + {idx + 1} + + ))} + { + handlePageChange(currentPage + 1) + }} + > + + +
+ ) +} + +export default function TransactionTable({ + initialJourneyTransactions, +}: { + initialJourneyTransactions: { + data: { transactions: Transactions } + meta: { totalPages: number } + } +}) { + const limit = 5 + const [page, setPage] = useState(1) + // const [currentTransactions, setCurrentTransactions] = useState( + // [] + // ) + const { data, isFetching, isLoading } = + trpc.user.transaction.friendTransactions.useQuery( + { + limit, + page, + }, + { + initialData: initialJourneyTransactions, + } + ) + + // useEffect(() => { + // if (data?.data.transactions) { + // setCurrentTransactions(data.data.transactions) + // } + // }, [data?.data.transactions]) + // const totalPages = data?.meta.totalPages || 0 + return isLoading ? ( + + ) : ( + <> + + + {data && data.meta.totalPages > 1 ? ( + + ) : null} + + ) +} diff --git a/components/MyPages/Blocks/Points/EarnAndBurn/JourneyTable/index.tsx b/components/MyPages/Blocks/Points/EarnAndBurn/JourneyTable/index.tsx index 71c624dd6..1a7922669 100644 --- a/components/MyPages/Blocks/Points/EarnAndBurn/JourneyTable/index.tsx +++ b/components/MyPages/Blocks/Points/EarnAndBurn/JourneyTable/index.tsx @@ -1,124 +1,18 @@ -"use client" +import { serverClient } from "@/lib/trpc/server" -import { useEffect, useState } from "react" +import ClientJourney from "./Client" -import { trpc } from "@/lib/trpc/client" - -import { ChevronRightIcon } from "@/components/Icons" - -import DesktopTable from "./Desktop" -import MobileTable from "./Mobile" - -import styles from "../earnAndBurn.module.css" - -import { Transactions } from "@/types/components/myPages/myPage/earnAndBurn" - -function PaginationButton({ - children, - isActive, - handleClick, - disabled, -}: React.PropsWithChildren<{ - disabled: boolean - isActive?: boolean - handleClick: () => void -}>) { - return ( - - ) -} - -function Pagination({ - pageCount, - isFetching, - handlePageChange, - currentPage, -}: { - pageCount: number - isFetching: boolean - handlePageChange: (page: number) => void - currentPage: number -}) { - const isOnFirstPage = currentPage === 1 - const isOnLastPage = currentPage === pageCount - return ( -
- { - handlePageChange(currentPage - 1) - }} - > - - - {[...Array(pageCount)].map((_, idx) => ( - { - handlePageChange(idx + 1) - }} - > - {idx + 1} - - ))} - { - handlePageChange(currentPage + 1) - }} - > - - -
- ) -} - -export default function TransactionTable() { - const limit = 5 - const [page, setPage] = useState(1) - const [totalPages, setTotalPages] = useState(0) - const [currentTransactions, setCurrentTransactions] = useState( - [] - ) - const { data, isFetching, isLoading } = - trpc.user.transaction.friendTransactions.useQuery({ - limit, - page, +export default async function JourneyTable() { + const initialJourneyTransactions = + await serverClient().user.transaction.friendTransactions({ + page: 1, + limit: 5, }) - useEffect(() => { - if (typeof data?.meta.totalPages === "number") { - setTotalPages(data?.meta.totalPages) - } - }, [data?.meta.totalPages]) + if (!initialJourneyTransactions?.data.transactions.length) { + return null + } - useEffect(() => { - if (data?.data.transactions) { - setCurrentTransactions(data.data.transactions) - } - }, [data?.data.transactions]) - - return !currentTransactions.length ? ( - "Loading..." // Add loading state table - ) : ( - <> - - - {totalPages > 1 ? ( - - ) : null} - + return ( + ) } diff --git a/components/MyPages/Blocks/Points/EarnAndBurn/index.tsx b/components/MyPages/Blocks/Points/EarnAndBurn/index.tsx index 3eb3d5496..83a325a04 100644 --- a/components/MyPages/Blocks/Points/EarnAndBurn/index.tsx +++ b/components/MyPages/Blocks/Points/EarnAndBurn/index.tsx @@ -1,5 +1,3 @@ -import { serverClient } from "@/lib/trpc/server" - import SectionContainer from "@/components/Section/Container" import SectionHeader from "@/components/Section/Header" import SectionLink from "@/components/Section/Link" @@ -13,14 +11,6 @@ export default async function EarnAndBurn({ subtitle, title, }: AccountPageComponentProps) { - const transactionsData = - await serverClient().user.transaction.friendTransactions({ - limit: 10, - page: 1, - }) - if (!transactionsData) { - return null - } return ( diff --git a/server/routers/user/query.ts b/server/routers/user/query.ts index a42b8ea1f..7f04257de 100644 --- a/server/routers/user/query.ts +++ b/server/routers/user/query.ts @@ -459,6 +459,7 @@ export const userQueryRouter = router({ .query(async ({ ctx, input }) => { const { limit, page } = input const apiResponse = await api.get(api.endpoints.v1.friendTransactions, { + cache: undefined, // override defaultOptions headers: { Authorization: `Bearer ${ctx.session.token.access_token}`, }, From a14157696e2b5c9ab399cb219c87b9562498f8eb Mon Sep 17 00:00:00 2001 From: Arvid Norlin Date: Mon, 19 Aug 2024 15:38:30 +0200 Subject: [PATCH 22/45] fix: remove initialData to avoid page load issues --- .../Points/EarnAndBurn/JourneyTable/Client.tsx | 16 +++++----------- .../Points/EarnAndBurn/JourneyTable/index.tsx | 2 +- 2 files changed, 6 insertions(+), 12 deletions(-) diff --git a/components/MyPages/Blocks/Points/EarnAndBurn/JourneyTable/Client.tsx b/components/MyPages/Blocks/Points/EarnAndBurn/JourneyTable/Client.tsx index e4905270f..ab286c9db 100644 --- a/components/MyPages/Blocks/Points/EarnAndBurn/JourneyTable/Client.tsx +++ b/components/MyPages/Blocks/Points/EarnAndBurn/JourneyTable/Client.tsx @@ -1,6 +1,7 @@ "use client" -import { useEffect, useState } from "react" +import { keepPreviousData } from "@tanstack/react-query" +import { useState } from "react" import { trpc } from "@/lib/trpc/client" @@ -93,9 +94,6 @@ export default function TransactionTable({ }) { const limit = 5 const [page, setPage] = useState(1) - // const [currentTransactions, setCurrentTransactions] = useState( - // [] - // ) const { data, isFetching, isLoading } = trpc.user.transaction.friendTransactions.useQuery( { @@ -103,16 +101,12 @@ export default function TransactionTable({ page, }, { - initialData: initialJourneyTransactions, + // TODO: fix the initial data issues on page load + // initialData: initialJourneyTransactions, + placeholderData: keepPreviousData, } ) - // useEffect(() => { - // if (data?.data.transactions) { - // setCurrentTransactions(data.data.transactions) - // } - // }, [data?.data.transactions]) - // const totalPages = data?.meta.totalPages || 0 return isLoading ? ( ) : ( diff --git a/components/MyPages/Blocks/Points/EarnAndBurn/JourneyTable/index.tsx b/components/MyPages/Blocks/Points/EarnAndBurn/JourneyTable/index.tsx index 1a7922669..08acf2e1d 100644 --- a/components/MyPages/Blocks/Points/EarnAndBurn/JourneyTable/index.tsx +++ b/components/MyPages/Blocks/Points/EarnAndBurn/JourneyTable/index.tsx @@ -8,7 +8,7 @@ export default async function JourneyTable() { page: 1, limit: 5, }) - if (!initialJourneyTransactions?.data.transactions.length) { + if (!initialJourneyTransactions?.data) { return null } From b0358ffa0e4c1008b12f63608982535faf6d963c Mon Sep 17 00:00:00 2001 From: Arvid Norlin Date: Mon, 19 Aug 2024 16:02:25 +0200 Subject: [PATCH 23/45] chore: break out pagination component --- .../EarnAndBurn/JourneyTable/Client.tsx | 73 +------------------ .../JourneyTable/Pagination/index.tsx | 72 ++++++++++++++++++ .../Pagination/pagination.module.css} | 5 -- 3 files changed, 73 insertions(+), 77 deletions(-) create mode 100644 components/MyPages/Blocks/Points/EarnAndBurn/JourneyTable/Pagination/index.tsx rename components/MyPages/Blocks/Points/EarnAndBurn/{earnAndBurn.module.css => JourneyTable/Pagination/pagination.module.css} (92%) diff --git a/components/MyPages/Blocks/Points/EarnAndBurn/JourneyTable/Client.tsx b/components/MyPages/Blocks/Points/EarnAndBurn/JourneyTable/Client.tsx index ab286c9db..f0cc9b2ef 100644 --- a/components/MyPages/Blocks/Points/EarnAndBurn/JourneyTable/Client.tsx +++ b/components/MyPages/Blocks/Points/EarnAndBurn/JourneyTable/Client.tsx @@ -5,85 +5,14 @@ import { useState } from "react" import { trpc } from "@/lib/trpc/client" -import { ChevronRightIcon } from "@/components/Icons" import LoadingSpinner from "@/components/LoadingSpinner" import DesktopTable from "./Desktop" import MobileTable from "./Mobile" - -import styles from "../earnAndBurn.module.css" +import Pagination from "./Pagination" import { Transactions } from "@/types/components/myPages/myPage/earnAndBurn" -function PaginationButton({ - children, - isActive, - handleClick, - disabled, -}: React.PropsWithChildren<{ - disabled: boolean - isActive?: boolean - handleClick: () => void -}>) { - return ( - - ) -} - -function Pagination({ - pageCount, - isFetching, - handlePageChange, - currentPage, -}: { - pageCount: number - isFetching: boolean - handlePageChange: (page: number) => void - currentPage: number -}) { - const isOnFirstPage = currentPage === 1 - const isOnLastPage = currentPage === pageCount - return ( -
- { - handlePageChange(currentPage - 1) - }} - > - - - {[...Array(pageCount)].map((_, idx) => ( - { - handlePageChange(idx + 1) - }} - > - {idx + 1} - - ))} - { - handlePageChange(currentPage + 1) - }} - > - - -
- ) -} - export default function TransactionTable({ initialJourneyTransactions, }: { diff --git a/components/MyPages/Blocks/Points/EarnAndBurn/JourneyTable/Pagination/index.tsx b/components/MyPages/Blocks/Points/EarnAndBurn/JourneyTable/Pagination/index.tsx new file mode 100644 index 000000000..b2ce233bb --- /dev/null +++ b/components/MyPages/Blocks/Points/EarnAndBurn/JourneyTable/Pagination/index.tsx @@ -0,0 +1,72 @@ +import { ChevronRightIcon } from "@/components/Icons" + +import styles from "./pagination.module.css" + +function PaginationButton({ + children, + isActive, + handleClick, + disabled, +}: React.PropsWithChildren<{ + disabled: boolean + isActive?: boolean + handleClick: () => void +}>) { + return ( + + ) +} + +export default function Pagination({ + pageCount, + isFetching, + handlePageChange, + currentPage, +}: { + pageCount: number + isFetching: boolean + handlePageChange: (page: number) => void + currentPage: number +}) { + const isOnFirstPage = currentPage === 1 + const isOnLastPage = currentPage === pageCount + return ( +
+ { + handlePageChange(currentPage - 1) + }} + > + + + {[...Array(pageCount)].map((_, idx) => ( + { + handlePageChange(idx + 1) + }} + > + {idx + 1} + + ))} + { + handlePageChange(currentPage + 1) + }} + > + + +
+ ) +} diff --git a/components/MyPages/Blocks/Points/EarnAndBurn/earnAndBurn.module.css b/components/MyPages/Blocks/Points/EarnAndBurn/JourneyTable/Pagination/pagination.module.css similarity index 92% rename from components/MyPages/Blocks/Points/EarnAndBurn/earnAndBurn.module.css rename to components/MyPages/Blocks/Points/EarnAndBurn/JourneyTable/Pagination/pagination.module.css index 6dc2aa8e8..493e06854 100644 --- a/components/MyPages/Blocks/Points/EarnAndBurn/earnAndBurn.module.css +++ b/components/MyPages/Blocks/Points/EarnAndBurn/JourneyTable/Pagination/pagination.module.css @@ -1,8 +1,3 @@ -.container { - display: grid; - gap: var(--Spacing-x3); -} - .pagination { display: flex; justify-content: center; From 04537286966cdc6a8a87c78fab78d9b93c083099 Mon Sep 17 00:00:00 2001 From: Arvid Norlin Date: Mon, 19 Aug 2024 16:29:22 +0200 Subject: [PATCH 24/45] chore: make pagination scale somewhat better on small devices awaiting design feedback on ellipsises --- .../JourneyTable/Pagination/pagination.module.css | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/components/MyPages/Blocks/Points/EarnAndBurn/JourneyTable/Pagination/pagination.module.css b/components/MyPages/Blocks/Points/EarnAndBurn/JourneyTable/Pagination/pagination.module.css index 493e06854..a18253c62 100644 --- a/components/MyPages/Blocks/Points/EarnAndBurn/JourneyTable/Pagination/pagination.module.css +++ b/components/MyPages/Blocks/Points/EarnAndBurn/JourneyTable/Pagination/pagination.module.css @@ -1,11 +1,13 @@ .pagination { display: flex; - justify-content: center; + justify-content: left; padding: var(--Spacing-x2); background-color: var(--Base-Surface-Primary-light-Normal); border-radius: var(--Corner-radius-Rounded); margin: auto; gap: var(--Spacing-x5); + max-width: 100%; + overflow-x: auto; } .paginationButton { @@ -19,6 +21,7 @@ display: flex; align-items: center; justify-content: center; + flex-shrink: 0; } .chevronLeft { @@ -31,3 +34,9 @@ background-color: var(--Base-Text-Accent); border-radius: var(--Corner-radius-Rounded); } + +@media screen and (min-width: 768px) { + .pagination { + justify-content: center; + } +} From 7e719bc4750c5a8c5ff0643a8a4f15d96816460c Mon Sep 17 00:00:00 2001 From: Arvid Norlin Date: Tue, 20 Aug 2024 09:30:03 +0200 Subject: [PATCH 25/45] chore: move typings --- .../JourneyTable/Pagination/index.tsx | 18 +++++++----------- .../Pagination/pagination.module.css | 4 ++++ types/components/myPages/myPage/earnAndBurn.ts | 13 +++++++++++++ 3 files changed, 24 insertions(+), 11 deletions(-) diff --git a/components/MyPages/Blocks/Points/EarnAndBurn/JourneyTable/Pagination/index.tsx b/components/MyPages/Blocks/Points/EarnAndBurn/JourneyTable/Pagination/index.tsx index b2ce233bb..bdbadc7e8 100644 --- a/components/MyPages/Blocks/Points/EarnAndBurn/JourneyTable/Pagination/index.tsx +++ b/components/MyPages/Blocks/Points/EarnAndBurn/JourneyTable/Pagination/index.tsx @@ -2,16 +2,17 @@ import { ChevronRightIcon } from "@/components/Icons" import styles from "./pagination.module.css" +import { + PaginationButtonProps, + PaginationProps, +} from "@/types/components/myPages/myPage/earnAndBurn" + function PaginationButton({ children, isActive, handleClick, disabled, -}: React.PropsWithChildren<{ - disabled: boolean - isActive?: boolean - handleClick: () => void -}>) { +}: React.PropsWithChildren) { return (