From c730fa703511d7f55039c45c7676b09125905fe3 Mon Sep 17 00:00:00 2001 From: Pontus Dreij Date: Mon, 19 Aug 2024 13:03:46 +0200 Subject: [PATCH 01/53] feat(SW-266): Replacing static metadata with data from Contentstack on Loyalty pages and Account Pages --- .../(protected)/my-pages/[...path]/page.tsx | 10 ++- .../(public)/[contentType]/[uid]/page.tsx | 19 +++++ app/[lang]/(live)/layout.tsx | 10 +-- .../Fragments/LoyaltyPage/MetaData.graphql | 17 ++++ .../Fragments/MyPages/MetaData.graphql | 17 ++++ lib/graphql/Query/MetaDataLoyaltyPage.graphql | 12 +++ lib/graphql/Query/MetaDataMyPages.graphql | 12 +++ server/routers/contentstack/index.ts | 2 + .../contentstack/loyaltyPage/output.ts | 1 + .../routers/contentstack/loyaltyPage/query.ts | 1 + server/routers/contentstack/metadata/index.ts | 5 ++ .../routers/contentstack/metadata/output.ts | 62 +++++++++++++++ server/routers/contentstack/metadata/query.ts | 78 +++++++++++++++++++ server/routers/contentstack/metadata/utils.ts | 34 ++++++++ types/components/metadata/index.ts | 5 ++ utils/generateMetadata.ts | 39 ++++++++++ 16 files changed, 316 insertions(+), 8 deletions(-) create mode 100644 lib/graphql/Fragments/LoyaltyPage/MetaData.graphql create mode 100644 lib/graphql/Fragments/MyPages/MetaData.graphql create mode 100644 lib/graphql/Query/MetaDataLoyaltyPage.graphql create mode 100644 lib/graphql/Query/MetaDataMyPages.graphql create mode 100644 server/routers/contentstack/metadata/index.ts create mode 100644 server/routers/contentstack/metadata/output.ts create mode 100644 server/routers/contentstack/metadata/query.ts create mode 100644 server/routers/contentstack/metadata/utils.ts create mode 100644 types/components/metadata/index.ts create mode 100644 utils/generateMetadata.ts diff --git a/app/[lang]/(live)/(protected)/my-pages/[...path]/page.tsx b/app/[lang]/(live)/(protected)/my-pages/[...path]/page.tsx index b35861357..584e0922b 100644 --- a/app/[lang]/(live)/(protected)/my-pages/[...path]/page.tsx +++ b/app/[lang]/(live)/(protected)/my-pages/[...path]/page.tsx @@ -5,11 +5,20 @@ import Title from "@/components/TempDesignSystem/Text/Title" import TrackingSDK from "@/components/TrackingSDK" import { getIntl } from "@/i18n" import { setLang } from "@/i18n/serverContext" +import { generateMetadata as generateBaseMetadata } from "@/utils/generateMetadata" import styles from "./page.module.css" import type { LangParams, PageArgs } from "@/types/params" +export async function generateMetadata({ params }: PageArgs) { + const accountPageRes = await serverClient().contentstack.accountPage.get() + return generateBaseMetadata({ + params, + pageTitle: accountPageRes?.accountPage?.title, + }) +} + export default async function MyPages({ params, }: PageArgs) { @@ -34,7 +43,6 @@ export default async function MyPages({

{formatMessage({ id: "No content published" })}

)} - ) diff --git a/app/[lang]/(live)/(public)/[contentType]/[uid]/page.tsx b/app/[lang]/(live)/(public)/[contentType]/[uid]/page.tsx index 0e8e98a05..c5b13354b 100644 --- a/app/[lang]/(live)/(public)/[contentType]/[uid]/page.tsx +++ b/app/[lang]/(live)/(public)/[contentType]/[uid]/page.tsx @@ -1,9 +1,12 @@ import { notFound } from "next/navigation" +import { serverClient } from "@/lib/trpc/server" + import ContentPage from "@/components/ContentType/ContentPage" import HotelPage from "@/components/ContentType/HotelPage/HotelPage" import LoyaltyPage from "@/components/ContentType/LoyaltyPage/LoyaltyPage" import { setLang } from "@/i18n/serverContext" +import { generateMetadata as generateBaseMetadata } from "@/utils/generateMetadata" import { ContentTypeParams, @@ -12,6 +15,22 @@ import { UIDParams, } from "@/types/params" +export async function generateMetadata({ + params, +}: PageArgs) { + switch (params.contentType) { + case "loyalty-page": + const loyaltyPageRes = await serverClient().contentstack.loyaltyPage.get() + return generateBaseMetadata({ + params, + pageTitle: loyaltyPageRes?.loyaltyPage?.title, + }) + // Add case "hotel-pages" etc when needed + default: + return + } +} + export default async function ContentTypePage({ params, }: PageArgs) { diff --git a/app/[lang]/(live)/layout.tsx b/app/[lang]/(live)/layout.tsx index 2da54eb0a..9f9a9263c 100644 --- a/app/[lang]/(live)/layout.tsx +++ b/app/[lang]/(live)/layout.tsx @@ -12,15 +12,11 @@ import { preloadUserTracking } from "@/components/TrackingSDK" import { getIntl } from "@/i18n" import ServerIntlProvider from "@/i18n/Provider" import { getLang, setLang } from "@/i18n/serverContext" - -import type { Metadata } from "next" +import { generateMetadata } from "@/utils/generateMetadata" import type { LangParams, LayoutArgs } from "@/types/params" -export const metadata: Metadata = { - description: "New web", - title: "Scandic Hotels", -} +export { generateMetadata } export default async function RootLayout({ children, @@ -33,8 +29,8 @@ export default async function RootLayout({ >) { setLang(params.lang) preloadUserTracking() - const { defaultLocale, locale, messages } = await getIntl() + return ( diff --git a/lib/graphql/Fragments/LoyaltyPage/MetaData.graphql b/lib/graphql/Fragments/LoyaltyPage/MetaData.graphql new file mode 100644 index 000000000..dcb0c704d --- /dev/null +++ b/lib/graphql/Fragments/LoyaltyPage/MetaData.graphql @@ -0,0 +1,17 @@ +#import "../Image.graphql" + +fragment LoyaltyPageMetaData on LoyaltyPage { + web { + seo_metadata { + title + description + imageConnection { + edges { + node { + ...Image + } + } + } + } + } +} diff --git a/lib/graphql/Fragments/MyPages/MetaData.graphql b/lib/graphql/Fragments/MyPages/MetaData.graphql new file mode 100644 index 000000000..1971efe3f --- /dev/null +++ b/lib/graphql/Fragments/MyPages/MetaData.graphql @@ -0,0 +1,17 @@ +#import "../Image.graphql" + +fragment MyPagesMetaData on AccountPage { + web { + seo_metadata { + title + description + imageConnection { + edges { + node { + ...Image + } + } + } + } + } +} diff --git a/lib/graphql/Query/MetaDataLoyaltyPage.graphql b/lib/graphql/Query/MetaDataLoyaltyPage.graphql new file mode 100644 index 000000000..9b6ba5d77 --- /dev/null +++ b/lib/graphql/Query/MetaDataLoyaltyPage.graphql @@ -0,0 +1,12 @@ +#import "../Fragments/LoyaltyPage/MetaData.graphql" + +query GetLoyaltyPageMetaData($locale: String!, $url: String!) { + all_loyalty_page(locale: $locale, where: { url: $url }) { + items { + ...LoyaltyPageMetaData + system { + uid + } + } + } +} diff --git a/lib/graphql/Query/MetaDataMyPages.graphql b/lib/graphql/Query/MetaDataMyPages.graphql new file mode 100644 index 000000000..14e255f02 --- /dev/null +++ b/lib/graphql/Query/MetaDataMyPages.graphql @@ -0,0 +1,12 @@ +#import "../Fragments/MyPages/MetaData.graphql" + +query GetMyPagesMetaData($locale: String!, $url: String!) { + all_account_page(locale: $locale, where: { url: $url }) { + items { + ...MyPagesMetaData + system { + uid + } + } + } +} diff --git a/server/routers/contentstack/index.ts b/server/routers/contentstack/index.ts index be1ef1ed3..2394251b3 100644 --- a/server/routers/contentstack/index.ts +++ b/server/routers/contentstack/index.ts @@ -6,6 +6,7 @@ import { breadcrumbsRouter } from "./breadcrumbs" import { hotelPageRouter } from "./hotelPage" import { languageSwitcherRouter } from "./languageSwitcher" import { loyaltyPageRouter } from "./loyaltyPage" +import { metaDataRouter } from "./metadata" import { myPagesRouter } from "./myPages" export const contentstackRouter = router({ @@ -16,4 +17,5 @@ export const contentstackRouter = router({ languageSwitcher: languageSwitcherRouter, loyaltyPage: loyaltyPageRouter, myPages: myPagesRouter, + metaData: metaDataRouter, }) diff --git a/server/routers/contentstack/loyaltyPage/output.ts b/server/routers/contentstack/loyaltyPage/output.ts index 205509a4c..a95b2e341 100644 --- a/server/routers/contentstack/loyaltyPage/output.ts +++ b/server/routers/contentstack/loyaltyPage/output.ts @@ -190,6 +190,7 @@ const loyaltyPageSidebarItem = z.discriminatedUnion("__typename", [ ]) export const validateLoyaltyPageSchema = z.object({ + title: z.string(), heading: z.string().nullable(), blocks: z.array(loyaltyPageBlockItem).nullable(), sidebar: z.array(loyaltyPageSidebarItem).nullable(), diff --git a/server/routers/contentstack/loyaltyPage/query.ts b/server/routers/contentstack/loyaltyPage/query.ts index c23422c4a..7f4fee2e9 100644 --- a/server/routers/contentstack/loyaltyPage/query.ts +++ b/server/routers/contentstack/loyaltyPage/query.ts @@ -209,6 +209,7 @@ export const loyaltyPageQueryRouter = router({ : null const loyaltyPage = { + title: response.data.loyalty_page.title, heading: response.data.loyalty_page.heading, system: response.data.loyalty_page.system, blocks, diff --git a/server/routers/contentstack/metadata/index.ts b/server/routers/contentstack/metadata/index.ts new file mode 100644 index 000000000..fa2618123 --- /dev/null +++ b/server/routers/contentstack/metadata/index.ts @@ -0,0 +1,5 @@ +import { mergeRouters } from "@/server/trpc" + +import { metaDataQueryRouter } from "./query" + +export const metaDataRouter = mergeRouters(metaDataQueryRouter) diff --git a/server/routers/contentstack/metadata/output.ts b/server/routers/contentstack/metadata/output.ts new file mode 100644 index 000000000..ae0367a3c --- /dev/null +++ b/server/routers/contentstack/metadata/output.ts @@ -0,0 +1,62 @@ +import { z } from "zod" + +export const getMetaDataSchema = z.object({ + title: z.string().optional(), + description: z.string().optional(), + imageConnection: z + .object({ + edges: z.array( + z.object({ + node: z.object({ + url: z.string(), + }), + }) + ), + }) + .optional(), +}) + +const page = z.object({ + web: z.object({ + seo_metadata: z.object({ + title: z.string().optional(), + description: z.string().optional(), + imageConnection: z + .object({ + edges: z.array( + z.object({ + node: z.object({ + url: z.string(), + }), + }) + ), + }) + .optional(), + }), + }), + system: z.object({ + uid: z.string(), + }), +}) + +export type Page = z.infer + +const metaDataItems = z.object({ + items: z.array(page), +}) + +export const validateMyPagesMetaDataContentstackSchema = z.object({ + all_account_page: metaDataItems, +}) + +export type GetMyPagesMetaDataData = z.infer< + typeof validateMyPagesMetaDataContentstackSchema +> + +export const validateLoyaltyPageMetaDataContentstackSchema = z.object({ + all_loyalty_page: metaDataItems, +}) + +export type GetLoyaltyPageMetaDataData = z.infer< + typeof validateLoyaltyPageMetaDataContentstackSchema +> diff --git a/server/routers/contentstack/metadata/query.ts b/server/routers/contentstack/metadata/query.ts new file mode 100644 index 000000000..3dbc2f2a2 --- /dev/null +++ b/server/routers/contentstack/metadata/query.ts @@ -0,0 +1,78 @@ +import { GetLoyaltyPageMetaData } from "@/lib/graphql/Query/MetaDataLoyaltyPage.graphql" +import { GetMyPagesMetaData } from "@/lib/graphql/Query/MetaDataMyPages.graphql" +import { contentstackExtendedProcedureUID, router } from "@/server/trpc" + +import { + type GetLoyaltyPageMetaDataData, + type GetMyPagesMetaDataData, + validateLoyaltyPageMetaDataContentstackSchema, + validateMyPagesMetaDataContentstackSchema, +} from "./output" +import { getMetaData, getResponse, Variables } from "./utils" + +import { PageTypeEnum } from "@/types/requests/pageType" + +async function getLoyaltyPageMetaData(variables: Variables) { + const response = await getResponse( + GetLoyaltyPageMetaData, + variables + ) + + if (!response.data.all_loyalty_page.items[0].web?.seo_metadata?.title) { + return null + } + const validatedMetaDataData = + validateLoyaltyPageMetaDataContentstackSchema.safeParse(response.data) + + if (!validatedMetaDataData.success) { + console.error( + `Failed to validate Loyaltypage MetaData Data - (url: ${variables.url})` + ) + console.error(validatedMetaDataData.error) + return null + } + + return getMetaData(validatedMetaDataData.data.all_loyalty_page.items[0]) +} + +async function getMyPagesMetaData(variables: Variables) { + const response = await getResponse( + GetMyPagesMetaData, + variables + ) + + if (!response.data.all_account_page.items[0].web?.seo_metadata?.title) { + return [] + } + + const validatedMetaDataData = + validateMyPagesMetaDataContentstackSchema.safeParse(response.data) + + if (!validatedMetaDataData.success) { + console.error( + `Failed to validate My Page MetaData Data - (url: ${variables.url})` + ) + console.error(validatedMetaDataData.error) + return null + } + + return getMetaData(validatedMetaDataData.data.all_account_page.items[0]) +} + +export const metaDataQueryRouter = router({ + get: contentstackExtendedProcedureUID.query(async ({ ctx }) => { + const variables = { + locale: ctx.lang, + url: ctx.pathname, + } + + switch (ctx.contentType) { + case PageTypeEnum.accountPage: + return await getMyPagesMetaData(variables) + case PageTypeEnum.loyaltyPage: + return await getLoyaltyPageMetaData(variables) + default: + return [] + } + }), +}) diff --git a/server/routers/contentstack/metadata/utils.ts b/server/routers/contentstack/metadata/utils.ts new file mode 100644 index 000000000..f2e80b604 --- /dev/null +++ b/server/routers/contentstack/metadata/utils.ts @@ -0,0 +1,34 @@ +import { Lang } from "@/constants/languages" +import { request } from "@/lib/graphql/request" +import { internalServerError, notFound } from "@/server/errors/trpc" + +import { getMetaDataSchema, Page } from "./output" + +export type Variables = { + locale: Lang + url: string +} + +export async function getResponse(query: string, variables: Variables) { + const response = await request(query, variables) + if (!response.data) { + throw notFound(response) + } + + return response +} + +export function getMetaData(page: Page) { + const pageMetaData = { + title: page.web.seo_metadata.title, + description: page.web.seo_metadata.description, + imageConnection: page.web.seo_metadata.imageConnection, + uid: page.system.uid, + } + const validatedMetaData = getMetaDataSchema.safeParse(pageMetaData) + if (!validatedMetaData.success) { + throw internalServerError(validatedMetaData.error) + } + + return validatedMetaData.data +} diff --git a/types/components/metadata/index.ts b/types/components/metadata/index.ts new file mode 100644 index 000000000..bcc71f13f --- /dev/null +++ b/types/components/metadata/index.ts @@ -0,0 +1,5 @@ +import { z } from "zod" + +import { getMetaDataSchema } from "@/server/routers/contentstack/metadata/output" + +export interface MetaData extends z.infer {} diff --git a/utils/generateMetadata.ts b/utils/generateMetadata.ts new file mode 100644 index 000000000..e44e14354 --- /dev/null +++ b/utils/generateMetadata.ts @@ -0,0 +1,39 @@ +import { Metadata } from "next" + +import { serverClient } from "@/lib/trpc/server" + +import { MetaData } from "@/types/components/metadata" +import { LangParams, PageArgs } from "@/types/params" + +export async function generateMetadata({ + params, + pageTitle, +}: PageArgs & { pageTitle?: string }): Promise { + console.log("PARAMS", params) + const metaData: MetaData | never[] | null = + await serverClient().contentstack.metaData.get() + + if (Array.isArray(metaData)) { + return { + title: pageTitle ?? "", + description: "", + openGraph: { + images: [], + }, + } + } + const title = metaData?.title ?? pageTitle ?? "" + const description = metaData?.description ?? "" + const images = + metaData?.imageConnection?.edges?.map((edge) => ({ + url: edge.node.url, + })) || [] + + return { + title, + description, + openGraph: { + images, + }, + } +} From b3f4ba822c031913f45921ea3f8ca91cd7bf2e40 Mon Sep 17 00:00:00 2001 From: Pontus Dreij Date: Tue, 20 Aug 2024 10:57:30 +0200 Subject: [PATCH 02/53] feat(SW-266): updated to use breadcrumbs title as default --- app/[lang]/(live)/(protected)/my-pages/[...path]/page.tsx | 7 +++++-- app/[lang]/(live)/(public)/[contentType]/[uid]/page.tsx | 8 ++++++-- utils/generateMetadata.ts | 5 +++-- 3 files changed, 14 insertions(+), 6 deletions(-) diff --git a/app/[lang]/(live)/(protected)/my-pages/[...path]/page.tsx b/app/[lang]/(live)/(protected)/my-pages/[...path]/page.tsx index 584e0922b..c153db1a4 100644 --- a/app/[lang]/(live)/(protected)/my-pages/[...path]/page.tsx +++ b/app/[lang]/(live)/(protected)/my-pages/[...path]/page.tsx @@ -12,10 +12,13 @@ import styles from "./page.module.css" import type { LangParams, PageArgs } from "@/types/params" export async function generateMetadata({ params }: PageArgs) { - const accountPageRes = await serverClient().contentstack.accountPage.get() + const breadcrumbs = await serverClient().contentstack.breadcrumbs.get() + if (!breadcrumbs?.length) { + return null + } return generateBaseMetadata({ params, - pageTitle: accountPageRes?.accountPage?.title, + pageTitle: breadcrumbs.at(-1)?.title, }) } diff --git a/app/[lang]/(live)/(public)/[contentType]/[uid]/page.tsx b/app/[lang]/(live)/(public)/[contentType]/[uid]/page.tsx index c5b13354b..a4976c22d 100644 --- a/app/[lang]/(live)/(public)/[contentType]/[uid]/page.tsx +++ b/app/[lang]/(live)/(public)/[contentType]/[uid]/page.tsx @@ -20,11 +20,15 @@ export async function generateMetadata({ }: PageArgs) { switch (params.contentType) { case "loyalty-page": - const loyaltyPageRes = await serverClient().contentstack.loyaltyPage.get() + const breadcrumbs = await serverClient().contentstack.breadcrumbs.get() + if (!breadcrumbs?.length) { + return null + } return generateBaseMetadata({ params, - pageTitle: loyaltyPageRes?.loyaltyPage?.title, + pageTitle: breadcrumbs.at(-1)?.title, }) + // Add case "hotel-pages" etc when needed default: return diff --git a/utils/generateMetadata.ts b/utils/generateMetadata.ts index e44e14354..11a09d34f 100644 --- a/utils/generateMetadata.ts +++ b/utils/generateMetadata.ts @@ -8,8 +8,9 @@ import { LangParams, PageArgs } from "@/types/params" export async function generateMetadata({ params, pageTitle, -}: PageArgs & { pageTitle?: string }): Promise { - console.log("PARAMS", params) +}: PageArgs & { + pageTitle?: string +}): Promise { const metaData: MetaData | never[] | null = await serverClient().contentstack.metaData.get() From 8109148bc5eb94f46636e844f5fe26302bd52111 Mon Sep 17 00:00:00 2001 From: Arvid Norlin Date: Wed, 21 Aug 2024 12:48:01 +0200 Subject: [PATCH 03/53] fix(SW287): update translation regarding current level --- components/MyPages/Blocks/Overview/Friend/index.tsx | 2 +- i18n/dictionaries/da.json | 7 +++++++ i18n/dictionaries/de.json | 9 ++++++++- i18n/dictionaries/en.json | 7 +++++++ i18n/dictionaries/fi.json | 7 +++++++ i18n/dictionaries/no.json | 7 +++++++ i18n/dictionaries/sv.json | 7 +++++++ 7 files changed, 44 insertions(+), 2 deletions(-) diff --git a/components/MyPages/Blocks/Overview/Friend/index.tsx b/components/MyPages/Blocks/Overview/Friend/index.tsx index b1c41884a..c1b89c3e6 100644 --- a/components/MyPages/Blocks/Overview/Friend/index.tsx +++ b/components/MyPages/Blocks/Overview/Friend/index.tsx @@ -31,7 +31,7 @@ export default async function Friend({ {formatMessage( isHighestLevel ? { id: "Highest level" } - : { id: "Your current level" } + : { id: `Level ${membershipLevels[membership.membershipLevel]}` } )} {membership ? ( diff --git a/i18n/dictionaries/da.json b/i18n/dictionaries/da.json index 4af82935b..3c04f265b 100644 --- a/i18n/dictionaries/da.json +++ b/i18n/dictionaries/da.json @@ -49,6 +49,13 @@ "From": "Fra", "Get inspired": "Bliv inspireret", "Go back to overview": "Gå tilbage til oversigten", + "Level 1": "Niveau 1", + "Level 2": "Niveau 2", + "Level 3": "Niveau 3", + "Level 4": "Niveau 4", + "Level 5": "Niveau 5", + "Level 6": "Niveau 6", + "Level 7": "Niveau 7", "Highest level": "Højeste niveau", "How do you want to sleep?": "Hvordan vil du sove?", "How it works": "Hvordan det virker", diff --git a/i18n/dictionaries/de.json b/i18n/dictionaries/de.json index ca524c900..eebcb7095 100644 --- a/i18n/dictionaries/de.json +++ b/i18n/dictionaries/de.json @@ -48,6 +48,13 @@ "From": "Fromm", "Get inspired": "Lassen Sie sich inspieren", "Go back to overview": "Zurück zur Übersicht", + "Level 1": "Level 1", + "Level 2": "Level 2", + "Level 3": "Level 3", + "Level 4": "Level 4", + "Level 5": "Level 5", + "Level 6": "Level 6", + "Level 7": "Level 7", "Highest level": "Höchstes Level", "How do you want to sleep?": "Wie möchtest du schlafen?", "How it works": "Wie es funktioniert", @@ -88,7 +95,7 @@ "Points": "Punkte", "Points may take up to 10 days to be displayed.": "Es kann bis zu 10 Tage dauern, bis Punkte angezeigt werden.", "Points needed to level up": "Punkte, die zum Levelaufstieg benötigt werden", - "Points needed to stay on level": "Erforderliche Punkte, um auf diesem Niveau zu bleiben", + "Points needed to stay on level": "Erforderliche Punkte, um auf diesem Level zu bleiben", "spendable points expiring by": "Einlösbare punkte verfallen bis zum", "Previous victories": "Bisherige Siege", "Read more": "Mehr lesen", diff --git a/i18n/dictionaries/en.json b/i18n/dictionaries/en.json index 1cf4c1c46..528410878 100644 --- a/i18n/dictionaries/en.json +++ b/i18n/dictionaries/en.json @@ -54,6 +54,13 @@ "hotelPages.rooms.roomCard.person": "person", "hotelPages.rooms.roomCard.persons": "persons", "hotelPages.rooms.roomCard.seeRoomDetails": "See room details", + "Level 1": "Level 1", + "Level 2": "Level 2", + "Level 3": "Level 3", + "Level 4": "Level 4", + "Level 5": "Level 5", + "Level 6": "Level 6", + "Level 7": "Level 7", "Highest level": "Highest level", "How do you want to sleep?": "How do you want to sleep?", "How it works": "How it works", diff --git a/i18n/dictionaries/fi.json b/i18n/dictionaries/fi.json index 452693de1..26c0c08ff 100644 --- a/i18n/dictionaries/fi.json +++ b/i18n/dictionaries/fi.json @@ -49,6 +49,13 @@ "From": "From", "Get inspired": "Inspiroidu", "Go back to overview": "Palaa yleiskatsaukseen", + "Level 1": "Taso 1", + "Level 2": "Taso 2", + "Level 3": "Taso 3", + "Level 4": "Taso 4", + "Level 5": "Taso 5", + "Level 6": "Taso 6", + "Level 7": "Taso 7", "Highest level": "Korkein taso", "How do you want to sleep?": "Kuinka haluat nukkua?", "How it works": "Kuinka se toimii", diff --git a/i18n/dictionaries/no.json b/i18n/dictionaries/no.json index a3e2ce356..b2cf5817a 100644 --- a/i18n/dictionaries/no.json +++ b/i18n/dictionaries/no.json @@ -49,6 +49,13 @@ "From": "Fra", "Get inspired": "Bli inspirert", "Go back to overview": "Gå tilbake til oversikten", + "Level 1": "Nivå 1", + "Level 2": "Nivå 2", + "Level 3": "Nivå 3", + "Level 4": "Nivå 4", + "Level 5": "Nivå 5", + "Level 6": "Nivå 6", + "Level 7": "Nivå 7", "Highest level": "Høyeste nivå", "How do you want to sleep?": "Hvordan vil du sove?", "How it works": "Hvordan det fungerer", diff --git a/i18n/dictionaries/sv.json b/i18n/dictionaries/sv.json index 0b26bc9b4..e68e35ca6 100644 --- a/i18n/dictionaries/sv.json +++ b/i18n/dictionaries/sv.json @@ -49,6 +49,13 @@ "From": "Från", "Get inspired": "Bli inspirerad", "Go back to overview": "Gå tillbaka till översikten", + "Level 1": "Nivå 1", + "Level 2": "Nivå 2", + "Level 3": "Nivå 3", + "Level 4": "Nivå 4", + "Level 5": "Nivå 5", + "Level 6": "Nivå 6", + "Level 7": "Nivå 7", "Highest level": "Högsta nivå", "How do you want to sleep?": "Hur vill du sova?", "How it works": "Hur det fungerar", From 09639badb42a5dd5fb297a5c16ce5915cf523de6 Mon Sep 17 00:00:00 2001 From: Pontus Dreij Date: Wed, 21 Aug 2024 14:55:02 +0200 Subject: [PATCH 04/53] feat(SW-266): removed comment --- app/[lang]/(live)/(public)/[contentType]/[uid]/page.tsx | 2 -- 1 file changed, 2 deletions(-) diff --git a/app/[lang]/(live)/(public)/[contentType]/[uid]/page.tsx b/app/[lang]/(live)/(public)/[contentType]/[uid]/page.tsx index a4976c22d..655f34ee1 100644 --- a/app/[lang]/(live)/(public)/[contentType]/[uid]/page.tsx +++ b/app/[lang]/(live)/(public)/[contentType]/[uid]/page.tsx @@ -28,8 +28,6 @@ export async function generateMetadata({ params, pageTitle: breadcrumbs.at(-1)?.title, }) - - // Add case "hotel-pages" etc when needed default: return } From 63ce8a2dfbf7ca86e5f813357cca0685420efad4 Mon Sep 17 00:00:00 2001 From: Michael Zetterberg Date: Wed, 21 Aug 2024 15:14:24 +0200 Subject: [PATCH 05/53] fix(SW-104): use relative url for initiation --- .../(protected)/my-pages/profile/@creditCards/page.tsx | 4 +--- components/Profile/AddCreditCardButton/index.tsx | 8 +------- server/routers/user/input.ts | 2 -- server/routers/user/query.ts | 4 ++-- types/components/myPages/myProfile/addCreditCardButton.ts | 3 --- 5 files changed, 4 insertions(+), 17 deletions(-) delete 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 a3cff5783..68a19f188 100644 --- a/app/[lang]/(live)/(protected)/my-pages/profile/@creditCards/page.tsx +++ b/app/[lang]/(live)/(protected)/my-pages/profile/@creditCards/page.tsx @@ -44,9 +44,7 @@ export default async function CreditCardSlot({ params }: PageArgs) { ))} ) : null} - + ) } diff --git a/components/Profile/AddCreditCardButton/index.tsx b/components/Profile/AddCreditCardButton/index.tsx index 3c6286a7d..56081c20d 100644 --- a/components/Profile/AddCreditCardButton/index.tsx +++ b/components/Profile/AddCreditCardButton/index.tsx @@ -13,8 +13,6 @@ import useLang from "@/hooks/useLang" import styles from "./addCreditCardButton.module.css" -import { type AddCreditCardButtonProps } from "@/types/components/myPages/myProfile/addCreditCardButton" - let hasRunOnce = false function useAddCardResultToast() { @@ -47,9 +45,7 @@ function useAddCardResultToast() { }, [intl, pathname, router, searchParams]) } -export default function AddCreditCardButton({ - redirectUrl, -}: AddCreditCardButtonProps) { +export default function AddCreditCardButton() { const intl = useIntl() const router = useRouter() const lang = useLang() @@ -70,8 +66,6 @@ export default function AddCreditCardButton({ onClick={() => initiateAddCard.mutate({ language: lang, - mobileToken: false, - redirectUrl, }) } wrapping diff --git a/server/routers/user/input.ts b/server/routers/user/input.ts index 822b1d46d..db9fe1f2f 100644 --- a/server/routers/user/input.ts +++ b/server/routers/user/input.ts @@ -21,8 +21,6 @@ export const soonestUpcomingStaysInput = z export const initiateSaveCardInput = z.object({ language: z.string(), - mobileToken: z.boolean(), - redirectUrl: z.string(), }) export const saveCardInput = z.object({ diff --git a/server/routers/user/query.ts b/server/routers/user/query.ts index 7f04257de..d39c0f929 100644 --- a/server/routers/user/query.ts +++ b/server/routers/user/query.ts @@ -554,8 +554,8 @@ export const userQueryRouter = router({ }, body: { language: input.language, - mobileToken: input.mobileToken, - redirectUrl: input.redirectUrl, + mobileToken: false, + redirectUrl: `api/web/add-card-callback/${input.language}`, }, }) diff --git a/types/components/myPages/myProfile/addCreditCardButton.ts b/types/components/myPages/myProfile/addCreditCardButton.ts deleted file mode 100644 index 61a8e3aef..000000000 --- a/types/components/myPages/myProfile/addCreditCardButton.ts +++ /dev/null @@ -1,3 +0,0 @@ -export type AddCreditCardButtonProps = { - redirectUrl: string -} From a0fede38758755a16d26c9f69592118ff03887cf Mon Sep 17 00:00:00 2001 From: Pontus Dreij Date: Wed, 21 Aug 2024 15:27:44 +0200 Subject: [PATCH 06/53] feat(SW-266): Moved logic for breadcrumbs since I cant pass custom prop to generateMetadata --- .env.test | 2 ++ .../(protected)/my-pages/[...path]/page.tsx | 12 ----------- .../(public)/[contentType]/[uid]/page.tsx | 21 ------------------- utils/generateMetadata.ts | 13 +++++------- 4 files changed, 7 insertions(+), 41 deletions(-) diff --git a/.env.test b/.env.test index 8d70bab9d..801c4336b 100644 --- a/.env.test +++ b/.env.test @@ -12,6 +12,7 @@ CURITY_CLIENT_SECRET_SERVICE="test" CURITY_CLIENT_ID_USER="test" CURITY_CLIENT_SECRET_USER="test" CURITY_ISSUER_USER="test" +CURITY_ISSUER_SERVICE="test" CYPRESS_API_BASEURL="test" CYPRESS_CURITY_USERNAME="test" CYPRESS_CURITY_PASSWORD="test" @@ -35,3 +36,4 @@ SEAMLESS_LOGOUT_FI="test" SEAMLESS_LOGOUT_NO="test" SEAMLESS_LOGOUT_SV="test" WEBVIEW_ENCRYPTION_KEY="test" +BOOKING_ENCRYPTION_KEY="test" diff --git a/app/[lang]/(live)/(protected)/my-pages/[...path]/page.tsx b/app/[lang]/(live)/(protected)/my-pages/[...path]/page.tsx index c153db1a4..012c40de2 100644 --- a/app/[lang]/(live)/(protected)/my-pages/[...path]/page.tsx +++ b/app/[lang]/(live)/(protected)/my-pages/[...path]/page.tsx @@ -5,23 +5,11 @@ import Title from "@/components/TempDesignSystem/Text/Title" import TrackingSDK from "@/components/TrackingSDK" import { getIntl } from "@/i18n" import { setLang } from "@/i18n/serverContext" -import { generateMetadata as generateBaseMetadata } from "@/utils/generateMetadata" import styles from "./page.module.css" import type { LangParams, PageArgs } from "@/types/params" -export async function generateMetadata({ params }: PageArgs) { - const breadcrumbs = await serverClient().contentstack.breadcrumbs.get() - if (!breadcrumbs?.length) { - return null - } - return generateBaseMetadata({ - params, - pageTitle: breadcrumbs.at(-1)?.title, - }) -} - export default async function MyPages({ params, }: PageArgs) { diff --git a/app/[lang]/(live)/(public)/[contentType]/[uid]/page.tsx b/app/[lang]/(live)/(public)/[contentType]/[uid]/page.tsx index 655f34ee1..0e8e98a05 100644 --- a/app/[lang]/(live)/(public)/[contentType]/[uid]/page.tsx +++ b/app/[lang]/(live)/(public)/[contentType]/[uid]/page.tsx @@ -1,12 +1,9 @@ import { notFound } from "next/navigation" -import { serverClient } from "@/lib/trpc/server" - import ContentPage from "@/components/ContentType/ContentPage" import HotelPage from "@/components/ContentType/HotelPage/HotelPage" import LoyaltyPage from "@/components/ContentType/LoyaltyPage/LoyaltyPage" import { setLang } from "@/i18n/serverContext" -import { generateMetadata as generateBaseMetadata } from "@/utils/generateMetadata" import { ContentTypeParams, @@ -15,24 +12,6 @@ import { UIDParams, } from "@/types/params" -export async function generateMetadata({ - params, -}: PageArgs) { - switch (params.contentType) { - case "loyalty-page": - const breadcrumbs = await serverClient().contentstack.breadcrumbs.get() - if (!breadcrumbs?.length) { - return null - } - return generateBaseMetadata({ - params, - pageTitle: breadcrumbs.at(-1)?.title, - }) - default: - return - } -} - export default async function ContentTypePage({ params, }: PageArgs) { diff --git a/utils/generateMetadata.ts b/utils/generateMetadata.ts index 11a09d34f..6f321f853 100644 --- a/utils/generateMetadata.ts +++ b/utils/generateMetadata.ts @@ -3,16 +3,12 @@ import { Metadata } from "next" import { serverClient } from "@/lib/trpc/server" import { MetaData } from "@/types/components/metadata" -import { LangParams, PageArgs } from "@/types/params" -export async function generateMetadata({ - params, - pageTitle, -}: PageArgs & { - pageTitle?: string -}): Promise { +export async function generateMetadata(): Promise { const metaData: MetaData | never[] | null = await serverClient().contentstack.metaData.get() + const breadcrumbs = await serverClient().contentstack.breadcrumbs.get() + const pageTitle = breadcrumbs?.at(-1)?.title if (Array.isArray(metaData)) { return { @@ -23,7 +19,8 @@ export async function generateMetadata({ }, } } - const title = metaData?.title ?? pageTitle ?? "" + + const title = pageTitle ?? metaData?.title ?? "" // Use pageTitle first const description = metaData?.description ?? "" const images = metaData?.imageConnection?.edges?.map((edge) => ({ From bdafef492f595b6bf49b69eaea072a96d107257c Mon Sep 17 00:00:00 2001 From: Fredrik Thorsson Date: Mon, 19 Aug 2024 14:37:17 +0200 Subject: [PATCH 07/53] feat(SW-243): implement mobile design --- .../booking-confirmation/page.module.css | 30 ++++++++++++ .../booking-confirmation/page.tsx | 48 +++++++++++++++++++ .../confirmationCard.module.css | 41 ++++++++++++++++ .../ConfirmationCard/index.tsx | 41 ++++++++++++++++ .../confirmationSummary.module.css | 13 +++++ .../ConfirmationSummary/index.tsx | 33 +++++++++++++ .../confirmationTimes.module.css | 19 ++++++++ .../ConfirmationTimes/index.tsx | 29 +++++++++++ i18n/dictionaries/en.json | 3 ++ 9 files changed, 257 insertions(+) create mode 100644 app/[lang]/(live)/(public)/hotelreservation/booking-confirmation/page.module.css create mode 100644 app/[lang]/(live)/(public)/hotelreservation/booking-confirmation/page.tsx create mode 100644 components/HotelReservation/BookingConfirmation/ConfirmationCard/confirmationCard.module.css create mode 100644 components/HotelReservation/BookingConfirmation/ConfirmationCard/index.tsx create mode 100644 components/HotelReservation/BookingConfirmation/ConfirmationSummary/confirmationSummary.module.css create mode 100644 components/HotelReservation/BookingConfirmation/ConfirmationSummary/index.tsx create mode 100644 components/HotelReservation/BookingConfirmation/ConfirmationTimes/confirmationTimes.module.css create mode 100644 components/HotelReservation/BookingConfirmation/ConfirmationTimes/index.tsx diff --git a/app/[lang]/(live)/(public)/hotelreservation/booking-confirmation/page.module.css b/app/[lang]/(live)/(public)/hotelreservation/booking-confirmation/page.module.css new file mode 100644 index 000000000..cb5b6f63e --- /dev/null +++ b/app/[lang]/(live)/(public)/hotelreservation/booking-confirmation/page.module.css @@ -0,0 +1,30 @@ +.main { + display: flex; + flex-direction: column; + align-items: center; + gap: var(--Spacing-x4); + padding: var(--Spacing-x4) var(--Spacing-x4); + background-color: var(--Scandic-Brand-Warm-White); + min-height: 100dvh; +} + +.section { + display: flex; + flex-direction: column; + gap: var(--Spacing-x3); + width: 100%; + max-width: 525px; +} + +.buttons { + display: flex; + flex-direction: column; + align-items: center; + gap: var(--Spacing-x2); +} + +.button { + width: 100%; + max-width: 240px; + justify-content: center; +} diff --git a/app/[lang]/(live)/(public)/hotelreservation/booking-confirmation/page.tsx b/app/[lang]/(live)/(public)/hotelreservation/booking-confirmation/page.tsx new file mode 100644 index 000000000..7d2000906 --- /dev/null +++ b/app/[lang]/(live)/(public)/hotelreservation/booking-confirmation/page.tsx @@ -0,0 +1,48 @@ +import ConfirmationCard from "@/components/HotelReservation/BookingConfirmation/ConfirmationCard" +import ConfirmationSummary from "@/components/HotelReservation/BookingConfirmation/ConfirmationSummary" +import ConfirmationTimes from "@/components/HotelReservation/BookingConfirmation/ConfirmationTimes" +import Button from "@/components/TempDesignSystem/Button" +import Body from "@/components/TempDesignSystem/Text/Body" +import Title from "@/components/TempDesignSystem/Text/Title" + +import styles from "./page.module.css" + +export default function BookingConfirmationPage() { + return ( +
+
+
+ Thank you + + We look forward to your visit! + +
+ + We have sent a detailed confirmation of your booking to your email: + lisa.andersson@gmail.com. + +
+ + +
+
+ + + +
+ ) +} diff --git a/components/HotelReservation/BookingConfirmation/ConfirmationCard/confirmationCard.module.css b/components/HotelReservation/BookingConfirmation/ConfirmationCard/confirmationCard.module.css new file mode 100644 index 000000000..cca9b44a4 --- /dev/null +++ b/components/HotelReservation/BookingConfirmation/ConfirmationCard/confirmationCard.module.css @@ -0,0 +1,41 @@ +.test { + display: flex; + width: 100%; + max-width: 365px; + background-color: var(--Base-Surface-Primary-light-Normal); + border-radius: var(--Corner-radius-Small); + overflow: hidden; +} + +.image { + height: 100%; + width: 100%; + max-width: 105px; + object-fit: cover; +} + +.info { + display: flex; + flex: 1; + flex-direction: column; + gap: var(--Spacing-x1); + padding: var(--Spacing-x2); + font-family: var(--typography-Caption-Regular-fontFamily); + font-size: var(--typography-Caption-Regular-fontSize); +} + +.hotel { + display: flex; + flex-direction: column; + gap: var(--Spacing-x-half); +} + +.stay { + display: flex; + flex-direction: column; + gap: var(--Spacing-x-half); +} +.dates { + display: flex; + align-items: center; +} diff --git a/components/HotelReservation/BookingConfirmation/ConfirmationCard/index.tsx b/components/HotelReservation/BookingConfirmation/ConfirmationCard/index.tsx new file mode 100644 index 000000000..77b2ffc4c --- /dev/null +++ b/components/HotelReservation/BookingConfirmation/ConfirmationCard/index.tsx @@ -0,0 +1,41 @@ +import { ArrowRightIcon, ScandicLogoIcon } from "@/components/Icons" +import Image from "@/components/Image" +import Title from "@/components/TempDesignSystem/Text/Title" + +import styles from "./confirmationCard.module.css" + +export default function ConfirmationCard() { + return ( +
+
+ +
+
+
+ + + Helsinki Hub + +
+
+ Kaisaniemenkatu 7, Helsinki + Call us at +358 300 870680 +
+
+ 1 night +
+ 2024.03.09 + + 2024.03.10 +
+
+
+
+ ) +} diff --git a/components/HotelReservation/BookingConfirmation/ConfirmationSummary/confirmationSummary.module.css b/components/HotelReservation/BookingConfirmation/ConfirmationSummary/confirmationSummary.module.css new file mode 100644 index 000000000..929113390 --- /dev/null +++ b/components/HotelReservation/BookingConfirmation/ConfirmationSummary/confirmationSummary.module.css @@ -0,0 +1,13 @@ +.section { + width: 100%; + max-width: 365px; +} + +.summary { + display: flex; + justify-content: space-between; + padding: var(--Spacing-x2) var(--Spacing-x0); + font-family: var(--typography-Caption-Regular-fontFamily); + font-size: var(--typography-Caption-Regular-fontSize); + border-bottom: 1px solid var(--Base-Border-Subtle); +} diff --git a/components/HotelReservation/BookingConfirmation/ConfirmationSummary/index.tsx b/components/HotelReservation/BookingConfirmation/ConfirmationSummary/index.tsx new file mode 100644 index 000000000..3acf67fa5 --- /dev/null +++ b/components/HotelReservation/BookingConfirmation/ConfirmationSummary/index.tsx @@ -0,0 +1,33 @@ +import React from "react" + +import Title from "@/components/TempDesignSystem/Text/Title" +import { getIntl } from "@/i18n" + +import styles from "./confirmationSummary.module.css" + +export default async function ConfirmationSummary() { + const intl = await getIntl() + return ( +
+ + {intl.formatMessage({ id: "Summary" })} + +
+ Type of room: Standard Room + 1648 SEK +
+
+ Type of bed: King Bed + 0 SEK +
+
+ Breakfast: Breakfast Buffé + 198 SEK +
+
+ Flexibility: Free Rebooking + 200 SEK +
+
+ ) +} diff --git a/components/HotelReservation/BookingConfirmation/ConfirmationTimes/confirmationTimes.module.css b/components/HotelReservation/BookingConfirmation/ConfirmationTimes/confirmationTimes.module.css new file mode 100644 index 000000000..67b33ac0d --- /dev/null +++ b/components/HotelReservation/BookingConfirmation/ConfirmationTimes/confirmationTimes.module.css @@ -0,0 +1,19 @@ +.section { + display: flex; + justify-content: space-between; + padding: var(--Spacing-x2); + border-radius: var(--Corner-radius-Small); + background-color: var(--Base-Surface-Primary-dark-Normal); + width: 100%; + max-width: 365px; +} + +.breakfast, +.checkIn, +.checkOut { + display: flex; + flex-direction: column; + gap: var(--Spacing-x-half); + font-family: var(--typography-Caption-Regular-fontFamily); + font-size: var(--typography-Caption-Regular-fontSize); +} diff --git a/components/HotelReservation/BookingConfirmation/ConfirmationTimes/index.tsx b/components/HotelReservation/BookingConfirmation/ConfirmationTimes/index.tsx new file mode 100644 index 000000000..19bb0d71f --- /dev/null +++ b/components/HotelReservation/BookingConfirmation/ConfirmationTimes/index.tsx @@ -0,0 +1,29 @@ +import React from "react" + +import Body from "@/components/TempDesignSystem/Text/Body" +import { getIntl } from "@/i18n" + +import styles from "./confirmationTimes.module.css" + +export default async function ConfirmationTimes() { + const intl = await getIntl() + return ( +
+
+ {intl.formatMessage({ id: "Breakfast" })} + Mon-Fri 06:30-10:00 + Mon-Fri 06:30-10:00 +
+
+ {intl.formatMessage({ id: "Check in" })} + From + 15:00 +
+
+ {intl.formatMessage({ id: "Check out" })} + At latest + 12:00 +
+
+ ) +} diff --git a/i18n/dictionaries/en.json b/i18n/dictionaries/en.json index 528410878..5900321bb 100644 --- a/i18n/dictionaries/en.json +++ b/i18n/dictionaries/en.json @@ -149,6 +149,9 @@ "Hotel facilities": "Hotel facilities", "Hotel surroundings": "Hotel surroundings", "Show map": "Show map", + "Check in": "Check in", + "Check out": "Check out", + "Summary": "Summary", "Where to": "Where to", "When": "When", "Rooms & Guests": "Rooms & Guests", From a689f12965a362f7ef3b15e47b8c0d05c7aebcad Mon Sep 17 00:00:00 2001 From: Fredrik Thorsson Date: Mon, 19 Aug 2024 16:49:28 +0200 Subject: [PATCH 08/53] feat(SW-243): add breakpoints --- .../booking-confirmation/page.module.css | 27 ++++-------- .../booking-confirmation/page.tsx | 39 +++------------- .../confirmationCard.module.css | 1 - .../confirmationHead.module.css | 26 +++++++++++ .../ConfirmationHead/index.tsx | 44 +++++++++++++++++++ .../confirmationSummary.module.css | 1 - .../confirmationTimes.module.css | 1 - 7 files changed, 84 insertions(+), 55 deletions(-) create mode 100644 components/HotelReservation/BookingConfirmation/ConfirmationHead/confirmationHead.module.css create mode 100644 components/HotelReservation/BookingConfirmation/ConfirmationHead/index.tsx diff --git a/app/[lang]/(live)/(public)/hotelreservation/booking-confirmation/page.module.css b/app/[lang]/(live)/(public)/hotelreservation/booking-confirmation/page.module.css index cb5b6f63e..7d7358427 100644 --- a/app/[lang]/(live)/(public)/hotelreservation/booking-confirmation/page.module.css +++ b/app/[lang]/(live)/(public)/hotelreservation/booking-confirmation/page.module.css @@ -1,30 +1,21 @@ .main { display: flex; - flex-direction: column; - align-items: center; - gap: var(--Spacing-x4); - padding: var(--Spacing-x4) var(--Spacing-x4); + justify-content: center; + padding: var(--Spacing-x4); background-color: var(--Scandic-Brand-Warm-White); min-height: 100dvh; } .section { - display: flex; - flex-direction: column; - gap: var(--Spacing-x3); - width: 100%; - max-width: 525px; -} - -.buttons { display: flex; flex-direction: column; align-items: center; - gap: var(--Spacing-x2); -} - -.button { + gap: var(--Spacing-x4); width: 100%; - max-width: 240px; - justify-content: center; + max-width: 365px; +} +@media screen and (min-width: 1367px) { + .section { + max-width: 525px; + } } diff --git a/app/[lang]/(live)/(public)/hotelreservation/booking-confirmation/page.tsx b/app/[lang]/(live)/(public)/hotelreservation/booking-confirmation/page.tsx index 7d2000906..eee6e513d 100644 --- a/app/[lang]/(live)/(public)/hotelreservation/booking-confirmation/page.tsx +++ b/app/[lang]/(live)/(public)/hotelreservation/booking-confirmation/page.tsx @@ -1,9 +1,7 @@ import ConfirmationCard from "@/components/HotelReservation/BookingConfirmation/ConfirmationCard" +import ConfirmationHead from "@/components/HotelReservation/BookingConfirmation/ConfirmationHead" import ConfirmationSummary from "@/components/HotelReservation/BookingConfirmation/ConfirmationSummary" import ConfirmationTimes from "@/components/HotelReservation/BookingConfirmation/ConfirmationTimes" -import Button from "@/components/TempDesignSystem/Button" -import Body from "@/components/TempDesignSystem/Text/Body" -import Title from "@/components/TempDesignSystem/Text/Title" import styles from "./page.module.css" @@ -11,38 +9,11 @@ export default function BookingConfirmationPage() { return (
-
- Thank you - - We look forward to your visit! - -
- - We have sent a detailed confirmation of your booking to your email: - lisa.andersson@gmail.com. - -
- - -
+ + + +
- - -
) } diff --git a/components/HotelReservation/BookingConfirmation/ConfirmationCard/confirmationCard.module.css b/components/HotelReservation/BookingConfirmation/ConfirmationCard/confirmationCard.module.css index cca9b44a4..f8cdd76d3 100644 --- a/components/HotelReservation/BookingConfirmation/ConfirmationCard/confirmationCard.module.css +++ b/components/HotelReservation/BookingConfirmation/ConfirmationCard/confirmationCard.module.css @@ -1,7 +1,6 @@ .test { display: flex; width: 100%; - max-width: 365px; background-color: var(--Base-Surface-Primary-light-Normal); border-radius: var(--Corner-radius-Small); overflow: hidden; diff --git a/components/HotelReservation/BookingConfirmation/ConfirmationHead/confirmationHead.module.css b/components/HotelReservation/BookingConfirmation/ConfirmationHead/confirmationHead.module.css new file mode 100644 index 000000000..5b79c3796 --- /dev/null +++ b/components/HotelReservation/BookingConfirmation/ConfirmationHead/confirmationHead.module.css @@ -0,0 +1,26 @@ +.section { + display: flex; + flex-direction: column; + gap: var(--Spacing-x3); + width: 100%; +} + +.buttons { + display: flex; + flex-direction: column; + align-items: center; + gap: var(--Spacing-x2); +} + +.button { + width: 100%; + max-width: 240px; + justify-content: center; +} + +@media screen and (min-width: 1367px) { + .buttons { + flex-direction: row; + justify-content: space-around; + } +} diff --git a/components/HotelReservation/BookingConfirmation/ConfirmationHead/index.tsx b/components/HotelReservation/BookingConfirmation/ConfirmationHead/index.tsx new file mode 100644 index 000000000..bc227c7cc --- /dev/null +++ b/components/HotelReservation/BookingConfirmation/ConfirmationHead/index.tsx @@ -0,0 +1,44 @@ +import React from "react" + +import Button from "@/components/TempDesignSystem/Button" +import Body from "@/components/TempDesignSystem/Text/Body" +import Title from "@/components/TempDesignSystem/Text/Title" + +import styles from "./confirmationHead.module.css" + +export default function ConfirmationHead() { + return ( +
+
+ + Thank you + + + We look forward to your visit! + +
+ + We have sent a detailed confirmation of your booking to your email: + lisa.andersson@gmail.com. + +
+ + +
+
+ ) +} diff --git a/components/HotelReservation/BookingConfirmation/ConfirmationSummary/confirmationSummary.module.css b/components/HotelReservation/BookingConfirmation/ConfirmationSummary/confirmationSummary.module.css index 929113390..447ff673e 100644 --- a/components/HotelReservation/BookingConfirmation/ConfirmationSummary/confirmationSummary.module.css +++ b/components/HotelReservation/BookingConfirmation/ConfirmationSummary/confirmationSummary.module.css @@ -1,6 +1,5 @@ .section { width: 100%; - max-width: 365px; } .summary { diff --git a/components/HotelReservation/BookingConfirmation/ConfirmationTimes/confirmationTimes.module.css b/components/HotelReservation/BookingConfirmation/ConfirmationTimes/confirmationTimes.module.css index 67b33ac0d..2b905c7c5 100644 --- a/components/HotelReservation/BookingConfirmation/ConfirmationTimes/confirmationTimes.module.css +++ b/components/HotelReservation/BookingConfirmation/ConfirmationTimes/confirmationTimes.module.css @@ -5,7 +5,6 @@ border-radius: var(--Corner-radius-Small); background-color: var(--Base-Surface-Primary-dark-Normal); width: 100%; - max-width: 365px; } .breakfast, From 6a6dccaba56b12c0215c22f11090bcce786b0971 Mon Sep 17 00:00:00 2001 From: Fredrik Thorsson Date: Tue, 20 Aug 2024 09:47:19 +0200 Subject: [PATCH 09/53] feat(SW-243): make section responsive --- .../confirmationCard.module.css | 33 ++++++++++++++----- .../ConfirmationCard/index.tsx | 6 ++-- 2 files changed, 27 insertions(+), 12 deletions(-) diff --git a/components/HotelReservation/BookingConfirmation/ConfirmationCard/confirmationCard.module.css b/components/HotelReservation/BookingConfirmation/ConfirmationCard/confirmationCard.module.css index f8cdd76d3..d3745991d 100644 --- a/components/HotelReservation/BookingConfirmation/ConfirmationCard/confirmationCard.module.css +++ b/components/HotelReservation/BookingConfirmation/ConfirmationCard/confirmationCard.module.css @@ -1,4 +1,4 @@ -.test { +.section { display: flex; width: 100%; background-color: var(--Base-Surface-Primary-light-Normal); @@ -15,7 +15,6 @@ .info { display: flex; - flex: 1; flex-direction: column; gap: var(--Spacing-x1); padding: var(--Spacing-x2); @@ -23,12 +22,7 @@ font-size: var(--typography-Caption-Regular-fontSize); } -.hotel { - display: flex; - flex-direction: column; - gap: var(--Spacing-x-half); -} - +.hotel, .stay { display: flex; flex-direction: column; @@ -38,3 +32,26 @@ display: flex; align-items: center; } + +@media screen and (min-width: 1367px) { + .section { + flex-direction: column; + } + .image { + max-width: 100%; + height: 100%; + max-height: 195px; + } + + .info { + flex-direction: row; + justify-content: space-between; + gap: var(--Spacing-x4); + } + + .hotel, + .stay { + width: 100%; + max-width: 230px; + } +} diff --git a/components/HotelReservation/BookingConfirmation/ConfirmationCard/index.tsx b/components/HotelReservation/BookingConfirmation/ConfirmationCard/index.tsx index 77b2ffc4c..07bad6515 100644 --- a/components/HotelReservation/BookingConfirmation/ConfirmationCard/index.tsx +++ b/components/HotelReservation/BookingConfirmation/ConfirmationCard/index.tsx @@ -6,7 +6,7 @@ import styles from "./confirmationCard.module.css" export default function ConfirmationCard() { return ( -
+
-
+
Helsinki Hub -
-
Kaisaniemenkatu 7, Helsinki Call us at +358 300 870680
From bd14fb5fd22c86f56c641cfaf02efb20d7f5af1a Mon Sep 17 00:00:00 2001 From: Fredrik Thorsson Date: Tue, 20 Aug 2024 11:07:43 +0200 Subject: [PATCH 10/53] feat(SW-243): use Caption and Body --- .../confirmationCard.module.css | 16 ++++++++----- .../ConfirmationCard/index.tsx | 24 ++++++++++++------- 2 files changed, 25 insertions(+), 15 deletions(-) diff --git a/components/HotelReservation/BookingConfirmation/ConfirmationCard/confirmationCard.module.css b/components/HotelReservation/BookingConfirmation/ConfirmationCard/confirmationCard.module.css index d3745991d..a28c612a0 100644 --- a/components/HotelReservation/BookingConfirmation/ConfirmationCard/confirmationCard.module.css +++ b/components/HotelReservation/BookingConfirmation/ConfirmationCard/confirmationCard.module.css @@ -8,18 +8,16 @@ .image { height: 100%; - width: 100%; - max-width: 105px; + width: 105px; object-fit: cover; } .info { display: flex; flex-direction: column; + width: 100%; gap: var(--Spacing-x1); padding: var(--Spacing-x2); - font-family: var(--typography-Caption-Regular-fontFamily); - font-size: var(--typography-Caption-Regular-fontSize); } .hotel, @@ -28,9 +26,15 @@ flex-direction: column; gap: var(--Spacing-x-half); } + +.caption { + display: flex; + flex-direction: column; +} .dates { display: flex; align-items: center; + gap: var(--Spacing-x-half); } @media screen and (min-width: 1367px) { @@ -38,7 +42,7 @@ flex-direction: column; } .image { - max-width: 100%; + width: 100%; height: 100%; max-height: 195px; } @@ -50,7 +54,7 @@ } .hotel, - .stay { + .width { width: 100%; max-width: 230px; } diff --git a/components/HotelReservation/BookingConfirmation/ConfirmationCard/index.tsx b/components/HotelReservation/BookingConfirmation/ConfirmationCard/index.tsx index 07bad6515..3921c9da6 100644 --- a/components/HotelReservation/BookingConfirmation/ConfirmationCard/index.tsx +++ b/components/HotelReservation/BookingConfirmation/ConfirmationCard/index.tsx @@ -1,5 +1,7 @@ import { ArrowRightIcon, ScandicLogoIcon } from "@/components/Icons" import Image from "@/components/Image" +import Body from "@/components/TempDesignSystem/Text/Body" +import Caption from "@/components/TempDesignSystem/Text/Caption" import Title from "@/components/TempDesignSystem/Text/Title" import styles from "./confirmationCard.module.css" @@ -22,16 +24,20 @@ export default function ConfirmationCard() { Helsinki Hub - Kaisaniemenkatu 7, Helsinki - Call us at +358 300 870680 + + Kaisaniemenkatu 7, Helsinki + Call us at +358 300 870680 +
-
- 1 night -
- 2024.03.09 - - 2024.03.10 -
+
+ + 1 night + + 2024.03.09 + + 2024.03.10 + +
From 65509622d2c3168fbbb22edb9bc4eca5cefddd32 Mon Sep 17 00:00:00 2001 From: Fredrik Thorsson Date: Tue, 20 Aug 2024 14:29:14 +0200 Subject: [PATCH 11/53] feat(SW-243): add temp data --- .../booking-confirmation/page.tsx | 45 +++++++++-- .../ConfirmationCard/index.tsx | 45 ----------- .../confirmationTimes.module.css | 18 ----- .../ConfirmationTimes/index.tsx | 29 ------- .../index.tsx | 19 +++-- .../introSection.module.css} | 0 .../BookingConfirmation/StaySection/index.tsx | 79 +++++++++++++++++++ .../staySection.module.css} | 23 +++++- .../index.tsx | 14 ++-- .../summarySection.module.css} | 0 i18n/dictionaries/en.json | 5 ++ .../bookingConfirmation.ts | 40 ++++++++++ 12 files changed, 203 insertions(+), 114 deletions(-) delete mode 100644 components/HotelReservation/BookingConfirmation/ConfirmationCard/index.tsx delete mode 100644 components/HotelReservation/BookingConfirmation/ConfirmationTimes/confirmationTimes.module.css delete mode 100644 components/HotelReservation/BookingConfirmation/ConfirmationTimes/index.tsx rename components/HotelReservation/BookingConfirmation/{ConfirmationHead => IntroSection}/index.tsx (61%) rename components/HotelReservation/BookingConfirmation/{ConfirmationHead/confirmationHead.module.css => IntroSection/introSection.module.css} (100%) create mode 100644 components/HotelReservation/BookingConfirmation/StaySection/index.tsx rename components/HotelReservation/BookingConfirmation/{ConfirmationCard/confirmationCard.module.css => StaySection/staySection.module.css} (66%) rename components/HotelReservation/BookingConfirmation/{ConfirmationSummary => SummarySection}/index.tsx (57%) rename components/HotelReservation/BookingConfirmation/{ConfirmationSummary/confirmationSummary.module.css => SummarySection/summarySection.module.css} (100%) create mode 100644 types/components/hotelReservation/bookingConfirmation/bookingConfirmation.ts diff --git a/app/[lang]/(live)/(public)/hotelreservation/booking-confirmation/page.tsx b/app/[lang]/(live)/(public)/hotelreservation/booking-confirmation/page.tsx index eee6e513d..baf0a2fdd 100644 --- a/app/[lang]/(live)/(public)/hotelreservation/booking-confirmation/page.tsx +++ b/app/[lang]/(live)/(public)/hotelreservation/booking-confirmation/page.tsx @@ -1,18 +1,47 @@ -import ConfirmationCard from "@/components/HotelReservation/BookingConfirmation/ConfirmationCard" -import ConfirmationHead from "@/components/HotelReservation/BookingConfirmation/ConfirmationHead" -import ConfirmationSummary from "@/components/HotelReservation/BookingConfirmation/ConfirmationSummary" -import ConfirmationTimes from "@/components/HotelReservation/BookingConfirmation/ConfirmationTimes" +import IntroSection from "@/components/HotelReservation/BookingConfirmation/IntroSection" +import StaySection from "@/components/HotelReservation/BookingConfirmation/StaySection" +import SummarySection from "@/components/HotelReservation/BookingConfirmation/SummarySection" import styles from "./page.module.css" +import { BookingConfirmation } from "@/types/components/hotelReservation/bookingConfirmation/bookingConfirmation" + export default function BookingConfirmationPage() { + const confirmationData: BookingConfirmation = { + email: "lisa.andersson@outlook.com", + hotel: { + name: "Helsinki Hub", + address: "Kaisaniemenkatu 7, Helsinki", + location: "Helsinki", + phone: "+358 300 870680", + image: + "https://test3.scandichotels.com/imagevault/publishedmedia/i11isd60bh119s9486b7/downtown-camper-by-scandic-lobby-reception-desk-ch.jpg?w=640", + checkIn: "15.00", + checkOut: "12.00", + breakfast: { start: "06:30", end: "10:00" }, + }, + stay: { + nights: 1, + start: "2024.03.09", + end: "2024.03.10", + }, + summary: { + roomType: "Standard Room", + bedType: "King size", + breakfast: "Yes", + flexibility: "Yes", + }, + } + return (
- - - - + + +
) diff --git a/components/HotelReservation/BookingConfirmation/ConfirmationCard/index.tsx b/components/HotelReservation/BookingConfirmation/ConfirmationCard/index.tsx deleted file mode 100644 index 3921c9da6..000000000 --- a/components/HotelReservation/BookingConfirmation/ConfirmationCard/index.tsx +++ /dev/null @@ -1,45 +0,0 @@ -import { ArrowRightIcon, ScandicLogoIcon } from "@/components/Icons" -import Image from "@/components/Image" -import Body from "@/components/TempDesignSystem/Text/Body" -import Caption from "@/components/TempDesignSystem/Text/Caption" -import Title from "@/components/TempDesignSystem/Text/Title" - -import styles from "./confirmationCard.module.css" - -export default function ConfirmationCard() { - return ( -
-
- -
-
-
- - - Helsinki Hub - - - Kaisaniemenkatu 7, Helsinki - Call us at +358 300 870680 - -
-
- - 1 night - - 2024.03.09 - - 2024.03.10 - - -
-
-
- ) -} diff --git a/components/HotelReservation/BookingConfirmation/ConfirmationTimes/confirmationTimes.module.css b/components/HotelReservation/BookingConfirmation/ConfirmationTimes/confirmationTimes.module.css deleted file mode 100644 index 2b905c7c5..000000000 --- a/components/HotelReservation/BookingConfirmation/ConfirmationTimes/confirmationTimes.module.css +++ /dev/null @@ -1,18 +0,0 @@ -.section { - display: flex; - justify-content: space-between; - padding: var(--Spacing-x2); - border-radius: var(--Corner-radius-Small); - background-color: var(--Base-Surface-Primary-dark-Normal); - width: 100%; -} - -.breakfast, -.checkIn, -.checkOut { - display: flex; - flex-direction: column; - gap: var(--Spacing-x-half); - font-family: var(--typography-Caption-Regular-fontFamily); - font-size: var(--typography-Caption-Regular-fontSize); -} diff --git a/components/HotelReservation/BookingConfirmation/ConfirmationTimes/index.tsx b/components/HotelReservation/BookingConfirmation/ConfirmationTimes/index.tsx deleted file mode 100644 index 19bb0d71f..000000000 --- a/components/HotelReservation/BookingConfirmation/ConfirmationTimes/index.tsx +++ /dev/null @@ -1,29 +0,0 @@ -import React from "react" - -import Body from "@/components/TempDesignSystem/Text/Body" -import { getIntl } from "@/i18n" - -import styles from "./confirmationTimes.module.css" - -export default async function ConfirmationTimes() { - const intl = await getIntl() - return ( -
-
- {intl.formatMessage({ id: "Breakfast" })} - Mon-Fri 06:30-10:00 - Mon-Fri 06:30-10:00 -
-
- {intl.formatMessage({ id: "Check in" })} - From - 15:00 -
-
- {intl.formatMessage({ id: "Check out" })} - At latest - 12:00 -
-
- ) -} diff --git a/components/HotelReservation/BookingConfirmation/ConfirmationHead/index.tsx b/components/HotelReservation/BookingConfirmation/IntroSection/index.tsx similarity index 61% rename from components/HotelReservation/BookingConfirmation/ConfirmationHead/index.tsx rename to components/HotelReservation/BookingConfirmation/IntroSection/index.tsx index bc227c7cc..5ae2ae7f8 100644 --- a/components/HotelReservation/BookingConfirmation/ConfirmationHead/index.tsx +++ b/components/HotelReservation/BookingConfirmation/IntroSection/index.tsx @@ -3,23 +3,30 @@ import React from "react" import Button from "@/components/TempDesignSystem/Button" import Body from "@/components/TempDesignSystem/Text/Body" import Title from "@/components/TempDesignSystem/Text/Title" +import { getIntl } from "@/i18n" -import styles from "./confirmationHead.module.css" +import styles from "./introSection.module.css" + +import { IntroSectionProps } from "@/types/components/hotelReservation/bookingConfirmation/bookingConfirmation" + +export default async function IntroSection({ email }: IntroSectionProps) { + const intl = await getIntl() -export default function ConfirmationHead() { return (
- Thank you + {intl.formatMessage({ id: "Thank you" })} - We look forward to your visit! + {intl.formatMessage({ id: "We look forward to your visit!" })}
- We have sent a detailed confirmation of your booking to your email: - lisa.andersson@gmail.com. + {intl.formatMessage({ + id: "We have sent a detailed confirmation of your booking to your email: ", + })} + {email}
diff --git a/components/HotelReservation/BookingConfirmation/StaySection/index.tsx b/components/HotelReservation/BookingConfirmation/StaySection/index.tsx index 8d0db033c..33501b54c 100644 --- a/components/HotelReservation/BookingConfirmation/StaySection/index.tsx +++ b/components/HotelReservation/BookingConfirmation/StaySection/index.tsx @@ -1,5 +1,3 @@ -import { Stay } from "@/server/routers/user/output" - import { ArrowRightIcon, ScandicLogoIcon } from "@/components/Icons" import Image from "@/components/Image" import Body from "@/components/TempDesignSystem/Text/Body" @@ -58,20 +56,26 @@ export default async function StaySection({ hotel, stay }: StaySectionProps) { {intl.formatMessage({ id: "Breakfast" })} - {`Mon-fri ${hotel.breakfast.start}-${hotel.breakfast.end}`} - {`Sat-sun ${hotel.breakfast.start}-${hotel.breakfast.end}`} + + {`Mon-fri ${hotel.breakfast.start}-${hotel.breakfast.end}`} + {`Sat-sun ${hotel.breakfast.start}-${hotel.breakfast.end}`} +
{intl.formatMessage({ id: "Check in" })} - From - {hotel.checkIn} + + {intl.formatMessage({ id: "From" })} + {hotel.checkIn} +
{intl.formatMessage({ id: "Check out" })} - At latest - {hotel.checkOut} + + {intl.formatMessage({ id: "At latest" })} + {hotel.checkOut} +
diff --git a/components/HotelReservation/BookingConfirmation/StaySection/staySection.module.css b/components/HotelReservation/BookingConfirmation/StaySection/staySection.module.css index 5da503b8d..a833e36a1 100644 --- a/components/HotelReservation/BookingConfirmation/StaySection/staySection.module.css +++ b/components/HotelReservation/BookingConfirmation/StaySection/staySection.module.css @@ -31,6 +31,7 @@ display: flex; flex-direction: column; } + .dates { display: flex; align-items: center; @@ -52,8 +53,6 @@ display: flex; flex-direction: column; gap: var(--Spacing-x-half); - font-family: var(--typography-Caption-Regular-fontFamily); - font-size: var(--typography-Caption-Regular-fontSize); } @media screen and (min-width: 1367px) { @@ -69,7 +68,6 @@ .info { flex-direction: row; justify-content: space-between; - gap: var(--Spacing-x4); } .hotel, diff --git a/i18n/dictionaries/en.json b/i18n/dictionaries/en.json index 752aaffcb..d77763294 100644 --- a/i18n/dictionaries/en.json +++ b/i18n/dictionaries/en.json @@ -157,6 +157,7 @@ "We have sent a detailed confirmation of your booking to your email: ": "We have sent a detailed confirmation of your booking to your email: ", "Download the Scandic app": "Download the Scandic app", "View your booking": "View your booking", + "At latest": "At latest", "Where to": "Where to", "When": "When", "Rooms & Guests": "Rooms & Guests", From cda3816219a1f9e537fcb03d60761ecbe455209a Mon Sep 17 00:00:00 2001 From: Fredrik Thorsson Date: Tue, 20 Aug 2024 15:54:02 +0200 Subject: [PATCH 13/53] feat(SW-243): translations --- .../BookingConfirmation/IntroSection/index.tsx | 2 -- .../BookingConfirmation/SummarySection/index.tsx | 2 -- i18n/dictionaries/da.json | 9 +++++++++ i18n/dictionaries/de.json | 9 +++++++++ i18n/dictionaries/fi.json | 9 +++++++++ i18n/dictionaries/no.json | 9 +++++++++ i18n/dictionaries/sv.json | 9 +++++++++ 7 files changed, 45 insertions(+), 4 deletions(-) diff --git a/components/HotelReservation/BookingConfirmation/IntroSection/index.tsx b/components/HotelReservation/BookingConfirmation/IntroSection/index.tsx index 1934f36b2..40ecd27bd 100644 --- a/components/HotelReservation/BookingConfirmation/IntroSection/index.tsx +++ b/components/HotelReservation/BookingConfirmation/IntroSection/index.tsx @@ -1,5 +1,3 @@ -import React from "react" - import Button from "@/components/TempDesignSystem/Button" import Body from "@/components/TempDesignSystem/Text/Body" import Title from "@/components/TempDesignSystem/Text/Title" diff --git a/components/HotelReservation/BookingConfirmation/SummarySection/index.tsx b/components/HotelReservation/BookingConfirmation/SummarySection/index.tsx index 5f4fcedf5..0a60ab7ac 100644 --- a/components/HotelReservation/BookingConfirmation/SummarySection/index.tsx +++ b/components/HotelReservation/BookingConfirmation/SummarySection/index.tsx @@ -1,5 +1,3 @@ -import React from "react" - import Title from "@/components/TempDesignSystem/Text/Title" import { getIntl } from "@/i18n" diff --git a/i18n/dictionaries/da.json b/i18n/dictionaries/da.json index 3c04f265b..f1574251a 100644 --- a/i18n/dictionaries/da.json +++ b/i18n/dictionaries/da.json @@ -143,6 +143,15 @@ "Hotel facilities": "Hotel faciliteter", "Hotel surroundings": "Hotel omgivelser", "Show map": "Vis kort", + "Check in": "Check in", + "Check out": "Check out", + "Summary": "Summary", + "Thank you": "Thank you", + "We look forward to your visit!": "We look forward to your visit!", + "We have sent a detailed confirmation of your booking to your email: ": "We have sent a detailed confirmation of your booking to your email: ", + "Download the Scandic app": "Download the Scandic app", + "View your booking": "View your booking", + "At latest": "At latest", "Where to": "Hvorhen", "When": "Hvornår", "Rooms & Guests": "Værelser & gæster", diff --git a/i18n/dictionaries/de.json b/i18n/dictionaries/de.json index eebcb7095..07f712b51 100644 --- a/i18n/dictionaries/de.json +++ b/i18n/dictionaries/de.json @@ -137,6 +137,15 @@ "Hotel facilities": "Hotel-Infos", "Hotel surroundings": "Umgebung des Hotels", "Show map": "Karte anzeigen", + "Check in": "Check in", + "Check out": "Check out", + "Summary": "Summary", + "Thank you": "Thank you", + "We look forward to your visit!": "We look forward to your visit!", + "We have sent a detailed confirmation of your booking to your email: ": "We have sent a detailed confirmation of your booking to your email: ", + "Download the Scandic app": "Download the Scandic app", + "View your booking": "View your booking", + "At latest": "At latest", "Where to": "Wohin", "When": "Wann", "Rooms & Guests": "Zimmer & Gäste", diff --git a/i18n/dictionaries/fi.json b/i18n/dictionaries/fi.json index 26c0c08ff..6869d9ab9 100644 --- a/i18n/dictionaries/fi.json +++ b/i18n/dictionaries/fi.json @@ -143,6 +143,15 @@ "Hotel facilities": "Hotellin palvelut", "Hotel surroundings": "Hotellin ympäristö", "Show map": "Näytä kartta", + "Check in": "Check in", + "Check out": "Check out", + "Summary": "Summary", + "Thank you": "Thank you", + "We look forward to your visit!": "We look forward to your visit!", + "We have sent a detailed confirmation of your booking to your email: ": "We have sent a detailed confirmation of your booking to your email: ", + "Download the Scandic app": "Download the Scandic app", + "View your booking": "View your booking", + "At latest": "At latest", "Where to": "Minne", "When": "Kun", "Rooms & Guestss": "Huoneet & Vieraat", diff --git a/i18n/dictionaries/no.json b/i18n/dictionaries/no.json index b2cf5817a..dea0cd94e 100644 --- a/i18n/dictionaries/no.json +++ b/i18n/dictionaries/no.json @@ -143,6 +143,15 @@ "Hotel facilities": "Hotelfaciliteter", "Hotel surroundings": "Hotellomgivelser", "Show map": "Vis kart", + "Check in": "Check in", + "Check out": "Check out", + "Summary": "Summary", + "Thank you": "Thank you", + "We look forward to your visit!": "We look forward to your visit!", + "We have sent a detailed confirmation of your booking to your email: ": "We have sent a detailed confirmation of your booking to your email: ", + "Download the Scandic app": "Download the Scandic app", + "View your booking": "View your booking", + "At latest": "At latest", "Where to": "Hvor skal du", "When": "Når", "Rooms & Guests": "Rom og gjester", diff --git a/i18n/dictionaries/sv.json b/i18n/dictionaries/sv.json index e68e35ca6..d9b9fe9a5 100644 --- a/i18n/dictionaries/sv.json +++ b/i18n/dictionaries/sv.json @@ -145,6 +145,15 @@ "Hotel facilities": "Hotellfaciliteter", "Hotel surroundings": "Hotellomgivning", "Show map": "Visa karta", + "Check in": "Check in", + "Check out": "Check out", + "Summary": "Summary", + "Thank you": "Thank you", + "We look forward to your visit!": "We look forward to your visit!", + "We have sent a detailed confirmation of your booking to your email: ": "We have sent a detailed confirmation of your booking to your email: ", + "Download the Scandic app": "Download the Scandic app", + "View your booking": "View your booking", + "At latest": "At latest", "Where to": "Vart", "When": "När", "Rooms & Guests": "Rum och gäster", From 95c8fd1950dd728fe5ac64615b231791db57abb2 Mon Sep 17 00:00:00 2001 From: Fredrik Thorsson Date: Tue, 20 Aug 2024 16:09:03 +0200 Subject: [PATCH 14/53] feat(SW-243): all languages --- i18n/dictionaries/da.json | 18 +++++++++--------- i18n/dictionaries/de.json | 18 +++++++++--------- i18n/dictionaries/fi.json | 18 +++++++++--------- i18n/dictionaries/no.json | 18 +++++++++--------- i18n/dictionaries/sv.json | 18 +++++++++--------- 5 files changed, 45 insertions(+), 45 deletions(-) diff --git a/i18n/dictionaries/da.json b/i18n/dictionaries/da.json index f1574251a..2d0bdcd52 100644 --- a/i18n/dictionaries/da.json +++ b/i18n/dictionaries/da.json @@ -143,15 +143,15 @@ "Hotel facilities": "Hotel faciliteter", "Hotel surroundings": "Hotel omgivelser", "Show map": "Vis kort", - "Check in": "Check in", - "Check out": "Check out", - "Summary": "Summary", - "Thank you": "Thank you", - "We look forward to your visit!": "We look forward to your visit!", - "We have sent a detailed confirmation of your booking to your email: ": "We have sent a detailed confirmation of your booking to your email: ", - "Download the Scandic app": "Download the Scandic app", - "View your booking": "View your booking", - "At latest": "At latest", + "Check in": "Check ind", + "Check out": "Check ud", + "Summary": "Opsummering", + "Thank you": "Tak", + "We look forward to your visit!": "Vi ser frem til dit besøg!", + "We have sent a detailed confirmation of your booking to your email:": "Vi har sendt en detaljeret bekræftelse af din booking til din email:", + "Download the Scandic app": "Download Scandic-appen", + "View your booking": "Se din booking", + "At latest": "Senest", "Where to": "Hvorhen", "When": "Hvornår", "Rooms & Guests": "Værelser & gæster", diff --git a/i18n/dictionaries/de.json b/i18n/dictionaries/de.json index 07f712b51..af19591f8 100644 --- a/i18n/dictionaries/de.json +++ b/i18n/dictionaries/de.json @@ -137,15 +137,15 @@ "Hotel facilities": "Hotel-Infos", "Hotel surroundings": "Umgebung des Hotels", "Show map": "Karte anzeigen", - "Check in": "Check in", - "Check out": "Check out", - "Summary": "Summary", - "Thank you": "Thank you", - "We look forward to your visit!": "We look forward to your visit!", - "We have sent a detailed confirmation of your booking to your email: ": "We have sent a detailed confirmation of your booking to your email: ", - "Download the Scandic app": "Download the Scandic app", - "View your booking": "View your booking", - "At latest": "At latest", + "Check in": "Einchecken", + "Check out": "Auschecken", + "Summary": "Zusammenfassung", + "Thank you": "Danke", + "We look forward to your visit!": "Wir freuen uns auf Ihren Besuch!", + "We have sent a detailed confirmation of your booking to your email:": "Wir haben eine detaillierte Bestätigung Ihrer Buchung an Ihre E-Mail gesendet:", + "Download the Scandic app": "Laden Sie die Scandic-App herunter", + "View your booking": "Ihre Buchung ansehen", + "At latest": "Spätestens", "Where to": "Wohin", "When": "Wann", "Rooms & Guests": "Zimmer & Gäste", diff --git a/i18n/dictionaries/fi.json b/i18n/dictionaries/fi.json index 6869d9ab9..1d6bb2caf 100644 --- a/i18n/dictionaries/fi.json +++ b/i18n/dictionaries/fi.json @@ -143,15 +143,15 @@ "Hotel facilities": "Hotellin palvelut", "Hotel surroundings": "Hotellin ympäristö", "Show map": "Näytä kartta", - "Check in": "Check in", - "Check out": "Check out", - "Summary": "Summary", - "Thank you": "Thank you", - "We look forward to your visit!": "We look forward to your visit!", - "We have sent a detailed confirmation of your booking to your email: ": "We have sent a detailed confirmation of your booking to your email: ", - "Download the Scandic app": "Download the Scandic app", - "View your booking": "View your booking", - "At latest": "At latest", + "Check in": "Sisäänkirjautuminen", + "Check out": "Uloskirjautuminen", + "Summary": "Yhteenveto", + "Thank you": "Kiitos", + "We look forward to your visit!": "Odotamme innolla vierailuasi!", + "We have sent a detailed confirmation of your booking to your email:": "Olemme lähettäneet yksityiskohtaisen varausvahvistuksen sähköpostiisi:", + "Download the Scandic app": "Lataa Scandic-sovellus", + "View your booking": "Näytä varauksesi", + "At latest": "Viimeistään", "Where to": "Minne", "When": "Kun", "Rooms & Guestss": "Huoneet & Vieraat", diff --git a/i18n/dictionaries/no.json b/i18n/dictionaries/no.json index dea0cd94e..a5ec3dd67 100644 --- a/i18n/dictionaries/no.json +++ b/i18n/dictionaries/no.json @@ -143,15 +143,15 @@ "Hotel facilities": "Hotelfaciliteter", "Hotel surroundings": "Hotellomgivelser", "Show map": "Vis kart", - "Check in": "Check in", - "Check out": "Check out", - "Summary": "Summary", - "Thank you": "Thank you", - "We look forward to your visit!": "We look forward to your visit!", - "We have sent a detailed confirmation of your booking to your email: ": "We have sent a detailed confirmation of your booking to your email: ", - "Download the Scandic app": "Download the Scandic app", - "View your booking": "View your booking", - "At latest": "At latest", + "Check in": "Sjekk inn", + "Check out": "Sjekk ut", + "Summary": "Sammendrag", + "Thank you": "Takk", + "We look forward to your visit!": "Vi ser frem til ditt besøk!", + "We have sent a detailed confirmation of your booking to your email:": "Vi har sendt en detaljert bekreftelse av din bestilling til din e-post:", + "Download the Scandic app": "Last ned Scandic-appen", + "View your booking": "Se din bestilling", + "At latest": "Senest", "Where to": "Hvor skal du", "When": "Når", "Rooms & Guests": "Rom og gjester", diff --git a/i18n/dictionaries/sv.json b/i18n/dictionaries/sv.json index d9b9fe9a5..fd5b27b14 100644 --- a/i18n/dictionaries/sv.json +++ b/i18n/dictionaries/sv.json @@ -145,15 +145,15 @@ "Hotel facilities": "Hotellfaciliteter", "Hotel surroundings": "Hotellomgivning", "Show map": "Visa karta", - "Check in": "Check in", - "Check out": "Check out", - "Summary": "Summary", - "Thank you": "Thank you", - "We look forward to your visit!": "We look forward to your visit!", - "We have sent a detailed confirmation of your booking to your email: ": "We have sent a detailed confirmation of your booking to your email: ", - "Download the Scandic app": "Download the Scandic app", - "View your booking": "View your booking", - "At latest": "At latest", + "Check in": "Checka in", + "Check out": "Checka ut", + "Summary": "Sammanfattning", + "Thank you": "Tack", + "We look forward to your visit!": "Vi ser fram emot ditt besök!", + "We have sent a detailed confirmation of your booking to your email:": "Vi har skickat en detaljerad bekräftelse av din bokning till din e-post:", + "Download the Scandic app": "Ladda ner Scandic-appen", + "View your booking": "Visa din bokning", + "At latest": "Senast", "Where to": "Vart", "When": "När", "Rooms & Guests": "Rum och gäster", From 13d113169c1ebb48d1d78afc6abb4c61e232538b Mon Sep 17 00:00:00 2001 From: Fredrik Thorsson Date: Wed, 21 Aug 2024 09:57:04 +0200 Subject: [PATCH 15/53] feat(SW-243): css refactoring --- .../booking-confirmation/page.tsx | 10 +++--- .../BookingConfirmation/StaySection/index.tsx | 35 +++++++++---------- .../StaySection/staySection.module.css | 3 +- .../SummarySection/index.tsx | 18 +++++----- .../SummarySection/summarySection.module.css | 2 -- 5 files changed, 33 insertions(+), 35 deletions(-) diff --git a/app/[lang]/(live)/(public)/hotelreservation/booking-confirmation/page.tsx b/app/[lang]/(live)/(public)/hotelreservation/booking-confirmation/page.tsx index baf0a2fdd..1c404a393 100644 --- a/app/[lang]/(live)/(public)/hotelreservation/booking-confirmation/page.tsx +++ b/app/[lang]/(live)/(public)/hotelreservation/booking-confirmation/page.tsx @@ -7,7 +7,7 @@ import styles from "./page.module.css" import { BookingConfirmation } from "@/types/components/hotelReservation/bookingConfirmation/bookingConfirmation" export default function BookingConfirmationPage() { - const confirmationData: BookingConfirmation = { + const tempConfirmationData: BookingConfirmation = { email: "lisa.andersson@outlook.com", hotel: { name: "Helsinki Hub", @@ -36,12 +36,12 @@ export default function BookingConfirmationPage() { return (
- + - +
) diff --git a/components/HotelReservation/BookingConfirmation/StaySection/index.tsx b/components/HotelReservation/BookingConfirmation/StaySection/index.tsx index 33501b54c..21e900d69 100644 --- a/components/HotelReservation/BookingConfirmation/StaySection/index.tsx +++ b/components/HotelReservation/BookingConfirmation/StaySection/index.tsx @@ -11,6 +11,7 @@ import { StaySectionProps } from "@/types/components/hotelReservation/bookingCon export default async function StaySection({ hotel, stay }: StaySectionProps) { const intl = await getIntl() + const nights = stay.nights > 1 ? intl.formatMessage({ id: "nights" }) @@ -19,15 +20,13 @@ export default async function StaySection({ hotel, stay }: StaySectionProps) { return ( <>
-
- -
+
@@ -39,16 +38,14 @@ export default async function StaySection({ hotel, stay }: StaySectionProps) { {hotel.phone}
-
- - {`${stay.nights} ${nights}`} - - {stay.start} - - {stay.end} - - -
+ + {`${stay.nights} ${nights}`} + + {stay.start} + + {stay.end} + +
diff --git a/components/HotelReservation/BookingConfirmation/StaySection/staySection.module.css b/components/HotelReservation/BookingConfirmation/StaySection/staySection.module.css index a833e36a1..0cec5a570 100644 --- a/components/HotelReservation/BookingConfirmation/StaySection/staySection.module.css +++ b/components/HotelReservation/BookingConfirmation/StaySection/staySection.module.css @@ -2,6 +2,7 @@ display: flex; width: 100%; background-color: var(--Base-Surface-Primary-light-Normal); + border: 1px solid var(--Base-Border-Subtle); border-radius: var(--Corner-radius-Small); overflow: hidden; } @@ -71,7 +72,7 @@ } .hotel, - .width { + .stay { width: 100%; max-width: 230px; } diff --git a/components/HotelReservation/BookingConfirmation/SummarySection/index.tsx b/components/HotelReservation/BookingConfirmation/SummarySection/index.tsx index 0a60ab7ac..4240ba236 100644 --- a/components/HotelReservation/BookingConfirmation/SummarySection/index.tsx +++ b/components/HotelReservation/BookingConfirmation/SummarySection/index.tsx @@ -1,3 +1,4 @@ +import Caption from "@/components/TempDesignSystem/Text/Caption" import Title from "@/components/TempDesignSystem/Text/Title" import { getIntl } from "@/i18n" @@ -7,27 +8,28 @@ import { SummarySectionProps } from "@/types/components/hotelReservation/booking export default async function SummarySection({ summary }: SummarySectionProps) { const intl = await getIntl() + return (
{intl.formatMessage({ id: "Summary" })} -
+ {`Type of room: ${summary.roomType}`} 1648 SEK -
-
+ + {`Type of bed: ${summary.bedType}`} 0 SEK -
-
+ + {`Breakfast: ${summary.breakfast}`} 198 SEK -
-
+ + {`Flexibility: ${summary.flexibility}`} 200 SEK -
+
) } diff --git a/components/HotelReservation/BookingConfirmation/SummarySection/summarySection.module.css b/components/HotelReservation/BookingConfirmation/SummarySection/summarySection.module.css index 447ff673e..9f7cfce89 100644 --- a/components/HotelReservation/BookingConfirmation/SummarySection/summarySection.module.css +++ b/components/HotelReservation/BookingConfirmation/SummarySection/summarySection.module.css @@ -6,7 +6,5 @@ display: flex; justify-content: space-between; padding: var(--Spacing-x2) var(--Spacing-x0); - font-family: var(--typography-Caption-Regular-fontFamily); - font-size: var(--typography-Caption-Regular-fontSize); border-bottom: 1px solid var(--Base-Border-Subtle); } From 0362e5eff3c4ec6839df978350d71195769c85a5 Mon Sep 17 00:00:00 2001 From: Fredrik Thorsson Date: Wed, 21 Aug 2024 11:08:27 +0200 Subject: [PATCH 16/53] feat(SW-243): set button asChild --- .../BookingConfirmation/IntroSection/index.tsx | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/components/HotelReservation/BookingConfirmation/IntroSection/index.tsx b/components/HotelReservation/BookingConfirmation/IntroSection/index.tsx index 40ecd27bd..ff7f0d001 100644 --- a/components/HotelReservation/BookingConfirmation/IntroSection/index.tsx +++ b/components/HotelReservation/BookingConfirmation/IntroSection/index.tsx @@ -1,4 +1,5 @@ import Button from "@/components/TempDesignSystem/Button" +import Link from "@/components/TempDesignSystem/Link" import Body from "@/components/TempDesignSystem/Text/Body" import Title from "@/components/TempDesignSystem/Text/Title" import { getIntl } from "@/i18n" @@ -28,20 +29,26 @@ export default async function IntroSection({ email }: IntroSectionProps) {
From 47c1b763fcf73e7f84eb519023d9366d6670e823 Mon Sep 17 00:00:00 2001 From: Fredrik Thorsson Date: Wed, 21 Aug 2024 11:18:44 +0200 Subject: [PATCH 17/53] feat(SW-243): change title to subtitle --- .../BookingConfirmation/IntroSection/index.tsx | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/components/HotelReservation/BookingConfirmation/IntroSection/index.tsx b/components/HotelReservation/BookingConfirmation/IntroSection/index.tsx index ff7f0d001..9482f51fe 100644 --- a/components/HotelReservation/BookingConfirmation/IntroSection/index.tsx +++ b/components/HotelReservation/BookingConfirmation/IntroSection/index.tsx @@ -1,6 +1,7 @@ import Button from "@/components/TempDesignSystem/Button" import Link from "@/components/TempDesignSystem/Link" import Body from "@/components/TempDesignSystem/Text/Body" +import Subtitle from "@/components/TempDesignSystem/Text/Subtitle" import Title from "@/components/TempDesignSystem/Text/Title" import { getIntl } from "@/i18n" @@ -17,9 +18,9 @@ export default async function IntroSection({ email }: IntroSectionProps) { {intl.formatMessage({ id: "Thank you" })} - + <Subtitle textAlign="center" textTransform="uppercase"> {intl.formatMessage({ id: "We look forward to your visit!" })} - + {intl.formatMessage({ From f4e63bedcadfc4b939c7f9fa100d76b66e8ac93b Mon Sep 17 00:00:00 2001 From: Fredrik Thorsson Date: Wed, 21 Aug 2024 11:45:51 +0200 Subject: [PATCH 18/53] feat(SW-243): remove height on desktop --- .../BookingConfirmation/StaySection/staySection.module.css | 1 - .../SummarySection/summarySection.module.css | 5 ++++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/components/HotelReservation/BookingConfirmation/StaySection/staySection.module.css b/components/HotelReservation/BookingConfirmation/StaySection/staySection.module.css index 0cec5a570..1eae5c732 100644 --- a/components/HotelReservation/BookingConfirmation/StaySection/staySection.module.css +++ b/components/HotelReservation/BookingConfirmation/StaySection/staySection.module.css @@ -62,7 +62,6 @@ } .image { width: 100%; - height: 100%; max-height: 195px; } diff --git a/components/HotelReservation/BookingConfirmation/SummarySection/summarySection.module.css b/components/HotelReservation/BookingConfirmation/SummarySection/summarySection.module.css index 9f7cfce89..b65d92e76 100644 --- a/components/HotelReservation/BookingConfirmation/SummarySection/summarySection.module.css +++ b/components/HotelReservation/BookingConfirmation/SummarySection/summarySection.module.css @@ -5,6 +5,9 @@ .summary { display: flex; justify-content: space-between; - padding: var(--Spacing-x2) var(--Spacing-x0); border-bottom: 1px solid var(--Base-Border-Subtle); } + +.summary span { + padding: var(--Spacing-x2) var(--Spacing-x0); +} From 5c357bc88bda240bf0b93526fa6208f886a87d7a Mon Sep 17 00:00:00 2001 From: Fredrik Thorsson Date: Wed, 21 Aug 2024 14:25:05 +0200 Subject: [PATCH 19/53] feat(SW-243): add translations --- .../BookingConfirmation/StaySection/index.tsx | 8 ++++---- .../BookingConfirmation/SummarySection/index.tsx | 12 ++++++++---- i18n/dictionaries/da.json | 4 ++++ i18n/dictionaries/de.json | 4 ++++ i18n/dictionaries/en.json | 4 ++++ i18n/dictionaries/fi.json | 4 ++++ i18n/dictionaries/no.json | 4 ++++ i18n/dictionaries/sv.json | 4 ++++ 8 files changed, 36 insertions(+), 8 deletions(-) diff --git a/components/HotelReservation/BookingConfirmation/StaySection/index.tsx b/components/HotelReservation/BookingConfirmation/StaySection/index.tsx index 21e900d69..99ecae8ca 100644 --- a/components/HotelReservation/BookingConfirmation/StaySection/index.tsx +++ b/components/HotelReservation/BookingConfirmation/StaySection/index.tsx @@ -12,7 +12,7 @@ import { StaySectionProps } from "@/types/components/hotelReservation/bookingCon export default async function StaySection({ hotel, stay }: StaySectionProps) { const intl = await getIntl() - const nights = + const nightsText = stay.nights > 1 ? intl.formatMessage({ id: "nights" }) : intl.formatMessage({ id: "night" }) @@ -39,7 +39,7 @@ export default async function StaySection({ hotel, stay }: StaySectionProps) { - {`${stay.nights} ${nights}`} + {`${stay.nights} ${nightsText}`} {stay.start} @@ -54,8 +54,8 @@ export default async function StaySection({ hotel, stay }: StaySectionProps) { {intl.formatMessage({ id: "Breakfast" })} - {`Mon-fri ${hotel.breakfast.start}-${hotel.breakfast.end}`} - {`Sat-sun ${hotel.breakfast.start}-${hotel.breakfast.end}`} + {`${intl.formatMessage({ id: "Weekdays" })} ${hotel.breakfast.start}-${hotel.breakfast.end}`} + {`${intl.formatMessage({ id: "Weekends" })} ${hotel.breakfast.start}-${hotel.breakfast.end}`}
diff --git a/components/HotelReservation/BookingConfirmation/SummarySection/index.tsx b/components/HotelReservation/BookingConfirmation/SummarySection/index.tsx index 4240ba236..509af9c52 100644 --- a/components/HotelReservation/BookingConfirmation/SummarySection/index.tsx +++ b/components/HotelReservation/BookingConfirmation/SummarySection/index.tsx @@ -8,6 +8,10 @@ import { SummarySectionProps } from "@/types/components/hotelReservation/booking export default async function SummarySection({ summary }: SummarySectionProps) { const intl = await getIntl() + const roomType = `${intl.formatMessage({ id: "Type of room" })}: ${summary.roomType}` + const bedType = `${intl.formatMessage({ id: "Type of bed" })}: ${summary.bedType}` + const breakfast = `${intl.formatMessage({ id: "Breakfast" })}: ${summary.breakfast}` + const flexibility = `${intl.formatMessage({ id: "Flexibility" })}: ${summary.flexibility}` return (
@@ -15,19 +19,19 @@ export default async function SummarySection({ summary }: SummarySectionProps) { {intl.formatMessage({ id: "Summary" })} - {`Type of room: ${summary.roomType}`} + {roomType} 1648 SEK - {`Type of bed: ${summary.bedType}`} + {bedType} 0 SEK - {`Breakfast: ${summary.breakfast}`} + {breakfast} 198 SEK - {`Flexibility: ${summary.flexibility}`} + {flexibility} 200 SEK
diff --git a/i18n/dictionaries/da.json b/i18n/dictionaries/da.json index 2d0bdcd52..dc4301e19 100644 --- a/i18n/dictionaries/da.json +++ b/i18n/dictionaries/da.json @@ -152,6 +152,10 @@ "Download the Scandic app": "Download Scandic-appen", "View your booking": "Se din booking", "At latest": "Senest", + "Type of room": "Værelsestype", + "Type of bed": "Sengtype", + "Weekdays": "Hverdage", + "Weekends": "Weekender", "Where to": "Hvorhen", "When": "Hvornår", "Rooms & Guests": "Værelser & gæster", diff --git a/i18n/dictionaries/de.json b/i18n/dictionaries/de.json index af19591f8..421308f13 100644 --- a/i18n/dictionaries/de.json +++ b/i18n/dictionaries/de.json @@ -146,6 +146,10 @@ "Download the Scandic app": "Laden Sie die Scandic-App herunter", "View your booking": "Ihre Buchung ansehen", "At latest": "Spätestens", + "Type of room": "Zimmerart", + "Type of bed": "Bettentyp", + "Weekdays": "Wochentage", + "Weekends": "Wochenenden", "Where to": "Wohin", "When": "Wann", "Rooms & Guests": "Zimmer & Gäste", diff --git a/i18n/dictionaries/en.json b/i18n/dictionaries/en.json index d77763294..32a55c8dd 100644 --- a/i18n/dictionaries/en.json +++ b/i18n/dictionaries/en.json @@ -158,6 +158,10 @@ "Download the Scandic app": "Download the Scandic app", "View your booking": "View your booking", "At latest": "At latest", + "Type of room": "Type of room", + "Type of bed": "Type of bed", + "Weekdays": "Weekdays", + "Weekends": "Weekends", "Where to": "Where to", "When": "When", "Rooms & Guests": "Rooms & Guests", diff --git a/i18n/dictionaries/fi.json b/i18n/dictionaries/fi.json index 1d6bb2caf..9f5903970 100644 --- a/i18n/dictionaries/fi.json +++ b/i18n/dictionaries/fi.json @@ -152,6 +152,10 @@ "Download the Scandic app": "Lataa Scandic-sovellus", "View your booking": "Näytä varauksesi", "At latest": "Viimeistään", + "Type of room": "Huonetyyppi", + "Type of bed": "Vuodetyyppi", + "Weekdays": "Arkisin", + "Weekends": "Viikonloppuisin", "Where to": "Minne", "When": "Kun", "Rooms & Guestss": "Huoneet & Vieraat", diff --git a/i18n/dictionaries/no.json b/i18n/dictionaries/no.json index a5ec3dd67..8a7b15e6c 100644 --- a/i18n/dictionaries/no.json +++ b/i18n/dictionaries/no.json @@ -152,6 +152,10 @@ "Download the Scandic app": "Last ned Scandic-appen", "View your booking": "Se din bestilling", "At latest": "Senest", + "Type of room": "Romtype", + "Type of bed": "Sengtype", + "Weekdays": "Hverdager", + "Weekends": "Helger", "Where to": "Hvor skal du", "When": "Når", "Rooms & Guests": "Rom og gjester", diff --git a/i18n/dictionaries/sv.json b/i18n/dictionaries/sv.json index fd5b27b14..e96fa533c 100644 --- a/i18n/dictionaries/sv.json +++ b/i18n/dictionaries/sv.json @@ -154,6 +154,10 @@ "Download the Scandic app": "Ladda ner Scandic-appen", "View your booking": "Visa din bokning", "At latest": "Senast", + "Type of room": "Rumstyp", + "Type of bed": "Sängtyp", + "Weekdays": "Vardagar", + "Weekends": "Helger", "Where to": "Vart", "When": "När", "Rooms & Guests": "Rum och gäster", From 72ebc14f7db9e067455eff0c4a79362279f50fc4 Mon Sep 17 00:00:00 2001 From: Fredrik Thorsson Date: Wed, 21 Aug 2024 15:05:15 +0200 Subject: [PATCH 20/53] feat(SW-243): import tempdata --- .../booking-confirmation/page.tsx | 38 +++---------------- .../tempConfirmationData.ts | 27 +++++++++++++ 2 files changed, 32 insertions(+), 33 deletions(-) create mode 100644 components/HotelReservation/BookingConfirmation/tempConfirmationData.ts diff --git a/app/[lang]/(live)/(public)/hotelreservation/booking-confirmation/page.tsx b/app/[lang]/(live)/(public)/hotelreservation/booking-confirmation/page.tsx index 1c404a393..46f941bbc 100644 --- a/app/[lang]/(live)/(public)/hotelreservation/booking-confirmation/page.tsx +++ b/app/[lang]/(live)/(public)/hotelreservation/booking-confirmation/page.tsx @@ -1,47 +1,19 @@ import IntroSection from "@/components/HotelReservation/BookingConfirmation/IntroSection" import StaySection from "@/components/HotelReservation/BookingConfirmation/StaySection" import SummarySection from "@/components/HotelReservation/BookingConfirmation/SummarySection" +import { tempConfirmationData } from "@/components/HotelReservation/BookingConfirmation/tempConfirmationData" import styles from "./page.module.css" -import { BookingConfirmation } from "@/types/components/hotelReservation/bookingConfirmation/bookingConfirmation" - export default function BookingConfirmationPage() { - const tempConfirmationData: BookingConfirmation = { - email: "lisa.andersson@outlook.com", - hotel: { - name: "Helsinki Hub", - address: "Kaisaniemenkatu 7, Helsinki", - location: "Helsinki", - phone: "+358 300 870680", - image: - "https://test3.scandichotels.com/imagevault/publishedmedia/i11isd60bh119s9486b7/downtown-camper-by-scandic-lobby-reception-desk-ch.jpg?w=640", - checkIn: "15.00", - checkOut: "12.00", - breakfast: { start: "06:30", end: "10:00" }, - }, - stay: { - nights: 1, - start: "2024.03.09", - end: "2024.03.10", - }, - summary: { - roomType: "Standard Room", - bedType: "King size", - breakfast: "Yes", - flexibility: "Yes", - }, - } + const { email, hotel, stay, summary } = tempConfirmationData return (
- - - + + +
) diff --git a/components/HotelReservation/BookingConfirmation/tempConfirmationData.ts b/components/HotelReservation/BookingConfirmation/tempConfirmationData.ts new file mode 100644 index 000000000..2dbf572e7 --- /dev/null +++ b/components/HotelReservation/BookingConfirmation/tempConfirmationData.ts @@ -0,0 +1,27 @@ +import { BookingConfirmation } from "@/types/components/hotelReservation/bookingConfirmation/bookingConfirmation" + +export const tempConfirmationData: BookingConfirmation = { + email: "lisa.andersson@outlook.com", + hotel: { + name: "Helsinki Hub", + address: "Kaisaniemenkatu 7, Helsinki", + location: "Helsinki", + phone: "+358 300 870680", + image: + "https://test3.scandichotels.com/imagevault/publishedmedia/i11isd60bh119s9486b7/downtown-camper-by-scandic-lobby-reception-desk-ch.jpg?w=640", + checkIn: "15.00", + checkOut: "12.00", + breakfast: { start: "06:30", end: "10:00" }, + }, + stay: { + nights: 1, + start: "2024.03.09", + end: "2024.03.10", + }, + summary: { + roomType: "Standard Room", + bedType: "King size", + breakfast: "Yes", + flexibility: "Yes", + }, +} From 1ec10332677ac897968b0c26b45f2ab86222b18a Mon Sep 17 00:00:00 2001 From: Tobias Johansson Date: Wed, 21 Aug 2024 13:54:55 +0000 Subject: [PATCH 21/53] Merged in feat/SW-165-correct-labels (pull request #427) Feat/SW-165 correct labels * feat(SW-165): sort friend transactions and return additional properties * feat(SW-165): Added points being calculated label * feat(SW-165): added transactionDate for transactions without checkinDate * feat(SW-165): Updated description copy for various reward types * feat(SW-165): filter out expired transactions * feat(SW-165): removed Mobile table and unified them into Table instead * feat(SW-165): Added bookingUrl to friend transactions * fix(SW-165): style fixes * fix(SW-165): fix issues from merge * fix(SW-165): remove comment * fix(SW-165): fixed booking urls not being set and smaller fixes * fix(SW-165): added comment regarding 'BALFWD' Approved-by: Michael Zetterberg Approved-by: Christel Westerberg --- .../EarnAndBurn/JourneyTable/Client.tsx | 6 +- .../JourneyTable/Desktop/Row/AwardPoints.tsx | 26 ------ .../JourneyTable/Desktop/Row/index.tsx | 35 -------- .../EarnAndBurn/JourneyTable/Mobile/index.tsx | 69 ---------------- .../JourneyTable/Mobile/mobile.module.css | 52 ------------ .../JourneyTable/Table/Row/AwardPoints.tsx | 40 +++++++++ .../Row/awardPointsVariants.ts | 0 .../JourneyTable/Table/Row/index.tsx | 82 +++++++++++++++++++ .../{Desktop => Table}/Row/row.module.css | 24 ++++-- .../JourneyTable/{Desktop => Table}/index.tsx | 54 ++++++------ .../table.module.css} | 17 ++-- i18n/dictionaries/da.json | 10 ++- i18n/dictionaries/de.json | 8 ++ i18n/dictionaries/en.json | 8 ++ i18n/dictionaries/fi.json | 8 ++ i18n/dictionaries/no.json | 8 ++ i18n/dictionaries/sv.json | 8 ++ server/routers/user/output.ts | 6 ++ server/routers/user/query.ts | 54 +++++++++--- .../components/myPages/myPage/earnAndBurn.ts | 2 +- types/components/myPages/myPage/enums.ts | 14 ++++ 21 files changed, 292 insertions(+), 239 deletions(-) delete mode 100644 components/MyPages/Blocks/Points/EarnAndBurn/JourneyTable/Desktop/Row/AwardPoints.tsx delete mode 100644 components/MyPages/Blocks/Points/EarnAndBurn/JourneyTable/Desktop/Row/index.tsx delete mode 100644 components/MyPages/Blocks/Points/EarnAndBurn/JourneyTable/Mobile/index.tsx delete mode 100644 components/MyPages/Blocks/Points/EarnAndBurn/JourneyTable/Mobile/mobile.module.css create mode 100644 components/MyPages/Blocks/Points/EarnAndBurn/JourneyTable/Table/Row/AwardPoints.tsx rename components/MyPages/Blocks/Points/EarnAndBurn/JourneyTable/{Desktop => Table}/Row/awardPointsVariants.ts (100%) create mode 100644 components/MyPages/Blocks/Points/EarnAndBurn/JourneyTable/Table/Row/index.tsx rename components/MyPages/Blocks/Points/EarnAndBurn/JourneyTable/{Desktop => Table}/Row/row.module.css (50%) rename components/MyPages/Blocks/Points/EarnAndBurn/JourneyTable/{Desktop => Table}/index.tsx (54%) rename components/MyPages/Blocks/Points/EarnAndBurn/JourneyTable/{Desktop/desktop.module.css => Table/table.module.css} (79%) diff --git a/components/MyPages/Blocks/Points/EarnAndBurn/JourneyTable/Client.tsx b/components/MyPages/Blocks/Points/EarnAndBurn/JourneyTable/Client.tsx index f0cc9b2ef..3e08de171 100644 --- a/components/MyPages/Blocks/Points/EarnAndBurn/JourneyTable/Client.tsx +++ b/components/MyPages/Blocks/Points/EarnAndBurn/JourneyTable/Client.tsx @@ -7,9 +7,8 @@ import { trpc } from "@/lib/trpc/client" import LoadingSpinner from "@/components/LoadingSpinner" -import DesktopTable from "./Desktop" -import MobileTable from "./Mobile" import Pagination from "./Pagination" +import Table from "./Table" import { Transactions } from "@/types/components/myPages/myPage/earnAndBurn" @@ -40,8 +39,7 @@ export default function TransactionTable({ ) : ( <> - - + {data && data.meta.totalPages > 1 ? ( 0) { - variant = "addition" - } else if (awardPoints < 0) { - variant = "negation" - awardPoints = Math.abs(awardPoints) - } - - const classNames = awardPointsVariants({ - variant, - }) - - // sv hardcoded to force space on thousands - const formatter = new Intl.NumberFormat(Lang.sv) - return -} diff --git a/components/MyPages/Blocks/Points/EarnAndBurn/JourneyTable/Desktop/Row/index.tsx b/components/MyPages/Blocks/Points/EarnAndBurn/JourneyTable/Desktop/Row/index.tsx deleted file mode 100644 index 297aa39cf..000000000 --- a/components/MyPages/Blocks/Points/EarnAndBurn/JourneyTable/Desktop/Row/index.tsx +++ /dev/null @@ -1,35 +0,0 @@ -"use client" - -import { useIntl } from "react-intl" - -import { dt } from "@/lib/dt" - -import useLang from "@/hooks/useLang" - -import AwardPoints from "./AwardPoints" - -import styles from "./row.module.css" - -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(lang).format("DD MMM YYYY") - const departure = dt(transaction.checkoutDate) - .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 deleted file mode 100644 index d4de69ac4..000000000 --- a/components/MyPages/Blocks/Points/EarnAndBurn/JourneyTable/Mobile/index.tsx +++ /dev/null @@ -1,69 +0,0 @@ -import { useIntl } from "react-intl" - -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 useLang from "@/hooks/useLang" - -import styles from "./mobile.module.css" - -import type { TableProps } from "@/types/components/myPages/myPage/earnAndBurn" - -export default function MobileTable({ transactions }: TableProps) { - const intl = useIntl() - const lang = useLang() - return ( -
-
{formatter.format(awardPoints)} pts
{arrival}{description}{transaction.confirmationNumber}{departure}
- - - - - - - - - - - - {transactions.length ? ( - transactions.map((transaction, idx) => ( - - - - - )) - ) : ( - - - - )} - -
- {intl.formatMessage({ id: "Transactions" })} - - {intl.formatMessage({ id: "Points" })} -
- - {dt(transaction.checkinDate) - .locale(lang) - .format("DD MMM YYYY")} - - {transaction.hotelName && transaction.city ? ( - {`${transaction.hotelName}, ${transaction.city}`} - ) : null} - - {`${transaction.nights} ${intl.formatMessage({ id: transaction.nights === 1 ? "night" : "nights" })}`} - -
- {intl.formatMessage({ - id: "There are no transactions to display", - })} -
-
- ) -} diff --git a/components/MyPages/Blocks/Points/EarnAndBurn/JourneyTable/Mobile/mobile.module.css b/components/MyPages/Blocks/Points/EarnAndBurn/JourneyTable/Mobile/mobile.module.css deleted file mode 100644 index ac7e30bda..000000000 --- a/components/MyPages/Blocks/Points/EarnAndBurn/JourneyTable/Mobile/mobile.module.css +++ /dev/null @@ -1,52 +0,0 @@ -.table { - border-collapse: collapse; - border-spacing: 0; - width: 100%; -} - -.thead { - background-color: var(--Main-Grey-10); -} - -.th { - padding: var(--Spacing-x2); -} - -.tr { - border-top: 1px solid var(--Main-Grey-10); -} - -.td { - padding: var(--Spacing-x2); -} - -.transactionDetails { - display: grid; - font-size: var(--typography-Footnote-Regular-fontSize); -} - -.transactionDate { - font-weight: 700; -} - -.placeholder { - text-align: center; - 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 { - display: none; - } -} diff --git a/components/MyPages/Blocks/Points/EarnAndBurn/JourneyTable/Table/Row/AwardPoints.tsx b/components/MyPages/Blocks/Points/EarnAndBurn/JourneyTable/Table/Row/AwardPoints.tsx new file mode 100644 index 000000000..a2d281dca --- /dev/null +++ b/components/MyPages/Blocks/Points/EarnAndBurn/JourneyTable/Table/Row/AwardPoints.tsx @@ -0,0 +1,40 @@ +import { useIntl } from "react-intl" + +import { Lang } from "@/constants/languages" + +import { awardPointsVariants } from "./awardPointsVariants" + +import type { AwardPointsVariantProps } from "@/types/components/myPages/myPage/earnAndBurn" + +export default function AwardPoints({ + awardPoints, + isCalculated, +}: { + awardPoints: number + isCalculated: boolean +}) { + let variant: AwardPointsVariantProps["variant"] = undefined + const intl = useIntl() + + if (isCalculated) { + if (awardPoints > 0) { + variant = "addition" + } else if (awardPoints < 0) { + variant = "negation" + awardPoints = Math.abs(awardPoints) + } + } + const classNames = awardPointsVariants({ + variant, + }) + + // sv hardcoded to force space on thousands + const formatter = new Intl.NumberFormat(Lang.sv) + return ( + + {isCalculated + ? formatter.format(awardPoints) + : intl.formatMessage({ id: "Points being calculated" })} + + ) +} diff --git a/components/MyPages/Blocks/Points/EarnAndBurn/JourneyTable/Desktop/Row/awardPointsVariants.ts b/components/MyPages/Blocks/Points/EarnAndBurn/JourneyTable/Table/Row/awardPointsVariants.ts similarity index 100% rename from components/MyPages/Blocks/Points/EarnAndBurn/JourneyTable/Desktop/Row/awardPointsVariants.ts rename to components/MyPages/Blocks/Points/EarnAndBurn/JourneyTable/Table/Row/awardPointsVariants.ts diff --git a/components/MyPages/Blocks/Points/EarnAndBurn/JourneyTable/Table/Row/index.tsx b/components/MyPages/Blocks/Points/EarnAndBurn/JourneyTable/Table/Row/index.tsx new file mode 100644 index 000000000..c3fd5b5a5 --- /dev/null +++ b/components/MyPages/Blocks/Points/EarnAndBurn/JourneyTable/Table/Row/index.tsx @@ -0,0 +1,82 @@ +"use client" + +import { useIntl } from "react-intl" + +import { dt } from "@/lib/dt" + +import Link from "@/components/TempDesignSystem/Link" +import useLang from "@/hooks/useLang" + +import AwardPoints from "./AwardPoints" + +import styles from "./row.module.css" + +import type { RowProps } from "@/types/components/myPages/myPage/earnAndBurn" +import { RewardTransactionTypes } from "@/types/components/myPages/myPage/enums" + +export default function Row({ transaction }: RowProps) { + const intl = useIntl() + const lang = useLang() + + const nightString = `${transaction.nights} ${transaction.nights === 1 ? intl.formatMessage({ id: "night" }) : intl.formatMessage({ id: "nights" })}` + + let description = + transaction.hotelName && transaction.city + ? `${transaction.hotelName}, ${transaction.city} ${nightString}` + : `${nightString}` + + switch (transaction.type) { + case RewardTransactionTypes.stay: + if (transaction.hotelId === "ORS") + description = intl.formatMessage({ id: "Former Scandic Hotel" }) + break + case RewardTransactionTypes.ancillary: + description = intl.formatMessage({ id: "Extras to your booking" }) + break + case RewardTransactionTypes.enrollment: + description = intl.formatMessage({ id: "Sign up bonus" }) + break + case RewardTransactionTypes.mastercard_points: + description = intl.formatMessage({ id: "Scandic Friends Mastercard" }) + break + case RewardTransactionTypes.tui_points: + description = intl.formatMessage({ id: "TUI Points" }) + case RewardTransactionTypes.stayAdj: + if (transaction.confirmationNumber === "BALFWD") + description = intl.formatMessage({ + id: "Points earned prior to May 1, 2021", + }) + break + case RewardTransactionTypes.pointShop: + description = intl.formatMessage({ id: "Scandic Friends Point Shop" }) + break + } + + const arrival = dt(transaction.checkinDate).locale(lang).format("DD MMM YYYY") + const transactionDate = dt(transaction.transactionDate) + .locale(lang) + .format("DD MMM YYYY") + + return ( + + + {description} + + {transaction.type === RewardTransactionTypes.stay && + transaction.bookingUrl ? ( + + {transaction.confirmationNumber} + + ) : ( + transaction.confirmationNumber + )} + + + {transaction.checkinDate ? arrival : transactionDate} + + + ) +} diff --git a/components/MyPages/Blocks/Points/EarnAndBurn/JourneyTable/Desktop/Row/row.module.css b/components/MyPages/Blocks/Points/EarnAndBurn/JourneyTable/Table/Row/row.module.css similarity index 50% rename from components/MyPages/Blocks/Points/EarnAndBurn/JourneyTable/Desktop/Row/row.module.css rename to components/MyPages/Blocks/Points/EarnAndBurn/JourneyTable/Table/Row/row.module.css index 1a75072da..eb59b55ea 100644 --- a/components/MyPages/Blocks/Points/EarnAndBurn/JourneyTable/Desktop/Row/row.module.css +++ b/components/MyPages/Blocks/Points/EarnAndBurn/JourneyTable/Table/Row/row.module.css @@ -1,13 +1,21 @@ .tr { - border: 1px solid #e6e9ec; + border-bottom: 1px solid var(--Scandic-Brand-Pale-Peach); + &:last-child { + border-bottom: none; + } } .td { background-color: #fff; color: var(--UI-Text-High-contrast); - padding: var(--Spacing-x2) var(--Spacing-x4); + padding: var(--Spacing-x2); position: relative; text-align: left; + text-wrap: nowrap; +} + +.description { + font-weight: var(--typography-Body-Bold-fontWeight); } .addition { @@ -17,8 +25,7 @@ .addition::before { color: var(--Secondary-Light-On-Surface-Accent); content: "+"; - left: var(--Spacing-x2); - position: absolute; + margin-right: var(--Spacing-x-half); } .negation { @@ -28,6 +35,11 @@ .negation::before { color: var(--Base-Text-Accent); content: "-"; - left: var(--Spacing-x2); - position: absolute; + margin-right: var(--Spacing-x-half); +} + +@media screen and (min-width: 768px) { + .td { + padding: var(--Spacing-x3); + } } diff --git a/components/MyPages/Blocks/Points/EarnAndBurn/JourneyTable/Desktop/index.tsx b/components/MyPages/Blocks/Points/EarnAndBurn/JourneyTable/Table/index.tsx similarity index 54% rename from components/MyPages/Blocks/Points/EarnAndBurn/JourneyTable/Desktop/index.tsx rename to components/MyPages/Blocks/Points/EarnAndBurn/JourneyTable/Table/index.tsx index 3d50eb609..c49695e88 100644 --- a/components/MyPages/Blocks/Points/EarnAndBurn/JourneyTable/Desktop/index.tsx +++ b/components/MyPages/Blocks/Points/EarnAndBurn/JourneyTable/Table/index.tsx @@ -1,50 +1,48 @@ +"use client" + import { useIntl } from "react-intl" import Body from "@/components/TempDesignSystem/Text/Body" import Row from "./Row" -import styles from "./desktop.module.css" +import styles from "./table.module.css" import type { TableProps } from "@/types/components/myPages/myPage/earnAndBurn" const tableHeadings = [ - "Arrival date", + "Points", "Description", "Booking number", - "Transaction date", - "Points", + "Arrival date", ] -export default function DesktopTable({ transactions }: TableProps) { +export default function Table({ transactions }: TableProps) { const intl = useIntl() - return (
{transactions.length ? ( -
- - - - {tableHeadings.map((heading) => ( - - ))} - - - - {transactions.map((transaction, idx) => ( - +
- - {intl.formatMessage({ id: heading })} - -
+ + + {tableHeadings.map((heading) => ( + ))} - -
+ + {intl.formatMessage({ id: heading })} + +
-
+ + + + {transactions.map((transaction, index) => ( + + ))} + + ) : ( diff --git a/components/MyPages/Blocks/Points/EarnAndBurn/JourneyTable/Desktop/desktop.module.css b/components/MyPages/Blocks/Points/EarnAndBurn/JourneyTable/Table/table.module.css similarity index 79% rename from components/MyPages/Blocks/Points/EarnAndBurn/JourneyTable/Desktop/desktop.module.css rename to components/MyPages/Blocks/Points/EarnAndBurn/JourneyTable/Table/table.module.css index 113c23aba..8c319b619 100644 --- a/components/MyPages/Blocks/Points/EarnAndBurn/JourneyTable/Desktop/desktop.module.css +++ b/components/MyPages/Blocks/Points/EarnAndBurn/JourneyTable/Table/table.module.css @@ -1,5 +1,8 @@ .container { - display: none; + display: flex; + flex-direction: column; + overflow-x: auto; + border-radius: var(--Corner-radius-Small); } .table { @@ -17,7 +20,8 @@ .th { text-align: left; - padding: 20px 32px; + text-wrap: nowrap; + padding: var(--Spacing-x2); } .placeholder { @@ -49,9 +53,10 @@ } @media screen and (min-width: 768px) { .container { - display: flex; - flex-direction: column; - gap: 16px; - overflow-x: auto; + border-radius: var(--Corner-radius-Large); + } + + .th { + padding: var(--Spacing-x2) var(--Spacing-x3); } } diff --git a/i18n/dictionaries/da.json b/i18n/dictionaries/da.json index dc4301e19..31af5e52f 100644 --- a/i18n/dictionaries/da.json +++ b/i18n/dictionaries/da.json @@ -42,10 +42,12 @@ "Edit": "Redigere", "Edit profile": "Rediger profil", "Email": "E-mail", + "Extras to your booking": "Ekstra til din booking", "There are no transactions to display": "Der er ingen transaktioner at vise", "Explore all levels and benefits": "Udforsk alle niveauer og fordele", "Find booking": "Find booking", "Flexibility": "Fleksibilitet", + "Former Scandic Hotel": "Tidligere Scandic Hotel", "From": "Fra", "Get inspired": "Bliv inspireret", "Go back to overview": "Gå tilbage til oversigten", @@ -95,7 +97,9 @@ "Phone is required": "Telefonnummer er påkrævet", "Phone number": "Telefonnummer", "Please enter a valid phone number": "Indtast venligst et gyldigt telefonnummer", - "Points": "Point", + "Points": "Points", + "Points being calculated": "Point udregnes", + "Points earned prior to May 1, 2021": "Point optjent før 1. maj 2021", "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", @@ -107,6 +111,8 @@ "Retype new password": "Gentag den nye adgangskode", "Rooms": "Værelser", "Save": "Gemme", + "Scandic Friends Mastercard": "Scandic Friends Mastercard", + "Scandic Friends Point Shop": "Scandic Friends Point Shop", "Select a country": "Vælg et land", "Select country of residence": "Vælg bopælsland", "Select date of birth": "Vælg fødselsdato", @@ -115,6 +121,7 @@ "Show more": "Vis mere", "Show all amenities": "Vis alle faciliteter", "Skip to main content": "Spring over og gå til hovedindhold", + "Sign up bonus": "Tilmeldingsbonus", "Something went wrong!": "Noget gik galt!", "Street": "Gade", "special character": "speciel karakter", @@ -124,6 +131,7 @@ "Transactions": "Transaktioner", "Tripadvisor reviews": "{rating} ({count} anmeldelser på Tripadvisor)", "to": "til", + "TUI Points": "TUI-point", "User information": "Brugeroplysninger", "uppercase letter": "stort bogstav", "Visiting address": "Besøgsadresse", diff --git a/i18n/dictionaries/de.json b/i18n/dictionaries/de.json index 421308f13..7f0a26f2c 100644 --- a/i18n/dictionaries/de.json +++ b/i18n/dictionaries/de.json @@ -41,10 +41,12 @@ "Edit": "Bearbeiten", "Edit profile": "Profil bearbeiten", "Email": "Email", + "Extras to your booking": "Extras zu Ihrer Buchung", "There are no transactions to display": "Es sind keine Transaktionen zum Anzeigen vorhanden", "Explore all levels and benefits": "Entdecken Sie alle Levels und Vorteile", "Find booking": "Buchung finden", "Flexibility": "Flexibilität", + "Former Scandic Hotel": "Ehemaliges Scandic Hotel", "From": "Fromm", "Get inspired": "Lassen Sie sich inspieren", "Go back to overview": "Zurück zur Übersicht", @@ -93,6 +95,8 @@ "Phone number": "Telefonnummer", "Please enter a valid phone number": "Bitte geben Sie eine gültige Telefonnummer ein", "Points": "Punkte", + "Points being calculated": "Punkte werden berechnet", + "Points earned prior to May 1, 2021": "Vor dem 1. Mai 2021 gesammelte Punkte", "Points may take up to 10 days to be displayed.": "Es kann bis zu 10 Tage dauern, bis Punkte angezeigt werden.", "Points needed to level up": "Punkte, die zum Levelaufstieg benötigt werden", "Points needed to stay on level": "Erforderliche Punkte, um auf diesem Level zu bleiben", @@ -102,6 +106,8 @@ "Read more about the hotel": "Lesen Sie mehr über das Hotel", "Retype new password": "Neues Passwort erneut eingeben", "Save": "Speichern", + "Scandic Friends Mastercard": "Scandic Friends Mastercard", + "Scandic Friends Point Shop": "Scandic Friends Point Shop", "Select a country": "Wähle ein Land", "Select country of residence": "Wählen Sie das Land Ihres Wohnsitzes aus", "Select date of birth": "Geburtsdatum auswählen", @@ -110,6 +116,7 @@ "Show more": "Mehr anzeigen", "Show all amenities": "Alle Annehmlichkeiten anzeigen", "Skip to main content": "Direkt zum Inhalt", + "Sign up bonus": "Anmeldebonus", "Something went wrong!": "Etwas ist schief gelaufen!", "Street": "Straße", "special character": "sonderzeichen", @@ -119,6 +126,7 @@ "Transactions": "Transaktionen", "Tripadvisor reviews": "{rating} ({count} Bewertungen auf Tripadvisor)", "to": "zu", + "TUI Points": "TUI Punkte", "User information": "Nutzerinformation", "uppercase letter": "großbuchstabe", "Visiting address": "Besuchsadresse", diff --git a/i18n/dictionaries/en.json b/i18n/dictionaries/en.json index 32a55c8dd..62790f4fc 100644 --- a/i18n/dictionaries/en.json +++ b/i18n/dictionaries/en.json @@ -45,8 +45,10 @@ "Email": "Email", "There are no transactions to display": "There are no transactions to display", "Explore all levels and benefits": "Explore all levels and benefits", + "Extras to your booking": "Extras to your booking", "FAQ": "FAQ", "Find booking": "Find booking", + "Former Scandic Hotel": "Former Scandic Hotel", "Flexibility": "Flexibility", "From": "From", "Get inspired": "Get inspired", @@ -101,6 +103,8 @@ "Phone number": "Phone number", "Please enter a valid phone number": "Please enter a valid phone number", "Points": "Points", + "Points being calculated": "Points being calculated", + "Points earned prior to May 1, 2021": "Points earned prior to May 1, 2021", "Points may take up to 10 days to be displayed.": "Points may take up to 10 days to be displayed.", "Points needed to level up": "Points needed to level up", "Points needed to stay on level": "Points needed to stay on level", @@ -113,6 +117,8 @@ "Rooms": "Rooms", "Save": "Save", "See room details": "See room details", + "Scandic Friends Mastercard": "Scandic Friends Mastercard", + "Scandic Friends Point Shop": "Scandic Friends Point Shop", "Select a country": "Select a country", "Select country of residence": "Select country of residence", "Select date of birth": "Select date of birth", @@ -121,6 +127,7 @@ "Show more": "Show more", "Show all amenities": "Show all amenities", "Skip to main content": "Skip to main content", + "Sign up bonus": "Sign up bonus", "Something went wrong!": "Something went wrong!", "Street": "Street", "special character": "special character", @@ -130,6 +137,7 @@ "Transactions": "Transactions", "Tripadvisor reviews": "{rating} ({count} reviews on Tripadvisor)", "to": "to", + "TUI Points": "TUI Points", "User information": "User information", "uppercase letter": "uppercase letter", "Welcome": "Welcome", diff --git a/i18n/dictionaries/fi.json b/i18n/dictionaries/fi.json index 9f5903970..a244f16d3 100644 --- a/i18n/dictionaries/fi.json +++ b/i18n/dictionaries/fi.json @@ -42,10 +42,12 @@ "Edit": "Muokata", "Edit profile": "Muokkaa profiilia", "Email": "Sähköposti", + "Extras to your booking": "Lisävarusteet varaukseesi", "There are no transactions to display": "Näytettäviä tapahtumia ei ole", "Explore all levels and benefits": "Tutustu kaikkiin tasoihin ja etuihin", "Find booking": "Etsi varaus", "Flexibility": "Joustavuus", + "Former Scandic Hotel": "Entinen Scandic Hotel", "From": "From", "Get inspired": "Inspiroidu", "Go back to overview": "Palaa yleiskatsaukseen", @@ -96,6 +98,8 @@ "Phone number": "Puhelinnumero", "Please enter a valid phone number": "Ole hyvä ja näppäile voimassaoleva puhelinnumero", "Points": "Pistettä", + "Points being calculated": "Pisteitä lasketaan", + "Points earned prior to May 1, 2021": "Ennen 1. toukokuuta 2021 ansaitut pisteet", "Points may take up to 10 days to be displayed.": "Pisteiden näyttäminen voi kestää jopa 10 päivää.", "Points needed to level up": "Pisteitä tarvitaan tasolle pääsemiseksi", "Points needed to stay on level": "Tällä tasolla pysymiseen tarvittavat pisteet", @@ -107,6 +111,8 @@ "Retype new password": "Kirjoita uusi salasana uudelleen", "Rooms": "Huoneet", "Save": "Tallentaa", + "Scandic Friends Mastercard": "Scandic Friends Mastercard", + "Scandic Friends Point Shop": "Scandic Friends Point Shop", "Select a country": "Valitse maa", "Select country of residence": "Valitse asuinmaa", "Select date of birth": "Valitse syntymäaika", @@ -115,6 +121,7 @@ "Show more": "Näytä lisää", "Show all amenities": "Näytä kaikki mukavuudet", "Skip to main content": "Siirry pääsisältöön", + "Sign up bonus": "Rekisteröidy bonus", "Something went wrong!": "Jotain meni pieleen!", "Street": "Katu", "special character": "erikoishahmo", @@ -124,6 +131,7 @@ "Transactions": "Tapahtumat", "Tripadvisor reviews": "{rating} ({count} arvostelua TripAdvisorissa)", "to": "to", + "TUI Points": "TUI-pisteet", "User information": "Käyttäjän tiedot", "uppercase letter": "iso kirjain", "Visiting address": "Käyntiosoite", diff --git a/i18n/dictionaries/no.json b/i18n/dictionaries/no.json index 8a7b15e6c..cc79768ee 100644 --- a/i18n/dictionaries/no.json +++ b/i18n/dictionaries/no.json @@ -42,10 +42,12 @@ "Edit": "Redigere", "Edit profile": "Rediger profil", "Email": "E-post", + "Extras to your booking": "Ekstra til din bestilling", "There are no transactions to display": "Det er ingen transaksjoner å vise", "Explore all levels and benefits": "Utforsk alle nivåer og fordeler", "Find booking": "Finn booking", "Flexibility": "Fleksibilitet", + "Former Scandic Hotel": "Tidligere Scandic Hotel", "From": "Fra", "Get inspired": "Bli inspirert", "Go back to overview": "Gå tilbake til oversikten", @@ -96,6 +98,8 @@ "Phone number": "Telefonnummer", "Please enter a valid phone number": "Vennligst oppgi et gyldig telefonnummer", "Points": "Poeng", + "Points being calculated": "Poeng beregnes", + "Points earned prior to May 1, 2021": "Poeng opptjent før 1. mai 2021", "Points may take up to 10 days to be displayed.": "Det kan ta opptil 10 dager før poeng vises.", "Points needed to level up": "Poeng som trengs for å komme opp i nivå", "Points needed to stay on level": "Poeng som trengs for å holde seg på nivå", @@ -107,6 +111,8 @@ "Retype new password": "Skriv inn nytt passord på nytt", "Rooms": "Rom", "Save": "Lagre", + "Scandic Friends Mastercard": "Scandic Friends Mastercard", + "Scandic Friends Point Shop": "Scandic Friends Point Shop", "Select a country": "Velg et land", "Select country of residence": "Velg bostedsland", "Select date of birth": "Velg fødselsdato", @@ -115,6 +121,7 @@ "Show more": "Vis mer", "Show all amenities": "Vis alle fasiliteter", "Skip to main content": "Gå videre til hovedsiden", + "Sign up bonus": "Registreringsbonus", "Something went wrong!": "Noe gikk galt!", "Street": "Gate", "special character": "spesiell karakter", @@ -124,6 +131,7 @@ "Transactions": "Transaksjoner", "Tripadvisor reviews": "{rating} ({count} anmeldelser på Tripadvisor)", "to": "til", + "TUI Points": "TUI-poeng", "User information": "Brukerinformasjon", "uppercase letter": "stor bokstav", "Visiting address": "Besøksadresse", diff --git a/i18n/dictionaries/sv.json b/i18n/dictionaries/sv.json index e96fa533c..0e8f4695f 100644 --- a/i18n/dictionaries/sv.json +++ b/i18n/dictionaries/sv.json @@ -42,10 +42,12 @@ "Edit": "Redigera", "Edit profile": "Redigera profil", "Email": "E-post", + "Extras to your booking": "Extra till din bokning", "There are no transactions to display": "Det finns inga transaktioner att visa", "Explore all levels and benefits": "Utforska alla nivåer och fördelar", "Find booking": "Hitta bokning", "Flexibility": "Flexibilitet", + "Former Scandic Hotel": "Tidigare Scandic Hotel", "From": "Från", "Get inspired": "Bli inspirerad", "Go back to overview": "Gå tillbaka till översikten", @@ -99,6 +101,8 @@ "Phone number": "Telefonnummer", "Please enter a valid phone number": "Var vänlig och ange ett giltigt telefonnummer", "Points": "Poäng", + "Points being calculated": "Poäng beräknas", + "Points earned prior to May 1, 2021": "Poäng intjänade före 1 maj 2021", "Points may take up to 10 days to be displayed.": "Det kan ta upp till 10 dagar innan poäng visas.", "Points needed to level up": "Poäng som behövs för att gå upp i nivå", "Points needed to stay on level": "Poäng som behövs för att hålla sig på nivå", @@ -110,6 +114,8 @@ "Retype new password": "Upprepa nytt lösenord", "Rooms": "Rum", "Save": "Spara", + "Scandic Friends Mastercard": "Scandic Friends Mastercard", + "Scandic Friends Point Shop": "Scandic Friends Point Shop", "Select a country": "Välj ett land", "Select country of residence": "Välj bosättningsland", "Select date of birth": "Välj födelsedatum", @@ -118,6 +124,7 @@ "Show more": "Visa mer", "Show all amenities": "Visa alla bekvämligheter", "Skip to main content": "Fortsätt till huvudinnehåll", + "Sign up bonus": "Registreringsbonus", "Something went wrong!": "Något gick fel!", "Street": "Gata", "special character": "speciell karaktär", @@ -127,6 +134,7 @@ "Transactions": "Transaktioner", "Tripadvisor reviews": "{rating} ({count} recensioner på Tripadvisor)", "to": "till", + "TUI Points": "TUI-poäng", "User information": "Användar information", "uppercase letter": "stor bokstav", "Visiting address": "Besöksadress", diff --git a/server/routers/user/output.ts b/server/routers/user/output.ts index 77f8e992a..d079b8323 100644 --- a/server/routers/user/output.ts +++ b/server/routers/user/output.ts @@ -115,6 +115,8 @@ export const getFriendTransactionsSchema = z.object({ hotelOperaId: z.string().default(""), nights: z.number().default(1), pointsCalculated: z.boolean().default(true), + transactionDate: z.string().default(""), + bookingUrl: z.string().default(""), hotelInformation: z .object({ city: z.string().default(""), @@ -170,6 +172,10 @@ export const getFriendTransactionsSchema = z.object({ .nullable(), }) +type GetFriendTransactionsData = z.infer + +export type FriendTransaction = GetFriendTransactionsData["data"][number] + export const getCreditCardsSchema = z.object({ data: z.array( z.object({ diff --git a/server/routers/user/query.ts b/server/routers/user/query.ts index d39c0f929..47247658e 100644 --- a/server/routers/user/query.ts +++ b/server/routers/user/query.ts @@ -26,6 +26,7 @@ import { staysInput, } from "./input" import { + FriendTransaction, getCreditCardsSchema, getFriendTransactionsSchema, getMembershipCardsSchema, @@ -38,6 +39,7 @@ import { benefits, extendedUser, nextLevelPerks } from "./temp" import type { Session } from "next-auth" +import { RewardTransactionTypes } from "@/types/components/myPages/myPage/enums" import type { LoginType, TrackingSDKUserData, @@ -91,11 +93,23 @@ function fakingRequest(payload: T): Promise { }) } -const updateStaysBookingUrl = async ( +async function updateStaysBookingUrl( data: Stay[], token: string, lang: Lang -) => { +): Promise + +async function updateStaysBookingUrl( + data: FriendTransaction[], + token: string, + lang: Lang +): Promise + +async function updateStaysBookingUrl( + data: Stay[] | FriendTransaction[], + token: string, + lang: Lang +) { // Tenporary API call needed till we have user name in ctx session data const apiResponse = await api.get(api.endpoints.v1.profile, { cache: "no-store", @@ -135,9 +149,9 @@ const updateStaysBookingUrl = async ( if (apiResponse.ok) { const apiJson = await apiResponse.json() if (apiJson.data?.attributes) { - return data.map((stay: Stay) => { + return data.map((d) => { const originalString = - stay.attributes.confirmationNumber.toString() + + d.attributes.confirmationNumber.toString() + "," + apiJson.data.attributes.lastName const encryptedBookingValue = encryptValue(originalString) @@ -147,11 +161,11 @@ const updateStaysBookingUrl = async ( "?lastName=" + apiJson.data.attributes.lastName + "&bookingId=" + - stay.attributes.confirmationNumber + d.attributes.confirmationNumber return { - ...stay, + ...d, attributes: { - ...stay.attributes, + ...d.attributes, bookingUrl: bookingUrl, }, } @@ -492,15 +506,29 @@ export const userQueryRouter = router({ return null } - const pageData = verifiedData.data.data.slice( - limit * (page - 1), - limit * page + const updatedData = await updateStaysBookingUrl( + verifiedData.data.data, + ctx.session.token.access_token, + ctx.lang ) + const pageData = updatedData + .filter((t) => t.type !== RewardTransactionTypes.expired) + .sort((a, b) => { + // 'BALFWD' are transactions from Opera migration that happended in May 2021 + const isBalfwd = + a.type === RewardTransactionTypes.stayAdj && + a.attributes.confirmationNumber === "BALFWD" + if (isBalfwd) return 1 + return a.attributes.checkinDate > b.attributes.checkinDate ? -1 : 1 + }) + .slice(limit * (page - 1), limit * page) + return { data: { - transactions: pageData.map(({ attributes }) => { + transactions: pageData.map(({ type, attributes }) => { return { + type, awardPoints: attributes.awardPoints, checkinDate: attributes.checkinDate, checkoutDate: attributes.checkoutDate, @@ -508,6 +536,10 @@ export const userQueryRouter = router({ confirmationNumber: attributes.confirmationNumber, hotelName: attributes.hotelInformation?.name, nights: attributes.nights, + pointsCalculated: attributes.pointsCalculated, + hotelId: attributes.hotelOperaId, + transactionDate: attributes.transactionDate, + bookingUrl: attributes.bookingUrl, } }), }, diff --git a/types/components/myPages/myPage/earnAndBurn.ts b/types/components/myPages/myPage/earnAndBurn.ts index ebe3060ff..9bc0c69aa 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/JourneyTable/Desktop/Row/awardPointsVariants" +import { awardPointsVariants } from "@/components/MyPages/Blocks/Points/EarnAndBurn/JourneyTable/Table/Row/awardPointsVariants" import type { VariantProps } from "class-variance-authority" diff --git a/types/components/myPages/myPage/enums.ts b/types/components/myPages/myPage/enums.ts index 294944620..67ee8e12a 100644 --- a/types/components/myPages/myPage/enums.ts +++ b/types/components/myPages/myPage/enums.ts @@ -16,3 +16,17 @@ export enum ContentEntries { AccountPageContentShortcuts = "AccountPageContentShortcuts", AccountPageContentTextContent = "AccountPageContentTextContent", } + +export enum RewardTransactionTypes { + stay = "stay", + rewardNight = "rewardnight", + enrollment = "enrollment", + expired = "expired", + redgift = "redgift", + ancillary = "ancillary", + pointShop = "pointshop", + tui_points = "tui_points", + mastercard_points = "mastercard_points", + stayAdj = "stay/adj", + othersAdj = "others/adj", +} From 2af17ef4d8cabf2f059fb34043c31efb383a561f Mon Sep 17 00:00:00 2001 From: Christel Westerberg Date: Wed, 21 Aug 2024 13:13:29 +0200 Subject: [PATCH 22/53] fix: route unlocalized login and logout correctly --- .../(protected)/my-pages/profile/layout.tsx | 3 ++- constants/routes/handleAuth.js | 22 +++++++++++++++++++ 2 files changed, 24 insertions(+), 1 deletion(-) diff --git a/app/[lang]/(live)/(protected)/my-pages/profile/layout.tsx b/app/[lang]/(live)/(protected)/my-pages/profile/layout.tsx index 73496fe4e..cdf46a35d 100644 --- a/app/[lang]/(live)/(protected)/my-pages/profile/layout.tsx +++ b/app/[lang]/(live)/(protected)/my-pages/profile/layout.tsx @@ -15,7 +15,8 @@ export default function ProfileLayout({ {profile} {creditCards} - {communication} + {/* TODO: Implement communication preferences flow. Hidden until decided on where to send user. */} + {/* {communication} */} ) diff --git a/constants/routes/handleAuth.js b/constants/routes/handleAuth.js index 46d890133..0879d580f 100644 --- a/constants/routes/handleAuth.js +++ b/constants/routes/handleAuth.js @@ -12,6 +12,16 @@ export const login = { sv: "/sv/logga-in", } +/** @type {import('@/types/routes').LangRoute} */ +export const loginUnLocalized = { + da: "/da/login", + de: "/de/login", + en: "/en/login", + fi: "/fi/login", + no: "/no/login", + sv: "/sv/login", +} + /** @type {import('@/types/routes').LangRoute} */ export const logout = { da: "/da/log-ud", @@ -22,6 +32,16 @@ export const logout = { sv: "/sv/logga-ut", } +/** @type {import('@/types/routes').LangRoute} */ +export const logoutUnLocalized = { + da: "/da/logout", + de: "/de/logout", + en: "/en/logout", + fi: "/fi/logout", + no: "/no/logout", + sv: "/sv/logout", +} + /** @type {import('@/types/routes').LangRoute} */ export const verifymagiclink = { da: "/da/verifymagiclink", @@ -36,4 +56,6 @@ export const handleAuth = [ ...Object.values(login), ...Object.values(logout), ...Object.values(verifymagiclink), + ...Object.values(loginUnLocalized), + ...Object.values(logoutUnLocalized), ] From e9a64990868a9a909cade03cde811a89fce31d38 Mon Sep 17 00:00:00 2001 From: Tobias Johansson Date: Tue, 13 Aug 2024 10:20:59 +0200 Subject: [PATCH 23/53] feat(SW-245): Delete credit card --- .../profile/@creditCards/page.module.css | 11 --- .../my-pages/profile/@creditCards/page.tsx | 36 +------ app/api/web/add-card-callback/[lang]/route.ts | 42 ++++---- .../Profile/AddCreditCardButton/index.tsx | 61 ++++++++---- .../CreditCardList/CreditCardList.module.css | 4 + components/Profile/CreditCardList/index.tsx | 31 ++++++ .../CreditCardRow/creditCardRow.module.css | 10 ++ components/Profile/CreditCardRow/index.tsx | 22 +++++ .../Profile/DeleteCreditCardButton/index.tsx | 40 ++++++++ .../deleteCreditCardConfirmation.module.css | 70 +++++++++++++ .../DeleteCreditCardConfirmation/index.tsx | 99 +++++++++++++++++++ components/TempDesignSystem/Button/button.ts | 15 ++- components/TempDesignSystem/Button/index.tsx | 33 ++++--- components/TempDesignSystem/Toasts/index.tsx | 2 +- .../TempDesignSystem/Toasts/toasts.module.css | 5 +- i18n/dictionaries/da.json | 13 +++ i18n/dictionaries/de.json | 13 +++ i18n/dictionaries/en.json | 13 +++ i18n/dictionaries/fi.json | 13 +++ i18n/dictionaries/no.json | 13 +++ i18n/dictionaries/sv.json | 13 +++ lib/api/endpoints.ts | 2 + lib/api/index.ts | 16 +-- lib/trpc/client.ts | 4 + server/routers/user/index.ts | 3 +- server/routers/user/input.ts | 10 +- server/routers/user/mutation.ts | 85 ++++++++++++++++ server/routers/user/output.ts | 36 ++++--- server/routers/user/query.ts | 76 +------------- types/components/myPages/myProfile/card.ts | 3 - .../myPages/myProfile/creditCards.ts | 9 ++ types/fetch.ts | 4 +- types/user.ts | 4 +- 33 files changed, 603 insertions(+), 208 deletions(-) create mode 100644 components/Profile/CreditCardList/CreditCardList.module.css create mode 100644 components/Profile/CreditCardList/index.tsx create mode 100644 components/Profile/CreditCardRow/creditCardRow.module.css create mode 100644 components/Profile/CreditCardRow/index.tsx create mode 100644 components/Profile/DeleteCreditCardButton/index.tsx create mode 100644 components/Profile/DeleteCreditCardConfirmation/deleteCreditCardConfirmation.module.css create mode 100644 components/Profile/DeleteCreditCardConfirmation/index.tsx create mode 100644 server/routers/user/mutation.ts delete mode 100644 types/components/myPages/myProfile/card.ts create mode 100644 types/components/myPages/myProfile/creditCards.ts diff --git a/app/[lang]/(live)/(protected)/my-pages/profile/@creditCards/page.module.css b/app/[lang]/(live)/(protected)/my-pages/profile/@creditCards/page.module.css index 32b75c7b8..9f19f6eac 100644 --- a/app/[lang]/(live)/(protected)/my-pages/profile/@creditCards/page.module.css +++ b/app/[lang]/(live)/(protected)/my-pages/profile/@creditCards/page.module.css @@ -14,17 +14,6 @@ gap: var(--Spacing-x1); } -.card { - display: grid; - align-items: center; - column-gap: var(--Spacing-x1); - grid-template-columns: auto auto auto 1fr; - justify-items: flex-end; - padding: var(--Spacing-x1) var(--Spacing-x-one-and-half,); - border-radius: var(--Corner-radius-Small); - background-color: var(--Base-Background-Primary-Normal); -} - @media screen and (min-width: 768px) { .container { gap: var(--Spacing-x3); 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 68a19f188..400f10f2c 100644 --- a/app/[lang]/(live)/(protected)/my-pages/profile/@creditCards/page.tsx +++ b/app/[lang]/(live)/(protected)/my-pages/profile/@creditCards/page.tsx @@ -1,11 +1,9 @@ import { env } from "@/env/server" import { serverClient } from "@/lib/trpc/server" -import { CreditCard, Delete } from "@/components/Icons" import AddCreditCardButton from "@/components/Profile/AddCreditCardButton" -import Button from "@/components/TempDesignSystem/Button" +import CreditCardList from "@/components/Profile/CreditCardList" import Body from "@/components/TempDesignSystem/Text/Body" -import Caption from "@/components/TempDesignSystem/Text/Caption" import Subtitle from "@/components/TempDesignSystem/Text/Subtitle" import { getIntl } from "@/i18n" import { setLang } from "@/i18n/serverContext" @@ -33,38 +31,8 @@ export default async function CreditCardSlot({ params }: PageArgs) { })} - {creditCards?.length ? ( -
- {creditCards.map((card, idx) => ( - - ))} -
- ) : null} + ) } - -function CreditCardRow({ - truncatedNumber, - cardType, -}: { - truncatedNumber: string - cardType: string -}) { - const maskedCardNumber = `**** ${truncatedNumber.slice(12, 16)}` - return ( -
- - {cardType} -
- - - ) -} diff --git a/app/api/web/add-card-callback/[lang]/route.ts b/app/api/web/add-card-callback/[lang]/route.ts index 17527a66e..871b5dd14 100644 --- a/app/api/web/add-card-callback/[lang]/route.ts +++ b/app/api/web/add-card-callback/[lang]/route.ts @@ -2,46 +2,46 @@ import { NextRequest } from "next/server" import { env } from "process" import { Lang } from "@/constants/languages" +import { profile } from "@/constants/routes/myPages" 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 lang = params.lang as Lang + const returnUrl = new URL(`${env.PUBLIC_URL}/${profile[lang ?? Lang.en]}`) + try { const searchParams = request.nextUrl.searchParams const success = searchParams.get("success") const failure = searchParams.get("failure") + const cancel = searchParams.get("cancel") 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") - } + if (trxId) { + const saveCardSuccess = await serverClient().user.creditCard.save({ + transactionId: trxId, + }) - const saveCardSuccess = await serverClient().user.saveCard({ - transactionId: trxId, - }) - - if (saveCardSuccess) { - returnUrl.searchParams.set("success", "true") + if (saveCardSuccess) { + returnUrl.searchParams.set("success", "true") + } else { + returnUrl.searchParams.set("failure", "true") + } } else { - returnUrl.searchParams.set("failure", "true") + returnUrl.searchParams.set("error", "true") } } else if (failure) { returnUrl.searchParams.set("failure", "true") + } else if (cancel) { + returnUrl.searchParams.set("cancel", "true") } - - return Response.redirect(returnUrl, 307) } catch (error) { - console.error(error) - return internalServerError() + console.error("Error saving credit card", error) + returnUrl.searchParams.set("error", "true") } + + return Response.redirect(returnUrl, 307) } diff --git a/components/Profile/AddCreditCardButton/index.tsx b/components/Profile/AddCreditCardButton/index.tsx index 56081c20d..8bc1ca7f9 100644 --- a/components/Profile/AddCreditCardButton/index.tsx +++ b/components/Profile/AddCreditCardButton/index.tsx @@ -1,47 +1,53 @@ "use client" import { usePathname, useRouter, useSearchParams } from "next/navigation" -import { useEffect } from "react" +import { useEffect, useRef } 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 { toast } from "@/components/TempDesignSystem/Toasts" import useLang from "@/hooks/useLang" import styles from "./addCreditCardButton.module.css" -let hasRunOnce = false - function useAddCardResultToast() { + const hasRunOnce = useRef(false) const intl = useIntl() const router = useRouter() const pathname = usePathname() const searchParams = useSearchParams() useEffect(() => { - if (hasRunOnce) return + if (hasRunOnce.current) return const success = searchParams.get("success") const failure = searchParams.get("failure") + const cancel = searchParams.get("cancel") + const error = searchParams.get("error") 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!" })) - }) + toast.success( + intl.formatMessage({ id: "Your card was successfully saved!" }) + ) + } else if (cancel) { + toast.warning( + intl.formatMessage({ + id: "You canceled adding a new credit card.", + }) + ) + } else if (failure || error) { + toast.error( + intl.formatMessage({ + id: "Something went wrong and we couldn't add your card. Please try again later.", + }) + ) } router.replace(pathname) - hasRunOnce = true + hasRunOnce.current = true }, [intl, pathname, router, searchParams]) } @@ -51,10 +57,25 @@ export default function AddCreditCardButton() { 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!" })), + const initiateAddCard = trpc.user.creditCard.add.useMutation({ + onSuccess: (result) => { + if (result?.attribute.link) { + router.push(result.attribute.link) + } else { + toast.error( + intl.formatMessage({ + id: "We could not add a card right now, please try again later.", + }) + ) + } + }, + onError: () => { + toast.error( + intl.formatMessage({ + id: "An error occurred when adding a credit card, please try again later.", + }) + ) + }, }) return ( diff --git a/components/Profile/CreditCardList/CreditCardList.module.css b/components/Profile/CreditCardList/CreditCardList.module.css new file mode 100644 index 000000000..86929755c --- /dev/null +++ b/components/Profile/CreditCardList/CreditCardList.module.css @@ -0,0 +1,4 @@ +.cardContainer { + display: grid; + gap: var(--Spacing-x1); +} diff --git a/components/Profile/CreditCardList/index.tsx b/components/Profile/CreditCardList/index.tsx new file mode 100644 index 000000000..e97e4739b --- /dev/null +++ b/components/Profile/CreditCardList/index.tsx @@ -0,0 +1,31 @@ +"use client" + +import React from "react" + +import { trpc } from "@/lib/trpc/client" + +import CreditCardRow from "../CreditCardRow" + +import styles from "./CreditCardList.module.css" + +import type { CreditCard } from "@/types/user" + +export default function CreditCardList({ + initialData, +}: { + initialData?: CreditCard[] | null +}) { + const creditCards = trpc.user.creditCards.useQuery(undefined, { initialData }) + + if (!creditCards.data || !creditCards.data.length) { + return null + } + + return ( +
+ {creditCards.data.map((card) => ( + + ))} +
+ ) +} diff --git a/components/Profile/CreditCardRow/creditCardRow.module.css b/components/Profile/CreditCardRow/creditCardRow.module.css new file mode 100644 index 000000000..2c326c100 --- /dev/null +++ b/components/Profile/CreditCardRow/creditCardRow.module.css @@ -0,0 +1,10 @@ +.card { + display: grid; + align-items: center; + column-gap: var(--Spacing-x1); + grid-template-columns: auto auto auto 1fr; + justify-items: flex-end; + padding: var(--Spacing-x1) var(--Spacing-x-one-and-half,); + border-radius: var(--Corner-radius-Small); + background-color: var(--Base-Background-Primary-Normal); +} diff --git a/components/Profile/CreditCardRow/index.tsx b/components/Profile/CreditCardRow/index.tsx new file mode 100644 index 000000000..5aa54ab57 --- /dev/null +++ b/components/Profile/CreditCardRow/index.tsx @@ -0,0 +1,22 @@ +import { CreditCard } from "@/components/Icons" +import Body from "@/components/TempDesignSystem/Text/Body" +import Caption from "@/components/TempDesignSystem/Text/Caption" + +import DeleteCreditCardConfirmation from "../DeleteCreditCardConfirmation" + +import styles from "./creditCardRow.module.css" + +import type { CreditCardRowProps } from "@/types/components/myPages/myProfile/creditCards" + +export default function CreditCardRow({ card }: CreditCardRowProps) { + const maskedCardNumber = `**** ${card.truncatedNumber.slice(-4)}` + + return ( +
+ + {card.type} +
+ + + ) +} diff --git a/components/Profile/DeleteCreditCardButton/index.tsx b/components/Profile/DeleteCreditCardButton/index.tsx new file mode 100644 index 000000000..16448d0b1 --- /dev/null +++ b/components/Profile/DeleteCreditCardButton/index.tsx @@ -0,0 +1,40 @@ +"use client" +import { useIntl } from "react-intl" + +import { trpc } from "@/lib/trpc/client" + +import { Delete } from "@/components/Icons" +import LoadingSpinner from "@/components/LoadingSpinner" +import Button from "@/components/TempDesignSystem/Button" +import { toast } from "@/components/TempDesignSystem/Toasts" + +export default function DeleteCreditCardButton({ + creditCardId, +}: { + creditCardId: string +}) { + const { formatMessage } = useIntl() + const trpcUtils = trpc.useUtils() + + const deleteCreditCardMutation = trpc.user.creditCard.delete.useMutation({ + onSuccess() { + trpcUtils.user.creditCards.invalidate() + toast.success(formatMessage({ id: "Credit card deleted successfully" })) + }, + onError() { + toast.error( + formatMessage({ + id: "Failed to delete credit card, please try again later.", + }) + ) + }, + }) + async function handleDelete() { + deleteCreditCardMutation.mutate({ creditCardId }) + } + return ( + + ) +} diff --git a/components/Profile/DeleteCreditCardConfirmation/deleteCreditCardConfirmation.module.css b/components/Profile/DeleteCreditCardConfirmation/deleteCreditCardConfirmation.module.css new file mode 100644 index 000000000..ed6c3170d --- /dev/null +++ b/components/Profile/DeleteCreditCardConfirmation/deleteCreditCardConfirmation.module.css @@ -0,0 +1,70 @@ +.overlay { + position: fixed; + top: 0; + left: 0; + width: 100vw; + height: var(--visual-viewport-height); + background: rgba(0, 0, 0, 0.5); + display: flex; + align-items: center; + justify-content: center; + z-index: 100; + + &[data-entering] { + animation: modal-fade 200ms; + } + + &[data-exiting] { + animation: modal-fade 150ms reverse ease-in; + } +} + +.modal section { + background: var(--Main-Grey-White); + border-radius: var(--Corner-radius-Medium); + padding: var(--Spacing-x4); + padding-bottom: var(--Spacing-x6); +} + +.container { + display: flex; + flex-direction: column; + gap: var(--Spacing-x3); + font-family: var(--typography-Body-Regular-fontFamily); +} + +.title { + font-family: var(--typography-Subtitle-1-fontFamily); + text-align: center; + margin: 0; + padding-bottom: var(--Spacing-x1); +} + +.bodyText { + text-align: center; + max-width: 425px; + margin: 0; + padding: 0; +} + +.buttonContainer { + display: flex; + justify-content: space-between; + gap: var(--Spacing-x2); + flex-wrap: wrap; +} + +.buttonContainer button { + flex-grow: 1; + justify-content: center; +} + +@keyframes modal-fade { + from { + opacity: 0; + } + + to { + opacity: 1; + } +} diff --git a/components/Profile/DeleteCreditCardConfirmation/index.tsx b/components/Profile/DeleteCreditCardConfirmation/index.tsx new file mode 100644 index 000000000..7d7110c1a --- /dev/null +++ b/components/Profile/DeleteCreditCardConfirmation/index.tsx @@ -0,0 +1,99 @@ +"use client" + +import { + Dialog, + DialogTrigger, + Heading, + Modal, + ModalOverlay, +} from "react-aria-components" +import { useIntl } from "react-intl" + +import { trpc } from "@/lib/trpc/client" + +import { Delete } from "@/components/Icons" +import LoadingSpinner from "@/components/LoadingSpinner" +import Button from "@/components/TempDesignSystem/Button" +import { toast } from "@/components/TempDesignSystem/Toasts" + +import styles from "./deleteCreditCardConfirmation.module.css" + +import type { DeleteCreditCardConfirmationProps } from "@/types/components/myPages/myProfile/creditCards" + +export default function DeleteCreditCardConfirmation({ + card, +}: DeleteCreditCardConfirmationProps) { + const intl = useIntl() + const trpcUtils = trpc.useUtils() + + const deleteCard = trpc.user.creditCard.delete.useMutation({ + onSuccess() { + trpcUtils.user.creditCards.invalidate() + + toast.success( + intl.formatMessage({ id: "Your card was successfully removed!" }) + ) + }, + onError() { + toast.error( + intl.formatMessage({ + id: "Something went wrong and we couldn't remove your card. Please try again later.", + }) + ) + }, + }) + + const lastFourDigits = card.truncatedNumber.slice(-4) + + return ( +
+ + + + + + {({ close }) => ( +
+ + {intl.formatMessage({ + id: "Remove card from member profile", + })} + +

+ {`${intl.formatMessage({ + id: "Are you sure you want to remove the card ending with", + })} ${lastFourDigits} ${intl.formatMessage({ id: "from your member profile?" })}`} +

+ + {deleteCard.isPending ? ( + + ) : ( +
+ + +
+ )} +
+ )} +
+
+
+
+
+ ) +} diff --git a/components/TempDesignSystem/Button/button.ts b/components/TempDesignSystem/Button/button.ts index cf391bc56..618ff8caa 100644 --- a/components/TempDesignSystem/Button/button.ts +++ b/components/TempDesignSystem/Button/button.ts @@ -1,9 +1,20 @@ import { buttonVariants } from "./variants" import type { VariantProps } from "class-variance-authority" +import type { ButtonProps as ReactAriaButtonProps } from "react-aria-components" -export interface ButtonProps +export interface ButtonPropsRAC + extends Omit, + VariantProps { + asChild?: false | undefined | never + disabled?: ReactAriaButtonProps["isDisabled"] + onClick?: ReactAriaButtonProps["onPress"] +} + +export interface ButtonPropsSlot extends React.ButtonHTMLAttributes, VariantProps { - asChild?: boolean + asChild: true } + +export type ButtonProps = ButtonPropsSlot | ButtonPropsRAC diff --git a/components/TempDesignSystem/Button/index.tsx b/components/TempDesignSystem/Button/index.tsx index 5acb884b1..38261e61a 100644 --- a/components/TempDesignSystem/Button/index.tsx +++ b/components/TempDesignSystem/Button/index.tsx @@ -1,23 +1,16 @@ "use client" import { Slot } from "@radix-ui/react-slot" +import { Button as ButtonRAC } from "react-aria-components" import { buttonVariants } from "./variants" import type { ButtonProps } from "./button" -export default function Button({ - asChild = false, - theme, - className, - disabled, - intent, - size, - variant, - wrapping, - ...props -}: ButtonProps) { - const Comp = asChild ? Slot : "button" +export default function Button(props: ButtonProps) { + const { className, intent, size, theme, wrapping, variant, ...restProps } = + props + const classNames = buttonVariants({ className, intent, @@ -26,5 +19,19 @@ export default function Button({ wrapping, variant, }) - return + + if (restProps.asChild) { + const { asChild, ...slotProps } = restProps + return + } + + const { asChild, onClick, disabled, ...racProps } = restProps + return ( + + ) } diff --git a/components/TempDesignSystem/Toasts/index.tsx b/components/TempDesignSystem/Toasts/index.tsx index 5e3ad1922..486bc4f46 100644 --- a/components/TempDesignSystem/Toasts/index.tsx +++ b/components/TempDesignSystem/Toasts/index.tsx @@ -16,7 +16,7 @@ import { toastVariants } from "./variants" import styles from "./toasts.module.css" export function ToastHandler() { - return + return } function getIcon(variant: ToastsProps["variant"]) { diff --git a/components/TempDesignSystem/Toasts/toasts.module.css b/components/TempDesignSystem/Toasts/toasts.module.css index cc7b305fb..27ebcff09 100644 --- a/components/TempDesignSystem/Toasts/toasts.module.css +++ b/components/TempDesignSystem/Toasts/toasts.module.css @@ -31,8 +31,9 @@ .iconContainer { display: flex; - background-color: var(--icon-background-color); - padding: var(--Spacing-x2); align-items: center; justify-content: center; + background-color: var(--icon-background-color); + padding: var(--Spacing-x2); + height: 100%; } diff --git a/i18n/dictionaries/da.json b/i18n/dictionaries/da.json index 31af5e52f..3531fa46c 100644 --- a/i18n/dictionaries/da.json +++ b/i18n/dictionaries/da.json @@ -7,6 +7,8 @@ "All rooms comes with standard amenities": "Alle værelser er udstyret med standardfaciliteter", "Already a friend?": "Allerede en ven?", "Amenities": "Faciliteter", + "An error occurred when adding a credit card, please try again later.": "Der opstod en fejl under tilføjelse af et kreditkort. Prøv venligst igen senere.", + "Are you sure you want to remove the card ending with": "Er du sikker på, at du vil fjerne kortet, der slutter med", "Arrival date": "Ankomstdato", "as of today": "fra idag", "As our": "Som vores", @@ -31,6 +33,7 @@ "Could not find requested resource": "Kunne ikke finde den anmodede ressource", "Country": "Land", "Country code": "Landekode", + "Credit card deleted successfully": "Kreditkort blev slettet", "Your current level": "Dit nuværende niveau", "Current password": "Nuværende kodeord", "characters": "tegn", @@ -45,10 +48,12 @@ "Extras to your booking": "Ekstra til din booking", "There are no transactions to display": "Der er ingen transaktioner at vise", "Explore all levels and benefits": "Udforsk alle niveauer og fordele", + "Failed to delete credit card, please try again later.": "Kunne ikke slette kreditkort. Prøv venligst igen senere.", "Find booking": "Find booking", "Flexibility": "Fleksibilitet", "Former Scandic Hotel": "Tidligere Scandic Hotel", "From": "Fra", + "from your member profile?": "fra din medlemsprofil?", "Get inspired": "Bliv inspireret", "Go back to overview": "Gå tilbage til oversigten", "Level 1": "Niveau 1", @@ -82,6 +87,7 @@ "Next": "Næste", "next level:": "Næste niveau:", "No content published": "Intet indhold offentliggjort", + "No, keep card": "Nej, behold kortet", "No transactions available": "Ingen tilgængelige transaktioner", "Not found": "Ikke fundet", "night": "nat", @@ -107,6 +113,7 @@ "Previous victories": "Tidligere sejre", "Read more": "Læs mere", "Read more about the hotel": "Læs mere om hotellet", + "Remove card from member profile": "Fjern kortet fra medlemsprofilen", "Restaurant & Bar": "Restaurant & Bar", "Retype new password": "Gentag den nye adgangskode", "Rooms": "Værelser", @@ -123,6 +130,8 @@ "Skip to main content": "Spring over og gå til hovedindhold", "Sign up bonus": "Tilmeldingsbonus", "Something went wrong!": "Noget gik galt!", + "Something went wrong and we couldn't add your card. Please try again later.": "Noget gik galt, og vi kunne ikke tilføje dit kort. Prøv venligst igen senere.", + "Something went wrong and we couldn't remove your card. Please try again later.": "Noget gik galt, og vi kunne ikke fjerne dit kort. Prøv venligst igen senere.", "Street": "Gade", "special character": "speciel karakter", "Total Points": "Samlet antal point", @@ -135,14 +144,18 @@ "User information": "Brugeroplysninger", "uppercase letter": "stort bogstav", "Visiting address": "Besøgsadresse", + "We could not add a card right now, please try again later.": "Vi kunne ikke tilføje et kort lige nu. Prøv venligst igen senere.", "Welcome": "Velkommen", "Welcome to": "Velkommen til", "Wellness & Exercise": "Velvære & Motion", "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 canceled adding a new credit card.": "Du har annulleret tilføjelsen af et nyt kreditkort.", + "Yes, remove my card": "Ja, fjern mit kort", "You have no previous stays.": "Du har ingen tidligere ophold.", "You have no upcoming stays.": "Du har ingen kommende ophold.", + "Your card was successfully removed!": "Dit kort blev fjernet!", "Your card was successfully saved!": "Dit kort blev gemt!", "Your Challenges Conquer & Earn!": "Dine udfordringer Overvind og tjen!", "Your level": "Dit niveau", diff --git a/i18n/dictionaries/de.json b/i18n/dictionaries/de.json index 7f0a26f2c..fa5a3c4ee 100644 --- a/i18n/dictionaries/de.json +++ b/i18n/dictionaries/de.json @@ -6,6 +6,8 @@ "All rooms comes with standard amenities": "Alle Zimmer sind mit den üblichen Annehmlichkeiten ausgestattet", "Already a friend?": "Sind wir schon Freunde?", "Amenities": "Annehmlichkeiten", + "An error occurred when adding a credit card, please try again later.": "Beim Hinzufügen einer Kreditkarte ist ein Fehler aufgetreten. Bitte versuchen Sie es später erneut.", + "Are you sure you want to remove the card ending with": "Möchten Sie die Karte mit der Endung", "Arrival date": "Ankunftsdatum", "as of today": "Ab heute", "As our": "Als unser", @@ -30,6 +32,7 @@ "Could not find requested resource": "Die angeforderte Ressource konnte nicht gefunden werden.", "Country": "Land", "Country code": "Landesvorwahl", + "Credit card deleted successfully": "Kreditkarte erfolgreich gelöscht", "Your current level": "Ihr aktuelles Level", "Current password": "Aktuelles Passwort", "characters": "figuren", @@ -44,10 +47,12 @@ "Extras to your booking": "Extras zu Ihrer Buchung", "There are no transactions to display": "Es sind keine Transaktionen zum Anzeigen vorhanden", "Explore all levels and benefits": "Entdecken Sie alle Levels und Vorteile", + "Failed to delete credit card, please try again later.": "Kreditkarte konnte nicht gelöscht werden. Bitte versuchen Sie es später noch einmal.", "Find booking": "Buchung finden", "Flexibility": "Flexibilität", "Former Scandic Hotel": "Ehemaliges Scandic Hotel", "From": "Fromm", + "from your member profile?": "wirklich aus Ihrem Mitgliedsprofil entfernen?", "Get inspired": "Lassen Sie sich inspieren", "Go back to overview": "Zurück zur Übersicht", "Level 1": "Level 1", @@ -80,6 +85,7 @@ "Next": "Nächste", "next level:": "Nächstes Level:", "No content published": "Kein Inhalt veröffentlicht", + "No, keep card": "Nein, Karte behalten", "No transactions available": "Keine Transaktionen verfügbar", "Not found": "Nicht gefunden", "night": "nacht", @@ -104,6 +110,7 @@ "Previous victories": "Bisherige Siege", "Read more": "Mehr lesen", "Read more about the hotel": "Lesen Sie mehr über das Hotel", + "Remove card from member profile": "Karte aus dem Mitgliedsprofil entfernen", "Retype new password": "Neues Passwort erneut eingeben", "Save": "Speichern", "Scandic Friends Mastercard": "Scandic Friends Mastercard", @@ -118,6 +125,8 @@ "Skip to main content": "Direkt zum Inhalt", "Sign up bonus": "Anmeldebonus", "Something went wrong!": "Etwas ist schief gelaufen!", + "Something went wrong and we couldn't add your card. Please try again later.": "Ein Fehler ist aufgetreten und wir konnten Ihre Karte nicht hinzufügen. Bitte versuchen Sie es später erneut.", + "Something went wrong and we couldn't remove your card. Please try again later.": "Ein Fehler ist aufgetreten und wir konnten Ihre Karte nicht entfernen. Bitte versuchen Sie es später noch einmal.", "Street": "Straße", "special character": "sonderzeichen", "Total Points": "Gesamtpunktzahl", @@ -130,13 +139,17 @@ "User information": "Nutzerinformation", "uppercase letter": "großbuchstabe", "Visiting address": "Besuchsadresse", + "We could not add a card right now, please try again later.": "Wir konnten momentan keine Karte hinzufügen. Bitte versuchen Sie es später noch einmal.", "Welcome to": "Willkommen zu", "Welcome": "Willkommen", "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 canceled adding a new credit card.": "Sie haben das Hinzufügen einer neuen Kreditkarte abgebrochen.", + "Yes, remove my card": "Ja, meine Karte entfernen", "You have no previous stays.": "Sie haben keine vorherigen Aufenthalte.", "You have no upcoming stays.": "Sie haben keine bevorstehenden Aufenthalte.", + "Your card was successfully removed!": "Ihre Karte wurde erfolgreich entfernt!", "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", diff --git a/i18n/dictionaries/en.json b/i18n/dictionaries/en.json index 62790f4fc..93b93b252 100644 --- a/i18n/dictionaries/en.json +++ b/i18n/dictionaries/en.json @@ -7,6 +7,8 @@ "All rooms comes with standard amenities": "All rooms comes with standard amenities", "Already a friend?": "Already a friend?", "Amenities": "Amenities", + "An error occurred when adding a credit card, please try again later.": "An error occurred when adding a credit card, please try again later.", + "Are you sure you want to remove the card ending with": "Are you sure you want to remove the card ending with", "Arrival date": "Arrival date", "as of today": "as of today", "As our": "As our", @@ -34,6 +36,7 @@ "Your current level": "Your current level", "Current password": "Current password", "characters": "characters", + "Credit card deleted successfully": "Credit card deleted successfully", "Date of Birth": "Date of Birth", "Day": "Day", "Description": "Description", @@ -45,12 +48,14 @@ "Email": "Email", "There are no transactions to display": "There are no transactions to display", "Explore all levels and benefits": "Explore all levels and benefits", + "Failed to delete credit card, please try again later.": "Failed to delete credit card, please try again later.", "Extras to your booking": "Extras to your booking", "FAQ": "FAQ", "Find booking": "Find booking", "Former Scandic Hotel": "Former Scandic Hotel", "Flexibility": "Flexibility", "From": "From", + "from your member profile?": "from your member profile?", "Get inspired": "Get inspired", "Go back to overview": "Go back to overview", "hotelPages.rooms.roomCard.person": "person", @@ -87,6 +92,7 @@ "Next": "Next", "next level:": "next level:", "No content published": "No content published", + "No, keep card": "No, keep card", "No transactions available": "No transactions available", "Not found": "Not found", "night": "night", @@ -112,6 +118,7 @@ "Previous victories": "Previous victories", "Read more": "Read more", "Read more about the hotel": "Read more about the hotel", + "Remove card from member profile": "Remove card from member profile", "Restaurant & Bar": "Restaurant & Bar", "Retype new password": "Retype new password", "Rooms": "Rooms", @@ -129,10 +136,13 @@ "Skip to main content": "Skip to main content", "Sign up bonus": "Sign up bonus", "Something went wrong!": "Something went wrong!", + "Something went wrong and we couldn't add your card. Please try again later.": "Something went wrong and we couldn't add your card. Please try again later.", + "Something went wrong and we couldn't remove your card. Please try again later.": "Something went wrong and we couldn't remove your card. Please try again later.", "Street": "Street", "special character": "special character", "Total Points": "Total Points", "Your points to spend": "Your points to spend", + "You canceled adding a new credit card.": "You canceled adding a new credit card.", "Transaction date": "Transaction date", "Transactions": "Transactions", "Tripadvisor reviews": "{rating} ({count} reviews on Tripadvisor)", @@ -142,13 +152,16 @@ "uppercase letter": "uppercase letter", "Welcome": "Welcome", "Visiting address": "Visiting address", + "We could not add a card right now, please try again later.": "We could not add a card right now, please try again later.", "Welcome to": "Welcome to", "Wellness & Exercise": "Wellness & Exercise", "Where should you go next?": "Where should you go next?", "Which room class suits you the best?": "Which room class suits you the best?", "Year": "Year", + "Yes, remove my card": "Yes, remove my card", "You have no previous stays.": "You have no previous stays.", "You have no upcoming stays.": "You have no upcoming stays.", + "Your card was successfully removed!": "Your card was successfully removed!", "Your card was successfully saved!": "Your card was successfully saved!", "Your Challenges Conquer & Earn!": "Your Challenges Conquer & Earn!", "Your level": "Your level", diff --git a/i18n/dictionaries/fi.json b/i18n/dictionaries/fi.json index a244f16d3..a7fac093e 100644 --- a/i18n/dictionaries/fi.json +++ b/i18n/dictionaries/fi.json @@ -7,6 +7,8 @@ "All rooms comes with standard amenities": "Kaikissa huoneissa on perusmukavuudet", "Already a friend?": "Oletko jo ystävä?", "Amenities": "Mukavuudet", + "An error occurred when adding a credit card, please try again later.": "Luottokorttia lisättäessä tapahtui virhe. Yritä myöhemmin uudelleen.", + "Are you sure you want to remove the card ending with": "Haluatko varmasti poistaa kortin, joka päättyy numeroon", "Arrival date": "Saapumispäivä", "as of today": "tästä päivästä lähtien", "As our": "Kuin meidän", @@ -31,6 +33,7 @@ "Could not find requested resource": "Pyydettyä resurssia ei löytynyt", "Country": "Maa", "Country code": "Maatunnus", + "Credit card deleted successfully": "Luottokortti poistettu onnistuneesti", "Your current level": "Nykyinen tasosi", "Current password": "Nykyinen salasana", "characters": "hahmoja", @@ -45,10 +48,12 @@ "Extras to your booking": "Lisävarusteet varaukseesi", "There are no transactions to display": "Näytettäviä tapahtumia ei ole", "Explore all levels and benefits": "Tutustu kaikkiin tasoihin ja etuihin", + "Failed to delete credit card, please try again later.": "Luottokortin poistaminen epäonnistui, yritä myöhemmin uudelleen.", "Find booking": "Etsi varaus", "Flexibility": "Joustavuus", "Former Scandic Hotel": "Entinen Scandic Hotel", "From": "From", + "from your member profile?": "jäsenprofiilistasi?", "Get inspired": "Inspiroidu", "Go back to overview": "Palaa yleiskatsaukseen", "Level 1": "Taso 1", @@ -82,6 +87,7 @@ "Next": "Seuraava", "next level:": "Seuraava taso:", "No content published": "Ei julkaistua sisältöä", + "No, keep card": "Ei, pidä kortti", "No transactions available": "Ei tapahtumia saatavilla", "Not found": "Ei löydetty", "night": "yö", @@ -107,6 +113,7 @@ "Previous victories": "Edelliset voitot", "Read more": "Lue lisää", "Read more about the hotel": "Lue lisää hotellista", + "Remove card from member profile": "Poista kortti jäsenprofiilista", "Restaurant & Bar": "Ravintola & Baari", "Retype new password": "Kirjoita uusi salasana uudelleen", "Rooms": "Huoneet", @@ -123,6 +130,8 @@ "Skip to main content": "Siirry pääsisältöön", "Sign up bonus": "Rekisteröidy bonus", "Something went wrong!": "Jotain meni pieleen!", + "Something went wrong and we couldn't add your card. Please try again later.": "Jotain meni pieleen, emmekä voineet lisätä korttiasi. Yritä myöhemmin uudelleen.", + "Something went wrong and we couldn't remove your card. Please try again later.": "Jotain meni pieleen, emmekä voineet poistaa korttiasi. Yritä myöhemmin uudelleen.", "Street": "Katu", "special character": "erikoishahmo", "Total Points": "Kokonaispisteet", @@ -135,15 +144,19 @@ "User information": "Käyttäjän tiedot", "uppercase letter": "iso kirjain", "Visiting address": "Käyntiosoite", + "We could not add a card right now, please try again later.": "Emme voineet lisätä korttia juuri nyt. Yritä myöhemmin uudelleen.", "Welcome": "Tervetuloa", "Welcome to": "Tervetuloa", "Wellness & Exercise": "Hyvinvointi & Liikunta", "Where should you go next?": "Mihin menisit seuraavaksi?", "Which room class suits you the best?": "Mikä huoneluokka sopii sinulle parhaiten?", "Year": "Vuosi", + "Yes, remove my card": "Kyllä, poista korttini", "You have no previous stays.": "Sinulla ei ole aiempaa oleskelua.", "You have no upcoming stays.": "Sinulla ei ole tulevia oleskeluja.", + "Your card was successfully removed!": "Korttisi poistettiin onnistuneesti!", "Your card was successfully saved!": "Korttisi tallennettu onnistuneesti!", + "You canceled adding a new credit card.": "Peruutit uuden luottokortin lisäämisen.", "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 cc79768ee..33c1916c2 100644 --- a/i18n/dictionaries/no.json +++ b/i18n/dictionaries/no.json @@ -7,6 +7,8 @@ "All rooms comes with standard amenities": "Alle rommene har standard fasiliteter", "Already a friend?": "Allerede Friend?", "Amenities": "Fasiliteter", + "An error occurred when adding a credit card, please try again later.": "Det oppstod en feil ved å legge til et kredittkort. Prøv igjen senere.", + "Are you sure you want to remove the card ending with": "Er du sikker på at du vil fjerne kortet som slutter på", "Arrival date": "Ankomstdato", "as of today": "per idag", "As our": "Som vår", @@ -34,6 +36,7 @@ "Your current level": "Ditt nåværende nivå", "Current password": "Nåværende passord", "characters": "tegn", + "Credit card deleted successfully": "Kredittkort slettet", "Date of Birth": "Fødselsdato", "Day": "Dag", "Description": "Beskrivelse", @@ -45,10 +48,12 @@ "Extras to your booking": "Ekstra til din bestilling", "There are no transactions to display": "Det er ingen transaksjoner å vise", "Explore all levels and benefits": "Utforsk alle nivåer og fordeler", + "Failed to delete credit card, please try again later.": "Kunne ikke slette kredittkortet, prøv igjen senere.", "Find booking": "Finn booking", "Flexibility": "Fleksibilitet", "Former Scandic Hotel": "Tidligere Scandic Hotel", "From": "Fra", + "from your member profile?": "fra medlemsprofilen din?", "Get inspired": "Bli inspirert", "Go back to overview": "Gå tilbake til oversikten", "Level 1": "Nivå 1", @@ -82,6 +87,7 @@ "Next": "Neste", "next level:": "Neste nivå:", "No content published": "Ingen innhold publisert", + "No, keep card": "Nei, behold kortet", "No transactions available": "Ingen transaksjoner tilgjengelig", "Not found": "Ikke funnet", "night": "natt", @@ -107,6 +113,7 @@ "Previous victories": "Tidligere seire", "Read more": "Les mer", "Read more about the hotel": "Les mer om hotellet", + "Remove card from member profile": "Fjern kortet fra medlemsprofilen", "Restaurant & Bar": "Restaurant & Bar", "Retype new password": "Skriv inn nytt passord på nytt", "Rooms": "Rom", @@ -123,6 +130,8 @@ "Skip to main content": "Gå videre til hovedsiden", "Sign up bonus": "Registreringsbonus", "Something went wrong!": "Noe gikk galt!", + "Something went wrong and we couldn't add your card. Please try again later.": "Noe gikk galt, og vi kunne ikke legge til kortet ditt. Prøv igjen senere.", + "Something went wrong and we couldn't remove your card. Please try again later.": "Noe gikk galt, og vi kunne ikke fjerne kortet ditt. Vennligst prøv igjen senere.", "Street": "Gate", "special character": "spesiell karakter", "Total Points": "Totale poeng", @@ -135,14 +144,18 @@ "User information": "Brukerinformasjon", "uppercase letter": "stor bokstav", "Visiting address": "Besøksadresse", + "We could not add a card right now, please try again later.": "Vi kunne ikke legge til et kort akkurat nå. Prøv igjen senere.", "Welcome": "Velkommen", "Welcome to": "Velkommen til", "Wellness & Exercise": "Velvære & Trening", "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 canceled adding a new credit card.": "Du kansellerte å legge til et nytt kredittkort.", + "Yes, remove my card": "Ja, fjern kortet mitt", "You have no previous stays.": "Du har ingen tidligere opphold.", "You have no upcoming stays.": "Du har ingen kommende opphold.", + "Your card was successfully removed!": "Kortet ditt ble fjernet!", "Your card was successfully saved!": "Kortet ditt ble lagret!", "Your Challenges Conquer & Earn!": "Dine utfordringer Erobre og tjen!", "Your level": "Ditt nivå", diff --git a/i18n/dictionaries/sv.json b/i18n/dictionaries/sv.json index 0e8f4695f..b40f77323 100644 --- a/i18n/dictionaries/sv.json +++ b/i18n/dictionaries/sv.json @@ -7,6 +7,8 @@ "All rooms comes with standard amenities": "Alla rum har standardbekvämligheter", "Already a friend?": "Är du redan en vän?", "Amenities": "Bekvämligheter", + "An error occurred when adding a credit card, please try again later.": "Ett fel uppstod när ett kreditkort lades till, försök igen senare.", + "Are you sure you want to remove the card ending with": "Är du säker på att du vill ta bort kortet som slutar med", "Arrival date": "Ankomstdatum", "as of today": "från och med idag", "As our": "Som vår", @@ -31,6 +33,7 @@ "Could not find requested resource": "Det gick inte att hitta den begärda resursen", "Country": "Land", "Country code": "Landskod", + "Credit card deleted successfully": "Kreditkort har tagits bort", "Your current level": "Din nuvarande nivå", "Current password": "Nuvarande lösenord", "characters": "tecken", @@ -45,10 +48,12 @@ "Extras to your booking": "Extra till din bokning", "There are no transactions to display": "Det finns inga transaktioner att visa", "Explore all levels and benefits": "Utforska alla nivåer och fördelar", + "Failed to delete credit card, please try again later.": "Det gick inte att ta bort kreditkortet, försök igen senare.", "Find booking": "Hitta bokning", "Flexibility": "Flexibilitet", "Former Scandic Hotel": "Tidigare Scandic Hotel", "From": "Från", + "from your member profile?": "från din medlemsprofil?", "Get inspired": "Bli inspirerad", "Go back to overview": "Gå tillbaka till översikten", "Level 1": "Nivå 1", @@ -85,6 +90,7 @@ "Next": "Nästa", "next level:": "Nästa nivå:", "No content published": "Inget innehåll publicerat", + "No, keep card": "Nej, behåll kortet", "No transactions available": "Inga transaktioner tillgängliga", "Not found": "Hittades inte", "night": "natt", @@ -110,6 +116,7 @@ "Previous victories": "Tidigare segrar", "Read more": "Läs mer", "Read more about the hotel": "Läs mer om hotellet", + "Remove card from member profile": "Ta bort kortet från medlemsprofilen", "Restaurant & Bar": "Restaurang & Bar", "Retype new password": "Upprepa nytt lösenord", "Rooms": "Rum", @@ -126,6 +133,8 @@ "Skip to main content": "Fortsätt till huvudinnehåll", "Sign up bonus": "Registreringsbonus", "Something went wrong!": "Något gick fel!", + "Something went wrong and we couldn't add your card. Please try again later.": "Något gick fel och vi kunde inte lägga till ditt kort. Försök igen senare.", + "Something went wrong and we couldn't remove your card. Please try again later.": "Något gick fel och vi kunde inte ta bort ditt kort. Försök igen senare.", "Street": "Gata", "special character": "speciell karaktär", "Total Points": "Poäng totalt", @@ -138,13 +147,17 @@ "User information": "Användar information", "uppercase letter": "stor bokstav", "Visiting address": "Besöksadress", + "We could not add a card right now, please try again later.": "Vi kunde inte lägga till ett kort just nu, vänligen försök igen senare.", "Welcome": "Välkommen", "Wellness & Exercise": "Hälsa & Träning", "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 canceled adding a new credit card.": "Du avbröt att lägga till ett nytt kreditkort.", + "Yes, remove my card": "Ja, ta bort mitt kort", "You have no previous stays.": "Du har inga tidigare vistelser.", "You have no upcoming stays.": "Du har inga planerade resor.", + "Your card was successfully removed!": "Ditt kort har tagits bort!", "Your card was successfully saved!": "Ditt kort har sparats!", "Your Challenges Conquer & Earn!": "Dina utmaningar Erövra och tjäna!", "Your level": "Din nivå", diff --git a/lib/api/endpoints.ts b/lib/api/endpoints.ts index 7235b9df9..7fb7e5e9f 100644 --- a/lib/api/endpoints.ts +++ b/lib/api/endpoints.ts @@ -13,6 +13,8 @@ export namespace endpoints { upcomingStays = "booking/v1/Stays/future", previousStays = "booking/v1/Stays/past", hotels = "hotel/v1/Hotels", + intiateSaveCard = `${creditCards}/initiateSaveCard`, + deleteCreditCard = `${profile}/creditCards`, } } diff --git a/lib/api/index.ts b/lib/api/index.ts index 97d24039b..a3ec13a6c 100644 --- a/lib/api/index.ts +++ b/lib/api/index.ts @@ -27,7 +27,7 @@ const fetch = fetchRetry(global.fetch, { }) export async function get( - endpoint: Endpoint | `${endpoints.v1.hotels}/${string}`, + endpoint: Endpoint | `${Endpoint}/${string}`, options: RequestOptionsWithOutBody, params?: URLSearchParams ) { @@ -38,7 +38,7 @@ export async function get( } export async function patch( - endpoint: Endpoint, + endpoint: Endpoint | `${Endpoint}/${string}`, options: RequestOptionsWithJSONBody ) { const { body, ...requestOptions } = options @@ -54,11 +54,12 @@ export async function patch( export async function post( endpoint: Endpoint | `${Endpoint}/${string}`, - options: RequestOptionsWithJSONBody + options: RequestOptionsWithJSONBody, + params?: URLSearchParams ) { const { body, ...requestOptions } = options return fetch( - `${env.API_BASEURL}/${endpoint}`, + `${env.API_BASEURL}/${endpoint}${params ? `?${params.toString()}` : ""}`, merge.all([ defaultOptions, { body: JSON.stringify(body), method: "POST" }, @@ -68,11 +69,12 @@ export async function post( } export async function remove( - endpoint: Endpoint, - options: RequestOptionsWithOutBody + endpoint: Endpoint | `${Endpoint}/${string}`, + options: RequestOptionsWithOutBody, + params?: URLSearchParams ) { return fetch( - `${env.API_BASEURL}/${endpoint}`, + `${env.API_BASEURL}/${endpoint}${params ? `?${params.toString()}` : ""}`, merge.all([defaultOptions, { method: "DELETE" }, options]) ) } diff --git a/lib/trpc/client.ts b/lib/trpc/client.ts index d3ba9ef68..46d50e91a 100644 --- a/lib/trpc/client.ts +++ b/lib/trpc/client.ts @@ -1,5 +1,9 @@ import { createTRPCReact } from "@trpc/react-query" +import { inferRouterInputs, inferRouterOutputs } from "@trpc/server" import type { AppRouter } from "@/server" export const trpc = createTRPCReact() + +export type RouterInput = inferRouterInputs +export type RouterOutput = inferRouterOutputs diff --git a/server/routers/user/index.ts b/server/routers/user/index.ts index 6e21035fe..21941006d 100644 --- a/server/routers/user/index.ts +++ b/server/routers/user/index.ts @@ -1,5 +1,6 @@ import { mergeRouters } from "@/server/trpc" +import { userMutationRouter } from "./mutation" import { userQueryRouter } from "./query" -export const userRouter = mergeRouters(userQueryRouter) +export const userRouter = mergeRouters(userQueryRouter, userMutationRouter) diff --git a/server/routers/user/input.ts b/server/routers/user/input.ts index db9fe1f2f..a3dea492e 100644 --- a/server/routers/user/input.ts +++ b/server/routers/user/input.ts @@ -1,5 +1,7 @@ import { z } from "zod" +import { Lang } from "@/constants/languages" + export const getUserInputSchema = z .object({ mask: z.boolean().default(true), @@ -19,11 +21,15 @@ export const soonestUpcomingStaysInput = z }) .default({ limit: 3 }) -export const initiateSaveCardInput = z.object({ +export const addCreditCardInput = z.object({ language: z.string(), }) -export const saveCardInput = z.object({ +export const deleteCreditCardInput = z.object({ + creditCardId: z.string(), +}) + +export const saveCreditCardInput = z.object({ transactionId: z.string(), merchantId: z.string().optional(), }) diff --git a/server/routers/user/mutation.ts b/server/routers/user/mutation.ts new file mode 100644 index 000000000..cf568725c --- /dev/null +++ b/server/routers/user/mutation.ts @@ -0,0 +1,85 @@ +import * as api from "@/lib/api" +import { initiateSaveCardSchema } from "@/server/routers/user/output" +import { protectedProcedure, router } from "@/server/trpc" + +import { + addCreditCardInput, + deleteCreditCardInput, + saveCreditCardInput, +} from "./input" + +export const userMutationRouter = router({ + creditCard: router({ + add: protectedProcedure.input(addCreditCardInput).mutation(async function ({ + ctx, + input, + }) { + const apiResponse = await api.post(api.endpoints.v1.intiateSaveCard, { + headers: { + Authorization: `Bearer ${ctx.session.token.access_token}`, + }, + body: { + language: input.language, + mobileToken: false, + redirectUrl: `api/web/add-card-callback/${input.language}`, + }, + }) + + if (!apiResponse.ok) { + console.info(`API Response Failed - Initiating add Creadit Card flow`) + console.error(apiResponse) + return null + } + + const apiJson = await apiResponse.json() + const verifiedData = initiateSaveCardSchema.safeParse(apiJson) + if (!verifiedData.success) { + console.error(`Failed to initiate save card data`) + console.error(verifiedData.error) + return null + } + + return verifiedData.data.data + }), + save: protectedProcedure + .input(saveCreditCardInput) + .mutation(async function ({ ctx, input }) { + const apiResponse = await api.post( + `${api.endpoints.v1.creditCards}/${input.transactionId}`, + { + headers: { + Authorization: `Bearer ${ctx.session.token.access_token}`, + }, + } + ) + + if (!apiResponse.ok) { + console.error(`API Response Failed - Save card`) + console.error(apiResponse) + return false + } + + return true + }), + delete: protectedProcedure + .input(deleteCreditCardInput) + .mutation(async function ({ ctx, input }) { + const apiResponse = await api.remove( + `${api.endpoints.v1.creditCards}/${input.creditCardId}`, + { + headers: { + Authorization: `Bearer ${ctx.session.token.access_token}`, + }, + } + ) + + if (!apiResponse.ok) { + console.error(`API Response Failed - Delete credit card`) + console.error(apiResponse) + return false + } + + return true + }), + }), +}) diff --git a/server/routers/user/output.ts b/server/routers/user/output.ts index d079b8323..e135ace77 100644 --- a/server/routers/user/output.ts +++ b/server/routers/user/output.ts @@ -176,20 +176,28 @@ type GetFriendTransactionsData = z.infer export type FriendTransaction = GetFriendTransactionsData["data"][number] -export const getCreditCardsSchema = z.object({ - data: z.array( - z.object({ - attribute: z.object({ - cardName: z.string().optional(), - alias: z.string(), - truncatedNumber: z.string(), - expirationDate: z.string(), - cardType: z.string(), - }), - id: z.string(), - type: z.string(), - }) - ), +export const creditCardSchema = z + .object({ + attribute: z.object({ + cardName: z.string().optional(), + alias: z.string(), + truncatedNumber: z.string(), + expirationDate: z.string(), + cardType: z.string(), + }), + id: z.string(), + type: z.string(), + }) + .transform((apiResponse) => { + return { + id: apiResponse.id, + type: apiResponse.attribute.cardType, + truncatedNumber: apiResponse.attribute.truncatedNumber, + } + }) + +export const creditCardsSchema = z.object({ + data: z.array(creditCardSchema), }) export const getMembershipCardsSchema = z.array( diff --git a/server/routers/user/query.ts b/server/routers/user/query.ts index 47247658e..c2cad2400 100644 --- a/server/routers/user/query.ts +++ b/server/routers/user/query.ts @@ -1,12 +1,6 @@ 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, @@ -21,18 +15,15 @@ import encryptValue from "../utils/encryptValue" import { friendTransactionsInput, getUserInputSchema, - initiateSaveCardInput, - saveCardInput, staysInput, } from "./input" import { + creditCardsSchema, FriendTransaction, - getCreditCardsSchema, getFriendTransactionsSchema, getMembershipCardsSchema, getStaysSchema, getUserSchema, - initiateSaveCardSchema, Stay, } from "./output" import { benefits, extendedUser, nextLevelPerks } from "./temp" @@ -566,7 +557,7 @@ export const userQueryRouter = router({ } const apiJson = await apiResponse.json() - const verifiedData = getCreditCardsSchema.safeParse(apiJson) + const verifiedData = creditCardsSchema.safeParse(apiJson) if (!verifiedData.success) { console.error(`Failed to validate Credit Cards Data`) console.error(`User: (${JSON.stringify(ctx.session.user)})`) @@ -577,69 +568,6 @@ 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: false, - redirectUrl: `api/web/add-card-callback/${input.language}`, - }, - }) - - 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/myPages/myProfile/card.ts b/types/components/myPages/myProfile/card.ts deleted file mode 100644 index cece9fef6..000000000 --- a/types/components/myPages/myProfile/card.ts +++ /dev/null @@ -1,3 +0,0 @@ -export interface CardProps extends React.HTMLAttributes { - tag?: "article" | "div" | "section" -} diff --git a/types/components/myPages/myProfile/creditCards.ts b/types/components/myPages/myProfile/creditCards.ts new file mode 100644 index 000000000..6290f5288 --- /dev/null +++ b/types/components/myPages/myProfile/creditCards.ts @@ -0,0 +1,9 @@ +import type { CreditCard } from "@/types/user" + +export type CreditCardRowProps = { + card: CreditCard +} + +export type DeleteCreditCardConfirmationProps = { + card: CreditCard +} diff --git a/types/fetch.ts b/types/fetch.ts index 5f71dc355..a727709a9 100644 --- a/types/fetch.ts +++ b/types/fetch.ts @@ -1,7 +1,7 @@ export interface RequestOptionsWithJSONBody extends Omit { - body: Record + body?: Record } export interface RequestOptionsWithOutBody - extends Omit { } + extends Omit {} diff --git a/types/user.ts b/types/user.ts index c4599e579..a90602661 100644 --- a/types/user.ts +++ b/types/user.ts @@ -1,6 +1,6 @@ import { z } from "zod" -import { getUserSchema } from "@/server/routers/user/output" +import { creditCardSchema, getUserSchema } from "@/server/routers/user/output" type Journey = { tag: string @@ -28,3 +28,5 @@ export interface User extends z.infer { shortcuts: ShortcutLink[] victories: Victory[] } + +export type CreditCard = z.output From 2361ba696d8faedcbe52c5ae6c690848f554492f Mon Sep 17 00:00:00 2001 From: Michael Zetterberg Date: Thu, 22 Aug 2024 07:25:08 +0200 Subject: [PATCH 24/53] fix: improve auth handling and logging --- app/[lang]/(live)/(public)/login/route.ts | 52 ++++++++++++++++------- middlewares/authRequired.ts | 2 +- middlewares/currentWebLogin.ts | 1 + middlewares/currentWebLoginEmail.ts | 2 +- 4 files changed, 40 insertions(+), 17 deletions(-) diff --git a/app/[lang]/(live)/(public)/login/route.ts b/app/[lang]/(live)/(public)/login/route.ts index ba8292d89..91668330e 100644 --- a/app/[lang]/(live)/(public)/login/route.ts +++ b/app/[lang]/(live)/(public)/login/route.ts @@ -15,28 +15,46 @@ export async function GET( let redirectTo: string const returnUrl = request.headers.get("x-returnurl") - const isMFA = request.headers.get("x-mfa-login") - - // This is to support seamless login when using magic link login - const isMagicLinkUpdateLogin = !!request.headers.get("x-magic-link") + const isSeamless = request.headers.get("x-login-source") === "seamless" + const isMFA = request.headers.get("x-login-source") === "mfa" + const isSeamlessMagicLink = + request.headers.get("x-login-source") === "seamless-magiclink" if (!env.PUBLIC_URL) { throw internalServerError("No value for env.PUBLIC_URL") } - if (returnUrl) { - // Seamless login request from Current web - redirectTo = returnUrl + console.log( + `[login] source: ${request.headers.get("x-login-source") || "normal"}` + ) + + const redirectToCookieValue = request.cookies.get("redirectTo")?.value // Cookie gets set by authRequired middleware + const redirectToSearchParamValue = + request.nextUrl.searchParams.get("redirectTo") + const redirectToFallback = "/" + console.log(`[login] redirectTo cookie value: ${redirectToCookieValue}`) + console.log( + `[login] redirectTo search param value: ${redirectToSearchParamValue}` + ) + + if (isSeamless) { + if (returnUrl) { + redirectTo = returnUrl + } else { + console.log( + `[login] missing returnUrl, using fallback: ${redirectToFallback}` + ) + redirectTo = redirectToFallback + } } else { - // Normal login request from New web redirectTo = - request.cookies.get("redirectTo")?.value || // Cookie gets set by authRequired middleware - request.nextUrl.searchParams.get("redirectTo") || - "/" + redirectToCookieValue || redirectToSearchParamValue || redirectToFallback // Make relative URL to absolute URL if (redirectTo.startsWith("/")) { + console.log(`[login] make redirectTo absolute, from ${redirectTo}`) redirectTo = new URL(redirectTo, env.PUBLIC_URL).href + console.log(`[login] make redirectTo absolute, to ${redirectTo}`) } // Clean up cookie from authRequired middleware @@ -70,7 +88,11 @@ export async function GET( break } const redirectUrl = new URL(redirectUrlValue) + console.log(`[login] creating redirect to seamless login: ${redirectUrl}`) redirectUrl.searchParams.set("returnurl", redirectTo) + console.log( + `[login] returnurl for seamless login: ${redirectUrl.searchParams.get("returnurl")}` + ) redirectTo = redirectUrl.toString() /** Set cookie with redirect Url to appropriately redirect user when using magic link login */ @@ -94,10 +116,8 @@ export async function GET( * automatically redirecting to it inside of `signIn`. * https://github.com/nextauthjs/next-auth/blob/3c035ec/packages/next-auth/src/lib/actions.ts#L76 */ - console.log({ login_NEXTAUTH_URL: process.env.NEXTAUTH_URL }) + console.log(`[login] final redirectUrl: ${redirectTo}`) console.log({ login_env: process.env }) - - console.log({ login_redirectTo: redirectTo }) const params = { ui_locales: context.params.lang, scope: ["openid", "profile"].join(" "), @@ -117,6 +137,7 @@ export async function GET( // This is new param set for differentiate between the Magic link login of New web and current web version: "2", } + if (isMFA) { // Append profile_update scope for MFA params.scope = params.scope + " profile_udpate" @@ -126,9 +147,10 @@ export async function GET( */ params.acr_values = "urn:se:curity:authentication:otp-authenticator:OTP-Authenticator_web" - } else if (isMagicLinkUpdateLogin) { + } else if (isSeamlessMagicLink) { params.acr_values = "abc" } + const redirectUrl = await signIn( "curity", { diff --git a/middlewares/authRequired.ts b/middlewares/authRequired.ts index 222f408fe..896bbfd3a 100644 --- a/middlewares/authRequired.ts +++ b/middlewares/authRequired.ts @@ -67,8 +67,8 @@ export const middleware = auth(async (request) => { if (isLoggedIn && isMFAPath && isMFAInvalid()) { const headers = new Headers(request.headers) - headers.set("x-mfa-login", "true") headers.set("x-returnurl", nextUrlClone.href) + headers.set("x-login-source", "mfa") return NextResponse.rewrite(new URL(`/${lang}/login`, request.nextUrl), { request: { headers, diff --git a/middlewares/currentWebLogin.ts b/middlewares/currentWebLogin.ts index 49c664be3..14fb89e67 100644 --- a/middlewares/currentWebLogin.ts +++ b/middlewares/currentWebLogin.ts @@ -19,6 +19,7 @@ export const middleware: NextMiddleware = (request) => { const headers = new Headers(request.headers) headers.set("x-returnurl", returnUrl) + headers.set("x-login-source", "seamless") return NextResponse.rewrite(new URL(`/${lang}/login`, request.nextUrl), { request: { diff --git a/middlewares/currentWebLoginEmail.ts b/middlewares/currentWebLoginEmail.ts index 9e27016ed..3f53417df 100644 --- a/middlewares/currentWebLoginEmail.ts +++ b/middlewares/currentWebLoginEmail.ts @@ -19,7 +19,7 @@ export const middleware: NextMiddleware = (request) => { const headers = new Headers(request.headers) headers.set("x-returnurl", returnUrl) - headers.set("x-magic-link", "1") + headers.set("x-login-source", "seamless-magiclink") return NextResponse.rewrite(new URL(`/${lang}/login`, request.nextUrl), { request: { From 48178cd4eafda8a6a39700beb276694444d90ecb Mon Sep 17 00:00:00 2001 From: Simon Emanuelsson Date: Thu, 22 Aug 2024 08:39:16 +0200 Subject: [PATCH 25/53] fix: profile_update scope typo fix --- app/[lang]/(live)/(public)/login/route.ts | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/app/[lang]/(live)/(public)/login/route.ts b/app/[lang]/(live)/(public)/login/route.ts index 91668330e..d036fbc4c 100644 --- a/app/[lang]/(live)/(public)/login/route.ts +++ b/app/[lang]/(live)/(public)/login/route.ts @@ -118,9 +118,10 @@ export async function GET( */ console.log(`[login] final redirectUrl: ${redirectTo}`) console.log({ login_env: process.env }) - const params = { + /** Record is next-auth typings */ + const params: Record = { ui_locales: context.params.lang, - scope: ["openid", "profile"].join(" "), + scope: ["openid", "profile"], /** * The `acr_values` param is used to make Curity display the proper login * page for Scandic. Without the parameter Curity presents some choices @@ -140,7 +141,7 @@ export async function GET( if (isMFA) { // Append profile_update scope for MFA - params.scope = params.scope + " profile_udpate" + params.scope.push("profile_update") /** * The below acr value is required as for New Web same Curity Client is used for MFA * while in current web it is being setup using different Curity Client @@ -150,7 +151,7 @@ export async function GET( } else if (isSeamlessMagicLink) { params.acr_values = "abc" } - + params.scope = params.scope.join(" ") const redirectUrl = await signIn( "curity", { From df076f50f59750af631db476b7b0b17e646037a8 Mon Sep 17 00:00:00 2001 From: Christel Westerberg Date: Thu, 22 Aug 2024 09:01:04 +0200 Subject: [PATCH 26/53] fix: hide my pages menu for logged in users --- .../Loyalty/Sidebar/MyPagesNavigation/index.tsx | 13 +++++++++++++ components/Loyalty/Sidebar/index.tsx | 4 ++-- 2 files changed, 15 insertions(+), 2 deletions(-) create mode 100644 components/Loyalty/Sidebar/MyPagesNavigation/index.tsx diff --git a/components/Loyalty/Sidebar/MyPagesNavigation/index.tsx b/components/Loyalty/Sidebar/MyPagesNavigation/index.tsx new file mode 100644 index 000000000..19ea70405 --- /dev/null +++ b/components/Loyalty/Sidebar/MyPagesNavigation/index.tsx @@ -0,0 +1,13 @@ +import { serverClient } from "@/lib/trpc/server" + +import MyPagesSidebar from "@/components/MyPages/Sidebar" + +export async function MyPagesNavigation() { + const user = await serverClient().user.name() + + // Check if we have user, that means we are logged in andt the My Pages menu can show. + if (!user) { + return null + } + return +} diff --git a/components/Loyalty/Sidebar/index.tsx b/components/Loyalty/Sidebar/index.tsx index 912d86e27..53f0137b8 100644 --- a/components/Loyalty/Sidebar/index.tsx +++ b/components/Loyalty/Sidebar/index.tsx @@ -1,7 +1,7 @@ import JsonToHtml from "@/components/JsonToHtml" -import SidebarMyPages from "@/components/MyPages/Sidebar" import JoinLoyaltyContact from "./JoinLoyalty" +import { MyPagesNavigation } from "./MyPagesNavigation" import styles from "./sidebar.module.css" @@ -38,7 +38,7 @@ export default function SidebarLoyalty({ blocks }: SidebarProps) { case SidebarTypenameEnum.LoyaltyPageSidebarDynamicContent: switch (block.dynamic_content.component) { case LoyaltySidebarDynamicComponentEnum.my_pages_navigation: - return + return default: return null } From f1ca9a0704cd0b95793d77882df5b18d8c582b9c Mon Sep 17 00:00:00 2001 From: Erik Tiekstra Date: Tue, 13 Aug 2024 13:58:06 +0200 Subject: [PATCH 27/53] feat(SW-190): added hero to loyalty pages --- .../ContentType/LoyaltyPage/LoyaltyPage.tsx | 18 +++++++++++++++--- .../LoyaltyPage/loyaltyPage.module.css | 5 +++++ .../TempDesignSystem/Hero/hero.module.css | 8 ++++++++ components/TempDesignSystem/Hero/hero.ts | 4 ++++ components/TempDesignSystem/Hero/index.tsx | 17 +++++++++++++++++ lib/graphql/Query/LoyaltyPage.graphql | 3 +++ .../routers/contentstack/loyaltyPage/output.ts | 5 +++++ .../routers/contentstack/loyaltyPage/query.ts | 9 +++++++++ 8 files changed, 66 insertions(+), 3 deletions(-) create mode 100644 components/TempDesignSystem/Hero/hero.module.css create mode 100644 components/TempDesignSystem/Hero/hero.ts create mode 100644 components/TempDesignSystem/Hero/index.tsx diff --git a/components/ContentType/LoyaltyPage/LoyaltyPage.tsx b/components/ContentType/LoyaltyPage/LoyaltyPage.tsx index afa0e405e..484117c47 100644 --- a/components/ContentType/LoyaltyPage/LoyaltyPage.tsx +++ b/components/ContentType/LoyaltyPage/LoyaltyPage.tsx @@ -3,12 +3,16 @@ import { serverClient } from "@/lib/trpc/server" import { Blocks } from "@/components/Loyalty/Blocks" import Sidebar from "@/components/Loyalty/Sidebar" import MaxWidth from "@/components/MaxWidth" +import Hero from "@/components/TempDesignSystem/Hero" import Title from "@/components/TempDesignSystem/Text/Title" import TrackingSDK from "@/components/TrackingSDK" import styles from "./loyaltyPage.module.css" -export default async function LoyaltyPage() { +import { ImageVaultAsset } from "@/types/components/imageVaultImage" +import type { LangParams } from "@/types/params" + +export default async function LoyaltyPage({ lang }: LangParams) { const loyaltyPageRes = await serverClient().contentstack.loyaltyPage.get() if (!loyaltyPageRes) { @@ -16,7 +20,7 @@ export default async function LoyaltyPage() { } const { tracking, loyaltyPage } = loyaltyPageRes - + const heroImage: ImageVaultAsset = loyaltyPage.header?.hero_image return ( <>
@@ -25,7 +29,15 @@ export default async function LoyaltyPage() { ) : null} - {loyaltyPage.heading} +
+ {loyaltyPage.heading} + {heroImage ? ( + + ) : null} +
{loyaltyPage.blocks ? : null}
diff --git a/components/ContentType/LoyaltyPage/loyaltyPage.module.css b/components/ContentType/LoyaltyPage/loyaltyPage.module.css index a0f140faf..45470127c 100644 --- a/components/ContentType/LoyaltyPage/loyaltyPage.module.css +++ b/components/ContentType/LoyaltyPage/loyaltyPage.module.css @@ -15,6 +15,11 @@ padding-right: var(--Spacing-x2); } +.header { + display: grid; + gap: var(--Spacing-x4); +} + @media screen and (min-width: 1367px) { .content { gap: var(--Spacing-x5); diff --git a/components/TempDesignSystem/Hero/hero.module.css b/components/TempDesignSystem/Hero/hero.module.css new file mode 100644 index 000000000..34ad1b9ea --- /dev/null +++ b/components/TempDesignSystem/Hero/hero.module.css @@ -0,0 +1,8 @@ +.hero { + height: 480px; + margin-bottom: var(--Spacing-x2); + max-width: 100%; + object-fit: cover; + border-radius: var(--Corner-radius-Medium); + margin: 0; +} diff --git a/components/TempDesignSystem/Hero/hero.ts b/components/TempDesignSystem/Hero/hero.ts new file mode 100644 index 000000000..29fd1b8d1 --- /dev/null +++ b/components/TempDesignSystem/Hero/hero.ts @@ -0,0 +1,4 @@ +export interface HeroProps { + alt: string + src: string +} diff --git a/components/TempDesignSystem/Hero/index.tsx b/components/TempDesignSystem/Hero/index.tsx new file mode 100644 index 000000000..fade3e6b0 --- /dev/null +++ b/components/TempDesignSystem/Hero/index.tsx @@ -0,0 +1,17 @@ +import Image from "@/components/Image" + +import { HeroProps } from "./hero" + +import styles from "./hero.module.css" + +export default async function Hero({ alt, src }: HeroProps) { + return ( + + ) +} diff --git a/lib/graphql/Query/LoyaltyPage.graphql b/lib/graphql/Query/LoyaltyPage.graphql index b0cd9cb29..6ed01ef83 100644 --- a/lib/graphql/Query/LoyaltyPage.graphql +++ b/lib/graphql/Query/LoyaltyPage.graphql @@ -107,6 +107,9 @@ query GetLoyaltyPage($locale: String!, $uid: String!) { } title heading + header { + hero_image + } sidebar { __typename ... on LoyaltyPageSidebarDynamicContent { diff --git a/server/routers/contentstack/loyaltyPage/output.ts b/server/routers/contentstack/loyaltyPage/output.ts index 205509a4c..20a8e3179 100644 --- a/server/routers/contentstack/loyaltyPage/output.ts +++ b/server/routers/contentstack/loyaltyPage/output.ts @@ -191,6 +191,11 @@ const loyaltyPageSidebarItem = z.discriminatedUnion("__typename", [ export const validateLoyaltyPageSchema = z.object({ heading: z.string().nullable(), + header: z + .object({ + hero_image: z.any(), + }) + .nullable(), blocks: z.array(loyaltyPageBlockItem).nullable(), sidebar: z.array(loyaltyPageSidebarItem).nullable(), system: z.object({ diff --git a/server/routers/contentstack/loyaltyPage/query.ts b/server/routers/contentstack/loyaltyPage/query.ts index c23422c4a..0a0b99142 100644 --- a/server/routers/contentstack/loyaltyPage/query.ts +++ b/server/routers/contentstack/loyaltyPage/query.ts @@ -208,8 +208,17 @@ export const loyaltyPageQueryRouter = router({ }) : null + const header = response.data.loyalty_page.header + ? { + hero_image: makeImageVaultImage( + response.data.loyalty_page.header.hero_image + ), + } + : null + const loyaltyPage = { heading: response.data.loyalty_page.heading, + header, system: response.data.loyalty_page.system, blocks, sidebar, From 8220a39a8f7a503ad73e7b35e370bb4f13ee3f72 Mon Sep 17 00:00:00 2001 From: Erik Tiekstra Date: Wed, 14 Aug 2024 09:29:00 +0200 Subject: [PATCH 28/53] feat(SW-190): added hero to static content pages --- .../(public)/[contentType]/[uid]/page.tsx | 4 +- components/ContentType/ContentPage.tsx | 3 - .../ContentType/ContentPage/ContentPage.tsx | 47 ++++++++++++++ .../ContentType/ContentPage/Intro/index.tsx | 22 +++++++ .../ContentPage/Intro/intro.module.css | 8 +++ .../ContentPage/contentPage.module.css | 17 +++++ .../ContentType/LoyaltyPage/LoyaltyPage.tsx | 3 +- components/MaxWidth/index.tsx | 14 ++-- components/MaxWidth/maxWidth.module.css | 18 +++++- components/MaxWidth/variants.ts | 21 ++++++ .../Fragments/ContentPage/Breadcrumbs.graphql | 25 ++++++++ lib/graphql/Query/ContentPage.graphql | 19 ++++++ lib/graphql/Query/LoyaltyPage.graphql | 5 +- .../routers/contentstack/contentPage/index.ts | 5 ++ .../contentstack/contentPage/output.ts | 30 +++++++++ .../routers/contentstack/contentPage/query.ts | 64 +++++++++++++++++++ .../routers/contentstack/contentPage/utils.ts | 9 +++ server/routers/contentstack/index.ts | 2 + .../contentstack/loyaltyPage/output.ts | 13 ++-- .../routers/contentstack/loyaltyPage/query.ts | 44 +------------ .../routers/contentstack/loyaltyPage/utils.ts | 33 ++++++++++ types/components/max-width.ts | 8 ++- types/components/tracking.ts | 1 + 23 files changed, 351 insertions(+), 64 deletions(-) delete mode 100644 components/ContentType/ContentPage.tsx create mode 100644 components/ContentType/ContentPage/ContentPage.tsx create mode 100644 components/ContentType/ContentPage/Intro/index.tsx create mode 100644 components/ContentType/ContentPage/Intro/intro.module.css create mode 100644 components/ContentType/ContentPage/contentPage.module.css create mode 100644 components/MaxWidth/variants.ts create mode 100644 lib/graphql/Fragments/ContentPage/Breadcrumbs.graphql create mode 100644 lib/graphql/Query/ContentPage.graphql create mode 100644 server/routers/contentstack/contentPage/index.ts create mode 100644 server/routers/contentstack/contentPage/output.ts create mode 100644 server/routers/contentstack/contentPage/query.ts create mode 100644 server/routers/contentstack/contentPage/utils.ts diff --git a/app/[lang]/(live)/(public)/[contentType]/[uid]/page.tsx b/app/[lang]/(live)/(public)/[contentType]/[uid]/page.tsx index 0e8e98a05..378d52bbf 100644 --- a/app/[lang]/(live)/(public)/[contentType]/[uid]/page.tsx +++ b/app/[lang]/(live)/(public)/[contentType]/[uid]/page.tsx @@ -1,6 +1,6 @@ import { notFound } from "next/navigation" -import ContentPage from "@/components/ContentType/ContentPage" +import ContentPage from "@/components/ContentType/ContentPage/ContentPage" import HotelPage from "@/components/ContentType/HotelPage/HotelPage" import LoyaltyPage from "@/components/ContentType/LoyaltyPage/LoyaltyPage" import { setLang } from "@/i18n/serverContext" @@ -19,7 +19,7 @@ export default async function ContentTypePage({ switch (params.contentType) { case "content-page": - return + return case "loyalty-page": return case "hotel-page": diff --git a/components/ContentType/ContentPage.tsx b/components/ContentType/ContentPage.tsx deleted file mode 100644 index bf2443621..000000000 --- a/components/ContentType/ContentPage.tsx +++ /dev/null @@ -1,3 +0,0 @@ -export default async function ContentPage() { - return null -} diff --git a/components/ContentType/ContentPage/ContentPage.tsx b/components/ContentType/ContentPage/ContentPage.tsx new file mode 100644 index 000000000..546bcec0d --- /dev/null +++ b/components/ContentType/ContentPage/ContentPage.tsx @@ -0,0 +1,47 @@ +import { serverClient } from "@/lib/trpc/server" + +import Hero from "@/components/TempDesignSystem/Hero" +import Preamble from "@/components/TempDesignSystem/Text/Preamble" +import Title from "@/components/TempDesignSystem/Text/Title" +import TrackingSDK from "@/components/TrackingSDK" + +import Intro from "./Intro" + +import styles from "./contentPage.module.css" + +import type { LangParams } from "@/types/params" + +export default async function ContentPage({ lang }: LangParams) { + const contentPageRes = await serverClient().contentstack.contentPage.get() + + if (!contentPageRes) { + return null + } + + const { tracking, contentPage } = contentPageRes + const heroImage = contentPage.hero_image + + return ( + <> +
+
+ + {contentPage.header.heading} + {contentPage.header.preamble} + +
+ + {heroImage ? ( +
+ +
+ ) : null} +
+ + + + ) +} diff --git a/components/ContentType/ContentPage/Intro/index.tsx b/components/ContentType/ContentPage/Intro/index.tsx new file mode 100644 index 000000000..a5401164d --- /dev/null +++ b/components/ContentType/ContentPage/Intro/index.tsx @@ -0,0 +1,22 @@ +import { PropsWithChildren } from "react" + +import MaxWidth from "@/components/MaxWidth" + +import styles from "./intro.module.css" + +export default async function Intro({ children }: PropsWithChildren) { + return ( +
+ + + {children} + + +
+ ) +} diff --git a/components/ContentType/ContentPage/Intro/intro.module.css b/components/ContentType/ContentPage/Intro/intro.module.css new file mode 100644 index 000000000..8ae34a4ad --- /dev/null +++ b/components/ContentType/ContentPage/Intro/intro.module.css @@ -0,0 +1,8 @@ +.intro { + padding: var(--Spacing-x4) var(--Spacing-x2); +} + +.content { + display: grid; + gap: var(--Spacing-x3); +} diff --git a/components/ContentType/ContentPage/contentPage.module.css b/components/ContentType/ContentPage/contentPage.module.css new file mode 100644 index 000000000..df5938084 --- /dev/null +++ b/components/ContentType/ContentPage/contentPage.module.css @@ -0,0 +1,17 @@ +.content { + display: grid; + padding-bottom: var(--Spacing-x9); + position: relative; + justify-content: center; + align-items: flex-start; +} + +.header { + background-color: var(--Base-Surface-Subtle-Normal); +} + +.hero { + display: grid; + justify-content: center; + padding: var(--Spacing-x4) var(--Spacing-x2); +} diff --git a/components/ContentType/LoyaltyPage/LoyaltyPage.tsx b/components/ContentType/LoyaltyPage/LoyaltyPage.tsx index 484117c47..b6b90bf47 100644 --- a/components/ContentType/LoyaltyPage/LoyaltyPage.tsx +++ b/components/ContentType/LoyaltyPage/LoyaltyPage.tsx @@ -9,7 +9,6 @@ import TrackingSDK from "@/components/TrackingSDK" import styles from "./loyaltyPage.module.css" -import { ImageVaultAsset } from "@/types/components/imageVaultImage" import type { LangParams } from "@/types/params" export default async function LoyaltyPage({ lang }: LangParams) { @@ -20,7 +19,7 @@ export default async function LoyaltyPage({ lang }: LangParams) { } const { tracking, loyaltyPage } = loyaltyPageRes - const heroImage: ImageVaultAsset = loyaltyPage.header?.hero_image + const heroImage = loyaltyPage.hero_image return ( <>
diff --git a/components/MaxWidth/index.tsx b/components/MaxWidth/index.tsx index 495447a76..0047d0dd2 100644 --- a/components/MaxWidth/index.tsx +++ b/components/MaxWidth/index.tsx @@ -1,17 +1,21 @@ "use client" -import { cva } from "class-variance-authority" -import styles from "./maxWidth.module.css" +import { maxWidthVariants } from "./variants" import type { MaxWidthProps } from "@/types/components/max-width" -const maxWidthVariants = cva(styles.container) - export default function MaxWidth({ className, tag = "section", + variant, + align, ...props }: MaxWidthProps) { const Cmp = tag - return + return ( + + ) } diff --git a/components/MaxWidth/maxWidth.module.css b/components/MaxWidth/maxWidth.module.css index 563ae5721..9197d79dc 100644 --- a/components/MaxWidth/maxWidth.module.css +++ b/components/MaxWidth/maxWidth.module.css @@ -1,4 +1,20 @@ .container { - max-width: var(--max-width, 1140px); position: relative; } + +.container.default { + max-width: var(--max-width, 1140px); +} + +.container.text { + max-width: 49.5rem; +} + +.container.content { + max-width: 74.75rem; +} + +.container.center { + margin: 0 auto; + width: 100vh; +} diff --git a/components/MaxWidth/variants.ts b/components/MaxWidth/variants.ts new file mode 100644 index 000000000..f7c917b5f --- /dev/null +++ b/components/MaxWidth/variants.ts @@ -0,0 +1,21 @@ +import { cva } from "class-variance-authority" + +import styles from "./maxWidth.module.css" + +export const maxWidthVariants = cva(styles.container, { + variants: { + variant: { + text: styles.text, + content: styles.content, + default: styles.default, + }, + align: { + center: styles.center, + left: styles.left, + }, + }, + defaultVariants: { + variant: "default", + align: "center", + }, +}) diff --git a/lib/graphql/Fragments/ContentPage/Breadcrumbs.graphql b/lib/graphql/Fragments/ContentPage/Breadcrumbs.graphql new file mode 100644 index 000000000..0e9dc795c --- /dev/null +++ b/lib/graphql/Fragments/ContentPage/Breadcrumbs.graphql @@ -0,0 +1,25 @@ +fragment ContentPageBreadcrumbs on ContentPage { + web { + breadcrumbs { + title + parentsConnection { + edges { + node { + ... on ContentPage { + web { + breadcrumbs { + title + } + } + system { + locale + uid + } + url + } + } + } + } + } + } +} diff --git a/lib/graphql/Query/ContentPage.graphql b/lib/graphql/Query/ContentPage.graphql new file mode 100644 index 000000000..ff7c0d24d --- /dev/null +++ b/lib/graphql/Query/ContentPage.graphql @@ -0,0 +1,19 @@ +#import "../Fragments/ContentPage/Breadcrumbs.graphql" + +query GetContentPage($locale: String!, $uid: String!) { + content_page(uid: $uid, locale: $locale) { + title + header { + heading + preamble + } + hero_image + ...ContentPageBreadcrumbs + system { + uid + created_at + updated_at + locale + } + } +} diff --git a/lib/graphql/Query/LoyaltyPage.graphql b/lib/graphql/Query/LoyaltyPage.graphql index 6ed01ef83..7026fe288 100644 --- a/lib/graphql/Query/LoyaltyPage.graphql +++ b/lib/graphql/Query/LoyaltyPage.graphql @@ -107,9 +107,8 @@ query GetLoyaltyPage($locale: String!, $uid: String!) { } title heading - header { - hero_image - } + preamble + hero_image sidebar { __typename ... on LoyaltyPageSidebarDynamicContent { diff --git a/server/routers/contentstack/contentPage/index.ts b/server/routers/contentstack/contentPage/index.ts new file mode 100644 index 000000000..52130d7b4 --- /dev/null +++ b/server/routers/contentstack/contentPage/index.ts @@ -0,0 +1,5 @@ +import { mergeRouters } from "@/server/trpc" + +import { contentPageQueryRouter } from "./query" + +export const contentPageRouter = mergeRouters(contentPageQueryRouter) diff --git a/server/routers/contentstack/contentPage/output.ts b/server/routers/contentstack/contentPage/output.ts new file mode 100644 index 000000000..32fe3ec08 --- /dev/null +++ b/server/routers/contentstack/contentPage/output.ts @@ -0,0 +1,30 @@ +import { z } from "zod" + +import { Lang } from "@/constants/languages" + +import { ImageVaultAsset } from "@/types/components/imageVaultImage" + +export const validateContentPageSchema = z.object({ + content_page: z.object({ + title: z.string(), + header: z.object({ + heading: z.string(), + preamble: z.string(), + }), + hero_image: z.any().nullable(), + system: z.object({ + uid: z.string(), + locale: z.nativeEnum(Lang), + created_at: z.string(), + updated_at: z.string(), + }), + }), +}) + +export type ContentPageDataRaw = z.infer + +type ContentPageRaw = ContentPageDataRaw["content_page"] + +export type ContentPage = Omit & { + hero_image?: ImageVaultAsset +} diff --git a/server/routers/contentstack/contentPage/query.ts b/server/routers/contentstack/contentPage/query.ts new file mode 100644 index 000000000..845dd89e3 --- /dev/null +++ b/server/routers/contentstack/contentPage/query.ts @@ -0,0 +1,64 @@ +import { Lang } from "@/constants/languages" +import { GetContentPage } from "@/lib/graphql/Query/ContentPage.graphql" +import { request } from "@/lib/graphql/request" +import { notFound } from "@/server/errors/trpc" +import { contentstackExtendedProcedureUID, router } from "@/server/trpc" + +import { + ContentPage, + ContentPageDataRaw, + validateContentPageSchema, +} from "./output" +import { makeImageVaultImage } from "./utils" + +import { + TrackingChannelEnum, + TrackingSDKPageData, +} from "@/types/components/tracking" + +export const contentPageQueryRouter = router({ + get: contentstackExtendedProcedureUID.query(async ({ ctx }) => { + const { lang, uid } = ctx + + const response = await request(GetContentPage, { + locale: lang, + uid, + }) + + if (!response.data) { + throw notFound(response) + } + + const validatedContentPage = validateContentPageSchema.safeParse( + response.data + ) + + if (!validatedContentPage.success) { + console.error( + `Failed to validate Contentpage Data - (lang: ${lang}, uid: ${uid})` + ) + console.error(validatedContentPage.error) + return null + } + + const contentPageData = validatedContentPage.data.content_page + const contentPage: ContentPage = { + ...contentPageData, + hero_image: makeImageVaultImage(contentPageData.hero_image), + } + + const tracking: TrackingSDKPageData = { + pageId: contentPageData.system.uid, + lang: contentPageData.system.locale as Lang, + publishedDate: contentPageData.system.updated_at, + createdDate: contentPageData.system.created_at, + channel: TrackingChannelEnum["static-content-page"], + pageType: "staticcontentpage", + } + + return { + contentPage, + tracking, + } + }), +}) diff --git a/server/routers/contentstack/contentPage/utils.ts b/server/routers/contentstack/contentPage/utils.ts new file mode 100644 index 000000000..d2f748d93 --- /dev/null +++ b/server/routers/contentstack/contentPage/utils.ts @@ -0,0 +1,9 @@ +import { insertResponseToImageVaultAsset } from "@/utils/imageVault" + +import { InsertResponse } from "@/types/components/imageVaultImage" + +export function makeImageVaultImage(image: any) { + return image && !!Object.keys(image).length + ? insertResponseToImageVaultAsset(image as InsertResponse) + : undefined +} diff --git a/server/routers/contentstack/index.ts b/server/routers/contentstack/index.ts index be1ef1ed3..127673eb0 100644 --- a/server/routers/contentstack/index.ts +++ b/server/routers/contentstack/index.ts @@ -3,6 +3,7 @@ import { router } from "@/server/trpc" import { accountPageRouter } from "./accountPage" import { baseRouter } from "./base" import { breadcrumbsRouter } from "./breadcrumbs" +import { contentPageRouter } from "./contentPage" import { hotelPageRouter } from "./hotelPage" import { languageSwitcherRouter } from "./languageSwitcher" import { loyaltyPageRouter } from "./loyaltyPage" @@ -15,5 +16,6 @@ export const contentstackRouter = router({ hotelPage: hotelPageRouter, languageSwitcher: languageSwitcherRouter, loyaltyPage: loyaltyPageRouter, + contentPage: contentPageRouter, myPages: myPagesRouter, }) diff --git a/server/routers/contentstack/loyaltyPage/output.ts b/server/routers/contentstack/loyaltyPage/output.ts index 20a8e3179..f89796ad9 100644 --- a/server/routers/contentstack/loyaltyPage/output.ts +++ b/server/routers/contentstack/loyaltyPage/output.ts @@ -191,11 +191,8 @@ const loyaltyPageSidebarItem = z.discriminatedUnion("__typename", [ export const validateLoyaltyPageSchema = z.object({ heading: z.string().nullable(), - header: z - .object({ - hero_image: z.any(), - }) - .nullable(), + preamble: z.string().nullable(), + hero_image: z.any().nullable(), blocks: z.array(loyaltyPageBlockItem).nullable(), sidebar: z.array(loyaltyPageSidebarItem).nullable(), system: z.object({ @@ -263,7 +260,11 @@ export type Sidebar = | SideBarDynamicContent type LoyaltyPageDataRaw = z.infer -export type LoyaltyPage = Omit & { +export type LoyaltyPage = Omit< + LoyaltyPageDataRaw, + "blocks" | "sidebar" | "hero_image" +> & { + hero_image?: ImageVaultAsset blocks: Block[] sidebar: Sidebar[] } diff --git a/server/routers/contentstack/loyaltyPage/query.ts b/server/routers/contentstack/loyaltyPage/query.ts index 0a0b99142..5fea2a8dd 100644 --- a/server/routers/contentstack/loyaltyPage/query.ts +++ b/server/routers/contentstack/loyaltyPage/query.ts @@ -12,7 +12,6 @@ import { generateTag, generateTags, } from "@/utils/generateTag" -import { insertResponseToImageVaultAsset } from "@/utils/imageVault" import { removeMultipleSlashes } from "@/utils/url" import { removeEmptyObjects } from "../../utils" @@ -22,9 +21,8 @@ import { validateLoyaltyPageRefsSchema, validateLoyaltyPageSchema, } from "./output" -import { getConnections } from "./utils" +import { getConnections, makeButtonObject, makeImageVaultImage } from "./utils" -import { InsertResponse } from "@/types/components/imageVaultImage" import { LoyaltyBlocksTypenameEnum, LoyaltyCardsGridEnum, @@ -35,35 +33,6 @@ import { TrackingSDKPageData, } from "@/types/components/tracking" -function makeImageVaultImage(image: any) { - return image && !!Object.keys(image).length - ? insertResponseToImageVaultAsset(image as InsertResponse) - : undefined -} - -function makeButtonObject(button: any) { - if (!button) return null - - const isContenstackLink = - button?.is_contentstack_link || button.linkConnection?.edges?.length - - return { - openInNewTab: button?.open_in_new_tab, - title: - button.cta_text || - (isContenstackLink - ? button.linkConnection.edges[0].node.title - : button.external_link.title), - href: isContenstackLink - ? button.linkConnection.edges[0].node.web?.original_url || - removeMultipleSlashes( - `/${button.linkConnection.edges[0].node.system.locale}/${button.linkConnection.edges[0].node.url}` - ) - : button.external_link.href, - isExternal: !isContenstackLink, - } -} - export const loyaltyPageQueryRouter = router({ get: contentstackExtendedProcedureUID.query(async ({ ctx }) => { const { lang, uid } = ctx @@ -208,17 +177,10 @@ export const loyaltyPageQueryRouter = router({ }) : null - const header = response.data.loyalty_page.header - ? { - hero_image: makeImageVaultImage( - response.data.loyalty_page.header.hero_image - ), - } - : null - const loyaltyPage = { heading: response.data.loyalty_page.heading, - header, + preamble: response.data.loyalty_page.preamble, + hero_image: makeImageVaultImage(response.data.loyalty_page.hero_image), system: response.data.loyalty_page.system, blocks, sidebar, diff --git a/server/routers/contentstack/loyaltyPage/utils.ts b/server/routers/contentstack/loyaltyPage/utils.ts index 95e251075..99e66571d 100644 --- a/server/routers/contentstack/loyaltyPage/utils.ts +++ b/server/routers/contentstack/loyaltyPage/utils.ts @@ -1,5 +1,9 @@ +import { insertResponseToImageVaultAsset } from "@/utils/imageVault" +import { removeMultipleSlashes } from "@/utils/url" + import { LoyaltyPageRefsDataRaw } from "./output" +import { InsertResponse } from "@/types/components/imageVaultImage" import { LoyaltyBlocksTypenameEnum, LoyaltyCardsGridEnum, @@ -77,3 +81,32 @@ export function getConnections(refs: LoyaltyPageRefsDataRaw) { return connections } + +export function makeImageVaultImage(image: any) { + return image && !!Object.keys(image).length + ? insertResponseToImageVaultAsset(image as InsertResponse) + : undefined +} + +export function makeButtonObject(button: any) { + if (!button) return null + + const isContenstackLink = + button?.is_contentstack_link || button.linkConnection?.edges?.length + + return { + openInNewTab: button?.open_in_new_tab, + title: + button.cta_text || + (isContenstackLink + ? button.linkConnection.edges[0].node.title + : button.external_link.title), + href: isContenstackLink + ? button.linkConnection.edges[0].node.web?.original_url || + removeMultipleSlashes( + `/${button.linkConnection.edges[0].node.system.locale}/${button.linkConnection.edges[0].node.url}` + ) + : button.external_link.href, + isExternal: !isContenstackLink, + } +} diff --git a/types/components/max-width.ts b/types/components/max-width.ts index e1327b581..4a99b2d99 100644 --- a/types/components/max-width.ts +++ b/types/components/max-width.ts @@ -1,3 +1,9 @@ -export interface MaxWidthProps extends React.HTMLAttributes { +import { VariantProps } from "class-variance-authority" + +import { maxWidthVariants } from "@/components/MaxWidth/variants" + +export interface MaxWidthProps + extends React.HTMLAttributes, + VariantProps { tag?: "article" | "div" | "main" | "section" } diff --git a/types/components/tracking.ts b/types/components/tracking.ts index 354212d8d..92128d939 100644 --- a/types/components/tracking.ts +++ b/types/components/tracking.ts @@ -4,6 +4,7 @@ import type { Lang } from "@/constants/languages" export enum TrackingChannelEnum { "scandic-friends" = "scandic-friends", + "static-content-page" = "static-content-page", } export type TrackingChannel = keyof typeof TrackingChannelEnum From a01aa10bf2146305b873356c6178f2a17006a70b Mon Sep 17 00:00:00 2001 From: Arvid Norlin Date: Wed, 14 Aug 2024 11:54:16 +0200 Subject: [PATCH 29/53] fix: add content-page handling in languageSwitcher query --- lib/graphql/Query/ContentPage.graphql | 36 +++++++++++++++++++ .../contentstack/languageSwitcher/query.ts | 18 ++++++++++ types/requests/pageType.ts | 1 + 3 files changed, 55 insertions(+) diff --git a/lib/graphql/Query/ContentPage.graphql b/lib/graphql/Query/ContentPage.graphql index ff7c0d24d..4d3076e14 100644 --- a/lib/graphql/Query/ContentPage.graphql +++ b/lib/graphql/Query/ContentPage.graphql @@ -17,3 +17,39 @@ query GetContentPage($locale: String!, $uid: String!) { } } } + +query GetDaDeEnUrlsContentPage($uid: String!) { + de: all_content_page(where: { uid: $uid }, locale: "de") { + items { + url + } + } + en: all_content_page(where: { uid: $uid }, locale: "en") { + items { + url + } + } + da: all_content_page(where: { uid: $uid }, locale: "da") { + items { + url + } + } +} + +query GetFiNoSvUrlsContentPage($uid: String!) { + fi: all_content_page(where: { uid: $uid }, locale: "fi") { + items { + url + } + } + no: all_content_page(where: { uid: $uid }, locale: "no") { + items { + url + } + } + sv: all_content_page(where: { uid: $uid }, locale: "sv") { + items { + url + } + } +} diff --git a/server/routers/contentstack/languageSwitcher/query.ts b/server/routers/contentstack/languageSwitcher/query.ts index a41fd2a99..35b8a3ded 100644 --- a/server/routers/contentstack/languageSwitcher/query.ts +++ b/server/routers/contentstack/languageSwitcher/query.ts @@ -5,6 +5,10 @@ import { GetDaDeEnUrlsAccountPage, GetFiNoSvUrlsAccountPage, } from "@/lib/graphql/Query/AccountPage.graphql" +import { + GetDaDeEnUrlsContentPage, + GetFiNoSvUrlsContentPage, +} from "@/lib/graphql/Query/ContentPage.graphql" import { GetDaDeEnUrlsHotelPage, GetFiNoSvUrlsHotelPage, @@ -101,6 +105,20 @@ async function getLanguageSwitcher(options: LanguageSwitcherVariables) { tags: tagsFiNoSv, }, ]) + case PageTypeEnum.contentPage: + return await batchRequest([ + { + document: GetDaDeEnUrlsContentPage, + variables, + tags: tagsDaDeEn, + }, + { + document: GetFiNoSvUrlsContentPage, + variables, + tags: tagsFiNoSv, + }, + ]) + default: console.error(`type: [${options.contentType}]`) console.error(`Trying to get a content type that is not supported`) diff --git a/types/requests/pageType.ts b/types/requests/pageType.ts index 993a697e4..001e6a486 100644 --- a/types/requests/pageType.ts +++ b/types/requests/pageType.ts @@ -2,5 +2,6 @@ export enum PageTypeEnum { accountPage = "account-page", loyaltyPage = "loyalty-page", hotelPage = "hotel-page", + contentPage = "content-page", currentBlocksPage = "current-blocks-page", } From e7ec6b09c385d83678d1ace35914383aa38b91d3 Mon Sep 17 00:00:00 2001 From: Erik Tiekstra Date: Wed, 14 Aug 2024 12:30:04 +0200 Subject: [PATCH 30/53] fix(SW-190): styling fixes --- .../(public)/[contentType]/[uid]/page.tsx | 2 +- .../ContentType/ContentPage/ContentPage.tsx | 6 ++---- .../ContentType/ContentPage/Intro/index.tsx | 20 +++++++++---------- .../ContentPage/Intro/intro.module.css | 12 ++++++----- .../ContentPage/contentPage.module.css | 5 ++--- .../ContentType/LoyaltyPage/Intro/index.tsx | 20 +++++++++++++++++++ .../LoyaltyPage/Intro/intro.module.css | 10 ++++++++++ .../ContentType/LoyaltyPage/LoyaltyPage.tsx | 17 +++++++++++----- components/MaxWidth/maxWidth.module.css | 3 ++- .../TempDesignSystem/Hero/hero.module.css | 10 ++++++++-- .../Fragments/ContentPage/Breadcrumbs.graphql | 12 +++++++++++ 11 files changed, 85 insertions(+), 32 deletions(-) create mode 100644 components/ContentType/LoyaltyPage/Intro/index.tsx create mode 100644 components/ContentType/LoyaltyPage/Intro/intro.module.css diff --git a/app/[lang]/(live)/(public)/[contentType]/[uid]/page.tsx b/app/[lang]/(live)/(public)/[contentType]/[uid]/page.tsx index 378d52bbf..5d94a02b9 100644 --- a/app/[lang]/(live)/(public)/[contentType]/[uid]/page.tsx +++ b/app/[lang]/(live)/(public)/[contentType]/[uid]/page.tsx @@ -19,7 +19,7 @@ export default async function ContentTypePage({ switch (params.contentType) { case "content-page": - return + return case "loyalty-page": return case "hotel-page": diff --git a/components/ContentType/ContentPage/ContentPage.tsx b/components/ContentType/ContentPage/ContentPage.tsx index 546bcec0d..e8a552058 100644 --- a/components/ContentType/ContentPage/ContentPage.tsx +++ b/components/ContentType/ContentPage/ContentPage.tsx @@ -9,9 +9,7 @@ import Intro from "./Intro" import styles from "./contentPage.module.css" -import type { LangParams } from "@/types/params" - -export default async function ContentPage({ lang }: LangParams) { +export default async function ContentPage() { const contentPageRes = await serverClient().contentstack.contentPage.get() if (!contentPageRes) { @@ -23,7 +21,7 @@ export default async function ContentPage({ lang }: LangParams) { return ( <> -
+
{contentPage.header.heading} diff --git a/components/ContentType/ContentPage/Intro/index.tsx b/components/ContentType/ContentPage/Intro/index.tsx index a5401164d..5ad4db448 100644 --- a/components/ContentType/ContentPage/Intro/index.tsx +++ b/components/ContentType/ContentPage/Intro/index.tsx @@ -6,17 +6,15 @@ import styles from "./intro.module.css" export default async function Intro({ children }: PropsWithChildren) { return ( -
- - - {children} - + + + {children} -
+ ) } diff --git a/components/ContentType/ContentPage/Intro/intro.module.css b/components/ContentType/ContentPage/Intro/intro.module.css index 8ae34a4ad..f64d9daaa 100644 --- a/components/ContentType/ContentPage/Intro/intro.module.css +++ b/components/ContentType/ContentPage/Intro/intro.module.css @@ -1,8 +1,10 @@ -.intro { - padding: var(--Spacing-x4) var(--Spacing-x2); -} - .content { display: grid; - gap: var(--Spacing-x3); + gap: var(--Spacing-x2); +} + +@media (min-width: 768px) { + .content { + gap: var(--Spacing-x3); + } } diff --git a/components/ContentType/ContentPage/contentPage.module.css b/components/ContentType/ContentPage/contentPage.module.css index df5938084..2d4bcba85 100644 --- a/components/ContentType/ContentPage/contentPage.module.css +++ b/components/ContentType/ContentPage/contentPage.module.css @@ -1,13 +1,12 @@ .content { + position: relative; display: grid; padding-bottom: var(--Spacing-x9); - position: relative; - justify-content: center; - align-items: flex-start; } .header { background-color: var(--Base-Surface-Subtle-Normal); + padding: var(--Spacing-x4) var(--Spacing-x2); } .hero { diff --git a/components/ContentType/LoyaltyPage/Intro/index.tsx b/components/ContentType/LoyaltyPage/Intro/index.tsx new file mode 100644 index 000000000..5ad4db448 --- /dev/null +++ b/components/ContentType/LoyaltyPage/Intro/index.tsx @@ -0,0 +1,20 @@ +import { PropsWithChildren } from "react" + +import MaxWidth from "@/components/MaxWidth" + +import styles from "./intro.module.css" + +export default async function Intro({ children }: PropsWithChildren) { + return ( + + + {children} + + + ) +} diff --git a/components/ContentType/LoyaltyPage/Intro/intro.module.css b/components/ContentType/LoyaltyPage/Intro/intro.module.css new file mode 100644 index 000000000..f64d9daaa --- /dev/null +++ b/components/ContentType/LoyaltyPage/Intro/intro.module.css @@ -0,0 +1,10 @@ +.content { + display: grid; + gap: var(--Spacing-x2); +} + +@media (min-width: 768px) { + .content { + gap: var(--Spacing-x3); + } +} diff --git a/components/ContentType/LoyaltyPage/LoyaltyPage.tsx b/components/ContentType/LoyaltyPage/LoyaltyPage.tsx index b6b90bf47..95e18d107 100644 --- a/components/ContentType/LoyaltyPage/LoyaltyPage.tsx +++ b/components/ContentType/LoyaltyPage/LoyaltyPage.tsx @@ -4,14 +4,15 @@ import { Blocks } from "@/components/Loyalty/Blocks" import Sidebar from "@/components/Loyalty/Sidebar" import MaxWidth from "@/components/MaxWidth" import Hero from "@/components/TempDesignSystem/Hero" +import Preamble from "@/components/TempDesignSystem/Text/Preamble" import Title from "@/components/TempDesignSystem/Text/Title" import TrackingSDK from "@/components/TrackingSDK" +import Intro from "./Intro" + import styles from "./loyaltyPage.module.css" -import type { LangParams } from "@/types/params" - -export default async function LoyaltyPage({ lang }: LangParams) { +export default async function LoyaltyPage() { const loyaltyPageRes = await serverClient().contentstack.loyaltyPage.get() if (!loyaltyPageRes) { @@ -27,9 +28,15 @@ export default async function LoyaltyPage({ lang }: LangParams) { ) : null} - +
- {loyaltyPage.heading} + + {loyaltyPage.heading} + {loyaltyPage.preamble ? ( + {loyaltyPage.preamble} + ) : null} + + {heroImage ? ( Date: Thu, 15 Aug 2024 08:14:23 +0200 Subject: [PATCH 31/53] chore: cleanup --- server/routers/contentstack/contentPage/query.ts | 3 ++- server/routers/contentstack/contentPage/utils.ts | 9 --------- server/routers/contentstack/loyaltyPage/query.ts | 3 ++- server/routers/contentstack/loyaltyPage/utils.ts | 9 --------- utils/imageVault.ts | 6 ++++++ 5 files changed, 10 insertions(+), 20 deletions(-) delete mode 100644 server/routers/contentstack/contentPage/utils.ts diff --git a/server/routers/contentstack/contentPage/query.ts b/server/routers/contentstack/contentPage/query.ts index 845dd89e3..8aa03e162 100644 --- a/server/routers/contentstack/contentPage/query.ts +++ b/server/routers/contentstack/contentPage/query.ts @@ -4,12 +4,13 @@ import { request } from "@/lib/graphql/request" import { notFound } from "@/server/errors/trpc" import { contentstackExtendedProcedureUID, router } from "@/server/trpc" +import { makeImageVaultImage } from "@/utils/imageVault" + import { ContentPage, ContentPageDataRaw, validateContentPageSchema, } from "./output" -import { makeImageVaultImage } from "./utils" import { TrackingChannelEnum, diff --git a/server/routers/contentstack/contentPage/utils.ts b/server/routers/contentstack/contentPage/utils.ts deleted file mode 100644 index d2f748d93..000000000 --- a/server/routers/contentstack/contentPage/utils.ts +++ /dev/null @@ -1,9 +0,0 @@ -import { insertResponseToImageVaultAsset } from "@/utils/imageVault" - -import { InsertResponse } from "@/types/components/imageVaultImage" - -export function makeImageVaultImage(image: any) { - return image && !!Object.keys(image).length - ? insertResponseToImageVaultAsset(image as InsertResponse) - : undefined -} diff --git a/server/routers/contentstack/loyaltyPage/query.ts b/server/routers/contentstack/loyaltyPage/query.ts index 5fea2a8dd..6839f1ced 100644 --- a/server/routers/contentstack/loyaltyPage/query.ts +++ b/server/routers/contentstack/loyaltyPage/query.ts @@ -12,6 +12,7 @@ import { generateTag, generateTags, } from "@/utils/generateTag" +import { makeImageVaultImage } from "@/utils/imageVault" import { removeMultipleSlashes } from "@/utils/url" import { removeEmptyObjects } from "../../utils" @@ -21,7 +22,7 @@ import { validateLoyaltyPageRefsSchema, validateLoyaltyPageSchema, } from "./output" -import { getConnections, makeButtonObject, makeImageVaultImage } from "./utils" +import { getConnections, makeButtonObject } from "./utils" import { LoyaltyBlocksTypenameEnum, diff --git a/server/routers/contentstack/loyaltyPage/utils.ts b/server/routers/contentstack/loyaltyPage/utils.ts index 99e66571d..1b473210a 100644 --- a/server/routers/contentstack/loyaltyPage/utils.ts +++ b/server/routers/contentstack/loyaltyPage/utils.ts @@ -1,9 +1,7 @@ -import { insertResponseToImageVaultAsset } from "@/utils/imageVault" import { removeMultipleSlashes } from "@/utils/url" import { LoyaltyPageRefsDataRaw } from "./output" -import { InsertResponse } from "@/types/components/imageVaultImage" import { LoyaltyBlocksTypenameEnum, LoyaltyCardsGridEnum, @@ -81,13 +79,6 @@ export function getConnections(refs: LoyaltyPageRefsDataRaw) { return connections } - -export function makeImageVaultImage(image: any) { - return image && !!Object.keys(image).length - ? insertResponseToImageVaultAsset(image as InsertResponse) - : undefined -} - export function makeButtonObject(button: any) { if (!button) return null diff --git a/utils/imageVault.ts b/utils/imageVault.ts index a6aaba6c7..9e8a0a414 100644 --- a/utils/imageVault.ts +++ b/utils/imageVault.ts @@ -29,3 +29,9 @@ export function insertResponseToImageVaultAsset( }, } } + +export function makeImageVaultImage(image: any) { + return image && !!Object.keys(image).length + ? insertResponseToImageVaultAsset(image as InsertResponse) + : undefined +} From c75452dd17555c572dc00c270275d62f5e307e90 Mon Sep 17 00:00:00 2001 From: Erik Tiekstra Date: Thu, 15 Aug 2024 12:23:49 +0200 Subject: [PATCH 32/53] fix(SW-190): moved Hero component from TempDesignSystem to components --- components/ContentType/ContentPage/ContentPage.tsx | 2 +- components/ContentType/LoyaltyPage/LoyaltyPage.tsx | 2 +- components/{TempDesignSystem => }/Hero/hero.module.css | 0 components/{TempDesignSystem => }/Hero/hero.ts | 0 components/{TempDesignSystem => }/Hero/index.tsx | 0 5 files changed, 2 insertions(+), 2 deletions(-) rename components/{TempDesignSystem => }/Hero/hero.module.css (100%) rename components/{TempDesignSystem => }/Hero/hero.ts (100%) rename components/{TempDesignSystem => }/Hero/index.tsx (100%) diff --git a/components/ContentType/ContentPage/ContentPage.tsx b/components/ContentType/ContentPage/ContentPage.tsx index e8a552058..04ae39ba9 100644 --- a/components/ContentType/ContentPage/ContentPage.tsx +++ b/components/ContentType/ContentPage/ContentPage.tsx @@ -1,6 +1,6 @@ import { serverClient } from "@/lib/trpc/server" -import Hero from "@/components/TempDesignSystem/Hero" +import Hero from "@/components/Hero" import Preamble from "@/components/TempDesignSystem/Text/Preamble" import Title from "@/components/TempDesignSystem/Text/Title" import TrackingSDK from "@/components/TrackingSDK" diff --git a/components/ContentType/LoyaltyPage/LoyaltyPage.tsx b/components/ContentType/LoyaltyPage/LoyaltyPage.tsx index 95e18d107..68ebfae3c 100644 --- a/components/ContentType/LoyaltyPage/LoyaltyPage.tsx +++ b/components/ContentType/LoyaltyPage/LoyaltyPage.tsx @@ -1,9 +1,9 @@ import { serverClient } from "@/lib/trpc/server" +import Hero from "@/components/Hero" import { Blocks } from "@/components/Loyalty/Blocks" import Sidebar from "@/components/Loyalty/Sidebar" import MaxWidth from "@/components/MaxWidth" -import Hero from "@/components/TempDesignSystem/Hero" import Preamble from "@/components/TempDesignSystem/Text/Preamble" import Title from "@/components/TempDesignSystem/Text/Title" import TrackingSDK from "@/components/TrackingSDK" diff --git a/components/TempDesignSystem/Hero/hero.module.css b/components/Hero/hero.module.css similarity index 100% rename from components/TempDesignSystem/Hero/hero.module.css rename to components/Hero/hero.module.css diff --git a/components/TempDesignSystem/Hero/hero.ts b/components/Hero/hero.ts similarity index 100% rename from components/TempDesignSystem/Hero/hero.ts rename to components/Hero/hero.ts diff --git a/components/TempDesignSystem/Hero/index.tsx b/components/Hero/index.tsx similarity index 100% rename from components/TempDesignSystem/Hero/index.tsx rename to components/Hero/index.tsx From b073b0ae737c27527b888e39d557b3f01e4ce9c8 Mon Sep 17 00:00:00 2001 From: Erik Tiekstra Date: Thu, 15 Aug 2024 12:26:21 +0200 Subject: [PATCH 33/53] fix(SW-190): renamed components to index.tsx --- app/[lang]/(live)/(public)/[contentType]/[uid]/page.tsx | 6 +++--- .../ContentType/ContentPage/{ContentPage.tsx => index.tsx} | 0 .../ContentType/HotelPage/{HotelPage.tsx => index.tsx} | 0 .../ContentType/LoyaltyPage/{LoyaltyPage.tsx => index.tsx} | 0 4 files changed, 3 insertions(+), 3 deletions(-) rename components/ContentType/ContentPage/{ContentPage.tsx => index.tsx} (100%) rename components/ContentType/HotelPage/{HotelPage.tsx => index.tsx} (100%) rename components/ContentType/LoyaltyPage/{LoyaltyPage.tsx => index.tsx} (100%) diff --git a/app/[lang]/(live)/(public)/[contentType]/[uid]/page.tsx b/app/[lang]/(live)/(public)/[contentType]/[uid]/page.tsx index 5d94a02b9..6d2d5ffc6 100644 --- a/app/[lang]/(live)/(public)/[contentType]/[uid]/page.tsx +++ b/app/[lang]/(live)/(public)/[contentType]/[uid]/page.tsx @@ -1,8 +1,8 @@ import { notFound } from "next/navigation" -import ContentPage from "@/components/ContentType/ContentPage/ContentPage" -import HotelPage from "@/components/ContentType/HotelPage/HotelPage" -import LoyaltyPage from "@/components/ContentType/LoyaltyPage/LoyaltyPage" +import ContentPage from "@/components/ContentType/ContentPage" +import HotelPage from "@/components/ContentType/HotelPage" +import LoyaltyPage from "@/components/ContentType/LoyaltyPage" import { setLang } from "@/i18n/serverContext" import { diff --git a/components/ContentType/ContentPage/ContentPage.tsx b/components/ContentType/ContentPage/index.tsx similarity index 100% rename from components/ContentType/ContentPage/ContentPage.tsx rename to components/ContentType/ContentPage/index.tsx diff --git a/components/ContentType/HotelPage/HotelPage.tsx b/components/ContentType/HotelPage/index.tsx similarity index 100% rename from components/ContentType/HotelPage/HotelPage.tsx rename to components/ContentType/HotelPage/index.tsx diff --git a/components/ContentType/LoyaltyPage/LoyaltyPage.tsx b/components/ContentType/LoyaltyPage/index.tsx similarity index 100% rename from components/ContentType/LoyaltyPage/LoyaltyPage.tsx rename to components/ContentType/LoyaltyPage/index.tsx From 33460d8e87f86dea1b453b4f5c5641691a6f08f9 Mon Sep 17 00:00:00 2001 From: Erik Tiekstra Date: Thu, 15 Aug 2024 12:32:56 +0200 Subject: [PATCH 34/53] fix(SW-190): moved types from output to own file inside /types --- server/routers/contentstack/contentPage/output.ts | 10 ---------- server/routers/contentstack/contentPage/query.ts | 10 +++++----- types/trpc/routers/contentstack/contentPage.ts | 13 +++++++++++++ 3 files changed, 18 insertions(+), 15 deletions(-) create mode 100644 types/trpc/routers/contentstack/contentPage.ts diff --git a/server/routers/contentstack/contentPage/output.ts b/server/routers/contentstack/contentPage/output.ts index 32fe3ec08..beb592c81 100644 --- a/server/routers/contentstack/contentPage/output.ts +++ b/server/routers/contentstack/contentPage/output.ts @@ -2,8 +2,6 @@ import { z } from "zod" import { Lang } from "@/constants/languages" -import { ImageVaultAsset } from "@/types/components/imageVaultImage" - export const validateContentPageSchema = z.object({ content_page: z.object({ title: z.string(), @@ -20,11 +18,3 @@ export const validateContentPageSchema = z.object({ }), }), }) - -export type ContentPageDataRaw = z.infer - -type ContentPageRaw = ContentPageDataRaw["content_page"] - -export type ContentPage = Omit & { - hero_image?: ImageVaultAsset -} diff --git a/server/routers/contentstack/contentPage/query.ts b/server/routers/contentstack/contentPage/query.ts index 8aa03e162..3f2cb3501 100644 --- a/server/routers/contentstack/contentPage/query.ts +++ b/server/routers/contentstack/contentPage/query.ts @@ -6,16 +6,16 @@ import { contentstackExtendedProcedureUID, router } from "@/server/trpc" import { makeImageVaultImage } from "@/utils/imageVault" -import { - ContentPage, - ContentPageDataRaw, - validateContentPageSchema, -} from "./output" +import { validateContentPageSchema } from "./output" import { TrackingChannelEnum, TrackingSDKPageData, } from "@/types/components/tracking" +import { + ContentPage, + ContentPageDataRaw, +} from "@/types/trpc/routers/contentstack/contentPage" export const contentPageQueryRouter = router({ get: contentstackExtendedProcedureUID.query(async ({ ctx }) => { diff --git a/types/trpc/routers/contentstack/contentPage.ts b/types/trpc/routers/contentstack/contentPage.ts new file mode 100644 index 000000000..eafdcc710 --- /dev/null +++ b/types/trpc/routers/contentstack/contentPage.ts @@ -0,0 +1,13 @@ +import { z } from "zod" + +import { validateContentPageSchema } from "@/server/routers/contentstack/contentPage/output" + +import { ImageVaultAsset } from "@/types/components/imageVaultImage" + +export type ContentPageDataRaw = z.infer + +type ContentPageRaw = ContentPageDataRaw["content_page"] + +export type ContentPage = Omit & { + hero_image?: ImageVaultAsset +} From 4f2bd0c2d6c81ebcd7e969c72882459133ba7d6f Mon Sep 17 00:00:00 2001 From: Erik Tiekstra Date: Thu, 15 Aug 2024 13:04:47 +0200 Subject: [PATCH 35/53] fix(SW-190): moved Intro component to components folder and removed 'duplicate' --- components/ContentType/ContentPage/index.tsx | 3 +-- .../ContentType/LoyaltyPage/Intro/index.tsx | 20 ------------------- .../LoyaltyPage/Intro/intro.module.css | 10 ---------- components/ContentType/LoyaltyPage/index.tsx | 3 +-- .../ContentPage => }/Intro/index.tsx | 0 .../ContentPage => }/Intro/intro.module.css | 0 6 files changed, 2 insertions(+), 34 deletions(-) delete mode 100644 components/ContentType/LoyaltyPage/Intro/index.tsx delete mode 100644 components/ContentType/LoyaltyPage/Intro/intro.module.css rename components/{ContentType/ContentPage => }/Intro/index.tsx (100%) rename components/{ContentType/ContentPage => }/Intro/intro.module.css (100%) diff --git a/components/ContentType/ContentPage/index.tsx b/components/ContentType/ContentPage/index.tsx index 04ae39ba9..699e378df 100644 --- a/components/ContentType/ContentPage/index.tsx +++ b/components/ContentType/ContentPage/index.tsx @@ -1,12 +1,11 @@ import { serverClient } from "@/lib/trpc/server" import Hero from "@/components/Hero" +import Intro from "@/components/Intro" import Preamble from "@/components/TempDesignSystem/Text/Preamble" import Title from "@/components/TempDesignSystem/Text/Title" import TrackingSDK from "@/components/TrackingSDK" -import Intro from "./Intro" - import styles from "./contentPage.module.css" export default async function ContentPage() { diff --git a/components/ContentType/LoyaltyPage/Intro/index.tsx b/components/ContentType/LoyaltyPage/Intro/index.tsx deleted file mode 100644 index 5ad4db448..000000000 --- a/components/ContentType/LoyaltyPage/Intro/index.tsx +++ /dev/null @@ -1,20 +0,0 @@ -import { PropsWithChildren } from "react" - -import MaxWidth from "@/components/MaxWidth" - -import styles from "./intro.module.css" - -export default async function Intro({ children }: PropsWithChildren) { - return ( - - - {children} - - - ) -} diff --git a/components/ContentType/LoyaltyPage/Intro/intro.module.css b/components/ContentType/LoyaltyPage/Intro/intro.module.css deleted file mode 100644 index f64d9daaa..000000000 --- a/components/ContentType/LoyaltyPage/Intro/intro.module.css +++ /dev/null @@ -1,10 +0,0 @@ -.content { - display: grid; - gap: var(--Spacing-x2); -} - -@media (min-width: 768px) { - .content { - gap: var(--Spacing-x3); - } -} diff --git a/components/ContentType/LoyaltyPage/index.tsx b/components/ContentType/LoyaltyPage/index.tsx index 68ebfae3c..252fca097 100644 --- a/components/ContentType/LoyaltyPage/index.tsx +++ b/components/ContentType/LoyaltyPage/index.tsx @@ -1,6 +1,7 @@ import { serverClient } from "@/lib/trpc/server" import Hero from "@/components/Hero" +import Intro from "@/components/Intro" import { Blocks } from "@/components/Loyalty/Blocks" import Sidebar from "@/components/Loyalty/Sidebar" import MaxWidth from "@/components/MaxWidth" @@ -8,8 +9,6 @@ import Preamble from "@/components/TempDesignSystem/Text/Preamble" import Title from "@/components/TempDesignSystem/Text/Title" import TrackingSDK from "@/components/TrackingSDK" -import Intro from "./Intro" - import styles from "./loyaltyPage.module.css" export default async function LoyaltyPage() { diff --git a/components/ContentType/ContentPage/Intro/index.tsx b/components/Intro/index.tsx similarity index 100% rename from components/ContentType/ContentPage/Intro/index.tsx rename to components/Intro/index.tsx diff --git a/components/ContentType/ContentPage/Intro/intro.module.css b/components/Intro/intro.module.css similarity index 100% rename from components/ContentType/ContentPage/Intro/intro.module.css rename to components/Intro/intro.module.css From 771338cc8080cb41a910ad75c163bf3bc544cfd5 Mon Sep 17 00:00:00 2001 From: Erik Tiekstra Date: Thu, 15 Aug 2024 14:19:52 +0200 Subject: [PATCH 36/53] fix(SW-190): added imageVaultAsset schema --- components/ContentType/ContentPage/index.tsx | 2 +- components/ContentType/LoyaltyPage/index.tsx | 2 +- .../LoyaltyCard/loyaltyCard.ts | 2 +- .../contentstack/contentPage/output.ts | 4 +- .../routers/contentstack/contentPage/query.ts | 2 +- .../contentstack/loyaltyPage/output.ts | 8 +- .../routers/contentstack/loyaltyPage/query.ts | 31 ++--- .../contentstack/schemas/imageVault.ts | 95 +++++++++++++++ types/components/imageContainer.ts | 2 +- types/components/imageVault.ts | 26 ++++ types/components/imageVaultImage.ts | 115 ------------------ types/requests/imageContainer.ts | 6 +- types/rte/attrs.ts | 6 +- .../trpc/routers/contentstack/contentPage.ts | 4 +- utils/imageVault.ts | 8 +- 15 files changed, 164 insertions(+), 149 deletions(-) create mode 100644 server/routers/contentstack/schemas/imageVault.ts create mode 100644 types/components/imageVault.ts delete mode 100644 types/components/imageVaultImage.ts diff --git a/components/ContentType/ContentPage/index.tsx b/components/ContentType/ContentPage/index.tsx index 699e378df..f2387febc 100644 --- a/components/ContentType/ContentPage/index.tsx +++ b/components/ContentType/ContentPage/index.tsx @@ -16,7 +16,7 @@ export default async function ContentPage() { } const { tracking, contentPage } = contentPageRes - const heroImage = contentPage.hero_image + const heroImage = contentPage.heroImage return ( <> diff --git a/components/ContentType/LoyaltyPage/index.tsx b/components/ContentType/LoyaltyPage/index.tsx index 252fca097..c63f9d3e3 100644 --- a/components/ContentType/LoyaltyPage/index.tsx +++ b/components/ContentType/LoyaltyPage/index.tsx @@ -19,7 +19,7 @@ export default async function LoyaltyPage() { } const { tracking, loyaltyPage } = loyaltyPageRes - const heroImage = loyaltyPage.hero_image + const heroImage = loyaltyPage.heroImage return ( <>
diff --git a/components/TempDesignSystem/LoyaltyCard/loyaltyCard.ts b/components/TempDesignSystem/LoyaltyCard/loyaltyCard.ts index 784d643ad..d5941ef5e 100644 --- a/components/TempDesignSystem/LoyaltyCard/loyaltyCard.ts +++ b/components/TempDesignSystem/LoyaltyCard/loyaltyCard.ts @@ -2,7 +2,7 @@ import { loyaltyCardVariants } from "./variants" import type { VariantProps } from "class-variance-authority" -import { ImageVaultAsset } from "@/types/components/imageVaultImage" +import { ImageVaultAsset } from "@/types/components/imageVault" export interface LoyaltyCardProps extends React.HTMLAttributes, diff --git a/server/routers/contentstack/contentPage/output.ts b/server/routers/contentstack/contentPage/output.ts index beb592c81..134a179f1 100644 --- a/server/routers/contentstack/contentPage/output.ts +++ b/server/routers/contentstack/contentPage/output.ts @@ -2,6 +2,8 @@ import { z } from "zod" import { Lang } from "@/constants/languages" +import { imageVaultAssetSchema } from "../schemas/imageVault" + export const validateContentPageSchema = z.object({ content_page: z.object({ title: z.string(), @@ -9,7 +11,7 @@ export const validateContentPageSchema = z.object({ heading: z.string(), preamble: z.string(), }), - hero_image: z.any().nullable(), + hero_image: imageVaultAssetSchema.nullable().optional(), system: z.object({ uid: z.string(), locale: z.nativeEnum(Lang), diff --git a/server/routers/contentstack/contentPage/query.ts b/server/routers/contentstack/contentPage/query.ts index 3f2cb3501..b49582be3 100644 --- a/server/routers/contentstack/contentPage/query.ts +++ b/server/routers/contentstack/contentPage/query.ts @@ -45,7 +45,7 @@ export const contentPageQueryRouter = router({ const contentPageData = validatedContentPage.data.content_page const contentPage: ContentPage = { ...contentPageData, - hero_image: makeImageVaultImage(contentPageData.hero_image), + heroImage: makeImageVaultImage(contentPageData.hero_image), } const tracking: TrackingSDKPageData = { diff --git a/server/routers/contentstack/loyaltyPage/output.ts b/server/routers/contentstack/loyaltyPage/output.ts index f89796ad9..fb6d5e6cc 100644 --- a/server/routers/contentstack/loyaltyPage/output.ts +++ b/server/routers/contentstack/loyaltyPage/output.ts @@ -2,7 +2,9 @@ import { z } from "zod" import { Lang } from "@/constants/languages" -import { ImageVaultAsset } from "@/types/components/imageVaultImage" +import { imageVaultAssetSchema } from "../schemas/imageVault" + +import { ImageVaultAsset } from "@/types/components/imageVault" import { JoinLoyaltyContactTypenameEnum, LoyaltyBlocksTypenameEnum, @@ -192,7 +194,7 @@ const loyaltyPageSidebarItem = z.discriminatedUnion("__typename", [ export const validateLoyaltyPageSchema = z.object({ heading: z.string().nullable(), preamble: z.string().nullable(), - hero_image: z.any().nullable(), + hero_image: imageVaultAssetSchema.nullable().optional(), blocks: z.array(loyaltyPageBlockItem).nullable(), sidebar: z.array(loyaltyPageSidebarItem).nullable(), system: z.object({ @@ -264,7 +266,7 @@ export type LoyaltyPage = Omit< LoyaltyPageDataRaw, "blocks" | "sidebar" | "hero_image" > & { - hero_image?: ImageVaultAsset + heroImage?: ImageVaultAsset blocks: Block[] sidebar: Sidebar[] } diff --git a/server/routers/contentstack/loyaltyPage/query.ts b/server/routers/contentstack/loyaltyPage/query.ts index 6839f1ced..264455fd1 100644 --- a/server/routers/contentstack/loyaltyPage/query.ts +++ b/server/routers/contentstack/loyaltyPage/query.ts @@ -85,6 +85,18 @@ export const loyaltyPageQueryRouter = router({ throw notFound(response) } + const validatedLoyaltyPage = validateLoyaltyPageSchema.safeParse( + response.data.loyalty_page + ) + + if (!validatedLoyaltyPage.success) { + console.error( + `Failed to validate Loyaltypage Data - (lang: ${lang}, uid: ${uid})` + ) + console.error(validatedLoyaltyPage.error) + return null + } + const blocks = response.data.loyalty_page.blocks ? response.data.loyalty_page.blocks.map((block: any) => { switch (block.__typename) { @@ -178,26 +190,17 @@ export const loyaltyPageQueryRouter = router({ }) : null - const loyaltyPage = { + console.log({ HERO_IMAGE: response.data.loyalty_page.hero_image }) + + const loyaltyPage: LoyaltyPage = { heading: response.data.loyalty_page.heading, preamble: response.data.loyalty_page.preamble, - hero_image: makeImageVaultImage(response.data.loyalty_page.hero_image), + heroImage: makeImageVaultImage(response.data.loyalty_page.hero_image), system: response.data.loyalty_page.system, blocks, sidebar, } - const validatedLoyaltyPage = - validateLoyaltyPageSchema.safeParse(loyaltyPage) - - if (!validatedLoyaltyPage.success) { - console.error( - `Failed to validate Loyaltypage Data - (lang: ${lang}, uid: ${uid})` - ) - console.error(validatedLoyaltyPage.error) - return null - } - const loyaltyTrackingData: TrackingSDKPageData = { pageId: response.data.loyalty_page.system.uid, lang: response.data.loyalty_page.system.locale as Lang, @@ -209,7 +212,7 @@ export const loyaltyPageQueryRouter = router({ // Assert LoyaltyPage type to get correct typings for RTE fields return { - loyaltyPage: validatedLoyaltyPage.data as LoyaltyPage, + loyaltyPage, tracking: loyaltyTrackingData, } }), diff --git a/server/routers/contentstack/schemas/imageVault.ts b/server/routers/contentstack/schemas/imageVault.ts new file mode 100644 index 000000000..87d76c9b0 --- /dev/null +++ b/server/routers/contentstack/schemas/imageVault.ts @@ -0,0 +1,95 @@ +import { z } from "zod" + +const metaData = z.object({ + DefinitionType: z.number().nullable().optional(), + Description: z.string().nullable(), + LanguageId: z.number().nullable(), + MetadataDefinitionId: z.number(), + Name: z.string(), + Value: z.string().nullable(), +}) + +/** + * Defines a media asset, original or conversion + */ +const mediaConversion = z.object({ + /** + * Aspect ratio of the conversion + */ + AspectRatio: z.number(), + /** + * Content type of the conversion + */ + ContentType: z.string(), + /** + * Aspect ratio of the selected/requested format + */ + FormatAspectRatio: z.number(), + /** + * Height of the selected/requested format + */ + FormatHeight: z.number(), + /** + * Width of the selected/requested format + */ + FormatWidth: z.number(), + /** + * Height, in pixels, of the conversion + */ + Height: z.number(), + /** + * Html representing the conversion + */ + Html: z.string(), + /** + * Id of the selected media format + */ + MediaFormatId: z.number(), + /** + * Name of the media format + */ + MediaFormatName: z.string(), + /** + * Name of the conversion + */ + Name: z.string(), + /** + * The url to the conversion + */ + Url: z.string(), + /** + * Width, in pixels, of the conversion + */ + Width: z.number(), +}) + +/** + * The response from ImageVault when inserting an asset + */ +export const imageVaultAssetSchema = z.object({ + /** + * The media item id of the asset + */ + Id: z.number(), + /** + * The id of the vault where the asset resides + */ + VaultId: z.number(), + /** + * The name of the asset + */ + Name: z.string(), + /** + * The conversion selected by the user. Is an array but will only contain one object + */ + MediaConversions: z.array(mediaConversion), + Metadata: z.array(metaData), + /** + * Date when the asset was added to ImageVault + */ + DateAdded: z.string(), + /** + * Name of the user that added the asset to ImageVault + */ + AddedBy: z.string(), +}) diff --git a/types/components/imageContainer.ts b/types/components/imageContainer.ts index 6f8e75393..bd4ea2ab1 100644 --- a/types/components/imageContainer.ts +++ b/types/components/imageContainer.ts @@ -1,4 +1,4 @@ -import type { ImageVaultAsset } from "./imageVaultImage" +import type { ImageVaultAsset } from "./imageVault" export type ImageContainerProps = { leftImage: ImageVaultAsset diff --git a/types/components/imageVault.ts b/types/components/imageVault.ts new file mode 100644 index 000000000..ceae6e278 --- /dev/null +++ b/types/components/imageVault.ts @@ -0,0 +1,26 @@ +/** + * @file TypeScript typings for ImageVault + * + * The types in this file are based on the source maps of the downloaded + * distribution at https://clientscript.imagevault.se/Installation/ImageVaultInsertMedia + * + * They have been clean up and amended to. + */ + +import { z } from "zod" + +import { imageVaultAssetSchema } from "@/server/routers/contentstack/schemas/imageVault" + +export type ImageVaultAssetResponse = z.infer + +export type ImageVaultAsset = { + id: number + title: string + url: string + dimensions: { + width: number + height: number + aspectRatio: number + } + meta: { alt: string | undefined | null; caption: string | undefined | null } +} diff --git a/types/components/imageVaultImage.ts b/types/components/imageVaultImage.ts deleted file mode 100644 index c543d3243..000000000 --- a/types/components/imageVaultImage.ts +++ /dev/null @@ -1,115 +0,0 @@ -/** - * @file TypeScript typings for ImageVault - * - * The types in this file are based on the source maps of the downloaded - * distribution at https://clientscript.imagevault.se/Installation/ImageVaultInsertMedia - * - * They have been clean up and amended to. - */ - -export type MetaData = { - DefinitionType?: number - Description: string | null - LanguageId: null - MetadataDefinitionId: number - Name: string - Value: string -} - -export type ImageVaultAsset = { - id: number - title: string - url: string - dimensions: { - width: number - height: number - aspectRatio: number - } - meta: { alt: string | undefined; caption: string | undefined } -} - -/** - * The response from ImageVault when inserting an asset - */ -export declare class InsertResponse { - /** - * The media item id of the asset - */ - Id: number - /** - * The id of the vault where the asset resides - */ - VaultId: number - /** - * The name of the asset - */ - Name: string - /** - * The conversion selected by the user. Is an array but will only contain one object - */ - MediaConversions: MediaConversion[] - /** - * Date when the asset was added to ImageVault - */ - DateAdded: string - /** - * Name of the user that added the asset to ImageVault - */ - AddedBy: string - - Metadata?: MetaData[] | undefined -} - -/** - * Defines a media asset, original or conversion - */ -export declare class MediaConversion { - /** - * The url to the conversion - */ - Url: string - /** - * Name of the conversion - */ - Name: string - /** - * Html representing the conversion - */ - Html: string - /** - * Content type of the conversion - */ - ContentType: string - /** - * Width, in pixels, of the conversion - */ - Width: number - /** - * Height, in pixels, of the conversion - */ - Height: number - /** - * Aspect ratio of the conversion - */ - AspectRatio: number - /** - * Width of the selected/requested format - */ - FormatWidth: number - /** - * Height of the selected/requested format - */ - FormatHeight: number - /** - * Aspect ratio of the selected/requested format - */ - FormatAspectRatio: number - /** - * Name of the media format - */ - MediaFormatName: string - /** - * Id of the selected media format - */ - MediaFormatId: number -} diff --git a/types/requests/imageContainer.ts b/types/requests/imageContainer.ts index 3eb85023c..2a604b948 100644 --- a/types/requests/imageContainer.ts +++ b/types/requests/imageContainer.ts @@ -1,11 +1,11 @@ -import { InsertResponse } from "../components/imageVaultImage" +import { ImageVaultAssetResponse } from "../components/imageVault" import { EmbedEnum } from "./utils/embeds" import { Typename } from "./utils/typename" export type ImageContainer = Typename< { - image_left: InsertResponse - image_right: InsertResponse + image_left: ImageVaultAssetResponse + image_right: ImageVaultAssetResponse system: { uid: string } diff --git a/types/rte/attrs.ts b/types/rte/attrs.ts index dd658872d..b99cca8b0 100644 --- a/types/rte/attrs.ts +++ b/types/rte/attrs.ts @@ -1,4 +1,4 @@ -import { InsertResponse } from "../components/imageVaultImage" +import { ImageVaultAssetResponse } from "../components/imageVault" import { RTEItemTypeEnum } from "./enums" import type { Lang } from "@/constants/languages" @@ -39,7 +39,9 @@ export interface RTELinkAttrs extends Attributes { type: RTEItemTypeEnum.entry } -export interface RTEImageVaultAttrs extends Attributes, InsertResponse { +export interface RTEImageVaultAttrs + extends Attributes, + ImageVaultAssetResponse { height: string width: string style: string[] diff --git a/types/trpc/routers/contentstack/contentPage.ts b/types/trpc/routers/contentstack/contentPage.ts index eafdcc710..d5a8cc322 100644 --- a/types/trpc/routers/contentstack/contentPage.ts +++ b/types/trpc/routers/contentstack/contentPage.ts @@ -2,12 +2,12 @@ import { z } from "zod" import { validateContentPageSchema } from "@/server/routers/contentstack/contentPage/output" -import { ImageVaultAsset } from "@/types/components/imageVaultImage" +import { ImageVaultAsset } from "@/types/components/imageVault" export type ContentPageDataRaw = z.infer type ContentPageRaw = ContentPageDataRaw["content_page"] export type ContentPage = Omit & { - hero_image?: ImageVaultAsset + heroImage?: ImageVaultAsset } diff --git a/utils/imageVault.ts b/utils/imageVault.ts index 9e8a0a414..d0dc7d086 100644 --- a/utils/imageVault.ts +++ b/utils/imageVault.ts @@ -1,10 +1,10 @@ import { ImageVaultAsset, - InsertResponse, -} from "@/types/components/imageVaultImage" + ImageVaultAssetResponse, +} from "@/types/components/imageVault" export function insertResponseToImageVaultAsset( - response: InsertResponse + response: ImageVaultAssetResponse ): ImageVaultAsset { const alt = response.Metadata?.find((meta) => meta.Name.includes("AltText_") @@ -32,6 +32,6 @@ export function insertResponseToImageVaultAsset( export function makeImageVaultImage(image: any) { return image && !!Object.keys(image).length - ? insertResponseToImageVaultAsset(image as InsertResponse) + ? insertResponseToImageVaultAsset(image as ImageVaultAssetResponse) : undefined } From 8c76cf62c73e44b6edf7329b16198a8a8c08837c Mon Sep 17 00:00:00 2001 From: Erik Tiekstra Date: Fri, 16 Aug 2024 09:04:16 +0200 Subject: [PATCH 37/53] fix(SW-190): reverted changes in MaxWidth --- components/ContentType/LoyaltyPage/index.tsx | 2 +- components/Intro/index.tsx | 15 +++----------- components/Intro/intro.module.css | 5 +++++ components/MaxWidth/index.tsx | 14 +++++-------- components/MaxWidth/maxWidth.module.css | 19 +----------------- components/MaxWidth/variants.ts | 21 -------------------- types/components/max-width.ts | 8 +------- 7 files changed, 16 insertions(+), 68 deletions(-) delete mode 100644 components/MaxWidth/variants.ts diff --git a/components/ContentType/LoyaltyPage/index.tsx b/components/ContentType/LoyaltyPage/index.tsx index c63f9d3e3..d28a3640c 100644 --- a/components/ContentType/LoyaltyPage/index.tsx +++ b/components/ContentType/LoyaltyPage/index.tsx @@ -27,7 +27,7 @@ export default async function LoyaltyPage() { ) : null} - +
{loyaltyPage.heading} diff --git a/components/Intro/index.tsx b/components/Intro/index.tsx index 5ad4db448..6c7a8a30d 100644 --- a/components/Intro/index.tsx +++ b/components/Intro/index.tsx @@ -1,20 +1,11 @@ import { PropsWithChildren } from "react" -import MaxWidth from "@/components/MaxWidth" - import styles from "./intro.module.css" export default async function Intro({ children }: PropsWithChildren) { return ( - - - {children} - - +
+
{children}
+
) } diff --git a/components/Intro/intro.module.css b/components/Intro/intro.module.css index f64d9daaa..5471f9aaa 100644 --- a/components/Intro/intro.module.css +++ b/components/Intro/intro.module.css @@ -1,3 +1,8 @@ +.intro { + max-width: 74.75rem; + margin: 0 auto; +} + .content { display: grid; gap: var(--Spacing-x2); diff --git a/components/MaxWidth/index.tsx b/components/MaxWidth/index.tsx index 0047d0dd2..495447a76 100644 --- a/components/MaxWidth/index.tsx +++ b/components/MaxWidth/index.tsx @@ -1,21 +1,17 @@ "use client" +import { cva } from "class-variance-authority" -import { maxWidthVariants } from "./variants" +import styles from "./maxWidth.module.css" import type { MaxWidthProps } from "@/types/components/max-width" +const maxWidthVariants = cva(styles.container) + export default function MaxWidth({ className, tag = "section", - variant, - align, ...props }: MaxWidthProps) { const Cmp = tag - return ( - - ) + return } diff --git a/components/MaxWidth/maxWidth.module.css b/components/MaxWidth/maxWidth.module.css index d13a093bd..563ae5721 100644 --- a/components/MaxWidth/maxWidth.module.css +++ b/components/MaxWidth/maxWidth.module.css @@ -1,21 +1,4 @@ .container { - position: relative; - max-width: 100%; - width: 100%; -} - -.container.default { max-width: var(--max-width, 1140px); -} - -.container.text { - max-width: 49.5rem; -} - -.container.content { - max-width: 74.75rem; -} - -.container.center { - margin: 0 auto; + position: relative; } diff --git a/components/MaxWidth/variants.ts b/components/MaxWidth/variants.ts deleted file mode 100644 index f7c917b5f..000000000 --- a/components/MaxWidth/variants.ts +++ /dev/null @@ -1,21 +0,0 @@ -import { cva } from "class-variance-authority" - -import styles from "./maxWidth.module.css" - -export const maxWidthVariants = cva(styles.container, { - variants: { - variant: { - text: styles.text, - content: styles.content, - default: styles.default, - }, - align: { - center: styles.center, - left: styles.left, - }, - }, - defaultVariants: { - variant: "default", - align: "center", - }, -}) diff --git a/types/components/max-width.ts b/types/components/max-width.ts index 4a99b2d99..e1327b581 100644 --- a/types/components/max-width.ts +++ b/types/components/max-width.ts @@ -1,9 +1,3 @@ -import { VariantProps } from "class-variance-authority" - -import { maxWidthVariants } from "@/components/MaxWidth/variants" - -export interface MaxWidthProps - extends React.HTMLAttributes, - VariantProps { +export interface MaxWidthProps extends React.HTMLAttributes { tag?: "article" | "div" | "main" | "section" } From 56312335b2b4b583053c3c9aefca785dd54cd521 Mon Sep 17 00:00:00 2001 From: Erik Tiekstra Date: Fri, 16 Aug 2024 09:07:13 +0200 Subject: [PATCH 38/53] fix(SW-190): added linkConnectionNode as variable for readability --- server/routers/contentstack/loyaltyPage/utils.ts | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/server/routers/contentstack/loyaltyPage/utils.ts b/server/routers/contentstack/loyaltyPage/utils.ts index 1b473210a..54da06669 100644 --- a/server/routers/contentstack/loyaltyPage/utils.ts +++ b/server/routers/contentstack/loyaltyPage/utils.ts @@ -84,18 +84,19 @@ export function makeButtonObject(button: any) { const isContenstackLink = button?.is_contentstack_link || button.linkConnection?.edges?.length + const linkConnnectionNode = button.linkConnection.edges[0].node return { openInNewTab: button?.open_in_new_tab, title: button.cta_text || (isContenstackLink - ? button.linkConnection.edges[0].node.title + ? linkConnnectionNode.title : button.external_link.title), href: isContenstackLink - ? button.linkConnection.edges[0].node.web?.original_url || + ? linkConnnectionNode.web?.original_url || removeMultipleSlashes( - `/${button.linkConnection.edges[0].node.system.locale}/${button.linkConnection.edges[0].node.url}` + `/${linkConnnectionNode.system.locale}/${linkConnnectionNode.url}` ) : button.external_link.href, isExternal: !isContenstackLink, From b2b7f4f85ad9d2017ba82f4175878cc32a922f29 Mon Sep 17 00:00:00 2001 From: Erik Tiekstra Date: Fri, 16 Aug 2024 10:14:45 +0200 Subject: [PATCH 39/53] fix(SW-190): added cache for content page query and added breadcrumbs fetching for content page --- components/MyPages/Breadcrumbs/index.tsx | 1 + .../Refs/ContentPage/Breadcrumbs.graphql | 38 ++++++++++++ .../{ => ContentPage}/ContentPage.graphql | 2 +- lib/graphql/Query/AccountPage.graphql | 2 +- .../Query/BreadcrumbsContentPage.graphql | 21 +++++++ lib/graphql/Query/ContentPage.graphql | 3 - lib/graphql/Query/LoyaltyPage.graphql | 4 +- lib/graphql/Query/NavigationMyPages.graphql | 2 +- .../contentstack/breadcrumbs/output.ts | 18 +++++- .../routers/contentstack/breadcrumbs/query.ts | 58 +++++++++++++++++++ .../routers/contentstack/contentPage/query.ts | 16 +++-- 11 files changed, 151 insertions(+), 14 deletions(-) create mode 100644 lib/graphql/Fragments/Refs/ContentPage/Breadcrumbs.graphql rename lib/graphql/Fragments/Refs/{ => ContentPage}/ContentPage.graphql (72%) create mode 100644 lib/graphql/Query/BreadcrumbsContentPage.graphql diff --git a/components/MyPages/Breadcrumbs/index.tsx b/components/MyPages/Breadcrumbs/index.tsx index 3fccd6f25..752f77d5f 100644 --- a/components/MyPages/Breadcrumbs/index.tsx +++ b/components/MyPages/Breadcrumbs/index.tsx @@ -11,6 +11,7 @@ export default async function Breadcrumbs() { if (!breadcrumbs?.length) { return null } + const homeBreadcrumb = breadcrumbs.shift() return (
{maskedCardNumber}{maskedCardNumber}