feat(SW-1273): find my booking page with rudimentary validation and redirect

This commit is contained in:
Christian Andolf
2025-02-25 15:27:40 +01:00
parent 7711f78ab3
commit 85cd815968
19 changed files with 324 additions and 5 deletions

View File

@@ -0,0 +1,12 @@
.main {
width: var(--max-width-content);
padding: var(--Spacing-x5) var(--Spacing-x1);
margin-left: auto;
margin-right: auto;
}
.form {
max-width: 640px;
margin-left: auto;
margin-right: auto;
}

View File

@@ -0,0 +1,13 @@
import FindMyBooking from "@/components/HotelReservation/FindMyBooking"
import styles from "./page.module.css"
export default async function GetBookingPage() {
return (
<main className={styles.main}>
<div className={styles.form}>
<FindMyBooking />
</div>
</main>
)
}

View File

@@ -2,6 +2,7 @@
import { usePathname } from "next/navigation" import { usePathname } from "next/navigation"
import { useIntl } from "react-intl" import { useIntl } from "react-intl"
import { findMyBooking } from "@/constants/myBooking"
import { logout } from "@/constants/routes/handleAuth" import { logout } from "@/constants/routes/handleAuth"
import { myPages } from "@/constants/routes/myPages" import { myPages } from "@/constants/routes/myPages"
import useDropdownStore from "@/stores/main-menu" import useDropdownStore from "@/stores/main-menu"
@@ -147,7 +148,10 @@ export function MainMenu({
<span className={styles.mobileSeparator} /> <span className={styles.mobileSeparator} />
</li> </li>
<li className={styles.mobileLinkRow}> <li className={styles.mobileLinkRow}>
<a className={styles.mobileLinkButton} href=""> <a
className={styles.mobileLinkButton}
href={findMyBooking[lang]}
>
{intl.formatMessage({ id: "Find booking" })} {intl.formatMessage({ id: "Find booking" })}
</a> </a>
</li> </li>

View File

@@ -5,10 +5,12 @@ import { Dialog, Modal } from "react-aria-components"
import { useIntl } from "react-intl" import { useIntl } from "react-intl"
import { useMediaQuery } from "usehooks-ts" import { useMediaQuery } from "usehooks-ts"
import { findMyBooking } from "@/constants/myBooking"
import useDropdownStore from "@/stores/main-menu" import useDropdownStore from "@/stores/main-menu"
import LanguageSwitcher from "@/components/LanguageSwitcher" import LanguageSwitcher from "@/components/LanguageSwitcher"
import { useHandleKeyUp } from "@/hooks/useHandleKeyUp" import { useHandleKeyUp } from "@/hooks/useHandleKeyUp"
import useLang from "@/hooks/useLang"
import HeaderLink from "../../HeaderLink" import HeaderLink from "../../HeaderLink"
import TopLink from "../../TopLink" import TopLink from "../../TopLink"
@@ -24,6 +26,7 @@ export default function MobileMenu({
topLink, topLink,
isLoggedIn, isLoggedIn,
}: React.PropsWithChildren<MobileMenuProps>) { }: React.PropsWithChildren<MobileMenuProps>) {
const lang = useLang()
const intl = useIntl() const intl = useIntl()
const { const {
toggleDropdown, toggleDropdown,
@@ -83,7 +86,7 @@ export default function MobileMenu({
> >
{children} {children}
<footer className={styles.footer}> <footer className={styles.footer}>
<HeaderLink href="#" iconName={IconName.Search}> <HeaderLink href={findMyBooking[lang]} iconName={IconName.Search}>
{intl.formatMessage({ id: "Find booking" })} {intl.formatMessage({ id: "Find booking" })}
</HeaderLink> </HeaderLink>
<TopLink isLoggedIn={isLoggedIn} topLink={topLink} iconSize={20} /> <TopLink isLoggedIn={isLoggedIn} topLink={topLink} iconSize={20} />

View File

@@ -1,3 +1,4 @@
import { findMyBooking } from "@/constants/myBooking"
import { getHeader } from "@/lib/trpc/memoizedRequests" import { getHeader } from "@/lib/trpc/memoizedRequests"
import { auth } from "@/auth" import { auth } from "@/auth"
@@ -5,6 +6,7 @@ import LanguageSwitcher from "@/components/LanguageSwitcher"
import SkeletonShimmer from "@/components/SkeletonShimmer" import SkeletonShimmer from "@/components/SkeletonShimmer"
import Caption from "@/components/TempDesignSystem/Text/Caption" import Caption from "@/components/TempDesignSystem/Text/Caption"
import { getIntl } from "@/i18n" import { getIntl } from "@/i18n"
import { getLang } from "@/i18n/serverContext"
import { isValidSession } from "@/utils/session" import { isValidSession } from "@/utils/session"
import HeaderLink from "../HeaderLink" import HeaderLink from "../HeaderLink"
@@ -18,6 +20,7 @@ export default async function TopMenu() {
// cached // cached
const intl = await getIntl() const intl = await getIntl()
// both preloaded // both preloaded
const lang = getLang()
const header = await getHeader() const header = await getHeader()
const session = await auth() const session = await auth()
const isLoggedIn = isValidSession(session) const isLoggedIn = isValidSession(session)
@@ -34,7 +37,7 @@ export default async function TopMenu() {
<LanguageSwitcher type="desktopHeader" /> <LanguageSwitcher type="desktopHeader" />
<Caption type="regular" color="textMediumContrast" asChild> <Caption type="regular" color="textMediumContrast" asChild>
<HeaderLink href="#" iconName={IconName.Search}> <HeaderLink href={findMyBooking[lang]} iconName={IconName.Search}>
{intl.formatMessage({ id: "Find booking" })} {intl.formatMessage({ id: "Find booking" })}
</HeaderLink> </HeaderLink>
</Caption> </Caption>

View File

@@ -24,3 +24,12 @@ export const myBooking = {
sv: "https://test.scandichotels.se/hotelreservation/din-bokning", sv: "https://test.scandichotels.se/hotelreservation/din-bokning",
}, },
} }
export const findMyBooking = {
da: "/da/hotelreservation/hent-booking",
de: "/de/hotelreservation/mein-bereich",
en: "/en/hotelreservation/get-booking",
fi: "/fi/hotelreservation/hae-varaus",
no: "/no/hotelreservation/get-booking",
sv: "/sv/hotelreservation/hitta-bokning",
}

View File

@@ -107,6 +107,7 @@
"By signing up you accept the Scandic Friends <termsAndConditionsLink>Terms and Conditions</termsAndConditionsLink>. Your membership is valid until further notice, and you can terminate your membership at any time by sending an email to Scandic's customer service": "Ved at tilmelde dig accepterer du Scandic Friends <termsAndConditionsLink>vilkår og betingelser</termsAndConditionsLink>. Dit medlemskab er gyldigt indtil videre, og du kan til enhver tid opsige dit medlemskab ved at sende en e-mail til Scandics kundeservice", "By signing up you accept the Scandic Friends <termsAndConditionsLink>Terms and Conditions</termsAndConditionsLink>. Your membership is valid until further notice, and you can terminate your membership at any time by sending an email to Scandic's customer service": "Ved at tilmelde dig accepterer du Scandic Friends <termsAndConditionsLink>vilkår og betingelser</termsAndConditionsLink>. Dit medlemskab er gyldigt indtil videre, og du kan til enhver tid opsige dit medlemskab ved at sende en e-mail til Scandics kundeservice",
"Cabaret seating": "Cabaret seating", "Cabaret seating": "Cabaret seating",
"Campaign": "Kampagne", "Campaign": "Kampagne",
"Can't find your stay?": "Kan du ikke finde dit ophold?",
"Cancel": "Afbestille", "Cancel": "Afbestille",
"Cancel stay": "Annuller ophold", "Cancel stay": "Annuller ophold",
"Cancellation cost": "Annulleret pris", "Cancellation cost": "Annulleret pris",
@@ -227,9 +228,11 @@
"Filter": "Filter", "Filter": "Filter",
"Filter and sort": "Filtrer og sorter", "Filter and sort": "Filtrer og sorter",
"Filter by": "Filtrer efter", "Filter by": "Filtrer efter",
"Find": "Finde",
"Find booking": "Find booking", "Find booking": "Find booking",
"Find hotels": "Find hotel", "Find hotels": "Find hotel",
"Find hotels and destinations": "Find hoteller og destinationer", "Find hotels and destinations": "Find hoteller og destinationer",
"Find your stay": "Find dit ophold",
"First name": "Fornavn", "First name": "Fornavn",
"First name can't contain any special characters": "Fornavn kan ikke indeholde specielle tegn", "First name can't contain any special characters": "Fornavn kan ikke indeholde specielle tegn",
"First name is required": "Fornavn er påkrævet", "First name is required": "Fornavn er påkrævet",
@@ -469,6 +472,7 @@
"Phone": "Telefon", "Phone": "Telefon",
"Phone is required": "Telefonnummer er påkrævet", "Phone is required": "Telefonnummer er påkrævet",
"Phone number": "Telefonnummer", "Phone number": "Telefonnummer",
"Please call customer service at {phoneNr}": "Ring venligst til kundeservice på {phoneNr}",
"Please enter a valid phone number": "Indtast venligst et gyldigt telefonnummer", "Please enter a valid phone number": "Indtast venligst et gyldigt telefonnummer",
"Please enter the code sent to <maskedContactInfo></maskedContactInfo> in order to confirm your account linking.": "Please enter the code sent to <maskedContactInfo></maskedContactInfo> in order to confirm your account linking.", "Please enter the code sent to <maskedContactInfo></maskedContactInfo> in order to confirm your account linking.": "Please enter the code sent to <maskedContactInfo></maskedContactInfo> in order to confirm your account linking.",
"Please enter the code sent to <maskedContactInfo></maskedContactInfo> in order to unlink your accounts.": "Please enter the code sent to <maskedContactInfo></maskedContactInfo> in order to unlink your accounts.", "Please enter the code sent to <maskedContactInfo></maskedContactInfo> in order to unlink your accounts.": "Please enter the code sent to <maskedContactInfo></maskedContactInfo> in order to unlink your accounts.",
@@ -653,6 +657,7 @@
"View all": "Vis alle", "View all": "Vis alle",
"View all hotels in {country}": "Se alle hoteller i {country}", "View all hotels in {country}": "Se alle hoteller i {country}",
"View and buy add-ons": "View and buy add-ons", "View and buy add-ons": "View and buy add-ons",
"View and manage your stay made on Scandic's website": "Se og administrer dit ophold på Scandics hjemmeside",
"View as list": "Vis som liste", "View as list": "Vis som liste",
"View as map": "Vis som kort", "View as map": "Vis som kort",
"View room details": "View room details", "View room details": "View room details",

View File

@@ -108,6 +108,7 @@
"By signing up you accept the Scandic Friends <termsAndConditionsLink>Terms and Conditions</termsAndConditionsLink>. Your membership is valid until further notice, and you can terminate your membership at any time by sending an email to Scandic's customer service": "Mit Ihrer Anmeldung akzeptieren Sie die <termsAndConditionsLink>Allgemeinen Geschäftsbedingungen</termsAndConditionsLink> von Scandic Friends. Ihre Mitgliedschaft ist bis auf Weiteres gültig und Sie können sie jederzeit kündigen, indem Sie eine E-Mail an den Kundenservice von Scandic senden.", "By signing up you accept the Scandic Friends <termsAndConditionsLink>Terms and Conditions</termsAndConditionsLink>. Your membership is valid until further notice, and you can terminate your membership at any time by sending an email to Scandic's customer service": "Mit Ihrer Anmeldung akzeptieren Sie die <termsAndConditionsLink>Allgemeinen Geschäftsbedingungen</termsAndConditionsLink> von Scandic Friends. Ihre Mitgliedschaft ist bis auf Weiteres gültig und Sie können sie jederzeit kündigen, indem Sie eine E-Mail an den Kundenservice von Scandic senden.",
"Cabaret seating": "Cabaret seating", "Cabaret seating": "Cabaret seating",
"Campaign": "Kampagne", "Campaign": "Kampagne",
"Can't find your stay?": "Sie können Ihren Aufenthalt nicht finden?",
"Cancel": "Stornieren", "Cancel": "Stornieren",
"Cancel stay": "Stornieren", "Cancel stay": "Stornieren",
"Cancellation cost": "Stornierungskosten", "Cancellation cost": "Stornierungskosten",
@@ -228,9 +229,11 @@
"Filter": "Filter", "Filter": "Filter",
"Filter and sort": "Filtern und sortieren", "Filter and sort": "Filtern und sortieren",
"Filter by": "Filtern nach", "Filter by": "Filtern nach",
"Find": "Finden",
"Find booking": "Buchung finden", "Find booking": "Buchung finden",
"Find hotels": "Hotels finden", "Find hotels": "Hotels finden",
"Find hotels and destinations": "Finden Sie Hotels und Reiseziele", "Find hotels and destinations": "Finden Sie Hotels und Reiseziele",
"Find your stay": "Finden Sie Ihren Aufenthalt",
"First name": "Vorname", "First name": "Vorname",
"First name can't contain any special characters": "Der Vorname darf keine Sonderzeichen enthalten", "First name can't contain any special characters": "Der Vorname darf keine Sonderzeichen enthalten",
"First name is required": "Vorname ist erforderlich", "First name is required": "Vorname ist erforderlich",
@@ -471,6 +474,7 @@
"Phone": "Telefon", "Phone": "Telefon",
"Phone is required": "Telefon ist erforderlich", "Phone is required": "Telefon ist erforderlich",
"Phone number": "Telefonnummer", "Phone number": "Telefonnummer",
"Please call customer service at {phoneNr}": "Bitte rufen Sie den Kundendienst unter {phoneNr} an",
"Please enter a valid phone number": "Bitte geben Sie eine gültige Telefonnummer ein", "Please enter a valid phone number": "Bitte geben Sie eine gültige Telefonnummer ein",
"Please enter the code sent to <maskedContactInfo></maskedContactInfo> in order to confirm your account linking.": "Please enter the code sent to <maskedContactInfo></maskedContactInfo> in order to confirm your account linking.", "Please enter the code sent to <maskedContactInfo></maskedContactInfo> in order to confirm your account linking.": "Please enter the code sent to <maskedContactInfo></maskedContactInfo> in order to confirm your account linking.",
"Please enter the code sent to <maskedContactInfo></maskedContactInfo> in order to unlink your accounts.": "Please enter the code sent to <maskedContactInfo></maskedContactInfo> in order to unlink your accounts.", "Please enter the code sent to <maskedContactInfo></maskedContactInfo> in order to unlink your accounts.": "Please enter the code sent to <maskedContactInfo></maskedContactInfo> in order to unlink your accounts.",
@@ -654,6 +658,7 @@
"View all": "Alle anzeigen", "View all": "Alle anzeigen",
"View all hotels in {country}": "Alle Hotels in {country} anzeigen", "View all hotels in {country}": "Alle Hotels in {country} anzeigen",
"View and buy add-ons": "View and buy add-ons", "View and buy add-ons": "View and buy add-ons",
"View and manage your stay made on Scandic's website": "Sehen und verwalten Sie Ihren Aufenthalt auf der Website von Scandic",
"View as list": "Als Liste anzeigen", "View as list": "Als Liste anzeigen",
"View as map": "Als Karte anzeigen", "View as map": "Als Karte anzeigen",
"View room details": "View room details", "View room details": "View room details",

View File

@@ -107,6 +107,7 @@
"By signing up you accept the Scandic Friends <termsAndConditionsLink>Terms and Conditions</termsAndConditionsLink>. Your membership is valid until further notice, and you can terminate your membership at any time by sending an email to Scandic's customer service": "By signing up you accept the Scandic Friends <termsAndConditionsLink>Terms and Conditions</termsAndConditionsLink>. Your membership is valid until further notice, and you can terminate your membership at any time by sending an email to Scandic's customer service", "By signing up you accept the Scandic Friends <termsAndConditionsLink>Terms and Conditions</termsAndConditionsLink>. Your membership is valid until further notice, and you can terminate your membership at any time by sending an email to Scandic's customer service": "By signing up you accept the Scandic Friends <termsAndConditionsLink>Terms and Conditions</termsAndConditionsLink>. Your membership is valid until further notice, and you can terminate your membership at any time by sending an email to Scandic's customer service",
"Cabaret seating": "Cabaret seating", "Cabaret seating": "Cabaret seating",
"Campaign": "Campaign", "Campaign": "Campaign",
"Can't find your stay?": "Can't find your stay?",
"Cancel": "Cancel", "Cancel": "Cancel",
"Cancel booking": "Cancel booking", "Cancel booking": "Cancel booking",
"Cancel stay": "Cancel stay", "Cancel stay": "Cancel stay",
@@ -229,9 +230,11 @@
"Filter": "Filter", "Filter": "Filter",
"Filter and sort": "Filter and sort", "Filter and sort": "Filter and sort",
"Filter by": "Filter by", "Filter by": "Filter by",
"Find": "Find",
"Find booking": "Find booking", "Find booking": "Find booking",
"Find hotels": "Find hotels", "Find hotels": "Find hotels",
"Find hotels and destinations": "Find hotels and destinations", "Find hotels and destinations": "Find hotels and destinations",
"Find your stay": "Find your stay",
"First name": "First name", "First name": "First name",
"First name can't contain any special characters": "First name can't contain any special characters", "First name can't contain any special characters": "First name can't contain any special characters",
"First name is required": "First name is required", "First name is required": "First name is required",
@@ -473,6 +476,7 @@
"Phone": "Phone", "Phone": "Phone",
"Phone is required": "Phone is required", "Phone is required": "Phone is required",
"Phone number": "Phone number", "Phone number": "Phone number",
"Please call customer service at {phoneNr}": "Please call customer service at {phoneNr}",
"Please enter a valid phone number": "Please enter a valid phone number", "Please enter a valid phone number": "Please enter a valid phone number",
"Please enter the code sent to <maskedContactInfo></maskedContactInfo> in order to confirm your account linking.": "Please enter the code sent to <maskedContactInfo></maskedContactInfo> in order to confirm your account linking.", "Please enter the code sent to <maskedContactInfo></maskedContactInfo> in order to confirm your account linking.": "Please enter the code sent to <maskedContactInfo></maskedContactInfo> in order to confirm your account linking.",
"Please enter the code sent to <maskedContactInfo></maskedContactInfo> in order to unlink your accounts.": "Please enter the code sent to <maskedContactInfo></maskedContactInfo> in order to unlink your accounts.", "Please enter the code sent to <maskedContactInfo></maskedContactInfo> in order to unlink your accounts.": "Please enter the code sent to <maskedContactInfo></maskedContactInfo> in order to unlink your accounts.",
@@ -659,6 +663,7 @@
"View all": "View all", "View all": "View all",
"View all hotels in {country}": "View all hotels in {country}", "View all hotels in {country}": "View all hotels in {country}",
"View and buy add-ons": "View and buy add-ons", "View and buy add-ons": "View and buy add-ons",
"View and manage your stay made on Scandic's website": "View and manage your stay made on Scandic's website",
"View as list": "View as list", "View as list": "View as list",
"View as map": "View as map", "View as map": "View as map",
"View room details": "View room details", "View room details": "View room details",

View File

@@ -106,6 +106,7 @@
"By signing up you accept the Scandic Friends <termsAndConditionsLink>Terms and Conditions</termsAndConditionsLink>. Your membership is valid until further notice, and you can terminate your membership at any time by sending an email to Scandic's customer service": "Rekisteröitymällä hyväksyt Scandic Friendsin <termsAndConditionsLink>käyttöehdot</termsAndConditionsLink>. Jäsenyytesi on voimassa toistaiseksi ja voit lopettaa jäsenyytesi milloin tahansa lähettämällä sähköpostia Scandicin asiakaspalveluun", "By signing up you accept the Scandic Friends <termsAndConditionsLink>Terms and Conditions</termsAndConditionsLink>. Your membership is valid until further notice, and you can terminate your membership at any time by sending an email to Scandic's customer service": "Rekisteröitymällä hyväksyt Scandic Friendsin <termsAndConditionsLink>käyttöehdot</termsAndConditionsLink>. Jäsenyytesi on voimassa toistaiseksi ja voit lopettaa jäsenyytesi milloin tahansa lähettämällä sähköpostia Scandicin asiakaspalveluun",
"Cabaret seating": "Cabaret seating", "Cabaret seating": "Cabaret seating",
"Campaign": "Kampanja", "Campaign": "Kampanja",
"Can't find your stay?": "Etkö löydä majoitusta?",
"Cancel": "Peruuttaa", "Cancel": "Peruuttaa",
"Cancel stay": "Peruuta majoitus", "Cancel stay": "Peruuta majoitus",
"Cancellation cost": "Peruutusmaksu", "Cancellation cost": "Peruutusmaksu",
@@ -227,9 +228,11 @@
"Filter": "Suodatin", "Filter": "Suodatin",
"Filter and sort": "Suodata ja lajittele", "Filter and sort": "Suodata ja lajittele",
"Filter by": "Suodatusperuste", "Filter by": "Suodatusperuste",
"Find": "Löytää",
"Find booking": "Etsi varaus", "Find booking": "Etsi varaus",
"Find hotels": "Etsi hotelleja", "Find hotels": "Etsi hotelleja",
"Find hotels and destinations": "Etsi hotelleja ja kohteita", "Find hotels and destinations": "Etsi hotelleja ja kohteita",
"Find your stay": "Etsi majoitus",
"First name": "Etunimi", "First name": "Etunimi",
"First name can't contain any special characters": "Etunimi ei voi sisältää erikoismerkkejä", "First name can't contain any special characters": "Etunimi ei voi sisältää erikoismerkkejä",
"First name is required": "Etunimi vaaditaan", "First name is required": "Etunimi vaaditaan",
@@ -470,6 +473,7 @@
"Phone": "Puhelin", "Phone": "Puhelin",
"Phone is required": "Puhelin vaaditaan", "Phone is required": "Puhelin vaaditaan",
"Phone number": "Puhelinnumero", "Phone number": "Puhelinnumero",
"Please call customer service at {phoneNr}": "Soita asiakaspalveluun numeroon {phoneNr}",
"Please enter a valid phone number": "Ole hyvä ja näppäile voimassaoleva puhelinnumero", "Please enter a valid phone number": "Ole hyvä ja näppäile voimassaoleva puhelinnumero",
"Please enter the code sent to <maskedContactInfo></maskedContactInfo> in order to confirm your account linking.": "Please enter the code sent to <maskedContactInfo></maskedContactInfo> in order to confirm your account linking.", "Please enter the code sent to <maskedContactInfo></maskedContactInfo> in order to confirm your account linking.": "Please enter the code sent to <maskedContactInfo></maskedContactInfo> in order to confirm your account linking.",
"Please enter the code sent to <maskedContactInfo></maskedContactInfo> in order to unlink your accounts.": "Please enter the code sent to <maskedContactInfo></maskedContactInfo> in order to unlink your accounts.", "Please enter the code sent to <maskedContactInfo></maskedContactInfo> in order to unlink your accounts.": "Please enter the code sent to <maskedContactInfo></maskedContactInfo> in order to unlink your accounts.",
@@ -654,6 +658,7 @@
"View all": "Näytä kaikki", "View all": "Näytä kaikki",
"View all hotels in {country}": "Näytä kaikki hotellit maassa {country}", "View all hotels in {country}": "Näytä kaikki hotellit maassa {country}",
"View and buy add-ons": "View and buy add-ons", "View and buy add-ons": "View and buy add-ons",
"View and manage your stay made on Scandic's website": "Tarkastele ja hallinnoi yöpymistäsi Scandicin verkkosivuilla",
"View as list": "Näytä listana", "View as list": "Näytä listana",
"View as map": "Näytä kartalla", "View as map": "Näytä kartalla",
"View room details": "View room details", "View room details": "View room details",

View File

@@ -106,6 +106,7 @@
"By signing up you accept the Scandic Friends <termsAndConditionsLink>Terms and Conditions</termsAndConditionsLink>. Your membership is valid until further notice, and you can terminate your membership at any time by sending an email to Scandic's customer service": "Ved å registrere deg godtar du Scandic Friends <termsAndConditionsLink>vilkår og betingelser</termsAndConditionsLink>. Medlemskapet ditt er gyldig inntil videre, og du kan si opp medlemskapet ditt når som helst ved å sende en e-post til Scandics kundeservice", "By signing up you accept the Scandic Friends <termsAndConditionsLink>Terms and Conditions</termsAndConditionsLink>. Your membership is valid until further notice, and you can terminate your membership at any time by sending an email to Scandic's customer service": "Ved å registrere deg godtar du Scandic Friends <termsAndConditionsLink>vilkår og betingelser</termsAndConditionsLink>. Medlemskapet ditt er gyldig inntil videre, og du kan si opp medlemskapet ditt når som helst ved å sende en e-post til Scandics kundeservice",
"Cabaret seating": "Cabaret seating", "Cabaret seating": "Cabaret seating",
"Campaign": "Kampanje", "Campaign": "Kampanje",
"Can't find your stay?": "Finner du ikke oppholdet ditt?",
"Cancel": "Avbryt", "Cancel": "Avbryt",
"Cancel stay": "Avbryt opphold", "Cancel stay": "Avbryt opphold",
"Cancellation cost": "Annulleret pris", "Cancellation cost": "Annulleret pris",
@@ -226,9 +227,11 @@
"Filter": "Filter", "Filter": "Filter",
"Filter and sort": "Filtrer og sorter", "Filter and sort": "Filtrer og sorter",
"Filter by": "Filtrer etter", "Filter by": "Filtrer etter",
"Find": "Finne",
"Find booking": "Finn booking", "Find booking": "Finn booking",
"Find hotels": "Finn hotell", "Find hotels": "Finn hotell",
"Find hotels and destinations": "Finn hoteller og destinasjoner", "Find hotels and destinations": "Finn hoteller og destinasjoner",
"Find your stay": "Finn ditt opphold",
"First name": "Fornavn", "First name": "Fornavn",
"First name can't contain any special characters": "Fornavn kan ikke inneholde spesielle tegn", "First name can't contain any special characters": "Fornavn kan ikke inneholde spesielle tegn",
"First name is required": "Fornavn kreves", "First name is required": "Fornavn kreves",
@@ -467,6 +470,7 @@
"Phone": "Telefon", "Phone": "Telefon",
"Phone is required": "Telefon kreves", "Phone is required": "Telefon kreves",
"Phone number": "Telefonnummer", "Phone number": "Telefonnummer",
"Please call customer service at {phoneNr}": "Ring kundeservice på {phoneNr}",
"Please enter a valid phone number": "Vennligst oppgi et gyldig telefonnummer", "Please enter a valid phone number": "Vennligst oppgi et gyldig telefonnummer",
"Please enter the code sent to <maskedContactInfo></maskedContactInfo> in order to confirm your account linking.": "Please enter the code sent to <maskedContactInfo></maskedContactInfo> in order to confirm your account linking.", "Please enter the code sent to <maskedContactInfo></maskedContactInfo> in order to confirm your account linking.": "Please enter the code sent to <maskedContactInfo></maskedContactInfo> in order to confirm your account linking.",
"Please enter the code sent to <maskedContactInfo></maskedContactInfo> in order to unlink your accounts.": "Please enter the code sent to <maskedContactInfo></maskedContactInfo> in order to unlink your accounts.", "Please enter the code sent to <maskedContactInfo></maskedContactInfo> in order to unlink your accounts.": "Please enter the code sent to <maskedContactInfo></maskedContactInfo> in order to unlink your accounts.",
@@ -650,6 +654,7 @@
"View all": "Vis alle", "View all": "Vis alle",
"View all hotels in {country}": "Se alle hotellene i {country}", "View all hotels in {country}": "Se alle hotellene i {country}",
"View and buy add-ons": "View and buy add-ons", "View and buy add-ons": "View and buy add-ons",
"View and manage your stay made on Scandic's website": "Se og administrer ditt opphold på Scandics nettside",
"View as list": "Vis som liste", "View as list": "Vis som liste",
"View as map": "Vis som kart", "View as map": "Vis som kart",
"View room details": "View room details", "View room details": "View room details",

View File

@@ -106,6 +106,7 @@
"By signing up you accept the Scandic Friends <termsAndConditionsLink>Terms and Conditions</termsAndConditionsLink>. Your membership is valid until further notice, and you can terminate your membership at any time by sending an email to Scandic's customer service": "Genom att registrera dig accepterar du Scandic Friends <termsAndConditionsLink>Användarvillkor</termsAndConditionsLink>. Ditt medlemskap gäller tills vidare och du kan när som helst säga upp ditt medlemskap genom att skicka ett mejl till Scandics kundtjänst", "By signing up you accept the Scandic Friends <termsAndConditionsLink>Terms and Conditions</termsAndConditionsLink>. Your membership is valid until further notice, and you can terminate your membership at any time by sending an email to Scandic's customer service": "Genom att registrera dig accepterar du Scandic Friends <termsAndConditionsLink>Användarvillkor</termsAndConditionsLink>. Ditt medlemskap gäller tills vidare och du kan när som helst säga upp ditt medlemskap genom att skicka ett mejl till Scandics kundtjänst",
"Cabaret seating": "Cabaret seating", "Cabaret seating": "Cabaret seating",
"Campaign": "Kampanj", "Campaign": "Kampanj",
"Can't find your stay?": "Hittar du inte din vistelse?",
"Cancel": "Avbryt", "Cancel": "Avbryt",
"Cancel stay": "Avboka vistelse", "Cancel stay": "Avboka vistelse",
"Cancellation cost": "Avbokningskostnad", "Cancellation cost": "Avbokningskostnad",
@@ -226,9 +227,11 @@
"Filter": "Filter", "Filter": "Filter",
"Filter and sort": "Filtrera och sortera", "Filter and sort": "Filtrera och sortera",
"Filter by": "Filtrera på", "Filter by": "Filtrera på",
"Find": "Hitta",
"Find booking": "Hitta bokning", "Find booking": "Hitta bokning",
"Find hotels": "Hitta hotell", "Find hotels": "Hitta hotell",
"Find hotels and destinations": "Hitta hotell och destinationer", "Find hotels and destinations": "Hitta hotell och destinationer",
"Find your stay": "Hitta din vistelse",
"First name": "Förnamn", "First name": "Förnamn",
"First name can't contain any special characters": "Förnamn får inte innehålla några specialtecken", "First name can't contain any special characters": "Förnamn får inte innehålla några specialtecken",
"First name is required": "Förnamn är obligatoriskt", "First name is required": "Förnamn är obligatoriskt",
@@ -467,6 +470,7 @@
"Phone": "Telefon", "Phone": "Telefon",
"Phone is required": "Telefonnummer är obligatorisk", "Phone is required": "Telefonnummer är obligatorisk",
"Phone number": "Telefonnummer", "Phone number": "Telefonnummer",
"Please call customer service at {phoneNr}": "Ring kundtjänst på {phoneNr}",
"Please enter a valid phone number": "Var vänlig och ange ett giltigt telefonnummer", "Please enter a valid phone number": "Var vänlig och ange ett giltigt telefonnummer",
"Please enter the code sent to <maskedContactInfo></maskedContactInfo> in order to confirm your account linking.": "Please enter the code sent to <maskedContactInfo></maskedContactInfo> in order to confirm your account linking.", "Please enter the code sent to <maskedContactInfo></maskedContactInfo> in order to confirm your account linking.": "Please enter the code sent to <maskedContactInfo></maskedContactInfo> in order to confirm your account linking.",
"Please enter the code sent to <maskedContactInfo></maskedContactInfo> in order to unlink your accounts.": "Please enter the code sent to <maskedContactInfo></maskedContactInfo> in order to unlink your accounts.", "Please enter the code sent to <maskedContactInfo></maskedContactInfo> in order to unlink your accounts.": "Please enter the code sent to <maskedContactInfo></maskedContactInfo> in order to unlink your accounts.",
@@ -650,6 +654,7 @@
"View all": "Visa alla", "View all": "Visa alla",
"View all hotels in {country}": "Visa alla hotell i {country}", "View all hotels in {country}": "Visa alla hotell i {country}",
"View and buy add-ons": "View and buy add-ons", "View and buy add-ons": "View and buy add-ons",
"View and manage your stay made on Scandic's website": "Se och hantera din vistelse gjord på Scandics hemsida",
"View as list": "Visa som lista", "View as list": "Visa som lista",
"View as map": "Visa som karta", "View as map": "Visa som karta",
"View room details": "View room details", "View room details": "View room details",

View File

@@ -54,7 +54,7 @@ const nextConfig = {
output: "standalone", output: "standalone",
webpack: function (config, options) { webpack: function (config) {
config.module.rules.push( config.module.rules.push(
{ {
test: /\.(graphql|gql)/, test: /\.(graphql|gql)/,
@@ -273,6 +273,31 @@ const nextConfig = {
destination: destination:
"/:lang/hotelreservation/payment-callback?status=:status", "/:lang/hotelreservation/payment-callback?status=:status",
}, },
// Find my booking
{
source: "/en/hotelreservation/get-booking",
destination: "/en/hotelreservation/get-booking",
},
{
source: "/no/hotelreservation/get-booking",
destination: "/no/hotelreservation/get-booking",
},
{
source: "/da/hotelreservation/hent-booking",
destination: "/da/hotelreservation/get-booking",
},
{
source: "/de/hotelreservation/mein-bereich",
destination: "/de/hotelreservation/get-booking",
},
{
source: "/fi/hotelreservation/hae-varaus",
destination: "/fi/hotelreservation/get-booking",
},
{
source: "/sv/hotelreservation/hitta-bokning",
destination: "/sv/hotelreservation/get-booking",
},
], ],
} }
}, },

View File

@@ -108,6 +108,11 @@ export const cancelBookingInput = z.object({
language: z.nativeEnum(Lang).transform((val) => langToApiLang[val]), language: z.nativeEnum(Lang).transform((val) => langToApiLang[val]),
}) })
export const createRefIdInput = z.object({
confirmationNumber: z.string(),
lastName: z.string(),
})
// Query // Query
const confirmationNumberInput = z.object({ const confirmationNumberInput = z.object({
confirmationNumber: z.string(), confirmationNumber: z.string(),

View File

@@ -10,7 +10,12 @@ import {
} from "@/server/trpc" } from "@/server/trpc"
import { getHotel } from "../hotels/query" import { getHotel } from "../hotels/query"
import { bookingConfirmationInput, getBookingStatusInput } from "./input" import encryptValue from "../utils/encryptValue"
import {
bookingConfirmationInput,
createRefIdInput,
getBookingStatusInput,
} from "./input"
import { bookingConfirmationSchema, createBookingSchema } from "./output" import { bookingConfirmationSchema, createBookingSchema } from "./output"
import { getBookedHotelRoom } from "./utils" import { getBookedHotelRoom } from "./utils"
@@ -228,4 +233,14 @@ export const bookingQueryRouter = router({
return verifiedData.data return verifiedData.data
}), }),
createRefId: serviceProcedure
.input(createRefIdInput)
.mutation(async function ({ input }) {
const { confirmationNumber, lastName } = input
const encryptedRefId = encryptValue(`${confirmationNumber},${lastName}`)
return {
refId: encryptedRefId,
}
}),
}) })

View File

@@ -1,6 +1,7 @@
import { metrics } from "@opentelemetry/api" import { metrics } from "@opentelemetry/api"
import { Lang } from "@/constants/languages" import { Lang } from "@/constants/languages"
import { findMyBooking } from "@/constants/myBooking"
import { baseUrls } from "@/constants/routes/baseUrls" import { baseUrls } from "@/constants/routes/baseUrls"
import { batchRequest } from "@/lib/graphql/batchRequest" import { batchRequest } from "@/lib/graphql/batchRequest"
import { import {
@@ -179,6 +180,18 @@ export const languageSwitcherQueryRouter = router({
} }
if (!uid || !lang) { if (!uid || !lang) {
// we have pages that are not currently routed within contentstack context,
// therefor this fix is needed for some of these pages
if (input && Object.values(findMyBooking).includes(input.pathName)) {
const urls: Record<string, { url: string }> = {}
return {
lang,
urls: Object.entries(findMyBooking).reduce((acc, [lang, url]) => {
acc[lang] = { url }
return urls
}, urls),
}
}
return { lang: lang, urls: baseUrls } return { lang: lang, urls: baseUrls }
} }

View File

@@ -0,0 +1,51 @@
.form {
box-shadow: 0 0 14px 6px rgba(0, 0, 0, 0.1);
}
.form > div {
padding: var(--Spacing-x3);
}
.inputs {
display: grid;
gap: var(--Spacing-x3);
}
@media screen and (min-width: 768px) {
.inputs {
grid-template-areas:
"a a"
"b c"
"d d";
}
.inputs > div:nth-child(1) {
grid-area: a;
}
.inputs > div:nth-child(2) {
grid-area: b;
}
.inputs > div:nth-child(3) {
grid-area: c;
}
.inputs > div:nth-child(4) {
grid-area: d;
}
}
.buttons {
display: flex;
justify-content: space-between;
align-items: center;
border-top: 1px solid var(--Base-Border-Subtle);
gap: var(--Spacing-x2);
}
.buttons > button {
width: 140px;
}
.footnote {
display: grid;
gap: var(--Spacing-x-half);
}

View File

@@ -0,0 +1,121 @@
"use client"
import { zodResolver } from "@hookform/resolvers/zod"
import { useRouter } from "next/navigation"
import { FormProvider, useForm } from "react-hook-form"
import { useIntl } from "react-intl"
import { trpc } from "@/lib/trpc/client"
import Button from "@/components/TempDesignSystem/Button"
import Input from "@/components/TempDesignSystem/Form/Input"
import Link from "@/components/TempDesignSystem/Link"
import Body from "@/components/TempDesignSystem/Text/Body"
import Caption from "@/components/TempDesignSystem/Text/Caption"
import Title from "@/components/TempDesignSystem/Text/Title"
import useLang from "@/hooks/useLang"
import { type FindMyBookingFormSchema, findMyBookingFormSchema } from "./schema"
import styles from "./findMyBooking.module.css"
export default function Form() {
const router = useRouter()
const intl = useIntl()
const lang = useLang()
const form = useForm<FindMyBookingFormSchema>({
defaultValues: {
reservationNumber: "",
firstName: "",
lastName: "",
email: "",
},
resolver: zodResolver(findMyBookingFormSchema),
mode: "all",
criteriaMode: "all",
reValidateMode: "onChange",
})
const update = trpc.booking.createRefId.useMutation({
onSuccess: (result) => {
router.push(
`/${lang}/hotelreservation/my-stay/${encodeURIComponent(result.refId)}`
)
},
onError: (error) => {
console.log("Failed to create ref id", error)
},
})
async function onSubmit(data: FindMyBookingFormSchema) {
const value = new URLSearchParams(data).toString()
document.cookie = `bv=${value}; Path=/; Max-Age=30; Secure; SameSite=Strict`
update.mutate({
confirmationNumber: data.reservationNumber,
lastName: data.lastName,
})
}
return (
<FormProvider {...form}>
<form onSubmit={form.handleSubmit(onSubmit)} className={styles.form}>
<div>
<Title level="h2">
{intl.formatMessage({ id: "Find your stay" })}
</Title>
<Body>
{intl.formatMessage({
id: "View and manage your stay made on Scandic's website",
})}
</Body>
</div>
<div className={styles.inputs}>
<Input
label="Reservation number"
name="reservationNumber"
placeholder="XXXXXX"
registerOptions={{ required: true }}
/>
<Input
label="First name"
name="firstName"
placeholder="Anna"
registerOptions={{ required: true }}
/>
<Input
label="Last name"
name="lastName"
placeholder="Andersson"
registerOptions={{ required: true }}
/>
<Input
label="Email"
name="email"
placeholder="anna@scandichotels.com"
registerOptions={{ required: true }}
/>
</div>
<div className={styles.buttons}>
<div className={styles.footnote}>
<Caption type="bold">
{intl.formatMessage({ id: "Can't find your stay?" })}
</Caption>
<Caption>
{intl.formatMessage<React.ReactNode>(
{ id: "Please call customer service at {phoneNr}" },
{ phoneNr: <Link href="tel:XXXXXX">XXXXXX</Link> }
)}
</Caption>
</div>
<Button
type="submit"
intent="primary"
theme="base"
disabled={form.formState.isSubmitting}
>
{intl.formatMessage({ id: "Find" })}
</Button>
</div>
</form>
</FormProvider>
)
}

View File

@@ -0,0 +1,15 @@
import { z } from "zod"
export const findMyBookingFormSchema = z.object({
reservationNumber: z.string(),
firstName: z.string().max(250).trim().min(1, {
message: "First name is required",
}),
lastName: z.string().max(250).trim().min(1, {
message: "Last name is required",
}),
email: z.string().max(250).email(),
})
export interface FindMyBookingFormSchema
extends z.output<typeof findMyBookingFormSchema> {}