feat(WEB-220): label translations
This commit is contained in:
@@ -1,11 +1,3 @@
|
|||||||
.content {
|
|
||||||
display: grid;
|
|
||||||
padding-bottom: var(--Spacing-x9);
|
|
||||||
padding-left: var(--Spacing-x0);
|
|
||||||
padding-right: var(--Spacing-x0);
|
|
||||||
position: relative;
|
|
||||||
}
|
|
||||||
|
|
||||||
.blocks {
|
.blocks {
|
||||||
display: grid;
|
display: grid;
|
||||||
gap: var(--Spacing-x5);
|
gap: var(--Spacing-x5);
|
||||||
@@ -14,13 +6,6 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
@media screen and (min-width: 1367px) {
|
@media screen and (min-width: 1367px) {
|
||||||
.content {
|
|
||||||
gap: var(--Spacing-x9);
|
|
||||||
grid-template-columns: 25rem 1fr;
|
|
||||||
padding-left: var(--Spacing-x3);
|
|
||||||
padding-right: var(--Spacing-x3);
|
|
||||||
}
|
|
||||||
|
|
||||||
.blocks {
|
.blocks {
|
||||||
gap: var(--Spacing-x7);
|
gap: var(--Spacing-x7);
|
||||||
padding-left: var(--Spacing-x0);
|
padding-left: var(--Spacing-x0);
|
||||||
|
|||||||
@@ -1,8 +1,7 @@
|
|||||||
import { _ } from "@/lib/translation"
|
|
||||||
import { serverClient } from "@/lib/trpc/server"
|
import { serverClient } from "@/lib/trpc/server"
|
||||||
|
|
||||||
import Content from "@/components/MyPages/AccountPage/Content"
|
import Content from "@/components/MyPages/AccountPage/Content"
|
||||||
import Sidebar from "@/components/MyPages/Sidebar"
|
import { getIntl } from "@/i18n"
|
||||||
|
|
||||||
import styles from "./page.module.css"
|
import styles from "./page.module.css"
|
||||||
|
|
||||||
@@ -12,17 +11,14 @@ export default async function MyPages({
|
|||||||
params,
|
params,
|
||||||
}: PageArgs<LangParams & { path: string[] }>) {
|
}: PageArgs<LangParams & { path: string[] }>) {
|
||||||
const accountPage = await serverClient().contentstack.accountPage.get()
|
const accountPage = await serverClient().contentstack.accountPage.get()
|
||||||
|
const { formatMessage } = await getIntl()
|
||||||
return (
|
return (
|
||||||
<section className={styles.content}>
|
|
||||||
<Sidebar lang={params.lang} />
|
|
||||||
<main className={styles.blocks}>
|
<main className={styles.blocks}>
|
||||||
{accountPage.content.length ? (
|
{accountPage.content.length ? (
|
||||||
<Content lang={params.lang} content={accountPage.content} />
|
<Content lang={params.lang} content={accountPage.content} />
|
||||||
) : (
|
) : (
|
||||||
<p>{_("No content published")}</p>
|
<p>{formatMessage({ id: "No content published" })}</p>
|
||||||
)}
|
)}
|
||||||
</main>
|
</main>
|
||||||
</section>
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,3 +8,20 @@
|
|||||||
grid-template-rows: auto 1fr;
|
grid-template-rows: auto 1fr;
|
||||||
min-height: 100dvh;
|
min-height: 100dvh;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.content {
|
||||||
|
display: grid;
|
||||||
|
padding-bottom: var(--Spacing-x9);
|
||||||
|
padding-left: var(--Spacing-x0);
|
||||||
|
padding-right: var(--Spacing-x0);
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media screen and (min-width: 1367px) {
|
||||||
|
.content {
|
||||||
|
gap: var(--Spacing-x9);
|
||||||
|
grid-template-columns: 25rem 1fr;
|
||||||
|
padding-left: var(--Spacing-x3);
|
||||||
|
padding-right: var(--Spacing-x3);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
import Sidebar from "@/components/MyPages/Sidebar"
|
||||||
|
|
||||||
import styles from "./layout.module.css"
|
import styles from "./layout.module.css"
|
||||||
|
|
||||||
import { LangParams, LayoutArgs } from "@/types/params"
|
import { LangParams, LayoutArgs } from "@/types/params"
|
||||||
@@ -5,13 +7,17 @@ import { LangParams, LayoutArgs } from "@/types/params"
|
|||||||
export default async function MyPagesLayout({
|
export default async function MyPagesLayout({
|
||||||
breadcrumbs,
|
breadcrumbs,
|
||||||
children,
|
children,
|
||||||
|
params,
|
||||||
}: React.PropsWithChildren<LayoutArgs<LangParams>> & {
|
}: React.PropsWithChildren<LayoutArgs<LangParams>> & {
|
||||||
breadcrumbs: React.ReactNode
|
breadcrumbs: React.ReactNode
|
||||||
}) {
|
}) {
|
||||||
return (
|
return (
|
||||||
<section className={styles.layout}>
|
<section className={styles.layout}>
|
||||||
{breadcrumbs}
|
{breadcrumbs}
|
||||||
|
<section className={styles.content}>
|
||||||
|
<Sidebar lang={params.lang} />
|
||||||
{children}
|
{children}
|
||||||
</section>
|
</section>
|
||||||
|
</section>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
"use client"
|
"use client"
|
||||||
|
import { useIntl } from "react-intl"
|
||||||
|
|
||||||
import { profile } from "@/constants/routes/myPages"
|
import { profile } from "@/constants/routes/myPages"
|
||||||
import { _ } from "@/lib/translation"
|
|
||||||
import { useProfileStore } from "@/stores/edit-profile"
|
import { useProfileStore } from "@/stores/edit-profile"
|
||||||
|
|
||||||
import Button from "@/components/TempDesignSystem/Button"
|
import Button from "@/components/TempDesignSystem/Button"
|
||||||
@@ -9,26 +10,31 @@ import Link from "@/components/TempDesignSystem/Link"
|
|||||||
import type { LangParams, PageArgs } from "@/types/params"
|
import type { LangParams, PageArgs } from "@/types/params"
|
||||||
|
|
||||||
export default function EditProfile({ params }: PageArgs<LangParams>) {
|
export default function EditProfile({ params }: PageArgs<LangParams>) {
|
||||||
|
const { formatMessage } = useIntl()
|
||||||
const isPending = useProfileStore((store) => store.pending)
|
const isPending = useProfileStore((store) => store.pending)
|
||||||
const isValid = useProfileStore((store) => store.valid)
|
const isValid = useProfileStore((store) => store.valid)
|
||||||
|
|
||||||
|
const cancel = formatMessage({ id: "Cancel" })
|
||||||
|
const save = formatMessage({ id: "Save" })
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Button
|
<Button
|
||||||
aria-label="Cancel"
|
aria-label={cancel}
|
||||||
asChild
|
asChild
|
||||||
form="edit-profile"
|
form="edit-profile"
|
||||||
size="small"
|
size="small"
|
||||||
type="reset"
|
type="reset"
|
||||||
>
|
>
|
||||||
<Link href={profile[params.lang]}>{_("Cancel")}</Link>
|
<Link href={profile[params.lang]}>{cancel}</Link>
|
||||||
</Button>
|
</Button>
|
||||||
<Button
|
<Button
|
||||||
|
aria-label={save}
|
||||||
disabled={!isValid || isPending}
|
disabled={!isValid || isPending}
|
||||||
form="edit-profile"
|
form="edit-profile"
|
||||||
size="small"
|
size="small"
|
||||||
type="submit"
|
type="submit"
|
||||||
>
|
>
|
||||||
{_("Save")}
|
{save}
|
||||||
</Button>
|
</Button>
|
||||||
</>
|
</>
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -1,15 +1,18 @@
|
|||||||
import { profileEdit } from "@/constants/routes/myPages"
|
import { profileEdit } from "@/constants/routes/myPages"
|
||||||
import { _ } from "@/lib/translation"
|
|
||||||
|
|
||||||
import Button from "@/components/TempDesignSystem/Button"
|
import Button from "@/components/TempDesignSystem/Button"
|
||||||
import Link from "@/components/TempDesignSystem/Link"
|
import Link from "@/components/TempDesignSystem/Link"
|
||||||
|
import { getIntl } from "@/i18n"
|
||||||
|
|
||||||
import type { LangParams, PageArgs } from "@/types/params"
|
import type { LangParams, PageArgs } from "@/types/params"
|
||||||
|
|
||||||
export default function ProfileView({ params }: PageArgs<LangParams>) {
|
export default async function ProfileView({ params }: PageArgs<LangParams>) {
|
||||||
|
const { formatMessage } = await getIntl()
|
||||||
return (
|
return (
|
||||||
<Button asChild size="small">
|
<Button asChild size="small">
|
||||||
<Link href={profileEdit[params.lang]}>{_("Edit")}</Link>
|
<Link href={profileEdit[params.lang]}>
|
||||||
|
{formatMessage({ id: "Edit" })}
|
||||||
|
</Link>
|
||||||
</Button>
|
</Button>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,5 +12,5 @@ export default function ContentTypeLayout({
|
|||||||
}: React.PropsWithChildren<
|
}: React.PropsWithChildren<
|
||||||
LayoutArgs<LangParams & ContentTypeParams & UIDParams>
|
LayoutArgs<LangParams & ContentTypeParams & UIDParams>
|
||||||
>) {
|
>) {
|
||||||
return <div className={styles.layout}>{children}</div>
|
return <section className={styles.layout}>{children}</section>
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,10 +14,10 @@ export default async function ContentTypePage({
|
|||||||
params,
|
params,
|
||||||
}: PageArgs<LangParams & ContentTypeParams & UIDParams, {}>) {
|
}: PageArgs<LangParams & ContentTypeParams & UIDParams, {}>) {
|
||||||
switch (params.contentType) {
|
switch (params.contentType) {
|
||||||
case "loyalty-page":
|
|
||||||
return <LoyaltyPage />
|
|
||||||
case "content-page":
|
case "content-page":
|
||||||
return <ContentPage />
|
return <ContentPage />
|
||||||
|
case "loyalty-page":
|
||||||
|
return <LoyaltyPage />
|
||||||
default:
|
default:
|
||||||
const type: never = params.contentType
|
const type: never = params.contentType
|
||||||
console.error(`Unsupported content type given: ${type}`)
|
console.error(`Unsupported content type given: ${type}`)
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
import { useParams, usePathname } from "next/navigation"
|
import { useParams, usePathname } from "next/navigation"
|
||||||
import { useEffect } from "react"
|
import { useEffect } from "react"
|
||||||
|
import { useIntl } from "react-intl"
|
||||||
|
|
||||||
import { findLang } from "@/constants/languages"
|
import { findLang } from "@/constants/languages"
|
||||||
import { login } from "@/constants/routes/handleAuth"
|
import { login } from "@/constants/routes/handleAuth"
|
||||||
@@ -16,6 +17,7 @@ export default function Error({
|
|||||||
}: {
|
}: {
|
||||||
error: Error & { digest?: string }
|
error: Error & { digest?: string }
|
||||||
}) {
|
}) {
|
||||||
|
const intl = useIntl()
|
||||||
const params = useParams<LangParams>()
|
const params = useParams<LangParams>()
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@@ -32,8 +34,12 @@ export default function Error({
|
|||||||
const lang = findLang(pathname)
|
const lang = findLang(pathname)
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={styles.layout}>
|
<section
|
||||||
<div className={styles.content}>{lang}: Something went wrong!</div>
|
className={styles.layout}
|
||||||
|
>
|
||||||
|
<div className={styles.content}>
|
||||||
|
{lang}: {intl.formatMessage({ id: "Something went wrong!" })}
|
||||||
</div>
|
</div>
|
||||||
|
</section>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,6 +9,8 @@ import AdobeScript from "@/components/Current/AdobeScript"
|
|||||||
import Footer from "@/components/Current/Footer"
|
import Footer from "@/components/Current/Footer"
|
||||||
import Header from "@/components/Current/Header"
|
import Header from "@/components/Current/Header"
|
||||||
import VwoScript from "@/components/Current/VwoScript"
|
import VwoScript from "@/components/Current/VwoScript"
|
||||||
|
import { getIntl } from "@/i18n"
|
||||||
|
import ServerIntlProvider from "@/i18n/Provider"
|
||||||
|
|
||||||
import type { Metadata } from "next"
|
import type { Metadata } from "next"
|
||||||
|
|
||||||
@@ -28,6 +30,7 @@ export default async function RootLayout({
|
|||||||
languageSwitcher: React.ReactNode
|
languageSwitcher: React.ReactNode
|
||||||
}
|
}
|
||||||
>) {
|
>) {
|
||||||
|
const { defaultLocale, locale, messages } = await getIntl()
|
||||||
return (
|
return (
|
||||||
<html lang={params.lang}>
|
<html lang={params.lang}>
|
||||||
<head>
|
<head>
|
||||||
@@ -50,11 +53,16 @@ export default async function RootLayout({
|
|||||||
<VwoScript />
|
<VwoScript />
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
|
<ServerIntlProvider intl={{ defaultLocale, locale, messages }}>
|
||||||
<TrpcProvider lang={params.lang}>
|
<TrpcProvider lang={params.lang}>
|
||||||
<Header lang={params.lang} languageSwitcher={languageSwitcher} />
|
<Header
|
||||||
|
lang={params.lang}
|
||||||
|
languageSwitcher={languageSwitcher}
|
||||||
|
/>
|
||||||
{children}
|
{children}
|
||||||
<Footer />
|
<Footer />
|
||||||
</TrpcProvider>
|
</TrpcProvider>
|
||||||
|
</ServerIntlProvider>
|
||||||
<Script id="page-tracking">{`
|
<Script id="page-tracking">{`
|
||||||
typeof _satellite !== "undefined" && _satellite.pageBottom();
|
typeof _satellite !== "undefined" && _satellite.pageBottom();
|
||||||
`}</Script>
|
`}</Script>
|
||||||
|
|||||||
@@ -1,8 +1,11 @@
|
|||||||
export default function NotFound() {
|
import { getIntl } from "@/i18n"
|
||||||
|
|
||||||
|
export default async function NotFound() {
|
||||||
|
const { formatMessage } = await getIntl()
|
||||||
return (
|
return (
|
||||||
<main>
|
<main>
|
||||||
<h1>Not found</h1>
|
<h1>{formatMessage({ id: "Not found" })}</h1>
|
||||||
<p>Could not find requested resource</p>
|
<p>{formatMessage({ id: "Could not find requested resource" })}</p>
|
||||||
</main>
|
</main>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,6 +7,8 @@ import Footer from "@/components/Current/Footer"
|
|||||||
import Header from "@/components/Current/Header"
|
import Header from "@/components/Current/Header"
|
||||||
import LangPopup from "@/components/Current/LangPopup"
|
import LangPopup from "@/components/Current/LangPopup"
|
||||||
import SkipToMainContent from "@/components/SkipToMainContent"
|
import SkipToMainContent from "@/components/SkipToMainContent"
|
||||||
|
import { getIntl } from "@/i18n"
|
||||||
|
import ServerIntlProvider from "@/i18n/Provider"
|
||||||
|
|
||||||
import type { Metadata } from "next"
|
import type { Metadata } from "next"
|
||||||
|
|
||||||
@@ -19,13 +21,14 @@ export const metadata: Metadata = {
|
|||||||
title: "Scandic Hotels New Web",
|
title: "Scandic Hotels New Web",
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function RootLayout({
|
export default async function RootLayout({
|
||||||
children,
|
children,
|
||||||
params,
|
params,
|
||||||
languageSwitcher,
|
languageSwitcher,
|
||||||
}: React.PropsWithChildren<
|
}: React.PropsWithChildren<
|
||||||
LayoutArgs<LangParams> & { languageSwitcher: React.ReactNode }
|
LayoutArgs<LangParams> & { languageSwitcher: React.ReactNode }
|
||||||
>) {
|
>) {
|
||||||
|
const { defaultLocale, locale, messages } = await getIntl()
|
||||||
return (
|
return (
|
||||||
<html lang={params.lang}>
|
<html lang={params.lang}>
|
||||||
<head>
|
<head>
|
||||||
@@ -78,9 +81,11 @@ export default function RootLayout({
|
|||||||
</head>
|
</head>
|
||||||
<body className="theme-00Corecolours theme-X0Oldcorecolours">
|
<body className="theme-00Corecolours theme-X0Oldcorecolours">
|
||||||
<LangPopup lang={params.lang} />
|
<LangPopup lang={params.lang} />
|
||||||
<SkipToMainContent lang={params.lang} />
|
<SkipToMainContent />
|
||||||
|
<ServerIntlProvider intl={{ defaultLocale, locale, messages }}>
|
||||||
<Header lang={params.lang} languageSwitcher={languageSwitcher} />
|
<Header lang={params.lang} languageSwitcher={languageSwitcher} />
|
||||||
{children}
|
{children}
|
||||||
|
</ServerIntlProvider>
|
||||||
<Footer />
|
<Footer />
|
||||||
<Script id="page-tracking">{`
|
<Script id="page-tracking">{`
|
||||||
typeof _satellite !== "undefined" && _satellite.pageBottom();
|
typeof _satellite !== "undefined" && _satellite.pageBottom();
|
||||||
|
|||||||
@@ -1,8 +1,11 @@
|
|||||||
export default function NotFound() {
|
import { getIntl } from "@/i18n"
|
||||||
|
|
||||||
|
export default async function NotFound() {
|
||||||
|
const { formatMessage } = await getIntl()
|
||||||
return (
|
return (
|
||||||
<main>
|
<main>
|
||||||
<h1>Not found</h1>
|
<h1>{formatMessage({ id: "Not found" })}</h1>
|
||||||
<p>Could not find requested resource</p>
|
<p>{formatMessage({ id: "Could not find requested resource" })}</p>
|
||||||
</main>
|
</main>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -51,7 +51,7 @@ export default function RootLayout({
|
|||||||
<body>
|
<body>
|
||||||
<InitLivePreview />
|
<InitLivePreview />
|
||||||
<LangPopup lang={params.lang} />
|
<LangPopup lang={params.lang} />
|
||||||
<SkipToMainContent lang={params.lang} />
|
<SkipToMainContent />
|
||||||
{children}
|
{children}
|
||||||
<Footer />
|
<Footer />
|
||||||
</body>
|
</body>
|
||||||
|
|||||||
@@ -6,8 +6,6 @@ import Navigation from "./Navigation"
|
|||||||
|
|
||||||
import styles from "./footer.module.css"
|
import styles from "./footer.module.css"
|
||||||
|
|
||||||
import type { LangParams } from "@/types/params"
|
|
||||||
|
|
||||||
export default async function Footer() {
|
export default async function Footer() {
|
||||||
const footerData = await serverClient().contentstack.config.footer()
|
const footerData = await serverClient().contentstack.config.footer()
|
||||||
return (
|
return (
|
||||||
|
|||||||
@@ -1,13 +1,16 @@
|
|||||||
|
"use client"
|
||||||
|
|
||||||
import "@scandic-hotels/design-system/current/style.css"
|
import "@scandic-hotels/design-system/current/style.css"
|
||||||
|
|
||||||
import { _ } from "@/lib/translation"
|
import { useIntl } from "react-intl"
|
||||||
|
|
||||||
import styles from "./bookingButton.module.css"
|
import styles from "./bookingButton.module.css"
|
||||||
|
|
||||||
export default function BookingButton({ href }: { href: string }) {
|
export default function BookingButton({ href }: { href: string }) {
|
||||||
|
const { formatMessage } = useIntl()
|
||||||
return (
|
return (
|
||||||
<a className={styles.button} href={href}>
|
<a className={styles.button} href={href}>
|
||||||
{_("Book")}
|
{formatMessage({ id: "Book" })}
|
||||||
</a>
|
</a>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
"use client"
|
"use client"
|
||||||
import { useState } from "react"
|
import { useState } from "react"
|
||||||
|
import { useIntl } from "react-intl"
|
||||||
|
|
||||||
import { login } from "@/constants/routes/handleAuth"
|
import { login } from "@/constants/routes/handleAuth"
|
||||||
import { myPages } from "@/constants/routes/myPages"
|
import { myPages } from "@/constants/routes/myPages"
|
||||||
import { _ } from "@/lib/translation"
|
|
||||||
|
|
||||||
import Image from "@/components/Image"
|
import Image from "@/components/Image"
|
||||||
import Link from "@/components/TempDesignSystem/Link"
|
import Link from "@/components/TempDesignSystem/Link"
|
||||||
@@ -25,6 +25,7 @@ export function MainMenu({
|
|||||||
isLoggedIn,
|
isLoggedIn,
|
||||||
lang,
|
lang,
|
||||||
}: MainMenuProps) {
|
}: MainMenuProps) {
|
||||||
|
const intl = useIntl()
|
||||||
const [isOpen, setIsOpen] = useState(false)
|
const [isOpen, setIsOpen] = useState(false)
|
||||||
|
|
||||||
function toogleIsOpen() {
|
function toogleIsOpen() {
|
||||||
@@ -78,7 +79,7 @@ export function MainMenu({
|
|||||||
className={styles.mobileLinkButton}
|
className={styles.mobileLinkButton}
|
||||||
href={myPages[lang]}
|
href={myPages[lang]}
|
||||||
>
|
>
|
||||||
{_("My pages")}
|
{intl.formatMessage({ id: "My pages" })}
|
||||||
</Link>
|
</Link>
|
||||||
</li>
|
</li>
|
||||||
</>
|
</>
|
||||||
@@ -98,7 +99,7 @@ export function MainMenu({
|
|||||||
className={styles.mobileLinkButton}
|
className={styles.mobileLinkButton}
|
||||||
href={login[lang]}
|
href={login[lang]}
|
||||||
>
|
>
|
||||||
{_("Log in")}
|
{intl.formatMessage({ id: "Log in" })}
|
||||||
</Link>
|
</Link>
|
||||||
</li>
|
</li>
|
||||||
</>
|
</>
|
||||||
@@ -109,7 +110,7 @@ export function MainMenu({
|
|||||||
</li>
|
</li>
|
||||||
<li className={styles.mobileLinkRow}>
|
<li className={styles.mobileLinkRow}>
|
||||||
<a className={styles.mobileLinkButton} href="">
|
<a className={styles.mobileLinkButton} href="">
|
||||||
{_("Find booking")}
|
{intl.formatMessage({ id: "Find booking" })}
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
import Link from "next/link"
|
|
||||||
|
|
||||||
import Image from "@/components/Image"
|
import Image from "@/components/Image"
|
||||||
|
import Link from "@/components/TempDesignSystem/Link"
|
||||||
|
|
||||||
import styles from "./jsontohtml.module.css"
|
import styles from "./jsontohtml.module.css"
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,3 @@
|
|||||||
import { _ } from "@/lib/translation"
|
|
||||||
|
|
||||||
import Header from "@/components/MyPages/Blocks/Header"
|
import Header from "@/components/MyPages/Blocks/Header"
|
||||||
import Card from "@/components/TempDesignSystem/Card"
|
import Card from "@/components/TempDesignSystem/Card"
|
||||||
import CardGrid from "@/components/TempDesignSystem/CardGrid"
|
import CardGrid from "@/components/TempDesignSystem/CardGrid"
|
||||||
|
|||||||
@@ -1,13 +1,13 @@
|
|||||||
import { _ } from "@/lib/translation"
|
|
||||||
|
|
||||||
import Title from "@/components/TempDesignSystem/Text/Title"
|
import Title from "@/components/TempDesignSystem/Text/Title"
|
||||||
|
import { getIntl } from "@/i18n"
|
||||||
|
|
||||||
import styles from "./howItWorks.module.css"
|
import styles from "./howItWorks.module.css"
|
||||||
|
|
||||||
export default function HowItWorks() {
|
export default async function HowItWorks() {
|
||||||
|
const { formatMessage } = await getIntl()
|
||||||
return (
|
return (
|
||||||
<section className={styles.container}>
|
<section className={styles.container}>
|
||||||
<Title level="h3">{_("How it works Placeholder")}</Title>
|
<Title level="h3">{formatMessage({ id: "How it works" })}</Title>
|
||||||
</section>
|
</section>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,9 +2,9 @@
|
|||||||
|
|
||||||
import { useParams } from "next/navigation"
|
import { useParams } from "next/navigation"
|
||||||
import { Check } from "react-feather"
|
import { Check } from "react-feather"
|
||||||
|
import { useIntl } from "react-intl"
|
||||||
|
|
||||||
import { Lang } from "@/constants/languages"
|
import { Lang } from "@/constants/languages"
|
||||||
import { _ } from "@/lib/translation"
|
|
||||||
|
|
||||||
import Image from "@/components/Image"
|
import Image from "@/components/Image"
|
||||||
import Button from "@/components/TempDesignSystem/Button"
|
import Button from "@/components/TempDesignSystem/Button"
|
||||||
@@ -18,31 +18,38 @@ import styles from "./loyaltyLevels.module.css"
|
|||||||
import type { Level, LevelCardProps } from "@/types/components/loyalty/blocks"
|
import type { Level, LevelCardProps } from "@/types/components/loyalty/blocks"
|
||||||
|
|
||||||
export default function LoyaltyLevels() {
|
export default function LoyaltyLevels() {
|
||||||
const { lang } = useParams()
|
const params = useParams()
|
||||||
|
const lang = params.lang as Lang
|
||||||
|
const { formatMessage } = useIntl()
|
||||||
|
|
||||||
const { levels } = levelsData[lang as Lang]
|
const { levels } = levelsData[lang]
|
||||||
return (
|
return (
|
||||||
<section className={styles.container}>
|
<section className={styles.container}>
|
||||||
<div className={styles.cardContainer}>
|
<div className={styles.cardContainer}>
|
||||||
{levels.map((level: Level) => (
|
{levels.map((level: Level) => (
|
||||||
<LevelCard key={level.tier} level={level} />
|
<LevelCard
|
||||||
|
key={level.tier}
|
||||||
|
formatMessage={formatMessage}
|
||||||
|
lang={lang}
|
||||||
|
level={level}
|
||||||
|
/>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
<div className={styles.buttonContainer}>
|
<div className={styles.buttonContainer}>
|
||||||
<Button intent="primary" asChild>
|
<Button intent="primary" asChild>
|
||||||
<Link href={"/compare-all-levels"}>{_("Compare all levels")}</Link>
|
<Link href={`/${lang}/compare-all-levels`}>
|
||||||
|
{formatMessage({ id: "Compare all levels" })}
|
||||||
|
</Link>
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
function LevelCard({ level }: LevelCardProps) {
|
function LevelCard({ formatMessage, lang, level }: LevelCardProps) {
|
||||||
const { lang } = useParams()
|
|
||||||
|
|
||||||
const pointsString = `${level.requiredPoints.toLocaleString(lang)}p`
|
const pointsString = `${level.requiredPoints.toLocaleString(lang)}p`
|
||||||
const qualifications = level.requiredNights
|
const qualifications = level.requiredNights
|
||||||
? `${pointsString} ${_("or")} ${level.requiredNights} ${_("nights")}`
|
? `${pointsString} ${formatMessage({ id: "or" })} ${level.requiredNights} ${formatMessage({ id: "nights" })}`
|
||||||
: pointsString
|
: pointsString
|
||||||
return (
|
return (
|
||||||
<article className={styles.card}>
|
<article className={styles.card}>
|
||||||
|
|||||||
@@ -2,9 +2,9 @@
|
|||||||
|
|
||||||
import { Dispatch, SetStateAction, useState } from "react"
|
import { Dispatch, SetStateAction, useState } from "react"
|
||||||
import { type Key } from "react-aria-components"
|
import { type Key } from "react-aria-components"
|
||||||
|
import { useIntl } from "react-intl"
|
||||||
|
|
||||||
import { Lang } from "@/constants/languages"
|
import { Lang } from "@/constants/languages"
|
||||||
import { _ } from "@/lib/translation"
|
|
||||||
|
|
||||||
import Select from "@/components/TempDesignSystem/Form/Select"
|
import Select from "@/components/TempDesignSystem/Form/Select"
|
||||||
|
|
||||||
@@ -53,6 +53,7 @@ function getLevelByTier(tier: number) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export default function OverviewTable() {
|
export default function OverviewTable() {
|
||||||
|
const intl = useIntl()
|
||||||
const [selectedLevelA, setSelectedLevelA] = useState(getLevelByTier(1))
|
const [selectedLevelA, setSelectedLevelA] = useState(getLevelByTier(1))
|
||||||
const [selectedLevelB, setSelectedLevelB] = useState(getLevelByTier(2))
|
const [selectedLevelB, setSelectedLevelB] = useState(getLevelByTier(2))
|
||||||
const [selectedLevelC, setSelectedLevelC] = useState(getLevelByTier(3))
|
const [selectedLevelC, setSelectedLevelC] = useState(getLevelByTier(3))
|
||||||
@@ -89,8 +90,8 @@ export default function OverviewTable() {
|
|||||||
<div className={styles.columnHeaderContainer}>
|
<div className={styles.columnHeaderContainer}>
|
||||||
<div className={styles.columnHeader}>
|
<div className={styles.columnHeader}>
|
||||||
<Select
|
<Select
|
||||||
name={"benefitA"}
|
name="benefitA"
|
||||||
label={"Level"}
|
label={intl.formatMessage({ id: "Level" })}
|
||||||
items={levelOptions}
|
items={levelOptions}
|
||||||
defaultSelectedKey={selectedLevelA.tier}
|
defaultSelectedKey={selectedLevelA.tier}
|
||||||
onSelect={handleSelectChange(setSelectedLevelA)}
|
onSelect={handleSelectChange(setSelectedLevelA)}
|
||||||
@@ -105,8 +106,8 @@ export default function OverviewTable() {
|
|||||||
</div>
|
</div>
|
||||||
<div className={styles.columnHeader}>
|
<div className={styles.columnHeader}>
|
||||||
<Select
|
<Select
|
||||||
name={"benefitB"}
|
name="benefitB"
|
||||||
label={"Level"}
|
label={intl.formatMessage({ id: "Level" })}
|
||||||
items={levelOptions}
|
items={levelOptions}
|
||||||
defaultSelectedKey={selectedLevelB.tier}
|
defaultSelectedKey={selectedLevelB.tier}
|
||||||
onSelect={handleSelectChange(setSelectedLevelB)}
|
onSelect={handleSelectChange(setSelectedLevelB)}
|
||||||
@@ -126,8 +127,8 @@ export default function OverviewTable() {
|
|||||||
<div className={styles.columnHeaderContainer}>
|
<div className={styles.columnHeaderContainer}>
|
||||||
<div className={styles.columnHeader}>
|
<div className={styles.columnHeader}>
|
||||||
<Select
|
<Select
|
||||||
name={"benefitA"}
|
name="benefitA"
|
||||||
label={"Level"}
|
label={intl.formatMessage({ id: "Level" })}
|
||||||
items={levelOptions}
|
items={levelOptions}
|
||||||
defaultSelectedKey={selectedLevelA.tier}
|
defaultSelectedKey={selectedLevelA.tier}
|
||||||
onSelect={handleSelectChange(setSelectedLevelA)}
|
onSelect={handleSelectChange(setSelectedLevelA)}
|
||||||
@@ -142,8 +143,8 @@ export default function OverviewTable() {
|
|||||||
</div>
|
</div>
|
||||||
<div className={styles.columnHeader}>
|
<div className={styles.columnHeader}>
|
||||||
<Select
|
<Select
|
||||||
name={"benefitB"}
|
name="benefitB"
|
||||||
label={"Level"}
|
label={intl.formatMessage({ id: "Level" })}
|
||||||
items={levelOptions}
|
items={levelOptions}
|
||||||
defaultSelectedKey={selectedLevelB.tier}
|
defaultSelectedKey={selectedLevelB.tier}
|
||||||
onSelect={handleSelectChange(setSelectedLevelB)}
|
onSelect={handleSelectChange(setSelectedLevelB)}
|
||||||
@@ -158,8 +159,8 @@ export default function OverviewTable() {
|
|||||||
</div>
|
</div>
|
||||||
<div className={styles.columnHeader}>
|
<div className={styles.columnHeader}>
|
||||||
<Select
|
<Select
|
||||||
name={"benefitC"}
|
name="benefitC"
|
||||||
label={"Level"}
|
label={intl.formatMessage({ id: "Level" })}
|
||||||
items={levelOptions}
|
items={levelOptions}
|
||||||
defaultSelectedKey={selectedLevelC.tier}
|
defaultSelectedKey={selectedLevelC.tier}
|
||||||
onSelect={handleSelectChange(setSelectedLevelC)}
|
onSelect={handleSelectChange(setSelectedLevelC)}
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
import { Lang } from "@/constants/languages"
|
|
||||||
import { serverClient } from "@/lib/trpc/server"
|
import { serverClient } from "@/lib/trpc/server"
|
||||||
|
|
||||||
import { getValueFromContactConfig } from "@/utils/contactConfig"
|
import { getValueFromContactConfig } from "@/utils/contactConfig"
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
import { _ } from "@/lib/translation"
|
|
||||||
|
|
||||||
import Title from "@/components/TempDesignSystem/Text/Title"
|
import Title from "@/components/TempDesignSystem/Text/Title"
|
||||||
|
import { getIntl } from "@/i18n"
|
||||||
|
|
||||||
import ContactRow from "./ContactRow"
|
import ContactRow from "./ContactRow"
|
||||||
|
|
||||||
@@ -10,9 +9,10 @@ import { JoinLoyaltyContactTypenameEnum } from "@/types/components/loyalty/enums
|
|||||||
import type { ContactProps } from "@/types/components/loyalty/sidebar"
|
import type { ContactProps } from "@/types/components/loyalty/sidebar"
|
||||||
|
|
||||||
export default async function Contact({ contactBlock }: ContactProps) {
|
export default async function Contact({ contactBlock }: ContactProps) {
|
||||||
|
const { formatMessage } = await getIntl()
|
||||||
return (
|
return (
|
||||||
<div className={styles.contactContainer}>
|
<div className={styles.contactContainer}>
|
||||||
<Title level="h5">{_("Contact us")}</Title>
|
<Title level="h5">{formatMessage({ id: "Contact us" })}</Title>
|
||||||
<section>
|
<section>
|
||||||
{contactBlock.map(({ contact, __typename }, i) => {
|
{contactBlock.map(({ contact, __typename }, i) => {
|
||||||
switch (__typename) {
|
switch (__typename) {
|
||||||
|
|||||||
@@ -1,9 +1,8 @@
|
|||||||
import { _ } from "@/lib/translation"
|
|
||||||
|
|
||||||
import Image from "@/components/Image"
|
import Image from "@/components/Image"
|
||||||
import Button from "@/components/TempDesignSystem/Button"
|
import Button from "@/components/TempDesignSystem/Button"
|
||||||
import Link from "@/components/TempDesignSystem/Link"
|
import Link from "@/components/TempDesignSystem/Link"
|
||||||
import Title from "@/components/TempDesignSystem/Text/Title"
|
import Title from "@/components/TempDesignSystem/Text/Title"
|
||||||
|
import { getIntl } from "@/i18n"
|
||||||
|
|
||||||
import Contact from "./Contact"
|
import Contact from "./Contact"
|
||||||
|
|
||||||
@@ -11,7 +10,10 @@ import styles from "./joinLoyalty.module.css"
|
|||||||
|
|
||||||
import type { JoinLoyaltyContactProps } from "@/types/components/loyalty/sidebar"
|
import type { JoinLoyaltyContactProps } from "@/types/components/loyalty/sidebar"
|
||||||
|
|
||||||
export default function JoinLoyaltyContact({ block }: JoinLoyaltyContactProps) {
|
export default async function JoinLoyaltyContact({
|
||||||
|
block,
|
||||||
|
}: JoinLoyaltyContactProps) {
|
||||||
|
const { formatMessage } = await getIntl()
|
||||||
return (
|
return (
|
||||||
<div className={styles.container}>
|
<div className={styles.container}>
|
||||||
<div className={styles.wrapper}>
|
<div className={styles.wrapper}>
|
||||||
@@ -25,12 +27,12 @@ export default function JoinLoyaltyContact({ block }: JoinLoyaltyContactProps) {
|
|||||||
/>
|
/>
|
||||||
{block.preamble && <p className={styles.preamble}>{block.preamble}</p>}
|
{block.preamble && <p className={styles.preamble}>{block.preamble}</p>}
|
||||||
<Button intent="primary">
|
<Button intent="primary">
|
||||||
<span>{_("Join Scandic Friends")}</span>
|
<span>{formatMessage({ id: "Join Scandic Friends" })}</span>
|
||||||
</Button>
|
</Button>
|
||||||
<div className={styles.linkContainer}>
|
<div className={styles.linkContainer}>
|
||||||
<Link href="/login" className={styles.loginLink}>
|
<Link href="/login" className={styles.loginLink}>
|
||||||
{_("Already a friend?")} <br />
|
{formatMessage({ id: "Already a friend?" })} <br />
|
||||||
{_("Click here to log in")}
|
{formatMessage({ id: "Click here to log in" })}
|
||||||
</Link>
|
</Link>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -8,8 +8,7 @@ import Shortcuts from "@/components/MyPages/Blocks/Shortcuts"
|
|||||||
import PreviousStays from "@/components/MyPages/Blocks/Stays/Previous"
|
import PreviousStays from "@/components/MyPages/Blocks/Stays/Previous"
|
||||||
import SoonestStays from "@/components/MyPages/Blocks/Stays/Soonest"
|
import SoonestStays from "@/components/MyPages/Blocks/Stays/Soonest"
|
||||||
import UpcomingStays from "@/components/MyPages/Blocks/Stays/Upcoming"
|
import UpcomingStays from "@/components/MyPages/Blocks/Stays/Upcoming"
|
||||||
|
import { removeMultipleSlashes } from "@/utils/url"
|
||||||
import { ExpiringPoints } from "../Blocks/Points/ExpiringPoints"
|
|
||||||
|
|
||||||
import {
|
import {
|
||||||
AccountPageContentProps,
|
AccountPageContentProps,
|
||||||
@@ -56,7 +55,9 @@ export default function Content({ lang, content }: ContentProps) {
|
|||||||
href:
|
href:
|
||||||
item.dynamic_content.link.linkConnection.edges[0].node
|
item.dynamic_content.link.linkConnection.edges[0].node
|
||||||
.original_url ||
|
.original_url ||
|
||||||
`/${lang}${item.dynamic_content.link.linkConnection.edges[0].node.url}`,
|
removeMultipleSlashes(
|
||||||
|
`/${lang}/${item.dynamic_content.link.linkConnection.edges[0].node.url}`
|
||||||
|
),
|
||||||
text: item.dynamic_content.link.link_text,
|
text: item.dynamic_content.link.link_text,
|
||||||
}
|
}
|
||||||
: null
|
: null
|
||||||
|
|||||||
@@ -23,19 +23,16 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.card {
|
.card {
|
||||||
flex: 1 1 0px;
|
align-items: center;
|
||||||
text-decoration: none;
|
background-color: var(--UI-Grey-10);
|
||||||
|
border-radius: 4px;
|
||||||
|
color: var(--Theme-Primary-Light-On-Surface-Text);
|
||||||
display: flex;
|
display: flex;
|
||||||
|
flex: 1 1 0px;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
gap: 10px;
|
gap: 10px;
|
||||||
min-height: 280px;
|
|
||||||
background-color: var(--UI-Grey-10);
|
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
align-items: center;
|
min-height: 280px;
|
||||||
padding: 30px;
|
padding: 30px;
|
||||||
border-radius: 4px;
|
|
||||||
|
|
||||||
text-decoration: none;
|
|
||||||
text-align: center;
|
text-align: center;
|
||||||
color: var(--Theme-Primary-Light-On-Surface-Text);
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,10 +1,9 @@
|
|||||||
import Link from "next/link"
|
|
||||||
|
|
||||||
import { Lang } from "@/constants/languages"
|
import { Lang } from "@/constants/languages"
|
||||||
import { serverClient } from "@/lib/trpc/server"
|
import { serverClient } from "@/lib/trpc/server"
|
||||||
|
|
||||||
import Header from "@/components/MyPages/Blocks/Header"
|
import Header from "@/components/MyPages/Blocks/Header"
|
||||||
import CardGrid from "@/components/TempDesignSystem/CardGrid"
|
import CardGrid from "@/components/TempDesignSystem/CardGrid"
|
||||||
|
import Link from "@/components/TempDesignSystem/Link"
|
||||||
import Title from "@/components/TempDesignSystem/Text/Title"
|
import Title from "@/components/TempDesignSystem/Text/Title"
|
||||||
|
|
||||||
import levelsData from "../data"
|
import levelsData from "../data"
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
import Link from "next/link"
|
|
||||||
import { Lock } from "react-feather"
|
import { Lock } from "react-feather"
|
||||||
|
|
||||||
import { serverClient } from "@/lib/trpc/server"
|
import { serverClient } from "@/lib/trpc/server"
|
||||||
@@ -6,6 +5,8 @@ import { serverClient } from "@/lib/trpc/server"
|
|||||||
import Header from "@/components/MyPages/Blocks/Header"
|
import Header from "@/components/MyPages/Blocks/Header"
|
||||||
import Button from "@/components/TempDesignSystem/Button"
|
import Button from "@/components/TempDesignSystem/Button"
|
||||||
import CardGrid from "@/components/TempDesignSystem/CardGrid"
|
import CardGrid from "@/components/TempDesignSystem/CardGrid"
|
||||||
|
import Link from "@/components/TempDesignSystem/Link"
|
||||||
|
import { getIntl } from "@/i18n"
|
||||||
|
|
||||||
import styles from "./next.module.css"
|
import styles from "./next.module.css"
|
||||||
|
|
||||||
@@ -17,7 +18,7 @@ export default async function NextLevelBenefitsBlock({
|
|||||||
link,
|
link,
|
||||||
}: AccountPageComponentProps) {
|
}: AccountPageComponentProps) {
|
||||||
const { nextLevel, perks } = await serverClient().user.benefits.nextLevel()
|
const { nextLevel, perks } = await serverClient().user.benefits.nextLevel()
|
||||||
|
const { formatMessage } = await getIntl()
|
||||||
return (
|
return (
|
||||||
<section className={styles.container}>
|
<section className={styles.container}>
|
||||||
<Header title={title} subtitle={subtitle} link={link} />
|
<Header title={title} subtitle={subtitle} link={link} />
|
||||||
@@ -26,7 +27,7 @@ export default async function NextLevelBenefitsBlock({
|
|||||||
<article key={perk.id} className={styles.card}>
|
<article key={perk.id} className={styles.card}>
|
||||||
<Button type="button" disabled>
|
<Button type="button" disabled>
|
||||||
<Lock height={16} />
|
<Lock height={16} />
|
||||||
Level up to unlock
|
{formatMessage({ id: "Level up to unlock" })}
|
||||||
</Button>
|
</Button>
|
||||||
<div>
|
<div>
|
||||||
<span className={styles.level}>As our {nextLevel}</span>{" "}
|
<span className={styles.level}>As our {nextLevel}</span>{" "}
|
||||||
@@ -36,9 +37,9 @@ export default async function NextLevelBenefitsBlock({
|
|||||||
))}
|
))}
|
||||||
</CardGrid>
|
</CardGrid>
|
||||||
<div className={styles.buttonContainer}>
|
<div className={styles.buttonContainer}>
|
||||||
<Button intent="primary" asChild>
|
<Button asChild intent="primary">
|
||||||
<Link href="#" className={styles.buttonText}>
|
<Link href="#">
|
||||||
Explore all levels and benefits
|
{formatMessage({ id: "Explore all levels and benefits" })}
|
||||||
</Link>
|
</Link>
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -45,20 +45,11 @@
|
|||||||
justify-content: center;
|
justify-content: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
.buttonText {
|
@media screen and (min-width: 950px) {
|
||||||
text-decoration: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
@media screen and (min-width: 1367px) {
|
|
||||||
.cardContainer {
|
.cardContainer {
|
||||||
grid-template-columns: 1fr 1fr 1fr;
|
grid-template-columns: 1fr 1fr 1fr;
|
||||||
}
|
}
|
||||||
|
|
||||||
.level {
|
|
||||||
font-size: var(--typography-Script-Desktop-fontSize);
|
|
||||||
font-weight: var(--typography-Script-Desktop-fontWeight);
|
|
||||||
}
|
|
||||||
|
|
||||||
.cardSubtitle {
|
.cardSubtitle {
|
||||||
font-size: var(--typography-Subtitle-Desktop-fontSize, 18px);
|
font-size: var(--typography-Subtitle-Desktop-fontSize, 18px);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,21 +1,26 @@
|
|||||||
import { _ } from "@/lib/translation"
|
|
||||||
|
|
||||||
import Image from "@/components/Image"
|
import Image from "@/components/Image"
|
||||||
import Title from "@/components/TempDesignSystem/Text/Title"
|
import Title from "@/components/TempDesignSystem/Text/Title"
|
||||||
|
import { getIntl } from "@/i18n"
|
||||||
|
|
||||||
import styles from "./challenges.module.css"
|
import styles from "./challenges.module.css"
|
||||||
|
|
||||||
import type { ChallengesProps } from "@/types/components/myPages/myPage/challenges"
|
import type { ChallengesProps } from "@/types/components/myPages/myPage/challenges"
|
||||||
|
|
||||||
export default function Challenges({ journeys, victories }: ChallengesProps) {
|
export default async function Challenges({
|
||||||
|
journeys,
|
||||||
|
victories,
|
||||||
|
}: ChallengesProps) {
|
||||||
|
const { formatMessage } = await getIntl()
|
||||||
return (
|
return (
|
||||||
<section className={styles.challenges}>
|
<section className={styles.challenges}>
|
||||||
<header className={styles.header}>
|
<header className={styles.header}>
|
||||||
<Title level="h2">{_("Your Challenges Conquer & Earn!")}</Title>
|
<Title level="h2">
|
||||||
|
{formatMessage({ id: "Your Challenges Conquer & Earn!" })}
|
||||||
|
</Title>
|
||||||
</header>
|
</header>
|
||||||
<section className={styles.section}>
|
<section className={styles.section}>
|
||||||
<header>
|
<header>
|
||||||
<Title level="h3">{_("On your journey")}</Title>
|
<Title level="h3">{formatMessage({ id: "On your journey" })}</Title>
|
||||||
</header>
|
</header>
|
||||||
<section className={styles.journeys}>
|
<section className={styles.journeys}>
|
||||||
{journeys.map((journey) => (
|
{journeys.map((journey) => (
|
||||||
@@ -28,7 +33,9 @@ export default function Challenges({ journeys, victories }: ChallengesProps) {
|
|||||||
</section>
|
</section>
|
||||||
<aside className={styles.aside}>
|
<aside className={styles.aside}>
|
||||||
<header>
|
<header>
|
||||||
<Title level="h3">{_("Previous victories")}</Title>
|
<Title level="h3">
|
||||||
|
{formatMessage({ id: "Previous victories" })}
|
||||||
|
</Title>
|
||||||
</header>
|
</header>
|
||||||
<section className={styles.victories}>
|
<section className={styles.victories}>
|
||||||
{victories.map((victory) => (
|
{victories.map((victory) => (
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ export default function Header({
|
|||||||
{title}
|
{title}
|
||||||
</Title>
|
</Title>
|
||||||
{link && (
|
{link && (
|
||||||
<Link className={styles.link} href={link.href}>
|
<Link className={styles.link} href={link.href} variant="myPage">
|
||||||
{link.text}
|
{link.text}
|
||||||
</Link>
|
</Link>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -1,22 +0,0 @@
|
|||||||
"use client"
|
|
||||||
|
|
||||||
import Image from "@/components/Image"
|
|
||||||
import Button from "@/components/TempDesignSystem/Button"
|
|
||||||
|
|
||||||
import styles from "./btn.module.css"
|
|
||||||
|
|
||||||
export default function MembershipCardButton() {
|
|
||||||
function handleShowMembershipCard() {
|
|
||||||
console.log(`Showing membership card`)
|
|
||||||
}
|
|
||||||
return (
|
|
||||||
<Button
|
|
||||||
className={styles.membershipBtn}
|
|
||||||
onClick={handleShowMembershipCard}
|
|
||||||
type="button"
|
|
||||||
>
|
|
||||||
<span>Membership Card</span>
|
|
||||||
<Image alt="QR icon" height={20} src="/_static/icons/qr.svg" width={20} />
|
|
||||||
</Button>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
@@ -1,19 +1,19 @@
|
|||||||
import { _ } from "@/lib/translation"
|
|
||||||
|
|
||||||
import { GoodFriend } from "@/components/Levels"
|
import { GoodFriend } from "@/components/Levels"
|
||||||
import BiroScript from "@/components/TempDesignSystem/Text/BiroScript"
|
import BiroScript from "@/components/TempDesignSystem/Text/BiroScript"
|
||||||
import Title from "@/components/TempDesignSystem/Text/Title"
|
import Title from "@/components/TempDesignSystem/Text/Title"
|
||||||
|
import { getIntl } from "@/i18n"
|
||||||
|
|
||||||
import styles from "./friend.module.css"
|
import styles from "./friend.module.css"
|
||||||
|
|
||||||
import type { UserProps } from "@/types/components/myPages/user"
|
import type { UserProps } from "@/types/components/myPages/user"
|
||||||
|
|
||||||
export default function Friend({ user }: UserProps) {
|
export default async function Friend({ user }: UserProps) {
|
||||||
|
const { formatMessage } = await getIntl()
|
||||||
return (
|
return (
|
||||||
<section className={styles.friend}>
|
<section className={styles.friend}>
|
||||||
<header className={styles.header}>
|
<header className={styles.header}>
|
||||||
<BiroScript className={styles.levelLabel} color="pale">
|
<BiroScript className={styles.levelLabel} color="pale">
|
||||||
Current level:
|
{formatMessage({ id: "Current level" })}:
|
||||||
</BiroScript>
|
</BiroScript>
|
||||||
<GoodFriend className={styles.level} color="pale" />
|
<GoodFriend className={styles.level} color="pale" />
|
||||||
</header>
|
</header>
|
||||||
@@ -22,7 +22,9 @@ export default function Friend({ user }: UserProps) {
|
|||||||
{user.name}
|
{user.name}
|
||||||
</Title>
|
</Title>
|
||||||
<div className={styles.membershipContainer}>
|
<div className={styles.membershipContainer}>
|
||||||
<p className={styles.membershipId}>{_("Membership ID:")}</p>
|
<p className={styles.membershipId}>
|
||||||
|
{formatMessage({ id: "Membership ID" })}:
|
||||||
|
</p>
|
||||||
<p className={styles.membershipId}>
|
<p className={styles.membershipId}>
|
||||||
{user.membership ? user.membership.membershipNumber : "N/A"}
|
{user.membership ? user.membership.membershipNumber : "N/A"}
|
||||||
</p>
|
</p>
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
import { _ } from "@/lib/translation"
|
|
||||||
|
|
||||||
import BiroScript from "@/components/TempDesignSystem/Text/BiroScript"
|
import BiroScript from "@/components/TempDesignSystem/Text/BiroScript"
|
||||||
import Title from "@/components/TempDesignSystem/Text/Title"
|
import Title from "@/components/TempDesignSystem/Text/Title"
|
||||||
|
import { getIntl } from "@/i18n"
|
||||||
|
|
||||||
import Label from "../Label"
|
import Label from "../Label"
|
||||||
|
|
||||||
@@ -9,13 +8,14 @@ import styles from "./nextLevel.module.css"
|
|||||||
|
|
||||||
import type { UserProps } from "@/types/components/myPages/user"
|
import type { UserProps } from "@/types/components/myPages/user"
|
||||||
|
|
||||||
export default function NextLevel({}: UserProps) {
|
export default async function NextLevel({}: UserProps) {
|
||||||
|
const { formatMessage } = await getIntl()
|
||||||
return (
|
return (
|
||||||
<section>
|
<section>
|
||||||
<Label>{_("Next level")}:</Label>
|
<Label>{formatMessage({ id: "Next level" })}:</Label>
|
||||||
<Title className={styles.nextLevel} color="pale" level="h3">
|
<Title className={styles.nextLevel} color="pale" level="h3">
|
||||||
{_("Close friend")}
|
N/A
|
||||||
<BiroScript>{_("Coming up")}!</BiroScript>
|
<BiroScript>{formatMessage({ id: "Coming up" })}!</BiroScript>
|
||||||
</Title>
|
</Title>
|
||||||
</section>
|
</section>
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -7,6 +7,7 @@
|
|||||||
@media screen and (min-width: 768px) {
|
@media screen and (min-width: 768px) {
|
||||||
.nextLevel {
|
.nextLevel {
|
||||||
gap: var(--Spacing-x1);
|
gap: var(--Spacing-x1);
|
||||||
grid-template-columns: 1fr auto;
|
grid-template-columns: auto auto;
|
||||||
|
justify-content: flex-start;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import Title from "@/components/TempDesignSystem/Text/Title"
|
import Title from "@/components/TempDesignSystem/Text/Title"
|
||||||
|
import { getIntl } from "@/i18n"
|
||||||
|
|
||||||
import Label from "../Label"
|
import Label from "../Label"
|
||||||
|
|
||||||
@@ -6,17 +7,18 @@ import styles from "./totalPoints.module.css"
|
|||||||
|
|
||||||
import type { UserProps } from "@/types/components/myPages/user"
|
import type { UserProps } from "@/types/components/myPages/user"
|
||||||
|
|
||||||
export default function Points({ user }: UserProps) {
|
export default async function Points({ user }: UserProps) {
|
||||||
|
const { formatMessage } = await getIntl()
|
||||||
return (
|
return (
|
||||||
<section className={styles.points}>
|
<section className={styles.points}>
|
||||||
<article>
|
<article>
|
||||||
<Label>Total Points</Label>
|
<Label>{formatMessage({ id: "Total Points" })}</Label>
|
||||||
<Title color="pale" level="h2">
|
<Title color="pale" level="h2">
|
||||||
{user.membership ? user.membership.currentPoints : "N/A"}
|
{user.membership ? user.membership.currentPoints : "N/A"}
|
||||||
</Title>
|
</Title>
|
||||||
</article>
|
</article>
|
||||||
<article>
|
<article>
|
||||||
<Label>Points until next level</Label>
|
<Label>{formatMessage({ id: "Points until next level" })}</Label>
|
||||||
<Title color="pale" level="h2">
|
<Title color="pale" level="h2">
|
||||||
{user.membership ? user.membership.currentPoints : "N/A"}
|
{user.membership ? user.membership.currentPoints : "N/A"}
|
||||||
</Title>
|
</Title>
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import { _ } from "@/lib/translation"
|
|
||||||
import { serverClient } from "@/lib/trpc/server"
|
import { serverClient } from "@/lib/trpc/server"
|
||||||
|
|
||||||
import Header from "@/components/MyPages/Blocks/Header"
|
import Header from "@/components/MyPages/Blocks/Header"
|
||||||
|
import { getIntl } from "@/i18n"
|
||||||
|
|
||||||
import styles from "./currentPointsBalance.module.css"
|
import styles from "./currentPointsBalance.module.css"
|
||||||
|
|
||||||
@@ -14,17 +14,18 @@ async function CurrentPointsBalance({
|
|||||||
lang,
|
lang,
|
||||||
}: AccountPageComponentProps) {
|
}: AccountPageComponentProps) {
|
||||||
const user = await serverClient().user.get()
|
const user = await serverClient().user.get()
|
||||||
|
const { formatMessage } = await getIntl()
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<Header title={title} link={link} subtitle={subtitle} />
|
<Header title={title} link={link} subtitle={subtitle} />
|
||||||
|
|
||||||
<div className={styles.card}>
|
<div className={styles.card}>
|
||||||
<h2>{`${_("Total points")}*`}</h2>
|
<h2>{`${formatMessage({ id: "Total points" })}*`}</h2>
|
||||||
<p
|
<p
|
||||||
className={styles.points}
|
className={styles.points}
|
||||||
>{`${_("Points")}: ${user.membership?.currentPoints || "N/A"}`}</p>
|
>{`${formatMessage({ id: "Points" })}: ${user.membership?.currentPoints || "N/A"}`}</p>
|
||||||
<p className={styles.disclaimer}>
|
<p className={styles.disclaimer}>
|
||||||
{`*${_("Points may take up to 10 days to be displayed.")}`}
|
{`*${formatMessage({ id: "Points may take up to 10 days to be displayed." })}`}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,11 +1,10 @@
|
|||||||
"use client"
|
"use client"
|
||||||
|
import { useIntl } from "react-intl"
|
||||||
|
|
||||||
import { dt } from "@/lib/dt"
|
import { dt } from "@/lib/dt"
|
||||||
import { _ } from "@/lib/translation"
|
|
||||||
import { trpc } from "@/lib/trpc/client"
|
import { trpc } from "@/lib/trpc/client"
|
||||||
|
|
||||||
import Header from "@/components/MyPages/Blocks/Header"
|
import Header from "@/components/MyPages/Blocks/Header"
|
||||||
import Button from "@/components/TempDesignSystem/Button"
|
|
||||||
|
|
||||||
import styles from "./earnAndBurn.module.css"
|
import styles from "./earnAndBurn.module.css"
|
||||||
|
|
||||||
@@ -13,11 +12,11 @@ import { AccountPageComponentProps } from "@/types/components/myPages/myPage/acc
|
|||||||
import { Page, RowProps } from "@/types/components/myPages/myPage/earnAndBurn"
|
import { Page, RowProps } from "@/types/components/myPages/myPage/earnAndBurn"
|
||||||
|
|
||||||
const tableHeadings = [
|
const tableHeadings = [
|
||||||
_("Arrival date"),
|
"Arrival date",
|
||||||
_("Description"),
|
"Description",
|
||||||
_("Booking number"),
|
"Booking number",
|
||||||
_("Transaction date"),
|
"Transaction date",
|
||||||
_("Points"),
|
"Points",
|
||||||
]
|
]
|
||||||
|
|
||||||
function EarnAndBurn({
|
function EarnAndBurn({
|
||||||
@@ -26,7 +25,8 @@ function EarnAndBurn({
|
|||||||
subtitle,
|
subtitle,
|
||||||
link,
|
link,
|
||||||
}: AccountPageComponentProps) {
|
}: AccountPageComponentProps) {
|
||||||
const { data, hasNextPage, isFetching, fetchNextPage } =
|
const intl = useIntl()
|
||||||
|
const { data, hasNextPage, fetchNextPage } =
|
||||||
trpc.user.transaction.friendTransactions.useInfiniteQuery(
|
trpc.user.transaction.friendTransactions.useInfiniteQuery(
|
||||||
{ limit: 5 },
|
{ limit: 5 },
|
||||||
{
|
{
|
||||||
@@ -49,8 +49,12 @@ function EarnAndBurn({
|
|||||||
<table className={styles.mobileTable}>
|
<table className={styles.mobileTable}>
|
||||||
<thead className={styles.mobileThead}>
|
<thead className={styles.mobileThead}>
|
||||||
<tr>
|
<tr>
|
||||||
<th className={styles.mobileTh}>{_("Transactions")}</th>
|
<th className={styles.mobileTh}>
|
||||||
<th className={styles.mobileTh}>{_("Points")}</th>
|
{intl.formatMessage({ id: "Transactions" })}
|
||||||
|
</th>
|
||||||
|
<th className={styles.mobileTh}>
|
||||||
|
{intl.formatMessage({ id: "Points" })}
|
||||||
|
</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
@@ -72,7 +76,7 @@ function EarnAndBurn({
|
|||||||
<span>{`${transaction.hotelName}, ${transaction.city}`}</span>
|
<span>{`${transaction.hotelName}, ${transaction.city}`}</span>
|
||||||
) : null}
|
) : null}
|
||||||
<span>
|
<span>
|
||||||
{`${transaction.nights} ${_(transaction.nights === 1 ? "night" : "nights")}`}
|
{`${transaction.nights} ${intl.formatMessage({ id: transaction.nights === 1 ? "night" : "nights" })}`}
|
||||||
</span>
|
</span>
|
||||||
</td>
|
</td>
|
||||||
<td
|
<td
|
||||||
@@ -85,7 +89,7 @@ function EarnAndBurn({
|
|||||||
) : (
|
) : (
|
||||||
<tr>
|
<tr>
|
||||||
<td className={styles.mobilePlaceholder} colSpan={2}>
|
<td className={styles.mobilePlaceholder} colSpan={2}>
|
||||||
Empty
|
{intl.formatMessage({ id: "Empty" })}
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
)}
|
)}
|
||||||
@@ -99,7 +103,7 @@ function EarnAndBurn({
|
|||||||
<tr>
|
<tr>
|
||||||
{tableHeadings.map((heading) => (
|
{tableHeadings.map((heading) => (
|
||||||
<th key={heading} className={styles.th}>
|
<th key={heading} className={styles.th}>
|
||||||
{heading}
|
{intl.formatMessage({ id: heading })}
|
||||||
</th>
|
</th>
|
||||||
))}
|
))}
|
||||||
</tr>
|
</tr>
|
||||||
@@ -123,7 +127,7 @@ function EarnAndBurn({
|
|||||||
// type="button"
|
// type="button"
|
||||||
// onClick={loadMoreData}
|
// onClick={loadMoreData}
|
||||||
// >
|
// >
|
||||||
// {_("See more transactions")}
|
// {intl.formatMessage({id:"See more transactions"})}
|
||||||
// </Button>
|
// </Button>
|
||||||
<table className={styles.table}>
|
<table className={styles.table}>
|
||||||
<thead className={styles.thead}>
|
<thead className={styles.thead}>
|
||||||
@@ -141,7 +145,7 @@ function EarnAndBurn({
|
|||||||
colSpan={tableHeadings.length}
|
colSpan={tableHeadings.length}
|
||||||
className={styles.placeholder}
|
className={styles.placeholder}
|
||||||
>
|
>
|
||||||
{_("No transactions available")}
|
{intl.formatMessage({ id: "No transactions available" })}
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
</tbody>
|
</tbody>
|
||||||
@@ -153,10 +157,11 @@ function EarnAndBurn({
|
|||||||
}
|
}
|
||||||
|
|
||||||
function Row({ transaction, lang }: RowProps) {
|
function Row({ transaction, lang }: RowProps) {
|
||||||
|
const intl = useIntl()
|
||||||
const description =
|
const description =
|
||||||
transaction.hotelName && transaction.city
|
transaction.hotelName && transaction.city
|
||||||
? `${_(transaction.hotelName)}, ${transaction.city} ${transaction.nights} ${_("nights")}`
|
? `${intl.formatMessage({ id: transaction.hotelName })}, ${transaction.city} ${transaction.nights} ${intl.formatMessage({ id: "nights" })}`
|
||||||
: `${transaction.nights} ${_("nights")}`
|
: `${transaction.nights} ${intl.formatMessage({ id: "nights" })}`
|
||||||
const arrival = dt(transaction.checkinDate).locale(lang).format("DD MMM YYYY")
|
const arrival = dt(transaction.checkinDate).locale(lang).format("DD MMM YYYY")
|
||||||
const departure = dt(transaction.checkoutDate)
|
const departure = dt(transaction.checkoutDate)
|
||||||
.locale(lang)
|
.locale(lang)
|
||||||
|
|||||||
@@ -1,14 +1,15 @@
|
|||||||
import { _ } from "@/lib/translation"
|
import { getIntl } from "@/i18n"
|
||||||
|
|
||||||
import styles from "./expiringPoints.module.css"
|
import styles from "./expiringPoints.module.css"
|
||||||
|
|
||||||
export function ExpiringPoints() {
|
export async function ExpiringPoints() {
|
||||||
|
const { formatMessage } = await getIntl()
|
||||||
return (
|
return (
|
||||||
<table className={styles.table}>
|
<table className={styles.table}>
|
||||||
<thead className={styles.thead}>
|
<thead className={styles.thead}>
|
||||||
<tr>
|
<tr>
|
||||||
<th className={styles.th}>{_("Arrival date")}</th>
|
<th className={styles.th}>{formatMessage({ id: "Arrival date" })}</th>
|
||||||
<th className={styles.th}>{_("Points")}</th>
|
<th className={styles.th}>{formatMessage({ id: "Points" })}</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
|
|||||||
@@ -1,14 +1,15 @@
|
|||||||
import { _ } from "@/lib/translation"
|
import { useIntl } from "react-intl"
|
||||||
|
|
||||||
import Title from "@/components/TempDesignSystem/Text/Title"
|
import Title from "@/components/TempDesignSystem/Text/Title"
|
||||||
|
|
||||||
import styles from "./emptyPreviousStays.module.css"
|
import styles from "./emptyPreviousStays.module.css"
|
||||||
|
|
||||||
export default function EmptyPreviousStaysBlock() {
|
export default function EmptyPreviousStaysBlock() {
|
||||||
|
const { formatMessage } = useIntl()
|
||||||
return (
|
return (
|
||||||
<section className={styles.container}>
|
<section className={styles.container}>
|
||||||
<Title as="h5" level="h3">
|
<Title as="h5" level="h3">
|
||||||
{_("You have no previous stays.")}
|
{formatMessage({ id: "You have no previous stays." })}
|
||||||
</Title>
|
</Title>
|
||||||
</section>
|
</section>
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -1,3 +1,7 @@
|
|||||||
|
"use client"
|
||||||
|
|
||||||
|
import { useIntl } from "react-intl"
|
||||||
|
|
||||||
import Button from "@/components/TempDesignSystem/Button"
|
import Button from "@/components/TempDesignSystem/Button"
|
||||||
|
|
||||||
import styles from "./button.module.css"
|
import styles from "./button.module.css"
|
||||||
@@ -8,16 +12,17 @@ export default function ShowMoreButton({
|
|||||||
disabled,
|
disabled,
|
||||||
loadMoreData,
|
loadMoreData,
|
||||||
}: ShowMoreButtonParams) {
|
}: ShowMoreButtonParams) {
|
||||||
|
const { formatMessage } = useIntl()
|
||||||
return (
|
return (
|
||||||
<div className={styles.container}>
|
<div className={styles.container}>
|
||||||
<Button
|
<Button
|
||||||
disabled={disabled}
|
disabled={disabled}
|
||||||
intent="primary"
|
intent="primary"
|
||||||
theme="primaryDark"
|
|
||||||
type="button"
|
|
||||||
onClick={loadMoreData}
|
onClick={loadMoreData}
|
||||||
|
theme="secondaryDark"
|
||||||
|
type="button"
|
||||||
>
|
>
|
||||||
Show more
|
{formatMessage({ id: "Show more" })}
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -1,24 +1,24 @@
|
|||||||
import { _ } from "@/lib/translation"
|
|
||||||
|
|
||||||
import Button from "@/components/TempDesignSystem/Button"
|
import Button from "@/components/TempDesignSystem/Button"
|
||||||
import Link from "@/components/TempDesignSystem/Link"
|
import Link from "@/components/TempDesignSystem/Link"
|
||||||
import Title from "@/components/TempDesignSystem/Text/Title"
|
import Title from "@/components/TempDesignSystem/Text/Title"
|
||||||
|
import { getIntl } from "@/i18n"
|
||||||
|
|
||||||
import styles from "./emptyUpcomingStays.module.css"
|
import styles from "./emptyUpcomingStays.module.css"
|
||||||
|
|
||||||
export default function EmptyUpcomingStaysBlock() {
|
export default async function EmptyUpcomingStaysBlock() {
|
||||||
|
const { formatMessage } = await getIntl()
|
||||||
return (
|
return (
|
||||||
<section className={styles.container}>
|
<section className={styles.container}>
|
||||||
<Title as="h5" level="h3">
|
<Title as="h5" level="h3">
|
||||||
{_("You have no upcoming stays.")}
|
{formatMessage({ id: "You have no upcoming stays." })}
|
||||||
<span className={styles.grayTitle}>
|
<span className={styles.grayTitle}>
|
||||||
{" "}
|
{" "}
|
||||||
{_("Where should you go next?")}
|
{formatMessage({ id: "Where should you go next?" })}
|
||||||
</span>
|
</span>
|
||||||
</Title>
|
</Title>
|
||||||
<Button intent="primary" asChild type="button">
|
<Button asChild intent="primary" type="button">
|
||||||
<Link className={styles.link} href="#" key="getInspired">
|
<Link className={styles.link} href="#" key="getInspired">
|
||||||
{_("Get inspired")}
|
{formatMessage({ id: "Get inspired" })}
|
||||||
</Link>
|
</Link>
|
||||||
</Button>
|
</Button>
|
||||||
</section>
|
</section>
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
import { serverClient } from "@/lib/trpc/server"
|
import { serverClient } from "@/lib/trpc/server"
|
||||||
|
|
||||||
import MaxWidth from "@/components/MaxWidth"
|
|
||||||
import CardGrid from "@/components/TempDesignSystem/CardGrid"
|
import CardGrid from "@/components/TempDesignSystem/CardGrid"
|
||||||
|
|
||||||
import Header from "../../Header"
|
import Header from "../../Header"
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { _ } from "@/lib/translation"
|
import { useIntl } from "react-intl"
|
||||||
|
|
||||||
import Button from "@/components/TempDesignSystem/Button"
|
import Button from "@/components/TempDesignSystem/Button"
|
||||||
import Link from "@/components/TempDesignSystem/Link"
|
import Link from "@/components/TempDesignSystem/Link"
|
||||||
@@ -7,18 +7,19 @@ import Title from "@/components/TempDesignSystem/Text/Title"
|
|||||||
import styles from "./emptyUpcomingStays.module.css"
|
import styles from "./emptyUpcomingStays.module.css"
|
||||||
|
|
||||||
export default function EmptyUpcomingStaysBlock() {
|
export default function EmptyUpcomingStaysBlock() {
|
||||||
|
const { formatMessage } = useIntl()
|
||||||
return (
|
return (
|
||||||
<section className={styles.container}>
|
<section className={styles.container}>
|
||||||
<Title as="h5" level="h3">
|
<Title as="h5" level="h3">
|
||||||
{_("You have no upcoming stays.")}
|
{formatMessage({ id: "You have no upcoming stays." })}
|
||||||
<span className={styles.grayTitle}>
|
<span className={styles.grayTitle}>
|
||||||
{" "}
|
{" "}
|
||||||
{_("Where should you go next?")}
|
{formatMessage({ id: "Where should you go next?" })}
|
||||||
</span>
|
</span>
|
||||||
</Title>
|
</Title>
|
||||||
<Button intent="primary" asChild type="button">
|
<Button asChild intent="primary" type="button">
|
||||||
<Link className={styles.link} href="#" key="getInspired">
|
<Link className={styles.link} href="#" key="getInspired">
|
||||||
{_("Get inspired")}
|
{formatMessage({ id: "Get inspired" })}
|
||||||
</Link>
|
</Link>
|
||||||
</Button>
|
</Button>
|
||||||
</section>
|
</section>
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
"use client"
|
"use client"
|
||||||
|
|
||||||
import { _ } from "@/lib/translation"
|
|
||||||
import { trpc } from "@/lib/trpc/client"
|
import { trpc } from "@/lib/trpc/client"
|
||||||
|
|
||||||
import LoadingSpinner from "@/components/LoadingSpinner"
|
import LoadingSpinner from "@/components/LoadingSpinner"
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
import { _ } from "@/lib/translation"
|
|
||||||
import { serverClient } from "@/lib/trpc/server"
|
import { serverClient } from "@/lib/trpc/server"
|
||||||
|
|
||||||
import Breadcrumb from "./Breadcrumb"
|
import Breadcrumb from "./Breadcrumb"
|
||||||
|
|||||||
@@ -1,43 +0,0 @@
|
|||||||
.user {
|
|
||||||
align-items: center;
|
|
||||||
background-color: var(--some-black-color, #000);
|
|
||||||
border-radius: 50%;
|
|
||||||
color: var(--some-white-color, #fff);
|
|
||||||
display: flex;
|
|
||||||
font-family: var(--typography-Body-Regular-fontFamily);
|
|
||||||
font-size: 1.2rem;
|
|
||||||
font-weight: 600;
|
|
||||||
height: 3.5rem;
|
|
||||||
justify-content: center;
|
|
||||||
position: relative;
|
|
||||||
width: 3.5rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.alert {
|
|
||||||
align-items: center;
|
|
||||||
background-color: var(--some-red-color, #ed2027);
|
|
||||||
border-radius: 50%;
|
|
||||||
display: flex;
|
|
||||||
font-size: 1rem;
|
|
||||||
height: 2rem;
|
|
||||||
justify-content: center;
|
|
||||||
position: absolute;
|
|
||||||
right: -1rem;
|
|
||||||
top: -0.5rem;
|
|
||||||
width: 2rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
@media screen and (min-width: 1367px) {
|
|
||||||
.user {
|
|
||||||
height: 2.8rem;
|
|
||||||
width: 2.8rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.alert {
|
|
||||||
font-size: 0.6rem;
|
|
||||||
height: 1rem;
|
|
||||||
right: -0.2rem;
|
|
||||||
top: -0.1rem;
|
|
||||||
width: 1rem;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -6,6 +6,7 @@ import { serverClient } from "@/lib/trpc/server"
|
|||||||
|
|
||||||
import Link from "@/components/TempDesignSystem/Link"
|
import Link from "@/components/TempDesignSystem/Link"
|
||||||
import Title from "@/components/TempDesignSystem/Text/Title"
|
import Title from "@/components/TempDesignSystem/Text/Title"
|
||||||
|
import { getIntl } from "@/i18n"
|
||||||
|
|
||||||
import styles from "./sidebar.module.css"
|
import styles from "./sidebar.module.css"
|
||||||
|
|
||||||
@@ -13,6 +14,7 @@ import { LangParams } from "@/types/params"
|
|||||||
|
|
||||||
export default async function Sidebar({ lang }: LangParams) {
|
export default async function Sidebar({ lang }: LangParams) {
|
||||||
const navigation = await serverClient().contentstack.myPages.navigation.get()
|
const navigation = await serverClient().contentstack.myPages.navigation.get()
|
||||||
|
const { formatMessage } = await getIntl()
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<aside className={styles.sidebar}>
|
<aside className={styles.sidebar}>
|
||||||
@@ -43,7 +45,7 @@ export default async function Sidebar({ lang }: LangParams) {
|
|||||||
))}
|
))}
|
||||||
|
|
||||||
<Link prefetch={false} href={logout[lang]} variant="sidebar">
|
<Link prefetch={false} href={logout[lang]} variant="sidebar">
|
||||||
Log out <LogOut height={16} width={16} />
|
{formatMessage({ id: "Log out" })} <LogOut height={16} width={16} />
|
||||||
</Link>
|
</Link>
|
||||||
</nav>
|
</nav>
|
||||||
</aside>
|
</aside>
|
||||||
|
|||||||
@@ -1,12 +1,16 @@
|
|||||||
import Card from "@/components/MyProfile/Card"
|
import Card from "@/components/MyProfile/Card"
|
||||||
import Title from "@/components/TempDesignSystem/Text/Title"
|
import Title from "@/components/TempDesignSystem/Text/Title"
|
||||||
|
import { getIntl } from "@/i18n"
|
||||||
|
|
||||||
import styles from "./com.module.css"
|
import styles from "./com.module.css"
|
||||||
|
|
||||||
export default function CommunicationPreferences() {
|
export default async function CommunicationPreferences() {
|
||||||
|
const { formatMessage } = await getIntl()
|
||||||
return (
|
return (
|
||||||
<Card className={styles.container}>
|
<Card className={styles.container}>
|
||||||
<Title level="h2">My communication preferences</Title>
|
<Title level="h4">
|
||||||
|
{formatMessage({ id: "My communication preferences" })}
|
||||||
|
</Title>
|
||||||
</Card>
|
</Card>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,12 +1,14 @@
|
|||||||
import Card from "@/components/MyProfile/Card"
|
import Card from "@/components/MyProfile/Card"
|
||||||
import Title from "@/components/TempDesignSystem/Text/Title"
|
import Title from "@/components/TempDesignSystem/Text/Title"
|
||||||
|
import { getIntl } from "@/i18n"
|
||||||
|
|
||||||
import styles from "./creditCards.module.css"
|
import styles from "./creditCards.module.css"
|
||||||
|
|
||||||
export default function CreditCards() {
|
export default async function CreditCards() {
|
||||||
|
const { formatMessage } = await getIntl()
|
||||||
return (
|
return (
|
||||||
<Card className={styles.container}>
|
<Card className={styles.container}>
|
||||||
<Title level="h2">My credit cards</Title>
|
<Title level="h4">{formatMessage({ id: "My credit cards" })}</Title>
|
||||||
</Card>
|
</Card>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,12 +1,14 @@
|
|||||||
import Card from "@/components/MyProfile/Card"
|
import Card from "@/components/MyProfile/Card"
|
||||||
import Title from "@/components/TempDesignSystem/Text/Title"
|
import Title from "@/components/TempDesignSystem/Text/Title"
|
||||||
|
import { getIntl } from "@/i18n"
|
||||||
|
|
||||||
import styles from "./membershipCard.module.css"
|
import styles from "./membershipCard.module.css"
|
||||||
|
|
||||||
export default function MembershipCard() {
|
export default async function MembershipCard() {
|
||||||
|
const { formatMessage } = await getIntl()
|
||||||
return (
|
return (
|
||||||
<Card className={styles.container}>
|
<Card className={styles.container}>
|
||||||
<Title level="h2">Membership cards</Title>
|
<Title level="h4">{formatMessage({ id: "Membership cards" })}</Title>
|
||||||
</Card>
|
</Card>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,12 +1,14 @@
|
|||||||
import Card from "@/components/MyProfile/Card"
|
import Card from "@/components/MyProfile/Card"
|
||||||
import Title from "@/components/TempDesignSystem/Text/Title"
|
import Title from "@/components/TempDesignSystem/Text/Title"
|
||||||
|
import { getIntl } from "@/i18n"
|
||||||
|
|
||||||
import styles from "./password.module.css"
|
import styles from "./password.module.css"
|
||||||
|
|
||||||
export default function Password() {
|
export default async function Password() {
|
||||||
|
const { formatMessage } = await getIntl()
|
||||||
return (
|
return (
|
||||||
<Card className={styles.container}>
|
<Card className={styles.container}>
|
||||||
<Title level="h2">Password</Title>
|
<Title level="h4">{formatMessage({ id: "Password" })}</Title>
|
||||||
</Card>
|
</Card>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,8 +2,8 @@
|
|||||||
import { useEffect } from "react"
|
import { useEffect } from "react"
|
||||||
import { useFormStatus } from "react-dom"
|
import { useFormStatus } from "react-dom"
|
||||||
import { useWatch } from "react-hook-form"
|
import { useWatch } from "react-hook-form"
|
||||||
|
import { useIntl } from "react-intl"
|
||||||
|
|
||||||
import { _ } from "@/lib/translation"
|
|
||||||
import { useProfileStore } from "@/stores/edit-profile"
|
import { useProfileStore } from "@/stores/edit-profile"
|
||||||
|
|
||||||
import {
|
import {
|
||||||
@@ -21,6 +21,7 @@ import Phone from "@/components/TempDesignSystem/Form/Phone"
|
|||||||
import type { EditFormContentProps } from "@/types/components/myPages/myProfile/edit"
|
import type { EditFormContentProps } from "@/types/components/myPages/myProfile/edit"
|
||||||
|
|
||||||
export default function FormContent({ control }: EditFormContentProps) {
|
export default function FormContent({ control }: EditFormContentProps) {
|
||||||
|
const { formatMessage } = useIntl()
|
||||||
const { pending } = useFormStatus()
|
const { pending } = useFormStatus()
|
||||||
const setIsPending = useProfileStore((store) => store.setIsPending)
|
const setIsPending = useProfileStore((store) => store.setIsPending)
|
||||||
const country = useWatch({ name: "address.country" })
|
const country = useWatch({ name: "address.country" })
|
||||||
@@ -29,11 +30,18 @@ export default function FormContent({ control }: EditFormContentProps) {
|
|||||||
setIsPending(pending)
|
setIsPending(pending)
|
||||||
}, [pending, setIsPending])
|
}, [pending, setIsPending])
|
||||||
|
|
||||||
|
const city = formatMessage({ id: "City" })
|
||||||
|
const email = formatMessage({ id: "Email" })
|
||||||
|
const street = formatMessage({ id: "Street" })
|
||||||
|
const zipCode = formatMessage({ id: "Zip code" })
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Field>
|
<Field>
|
||||||
<Field.Icon>{country}</Field.Icon>
|
<Field.Icon>{country}</Field.Icon>
|
||||||
<Field.Label htmlFor="address.country">*{_("Country")}</Field.Label>
|
<Field.Label htmlFor="address.country">
|
||||||
|
*{formatMessage({ id: "Country" })}
|
||||||
|
</Field.Label>
|
||||||
<Field.Content>
|
<Field.Content>
|
||||||
<CountrySelect name="address.country" />
|
<CountrySelect name="address.country" />
|
||||||
</Field.Content>
|
</Field.Content>
|
||||||
@@ -43,7 +51,9 @@ export default function FormContent({ control }: EditFormContentProps) {
|
|||||||
<Field.Icon>
|
<Field.Icon>
|
||||||
<CalendarIcon />
|
<CalendarIcon />
|
||||||
</Field.Icon>
|
</Field.Icon>
|
||||||
<Field.Label htmlFor="dateOfBirth">*{_("Date of Birth")}</Field.Label>
|
<Field.Label htmlFor="dateOfBirth">
|
||||||
|
*{formatMessage({ id: "Date of Birth" })}
|
||||||
|
</Field.Label>
|
||||||
<Field.Content>
|
<Field.Content>
|
||||||
<DateSelect
|
<DateSelect
|
||||||
control={control}
|
control={control}
|
||||||
@@ -57,13 +67,13 @@ export default function FormContent({ control }: EditFormContentProps) {
|
|||||||
<Field.Icon>
|
<Field.Icon>
|
||||||
<EmailIcon />
|
<EmailIcon />
|
||||||
</Field.Icon>
|
</Field.Icon>
|
||||||
<Field.Label htmlFor="email">*{_("Email")}</Field.Label>
|
<Field.Label htmlFor="email">*{email}</Field.Label>
|
||||||
<Field.Content>
|
<Field.Content>
|
||||||
<Input
|
<Input
|
||||||
aria-label={_("Email")}
|
aria-label={email}
|
||||||
control={control}
|
control={control}
|
||||||
name="email"
|
name="email"
|
||||||
placeholder={_("Email")}
|
placeholder={email}
|
||||||
registerOptions={{ required: true }}
|
registerOptions={{ required: true }}
|
||||||
type="email"
|
type="email"
|
||||||
/>
|
/>
|
||||||
@@ -74,7 +84,9 @@ export default function FormContent({ control }: EditFormContentProps) {
|
|||||||
<Field.Icon>
|
<Field.Icon>
|
||||||
<PhoneIcon />
|
<PhoneIcon />
|
||||||
</Field.Icon>
|
</Field.Icon>
|
||||||
<Field.Label htmlFor="phoneNumber">*{_("Phone")}</Field.Label>
|
<Field.Label htmlFor="phoneNumber">
|
||||||
|
*{formatMessage({ id: "Phone" })}
|
||||||
|
</Field.Label>
|
||||||
<Field.Content>
|
<Field.Content>
|
||||||
<Phone countrySelectName="address.country" name="phoneNumber" />
|
<Phone countrySelectName="address.country" name="phoneNumber" />
|
||||||
</Field.Content>
|
</Field.Content>
|
||||||
@@ -85,14 +97,14 @@ export default function FormContent({ control }: EditFormContentProps) {
|
|||||||
<HouseIcon />
|
<HouseIcon />
|
||||||
</Field.Icon>
|
</Field.Icon>
|
||||||
<Field.Label htmlFor="address.streetAddress">
|
<Field.Label htmlFor="address.streetAddress">
|
||||||
{_("Address")}
|
{formatMessage({ id: "Address" })}
|
||||||
</Field.Label>
|
</Field.Label>
|
||||||
<Field.Content>
|
<Field.Content>
|
||||||
<Input
|
<Input
|
||||||
aria-label={_("Street")}
|
aria-label={street}
|
||||||
control={control}
|
control={control}
|
||||||
name="address.streetAddress"
|
name="address.streetAddress"
|
||||||
placeholder={_("Street 123")}
|
placeholder={street}
|
||||||
/>
|
/>
|
||||||
</Field.Content>
|
</Field.Content>
|
||||||
</Field>
|
</Field>
|
||||||
@@ -101,13 +113,15 @@ export default function FormContent({ control }: EditFormContentProps) {
|
|||||||
<Field.Icon>
|
<Field.Icon>
|
||||||
<HouseIcon />
|
<HouseIcon />
|
||||||
</Field.Icon>
|
</Field.Icon>
|
||||||
<Field.Label htmlFor="address.city">{_("City/State")}</Field.Label>
|
<Field.Label htmlFor="address.city">
|
||||||
|
{formatMessage({ id: "City/State" })}
|
||||||
|
</Field.Label>
|
||||||
<Field.Content>
|
<Field.Content>
|
||||||
<Input
|
<Input
|
||||||
aria-label={_("City")}
|
aria-label={city}
|
||||||
control={control}
|
control={control}
|
||||||
name="address.city"
|
name="address.city"
|
||||||
placeholder={_("City")}
|
placeholder={city}
|
||||||
/>
|
/>
|
||||||
</Field.Content>
|
</Field.Content>
|
||||||
</Field>
|
</Field>
|
||||||
@@ -116,13 +130,13 @@ export default function FormContent({ control }: EditFormContentProps) {
|
|||||||
<Field.Icon>
|
<Field.Icon>
|
||||||
<HouseIcon />
|
<HouseIcon />
|
||||||
</Field.Icon>
|
</Field.Icon>
|
||||||
<Field.Label htmlFor="address.zipCode">*{_("Zip code")}</Field.Label>
|
<Field.Label htmlFor="address.zipCode">*{zipCode}</Field.Label>
|
||||||
<Field.Content>
|
<Field.Content>
|
||||||
<Input
|
<Input
|
||||||
aria-label={_("Zip code")}
|
aria-label={zipCode}
|
||||||
control={control}
|
control={control}
|
||||||
name="address.zipCode"
|
name="address.zipCode"
|
||||||
placeholder={_("Zip code")}
|
placeholder={zipCode}
|
||||||
registerOptions={{ required: true }}
|
registerOptions={{ required: true }}
|
||||||
/>
|
/>
|
||||||
</Field.Content>
|
</Field.Content>
|
||||||
|
|||||||
@@ -1,25 +1,17 @@
|
|||||||
import { z } from "zod"
|
import { z } from "zod"
|
||||||
|
|
||||||
import { _ } from "@/lib/translation"
|
|
||||||
|
|
||||||
import { phoneValidator } from "@/utils/phoneValidator"
|
import { phoneValidator } from "@/utils/phoneValidator"
|
||||||
|
|
||||||
export const editProfileSchema = z.object({
|
export const editProfileSchema = z.object({
|
||||||
"address.country": z
|
"address.country": z.string().min(1),
|
||||||
.string({ required_error: _("Country is required") })
|
|
||||||
.min(1, { message: _("Country is required") }),
|
|
||||||
"address.city": z.string().optional(),
|
"address.city": z.string().optional(),
|
||||||
"address.streetAddress": z.string().optional(),
|
"address.streetAddress": z.string().optional(),
|
||||||
"address.zipCode": z
|
"address.zipCode": z.string().min(1),
|
||||||
.string({ required_error: _("Zip code is required") })
|
dateOfBirth: z.string().min(1),
|
||||||
.min(1, { message: _("Zip code is required") }),
|
|
||||||
dateOfBirth: z
|
|
||||||
.string({ required_error: _("Date of Birth is required") })
|
|
||||||
.min(1, { message: _("Date of Birth is required") }),
|
|
||||||
email: z.string().email(),
|
email: z.string().email(),
|
||||||
phoneNumber: phoneValidator(
|
phoneNumber: phoneValidator(
|
||||||
_("Phone is required"),
|
"Phone is required",
|
||||||
_("Please enter a valid phone number")
|
"Please enter a valid phone number"
|
||||||
),
|
),
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
import { dt } from "@/lib/dt"
|
import { dt } from "@/lib/dt"
|
||||||
import { _ } from "@/lib/translation"
|
|
||||||
import { serverClient } from "@/lib/trpc/server"
|
import { serverClient } from "@/lib/trpc/server"
|
||||||
|
|
||||||
import {
|
import {
|
||||||
@@ -9,6 +8,7 @@ import {
|
|||||||
PhoneIcon,
|
PhoneIcon,
|
||||||
} from "@/components/Icons"
|
} from "@/components/Icons"
|
||||||
import { countries } from "@/components/TempDesignSystem/Form/Country/countries"
|
import { countries } from "@/components/TempDesignSystem/Form/Country/countries"
|
||||||
|
import { getIntl } from "@/i18n"
|
||||||
|
|
||||||
import Field from "../Field"
|
import Field from "../Field"
|
||||||
import Container from "./Container"
|
import Container from "./Container"
|
||||||
@@ -16,6 +16,7 @@ import Container from "./Container"
|
|||||||
import styles from "./profile.module.css"
|
import styles from "./profile.module.css"
|
||||||
|
|
||||||
export default async function Profile() {
|
export default async function Profile() {
|
||||||
|
const { formatMessage } = await getIntl()
|
||||||
const user = await serverClient().user.get()
|
const user = await serverClient().user.get()
|
||||||
const countryName = countries.find(
|
const countryName = countries.find(
|
||||||
(country) => country.code === user.address.country
|
(country) => country.code === user.address.country
|
||||||
@@ -26,49 +27,55 @@ export default async function Profile() {
|
|||||||
<section className={styles.info}>
|
<section className={styles.info}>
|
||||||
<Field>
|
<Field>
|
||||||
<Field.Icon>{user.address.country}</Field.Icon>
|
<Field.Icon>{user.address.country}</Field.Icon>
|
||||||
<Field.TextLabel>{_("Country")}</Field.TextLabel>
|
<Field.TextLabel>{formatMessage({ id: "Country" })}</Field.TextLabel>
|
||||||
<Field.Content>{countryName?.name}</Field.Content>
|
<Field.Content>{countryName?.name}</Field.Content>
|
||||||
</Field>
|
</Field>
|
||||||
<Field>
|
<Field>
|
||||||
<Field.Icon>
|
<Field.Icon>
|
||||||
<CalendarIcon />
|
<CalendarIcon />
|
||||||
</Field.Icon>
|
</Field.Icon>
|
||||||
<Field.TextLabel>{_("Date of Birth")}</Field.TextLabel>
|
<Field.TextLabel>
|
||||||
|
{formatMessage({ id: "Date of Birth" })}
|
||||||
|
</Field.TextLabel>
|
||||||
<Field.Content>{dob}</Field.Content>
|
<Field.Content>{dob}</Field.Content>
|
||||||
</Field>
|
</Field>
|
||||||
<Field>
|
<Field>
|
||||||
<Field.Icon>
|
<Field.Icon>
|
||||||
<EmailIcon />
|
<EmailIcon />
|
||||||
</Field.Icon>
|
</Field.Icon>
|
||||||
<Field.TextLabel>{_("Email")}</Field.TextLabel>
|
<Field.TextLabel>{formatMessage({ id: "Email" })}</Field.TextLabel>
|
||||||
<Field.Content>{user.email}</Field.Content>
|
<Field.Content>{user.email}</Field.Content>
|
||||||
</Field>
|
</Field>
|
||||||
<Field>
|
<Field>
|
||||||
<Field.Icon>
|
<Field.Icon>
|
||||||
<PhoneIcon />
|
<PhoneIcon />
|
||||||
</Field.Icon>
|
</Field.Icon>
|
||||||
<Field.TextLabel>{_("Phone number")}</Field.TextLabel>
|
<Field.TextLabel>
|
||||||
|
{formatMessage({ id: "Phone number" })}
|
||||||
|
</Field.TextLabel>
|
||||||
<Field.Content>{user.phoneNumber}</Field.Content>
|
<Field.Content>{user.phoneNumber}</Field.Content>
|
||||||
</Field>
|
</Field>
|
||||||
<Field>
|
<Field>
|
||||||
<Field.Icon>
|
<Field.Icon>
|
||||||
<HouseIcon />
|
<HouseIcon />
|
||||||
</Field.Icon>
|
</Field.Icon>
|
||||||
<Field.TextLabel>{_("Address")}</Field.TextLabel>
|
<Field.TextLabel>{formatMessage({ id: "Address" })}</Field.TextLabel>
|
||||||
<Field.Content>{user.address.streetAddress || "-"}</Field.Content>
|
<Field.Content>{user.address.streetAddress || "-"}</Field.Content>
|
||||||
</Field>
|
</Field>
|
||||||
<Field>
|
<Field>
|
||||||
<Field.Icon>
|
<Field.Icon>
|
||||||
<HouseIcon />
|
<HouseIcon />
|
||||||
</Field.Icon>
|
</Field.Icon>
|
||||||
<Field.TextLabel>{_("City/State")}</Field.TextLabel>
|
<Field.TextLabel>
|
||||||
|
{formatMessage({ id: "City/State" })}
|
||||||
|
</Field.TextLabel>
|
||||||
<Field.Content>{user.address.city || "-"}</Field.Content>
|
<Field.Content>{user.address.city || "-"}</Field.Content>
|
||||||
</Field>
|
</Field>
|
||||||
<Field>
|
<Field>
|
||||||
<Field.Icon>
|
<Field.Icon>
|
||||||
<HouseIcon />
|
<HouseIcon />
|
||||||
</Field.Icon>
|
</Field.Icon>
|
||||||
<Field.TextLabel>{_("Zip code")}</Field.TextLabel>
|
<Field.TextLabel>{formatMessage({ id: "Zip code" })}</Field.TextLabel>
|
||||||
<Field.Content>{user.address.zipCode}</Field.Content>
|
<Field.Content>{user.address.zipCode}</Field.Content>
|
||||||
</Field>
|
</Field>
|
||||||
</section>
|
</section>
|
||||||
|
|||||||
@@ -1,12 +1,14 @@
|
|||||||
import Card from "@/components/MyProfile/Card"
|
import Card from "@/components/MyProfile/Card"
|
||||||
import Title from "@/components/TempDesignSystem/Text/Title"
|
import Title from "@/components/TempDesignSystem/Text/Title"
|
||||||
|
import { getIntl } from "@/i18n"
|
||||||
|
|
||||||
import styles from "./wishes.module.css"
|
import styles from "./wishes.module.css"
|
||||||
|
|
||||||
export default function Wishes() {
|
export default async function Wishes() {
|
||||||
|
const { formatMessage } = await getIntl()
|
||||||
return (
|
return (
|
||||||
<Card className={styles.container}>
|
<Card className={styles.container}>
|
||||||
<Title level="h2">My wishes</Title>
|
<Title level="h4">{formatMessage({ id: "My wishes" })}</Title>
|
||||||
</Card>
|
</Card>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,30 +1,11 @@
|
|||||||
import { Lang } from "@/constants/languages"
|
import { getIntl } from "@/i18n"
|
||||||
|
|
||||||
export default function SkipToMainContent({ lang }: { lang: Lang }) {
|
|
||||||
let message = "Skip to main content"
|
|
||||||
|
|
||||||
switch (lang) {
|
|
||||||
case Lang.de:
|
|
||||||
message = "Direkt zum Inhalt"
|
|
||||||
break
|
|
||||||
case Lang.sv:
|
|
||||||
message = "Fortsätt till huvudinnehåll"
|
|
||||||
break
|
|
||||||
case Lang.da:
|
|
||||||
message = "Spring over og gå til hovedindhold"
|
|
||||||
break
|
|
||||||
case Lang.no:
|
|
||||||
message = "Gå videre til hovedsiden"
|
|
||||||
break
|
|
||||||
case Lang.fi:
|
|
||||||
message = "Siirry pääsisältöön"
|
|
||||||
break
|
|
||||||
}
|
|
||||||
|
|
||||||
|
export default async function SkipToMainContent() {
|
||||||
|
const { formatMessage } = await getIntl()
|
||||||
return (
|
return (
|
||||||
<div className="navigation--skip-to-content">
|
<div className="navigation--skip-to-content">
|
||||||
<a data-js="skip-to-content" href="#maincontent">
|
<a data-js="skip-to-content" href="#maincontent">
|
||||||
{message}
|
{formatMessage({ id: "Skip to main content" })}
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -2,6 +2,5 @@ import type { RegisterOptions } from "react-hook-form"
|
|||||||
|
|
||||||
export type CountryProps = {
|
export type CountryProps = {
|
||||||
name?: string
|
name?: string
|
||||||
placeholder?: string
|
|
||||||
registerOptions?: RegisterOptions
|
registerOptions?: RegisterOptions
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,8 +12,7 @@ import {
|
|||||||
Popover,
|
Popover,
|
||||||
} from "react-aria-components"
|
} from "react-aria-components"
|
||||||
import { useController, useFormContext } from "react-hook-form"
|
import { useController, useFormContext } from "react-hook-form"
|
||||||
|
import { useIntl } from "react-intl"
|
||||||
import { _ } from "@/lib/translation"
|
|
||||||
|
|
||||||
import SelectChevron from "../SelectChevron"
|
import SelectChevron from "../SelectChevron"
|
||||||
import { countries } from "./countries"
|
import { countries } from "./countries"
|
||||||
@@ -24,9 +23,9 @@ import type { CountryProps } from "./country"
|
|||||||
|
|
||||||
export default function CountrySelect({
|
export default function CountrySelect({
|
||||||
name = "country",
|
name = "country",
|
||||||
placeholder = "Select a country",
|
|
||||||
registerOptions,
|
registerOptions,
|
||||||
}: CountryProps) {
|
}: CountryProps) {
|
||||||
|
const { formatMessage } = useIntl()
|
||||||
const divRef = useRef<HTMLDivElement>(null)
|
const divRef = useRef<HTMLDivElement>(null)
|
||||||
const { control, setValue } = useFormContext()
|
const { control, setValue } = useFormContext()
|
||||||
const { field } = useController({
|
const { field } = useController({
|
||||||
@@ -39,10 +38,12 @@ export default function CountrySelect({
|
|||||||
setValue(name, country)
|
setValue(name, country)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const selectCountryLabel = formatMessage({ id: "Select a country" })
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={styles.container} ref={divRef}>
|
<div className={styles.container} ref={divRef}>
|
||||||
<ComboBox
|
<ComboBox
|
||||||
aria-label={_("Select country of residence")}
|
aria-label={formatMessage({ id: "Select country of residence" })}
|
||||||
className={styles.select}
|
className={styles.select}
|
||||||
isRequired={!!registerOptions?.required}
|
isRequired={!!registerOptions?.required}
|
||||||
name={field.name}
|
name={field.name}
|
||||||
@@ -53,9 +54,9 @@ export default function CountrySelect({
|
|||||||
>
|
>
|
||||||
<div className={styles.comboBoxContainer}>
|
<div className={styles.comboBoxContainer}>
|
||||||
<Input
|
<Input
|
||||||
aria-label="Selected country"
|
aria-label={selectCountryLabel}
|
||||||
className={styles.input}
|
className={styles.input}
|
||||||
placeholder={_(placeholder)}
|
placeholder={selectCountryLabel}
|
||||||
/>
|
/>
|
||||||
<Button className={styles.button}>
|
<Button className={styles.button}>
|
||||||
<SelectChevron />
|
<SelectChevron />
|
||||||
|
|||||||
@@ -7,9 +7,9 @@ import {
|
|||||||
Group,
|
Group,
|
||||||
} from "react-aria-components"
|
} from "react-aria-components"
|
||||||
import { useController, useFormContext, useWatch } from "react-hook-form"
|
import { useController, useFormContext, useWatch } from "react-hook-form"
|
||||||
|
import { useIntl } from "react-intl"
|
||||||
|
|
||||||
import { dt } from "@/lib/dt"
|
import { dt } from "@/lib/dt"
|
||||||
import { _ } from "@/lib/translation"
|
|
||||||
|
|
||||||
import { rangeArray } from "@/utils/rangeArray"
|
import { rangeArray } from "@/utils/rangeArray"
|
||||||
|
|
||||||
@@ -28,6 +28,7 @@ export default function DateSelect({
|
|||||||
name,
|
name,
|
||||||
registerOptions,
|
registerOptions,
|
||||||
}: DateProps) {
|
}: DateProps) {
|
||||||
|
const { formatMessage } = useIntl()
|
||||||
const d = useWatch({ name })
|
const d = useWatch({ name })
|
||||||
const { setValue } = useFormContext()
|
const { setValue } = useFormContext()
|
||||||
const { field } = useController({
|
const { field } = useController({
|
||||||
@@ -58,9 +59,13 @@ export default function DateSelect({
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const dayLabel = formatMessage({ id: "Day" })
|
||||||
|
const monthLabel = formatMessage({ id: "Month" })
|
||||||
|
const yearLabel = formatMessage({ id: "Year" })
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<DatePicker
|
<DatePicker
|
||||||
aria-label="Select date of birth"
|
aria-label={formatMessage({ id: "Select date of birth" })}
|
||||||
granularity="day"
|
granularity="day"
|
||||||
isRequired={!!registerOptions.required}
|
isRequired={!!registerOptions.required}
|
||||||
name={name}
|
name={name}
|
||||||
@@ -85,12 +90,12 @@ export default function DateSelect({
|
|||||||
return (
|
return (
|
||||||
<DateSegment className={styles.day} segment={segment}>
|
<DateSegment className={styles.day} segment={segment}>
|
||||||
<Select
|
<Select
|
||||||
aria-label={_("Day")}
|
aria-label={dayLabel}
|
||||||
items={days}
|
items={days}
|
||||||
label={_("Day")}
|
label={dayLabel}
|
||||||
name={DateName.date}
|
name={DateName.date}
|
||||||
onSelect={createOnSelect(DateName.date)}
|
onSelect={createOnSelect(DateName.date)}
|
||||||
placeholder={_("DD")}
|
placeholder="DD"
|
||||||
value={segment.value}
|
value={segment.value}
|
||||||
/>
|
/>
|
||||||
</DateSegment>
|
</DateSegment>
|
||||||
@@ -99,12 +104,12 @@ export default function DateSelect({
|
|||||||
return (
|
return (
|
||||||
<DateSegment className={styles.month} segment={segment}>
|
<DateSegment className={styles.month} segment={segment}>
|
||||||
<Select
|
<Select
|
||||||
aria-label={_("Month")}
|
aria-label={monthLabel}
|
||||||
items={months}
|
items={months}
|
||||||
label={_("Month")}
|
label={monthLabel}
|
||||||
name={DateName.month}
|
name={DateName.month}
|
||||||
onSelect={createOnSelect(DateName.month)}
|
onSelect={createOnSelect(DateName.month)}
|
||||||
placeholder={_("MM")}
|
placeholder="MM"
|
||||||
value={segment.value}
|
value={segment.value}
|
||||||
/>
|
/>
|
||||||
</DateSegment>
|
</DateSegment>
|
||||||
@@ -113,12 +118,12 @@ export default function DateSelect({
|
|||||||
return (
|
return (
|
||||||
<DateSegment className={styles.year} segment={segment}>
|
<DateSegment className={styles.year} segment={segment}>
|
||||||
<Select
|
<Select
|
||||||
aria-label={_("Year")}
|
aria-label={yearLabel}
|
||||||
items={years}
|
items={years}
|
||||||
label={_("Year")}
|
label={yearLabel}
|
||||||
name={DateName.year}
|
name={DateName.year}
|
||||||
onSelect={createOnSelect(DateName.year)}
|
onSelect={createOnSelect(DateName.year)}
|
||||||
placeholder={_("YYYY")}
|
placeholder="YYYY"
|
||||||
value={segment.value}
|
value={segment.value}
|
||||||
/>
|
/>
|
||||||
</DateSegment>
|
</DateSegment>
|
||||||
|
|||||||
@@ -6,6 +6,16 @@
|
|||||||
font-family: var(--typography-Body-Regular-fontFamily);
|
font-family: var(--typography-Body-Regular-fontFamily);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.myPage {
|
||||||
|
color: var(--some-black-color, #000);
|
||||||
|
font-family: var(--ff-fira-sans);
|
||||||
|
font-size: 1.6rem;
|
||||||
|
font-weight: 600;
|
||||||
|
letter-spacing: 0.6%;
|
||||||
|
line-height: 2.4rem;
|
||||||
|
text-decoration: underline;
|
||||||
|
}
|
||||||
|
|
||||||
.sidebar {
|
.sidebar {
|
||||||
align-items: center;
|
align-items: center;
|
||||||
color: var(--some-text-color, #111);
|
color: var(--some-text-color, #111);
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ export const linkVariants = cva(styles.link, {
|
|||||||
size: {},
|
size: {},
|
||||||
variant: {
|
variant: {
|
||||||
default: styles.default,
|
default: styles.default,
|
||||||
|
myPage: styles.myPage,
|
||||||
sidebar: styles.sidebar,
|
sidebar: styles.sidebar,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -1,18 +1,20 @@
|
|||||||
import { ArrowLeft } from "react-feather"
|
import { ArrowLeft } from "react-feather"
|
||||||
|
|
||||||
import { overview } from "@/constants/routes/webviews"
|
import { overview } from "@/constants/routes/webviews"
|
||||||
import { _ } from "@/lib/translation"
|
|
||||||
|
|
||||||
import Link from "@/components/TempDesignSystem/Link"
|
import Link from "@/components/TempDesignSystem/Link"
|
||||||
|
import { getIntl } from "@/i18n"
|
||||||
|
|
||||||
import styles from "./linkToOverview.module.css"
|
import styles from "./linkToOverview.module.css"
|
||||||
|
|
||||||
import { LangParams } from "@/types/params"
|
import type { LangParams } from "@/types/params"
|
||||||
|
|
||||||
export default function LinkToOverview({ lang }: LangParams) {
|
export default async function LinkToOverview({ lang }: LangParams) {
|
||||||
|
const { formatMessage } = await getIntl()
|
||||||
return (
|
return (
|
||||||
<Link className={styles.overviewLink} href={overview[lang]}>
|
<Link className={styles.overviewLink} href={overview[lang]}>
|
||||||
<ArrowLeft height={20} width={20} /> {_("Go back to overview")}
|
<ArrowLeft height={20} width={20} />{" "}
|
||||||
|
{formatMessage({ id: "Go back to overview" })}
|
||||||
</Link>
|
</Link>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
12
i18n/Provider.tsx
Normal file
12
i18n/Provider.tsx
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
"use client"
|
||||||
|
|
||||||
|
import { IntlProvider } from "react-intl"
|
||||||
|
|
||||||
|
import type { ServerIntlProviderProps } from "@/types/i18n"
|
||||||
|
|
||||||
|
export default function ServerIntlProvider({
|
||||||
|
children,
|
||||||
|
intl,
|
||||||
|
}: ServerIntlProviderProps) {
|
||||||
|
return <IntlProvider {...intl}>{children}</IntlProvider>
|
||||||
|
}
|
||||||
80
i18n/dictionaries/da.json
Normal file
80
i18n/dictionaries/da.json
Normal file
@@ -0,0 +1,80 @@
|
|||||||
|
{
|
||||||
|
"Address": "Adresse",
|
||||||
|
"Already a friend?": "Allerede en ven?",
|
||||||
|
"Arrival date": "Ankomstdato",
|
||||||
|
"Book": "Bestil",
|
||||||
|
"Booking number": "Bestillingsnummer",
|
||||||
|
"Cancel": "Afbestille",
|
||||||
|
"City": "By",
|
||||||
|
"City/State": "By/Stat",
|
||||||
|
"Click here to log in": "Klik her for at logge ind",
|
||||||
|
"Close": "Tæt",
|
||||||
|
"Coming up": "Kommer op",
|
||||||
|
"Compare all levels": "Sammenlign alle niveauer",
|
||||||
|
"Contact us": "Kontakt os",
|
||||||
|
"Continue": "Blive ved",
|
||||||
|
"Could not find requested resource": "Kunne ikke finde den anmodede ressource",
|
||||||
|
"Country": "Land",
|
||||||
|
"Current level": "Nuværende niveau",
|
||||||
|
"Date of Birth": "Fødselsdato",
|
||||||
|
"Day": "Dag",
|
||||||
|
"Description": "Beskrivelse",
|
||||||
|
"Edit": "Redigere",
|
||||||
|
"Email": "E-mail",
|
||||||
|
"Explore all levels and benefits": "Udforsk alle niveauer og fordele",
|
||||||
|
"Find booking": "Find booking",
|
||||||
|
"Get inspired": "Blive inspireret",
|
||||||
|
"Go back to overview": "Gå tilbage til oversigten",
|
||||||
|
"How it works": "Hvordan det virker",
|
||||||
|
"Join Scandic Friends": "Tilmeld dig Scandic Friends",
|
||||||
|
"Level": "Niveau",
|
||||||
|
"Level up to unlock": "Niveau op for at låse op",
|
||||||
|
"Log in": "Log på",
|
||||||
|
"Log out": "Log ud",
|
||||||
|
"Membership cards": "Medlemskort",
|
||||||
|
"Membership ID": "Medlems-id",
|
||||||
|
"Month": "Måned",
|
||||||
|
"My communication preferences": "Mine kommunikationspræferencer",
|
||||||
|
"My credit cards": "Mine kreditkort",
|
||||||
|
"My pages": "Mine sider",
|
||||||
|
"My wishes": "Mine ønsker",
|
||||||
|
"Next": "Næste",
|
||||||
|
"Next level": "Næste niveau",
|
||||||
|
"No content published": "Intet indhold offentliggjort",
|
||||||
|
"No transactions available": "Ingen tilgængelige transaktioner",
|
||||||
|
"Not found": "Ikke fundet",
|
||||||
|
"night": "nat",
|
||||||
|
"nights": "nætter",
|
||||||
|
"On your journey": "På din rejse",
|
||||||
|
"Open": "Åben",
|
||||||
|
"or": "eller",
|
||||||
|
"Password": "Adgangskode",
|
||||||
|
"Phone": "Telefon",
|
||||||
|
"Phone is required": "Telefonnummer er påkrævet",
|
||||||
|
"Phone number": "Telefonnummer",
|
||||||
|
"Please enter a valid phone number": "Indtast venligst et gyldigt telefonnummer",
|
||||||
|
"Points": "Points",
|
||||||
|
"Points may take up to 10 days to be displayed.": "Det kan tage op til 10 dage at få vist point.",
|
||||||
|
"Points until next level": "Point indtil næste niveau",
|
||||||
|
"Previous victories": "Tidligere sejre",
|
||||||
|
"points until next level": "point indtil næste niveau",
|
||||||
|
"Read more": "Læs mere",
|
||||||
|
"Save": "Gemme",
|
||||||
|
"Select a country": "Vælg et land",
|
||||||
|
"Select country of residence": "Vælg bopælsland",
|
||||||
|
"Select date of birth": "Vælg fødselsdato",
|
||||||
|
"Show more": "Vis mere",
|
||||||
|
"Skip to main content": "Spring over og gå til hovedindhold",
|
||||||
|
"Something went wrong!": "Noget gik galt!",
|
||||||
|
"Street": "Gade",
|
||||||
|
"Total Points": "Samlet antal point",
|
||||||
|
"Transaction date": "Overførselsdato",
|
||||||
|
"Transactions": "Transaktioner",
|
||||||
|
"Visiting address": "Besøgsadresse",
|
||||||
|
"Where should you go next?": "Hvor skal du tage hen næste gang?",
|
||||||
|
"Year": "År",
|
||||||
|
"You have no previous stays.": "Du har ingen tidligere ophold.",
|
||||||
|
"You have no upcoming stays.": "Du har ingen kommende ophold.",
|
||||||
|
"Your Challenges Conquer & Earn!": "Dine udfordringer Overvind og tjen!",
|
||||||
|
"Zip code": "Postnummer"
|
||||||
|
}
|
||||||
80
i18n/dictionaries/de.json
Normal file
80
i18n/dictionaries/de.json
Normal file
@@ -0,0 +1,80 @@
|
|||||||
|
{
|
||||||
|
"Address": "Adresse",
|
||||||
|
"Already a friend?": "Schon ein Freund?",
|
||||||
|
"Arrival date": "Ankunftsdatum",
|
||||||
|
"Book": "Buch",
|
||||||
|
"Booking number": "Buchungsnummer",
|
||||||
|
"Cancel": "Stornieren",
|
||||||
|
"City": "Stadt",
|
||||||
|
"City/State": "Stadt/Zustand",
|
||||||
|
"Click here to log in": "Klicken Sie hier, um sich einzuloggen",
|
||||||
|
"Close": "Schließen",
|
||||||
|
"Coming up": "Demnächst",
|
||||||
|
"Compare all levels": "Vergleichen Sie alle Ebenen",
|
||||||
|
"Contact us": "Kontaktiere uns",
|
||||||
|
"Continue": "Weitermachen",
|
||||||
|
"Could not find requested resource": "Die angeforderte Ressource konnte nicht gefunden werden.",
|
||||||
|
"Country": "Land",
|
||||||
|
"Current level": "Aktuelles Level",
|
||||||
|
"Date of Birth": "Geburtsdatum",
|
||||||
|
"Day": "Tag",
|
||||||
|
"Description": "Beschreibung",
|
||||||
|
"Edit": "Bearbeiten",
|
||||||
|
"Email": "Email",
|
||||||
|
"Explore all levels and benefits": "Entdecken Sie alle Levels und Vorteile",
|
||||||
|
"Find booking": "Buchung finden",
|
||||||
|
"Get inspired": "Lass dich inspirieren",
|
||||||
|
"Go back to overview": "Zurück zur Übersicht",
|
||||||
|
"How it works": "Wie es funktioniert",
|
||||||
|
"Join Scandic Friends": "Treten Sie Scandic Friends bei",
|
||||||
|
"Level": "Ebene",
|
||||||
|
"Level up to unlock": "Zum Freischalten aufsteigen",
|
||||||
|
"Log in": "Anmeldung",
|
||||||
|
"Log out": "Ausloggen",
|
||||||
|
"Membership cards": "Mitgliedskarten",
|
||||||
|
"Membership ID": "Mitglieds-ID",
|
||||||
|
"Month": "Monat",
|
||||||
|
"My communication preferences": "Meine Kommunikationseinstellungen",
|
||||||
|
"My credit cards": "Meine Kreditkarten",
|
||||||
|
"My pages": "Meine Seiten",
|
||||||
|
"My wishes": "Meine Wünsche",
|
||||||
|
"Next": "Nächste",
|
||||||
|
"Next level": "Nächste Ebene",
|
||||||
|
"No content published": "Kein Inhalt veröffentlicht",
|
||||||
|
"No transactions available": "Keine Transaktionen verfügbar",
|
||||||
|
"Not found": "Nicht gefunden",
|
||||||
|
"night": "nacht",
|
||||||
|
"nights": "nächte",
|
||||||
|
"On your journey": "Auf deiner Reise",
|
||||||
|
"Open": "Offen",
|
||||||
|
"or": "oder",
|
||||||
|
"Password": "Passwort",
|
||||||
|
"Phone": "Telefon",
|
||||||
|
"Phone is required": "Telefon ist erforderlich",
|
||||||
|
"Phone number": "Telefonnummer",
|
||||||
|
"Please enter a valid phone number": "Bitte geben Sie eine gültige Telefonnummer ein",
|
||||||
|
"Points": "Punkte",
|
||||||
|
"Points may take up to 10 days to be displayed.": "Es kann bis zu 10 Tage dauern, bis Punkte angezeigt werden.",
|
||||||
|
"Points until next level": "Punkte bis zum nächsten Level",
|
||||||
|
"Previous victories": "Bisherige Siege",
|
||||||
|
"points until next level": "punkte bis zum nächsten Level",
|
||||||
|
"Read more": "Mehr lesen",
|
||||||
|
"Save": "Speichern",
|
||||||
|
"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",
|
||||||
|
"Show more": "Zeig mehr",
|
||||||
|
"Skip to main content": "Direkt zum Inhalt",
|
||||||
|
"Something went wrong!": "Etwas ist schief gelaufen!",
|
||||||
|
"Street": "Straße",
|
||||||
|
"Total Points": "Gesamtpunktzahl",
|
||||||
|
"Transaction date": "Transaktionsdatum",
|
||||||
|
"Transactions": "Transaktionen",
|
||||||
|
"Visiting address": "Besuchsadresse",
|
||||||
|
"Where should you go next?": "Wohin soll es als nächstes gehen?",
|
||||||
|
"Year": "Jahr",
|
||||||
|
"You have no previous stays.": "Sie haben keine vorherigen Aufenthalte.",
|
||||||
|
"You have no upcoming stays.": "Sie haben keine bevorstehenden Aufenthalte.",
|
||||||
|
"Your Challenges Conquer & Earn!": "Meistern Sie Ihre Herausforderungen und verdienen Sie Geld!",
|
||||||
|
"Zip code": "PLZ"
|
||||||
|
}
|
||||||
80
i18n/dictionaries/en.json
Normal file
80
i18n/dictionaries/en.json
Normal file
@@ -0,0 +1,80 @@
|
|||||||
|
{
|
||||||
|
"Address": "Address",
|
||||||
|
"Already a friend?": "Already a friend?",
|
||||||
|
"Arrival date": "Arrival date",
|
||||||
|
"Book": "Book",
|
||||||
|
"Booking number": "Booking number",
|
||||||
|
"Cancel": "Cancel",
|
||||||
|
"City": "City",
|
||||||
|
"City/State": "City/State",
|
||||||
|
"Click here to log in": "Click here to log in",
|
||||||
|
"Close": "Close",
|
||||||
|
"Coming up": "Coming up",
|
||||||
|
"Compare all levels": "Compare all levels",
|
||||||
|
"Contact us": "Contact us",
|
||||||
|
"Continue": "Continue",
|
||||||
|
"Could not find requested resource": "Could not find requested resource",
|
||||||
|
"Country": "Country",
|
||||||
|
"Current level": "Current level",
|
||||||
|
"Date of Birth": "Date of Birth",
|
||||||
|
"Day": "Day",
|
||||||
|
"Description": "Description",
|
||||||
|
"Edit": "Edit",
|
||||||
|
"Email": "Email",
|
||||||
|
"Explore all levels and benefits": "Explore all levels and benefits",
|
||||||
|
"Find booking": "Find booking",
|
||||||
|
"Get inspired": "Get inspired",
|
||||||
|
"Go back to overview": "Go back to overview",
|
||||||
|
"How it works": "How it works",
|
||||||
|
"Join Scandic Friends": "Join Scandic Friends",
|
||||||
|
"Level": "Level",
|
||||||
|
"Level up to unlock": "Level up to unlock",
|
||||||
|
"Log in": "Log in",
|
||||||
|
"Log out": "Log out",
|
||||||
|
"Membership cards": "Membership cards",
|
||||||
|
"Membership ID": "Membership ID",
|
||||||
|
"Month": "Month",
|
||||||
|
"My communication preferences": "My communication preferences",
|
||||||
|
"My credit cards": "My credit cards",
|
||||||
|
"My pages": "My pages",
|
||||||
|
"My wishes": "My wishes",
|
||||||
|
"Next": "Next",
|
||||||
|
"Next level": "Next level",
|
||||||
|
"No content published": "No content published",
|
||||||
|
"No transactions available": "No transactions available",
|
||||||
|
"Not found": "Not found",
|
||||||
|
"night": "night",
|
||||||
|
"nights": "nights",
|
||||||
|
"On your journey": "On your journey",
|
||||||
|
"Open": "Open",
|
||||||
|
"or": "or",
|
||||||
|
"Password": "Password",
|
||||||
|
"Phone": "Phone",
|
||||||
|
"Phone is required": "Phone is required",
|
||||||
|
"Phone number": "Phone number",
|
||||||
|
"Please enter a valid phone number": "Please enter a valid phone number",
|
||||||
|
"Points": "Points",
|
||||||
|
"Points may take up to 10 days to be displayed.": "Points may take up to 10 days to be displayed.",
|
||||||
|
"Points until next level": "Points until next level",
|
||||||
|
"Previous victories": "Previous victories",
|
||||||
|
"points until next level": "points until next level",
|
||||||
|
"Read more": "Read more",
|
||||||
|
"Save": "Save",
|
||||||
|
"Select a country": "Select a country",
|
||||||
|
"Select country of residence": "Select country of residence",
|
||||||
|
"Select date of birth": "Select date of birth",
|
||||||
|
"Show more": "Show more",
|
||||||
|
"Skip to main content": "Skip to main content",
|
||||||
|
"Something went wrong!": "Something went wrong!",
|
||||||
|
"Street": "Street",
|
||||||
|
"Total Points": "Total Points",
|
||||||
|
"Transaction date": "Transaction date",
|
||||||
|
"Transactions": "Transactions",
|
||||||
|
"Where should you go next?": "Where should you go next?",
|
||||||
|
"Visiting address": "Visiting address",
|
||||||
|
"Year": "Year",
|
||||||
|
"You have no previous stays.": "You have no previous stays.",
|
||||||
|
"You have no upcoming stays.": "You have no upcoming stays.",
|
||||||
|
"Your Challenges Conquer & Earn!": "Your Challenges Conquer & Earn!",
|
||||||
|
"Zip code": "Zip code"
|
||||||
|
}
|
||||||
80
i18n/dictionaries/fi.json
Normal file
80
i18n/dictionaries/fi.json
Normal file
@@ -0,0 +1,80 @@
|
|||||||
|
{
|
||||||
|
"Address": "Osoite",
|
||||||
|
"Already a friend?": "Oletko jo ystävä?",
|
||||||
|
"Arrival date": "Saapumispäivä",
|
||||||
|
"Book": "Kirja",
|
||||||
|
"Booking number": "Varausnumero",
|
||||||
|
"Cancel": "Peruuttaa",
|
||||||
|
"City": "Kaupunki",
|
||||||
|
"City/State": "Kaupunki/Osavaltio",
|
||||||
|
"Click here to log in": "Napsauta tästä kirjautuaksesi sisään",
|
||||||
|
"Close": "Kiinni",
|
||||||
|
"Coming up": "Tulossa",
|
||||||
|
"Compare all levels": "Vertaa kaikkia tasoja",
|
||||||
|
"Contact us": "Ota meihin yhteyttä",
|
||||||
|
"Continue": "Jatkaa",
|
||||||
|
"Could not find requested resource": "Pyydettyä resurssia ei löytynyt",
|
||||||
|
"Country": "Maa",
|
||||||
|
"Current level": "Nykyinen taso",
|
||||||
|
"Date of Birth": "Syntymäaika",
|
||||||
|
"Day": "Päivä",
|
||||||
|
"Description": "Kuvaus",
|
||||||
|
"Edit": "Muokata",
|
||||||
|
"Email": "Sähköposti",
|
||||||
|
"Explore all levels and benefits": "Tutustu kaikkiin tasoihin ja etuihin",
|
||||||
|
"Find booking": "Etsi varaus",
|
||||||
|
"Get inspired": "Inspiroidu",
|
||||||
|
"Go back to overview": "Palaa yleiskatsaukseen",
|
||||||
|
"How it works": "Kuinka se toimii",
|
||||||
|
"Join Scandic Friends": "Liity Scandic Friends",
|
||||||
|
"Level": "Taso",
|
||||||
|
"Level up to unlock": "Nosta taso avataksesi lukituksen",
|
||||||
|
"Log in": "Kirjaudu sisään",
|
||||||
|
"Log out": "Kirjautua ulos",
|
||||||
|
"Membership cards": "Jäsenkortit",
|
||||||
|
"Membership ID": "Jäsentunnus",
|
||||||
|
"Month": "Kuukausi",
|
||||||
|
"My communication preferences": "Viestintämieltymykseni",
|
||||||
|
"My credit cards": "Minun luottokorttini",
|
||||||
|
"My pages": "Omat sivut",
|
||||||
|
"My wishes": "Toiveeni",
|
||||||
|
"Next": "Seuraava",
|
||||||
|
"Next level": "Seuraava taso",
|
||||||
|
"No content published": "Ei julkaistua sisältöä",
|
||||||
|
"No transactions available": "Ei tapahtumia saatavilla",
|
||||||
|
"Not found": "Ei löydetty",
|
||||||
|
"night": "yö",
|
||||||
|
"nights": "yöt",
|
||||||
|
"On your journey": "Matkallasi",
|
||||||
|
"Open": "Avata",
|
||||||
|
"or": "tai",
|
||||||
|
"Password": "Salasana",
|
||||||
|
"Phone": "Puhelin",
|
||||||
|
"Phone is required": "Puhelin vaaditaan",
|
||||||
|
"Phone number": "Puhelinnumero",
|
||||||
|
"Please enter a valid phone number": "Ole hyvä ja näppäile voimassaoleva puhelinnumero",
|
||||||
|
"Points": "Pisteet",
|
||||||
|
"Points may take up to 10 days to be displayed.": "Pisteiden näyttäminen voi kestää jopa 10 päivää.",
|
||||||
|
"Points until next level": "Pisteitä seuraavalle tasolle",
|
||||||
|
"Previous victories": "Edelliset voitot",
|
||||||
|
"points until next level": "pisteitä seuraavalle tasolle",
|
||||||
|
"Read more": "Lue lisää",
|
||||||
|
"Save": "Tallentaa",
|
||||||
|
"Select a country": "Valitse maa",
|
||||||
|
"Select country of residence": "Valitse asuinmaa",
|
||||||
|
"Select date of birth": "Valitse syntymäaika",
|
||||||
|
"Show more": "Näytä lisää",
|
||||||
|
"Skip to main content": "Siirry pääsisältöön",
|
||||||
|
"Something went wrong!": "Jotain meni pieleen!",
|
||||||
|
"Street": "Katu",
|
||||||
|
"Total Points": "Kokonaispisteet",
|
||||||
|
"Transaction date": "Tapahtuman päivämäärä",
|
||||||
|
"Transactions": "Tapahtumat",
|
||||||
|
"Visiting address": "Käyntiosoite",
|
||||||
|
"Where should you go next?": "Minne sinun pitäisi mennä seuraavaksi?",
|
||||||
|
"Year": "Vuosi",
|
||||||
|
"You have no previous stays.": "Sinulla ei ole aiempaa oleskelua.",
|
||||||
|
"You have no upcoming stays.": "Sinulla ei ole tulevia oleskeluja.",
|
||||||
|
"Your Challenges Conquer & Earn!": "Voita ja ansaitse haasteesi!",
|
||||||
|
"Zip code": "Postinumero"
|
||||||
|
}
|
||||||
80
i18n/dictionaries/no.json
Normal file
80
i18n/dictionaries/no.json
Normal file
@@ -0,0 +1,80 @@
|
|||||||
|
{
|
||||||
|
"Address": "Adresse",
|
||||||
|
"Already a friend?": "Allerede en venn?",
|
||||||
|
"Arrival date": "Ankomstdato",
|
||||||
|
"Book": "Bok",
|
||||||
|
"Booking number": "Bestillingsnummer",
|
||||||
|
"Cancel": "Avbryt",
|
||||||
|
"City": "By",
|
||||||
|
"City/State": "By/Stat",
|
||||||
|
"Click here to log in": "Klikk her for å logge inn",
|
||||||
|
"Close": "Lukk",
|
||||||
|
"Coming up": "Kommer opp",
|
||||||
|
"Compare all levels": "Sammenlign alle nivåer",
|
||||||
|
"Contact us": "Kontakt oss",
|
||||||
|
"Continue": "Fortsette",
|
||||||
|
"Could not find requested resource": "Kunne ikke finne den forespurte ressursen",
|
||||||
|
"Country": "Land",
|
||||||
|
"Current level": "Nåværende nivå",
|
||||||
|
"Date of Birth": "Fødselsdato",
|
||||||
|
"Day": "Dag",
|
||||||
|
"Description": "Beskrivelse",
|
||||||
|
"Edit": "Redigere",
|
||||||
|
"Email": "E-post",
|
||||||
|
"Explore all levels and benefits": "Utforsk alle nivåer og fordeler",
|
||||||
|
"Find booking": "Finn booking",
|
||||||
|
"Get inspired": "Bli inspirert",
|
||||||
|
"Go back to overview": "Gå tilbake til oversikten",
|
||||||
|
"How it works": "Hvordan det fungerer",
|
||||||
|
"Join Scandic Friends": "Bli med i Scandic Friends",
|
||||||
|
"Level": "Nivå",
|
||||||
|
"Level up to unlock": "Nivå opp for å låse opp",
|
||||||
|
"Log in": "Logg Inn",
|
||||||
|
"Log out": "Logg ut",
|
||||||
|
"Membership cards": "Medlemskort",
|
||||||
|
"Membership ID": "Medlems-ID",
|
||||||
|
"Month": "Måned",
|
||||||
|
"My communication preferences": "Mine kommunikasjonspreferanser",
|
||||||
|
"My credit cards": "Kredittkortene mine",
|
||||||
|
"My pages": "Mine sider",
|
||||||
|
"My wishes": "Mine ønsker",
|
||||||
|
"Next": "Neste",
|
||||||
|
"Next level": "Neste nivå",
|
||||||
|
"No content published": "Ingen innhold publisert",
|
||||||
|
"No transactions available": "Ingen transaksjoner tilgjengelig",
|
||||||
|
"Not found": "Ikke funnet",
|
||||||
|
"night": "natt",
|
||||||
|
"nights": "netter",
|
||||||
|
"On your journey": "På reisen din",
|
||||||
|
"Open": "Åpen",
|
||||||
|
"or": "eller",
|
||||||
|
"Password": "Passord",
|
||||||
|
"Phone": "Telefon",
|
||||||
|
"Phone is required": "Telefon kreves",
|
||||||
|
"Phone number": "Telefonnummer",
|
||||||
|
"Please enter a valid phone number": "Vennligst oppgi et gyldig telefonnummer",
|
||||||
|
"Points": "Poeng",
|
||||||
|
"Points may take up to 10 days to be displayed.": "Det kan ta opptil 10 dager før poeng vises.",
|
||||||
|
"Points until next level": "Poeng til neste nivå",
|
||||||
|
"Previous victories": "Tidligere seire",
|
||||||
|
"points until next level": "poeng til neste nivå",
|
||||||
|
"Read more": "Les mer",
|
||||||
|
"Save": "Lagre",
|
||||||
|
"Select a country": "Velg et land",
|
||||||
|
"Select country of residence": "Velg bostedsland",
|
||||||
|
"Select date of birth": "Velg fødselsdato",
|
||||||
|
"Show more": "Vis mer",
|
||||||
|
"Skip to main content": "Gå videre til hovedsiden",
|
||||||
|
"Something went wrong!": "Noe gikk galt!",
|
||||||
|
"Street": "Gate",
|
||||||
|
"Total Points": "Totale poeng",
|
||||||
|
"Transaction date": "Transaksjonsdato",
|
||||||
|
"Transactions": "Transaksjoner",
|
||||||
|
"Visiting address": "Besøksadresse",
|
||||||
|
"Where should you go next?": "Hvor bør du gå videre?",
|
||||||
|
"Year": "År",
|
||||||
|
"You have no previous stays.": "Du har ingen tidligere opphold.",
|
||||||
|
"You have no upcoming stays.": "Du har ingen kommende opphold.",
|
||||||
|
"Your Challenges Conquer & Earn!": "Dine utfordringer Erobre og tjen!",
|
||||||
|
"Zip code": "Post kode"
|
||||||
|
}
|
||||||
80
i18n/dictionaries/sv.json
Normal file
80
i18n/dictionaries/sv.json
Normal file
@@ -0,0 +1,80 @@
|
|||||||
|
{
|
||||||
|
"Address": "Adress",
|
||||||
|
"Already a friend?": "Redan en vän?",
|
||||||
|
"Arrival date": "Ankomstdatum",
|
||||||
|
"Book": "Boka",
|
||||||
|
"Booking number": "Bokningsnummer",
|
||||||
|
"Cancel": "Avbryt",
|
||||||
|
"City": "Ort",
|
||||||
|
"City/State": "Ort",
|
||||||
|
"Click here to log in": "Klicka här för att logga in",
|
||||||
|
"Close": "Stäng",
|
||||||
|
"Coming up": "Kommer härnäst",
|
||||||
|
"Compare all levels": "Jämför alla nivåer",
|
||||||
|
"Contact us": "Kontakta oss",
|
||||||
|
"Continue": "Fortsätt",
|
||||||
|
"Could not find requested resource": "Det gick inte att hitta den begärda resursen",
|
||||||
|
"Country": "Land",
|
||||||
|
"Current level": "Nuvarande nivå",
|
||||||
|
"Date of Birth": "Födelsedatum",
|
||||||
|
"Day": "Dag",
|
||||||
|
"Description": "Beskrivning",
|
||||||
|
"Edit": "Redigera",
|
||||||
|
"Email": "E-post",
|
||||||
|
"Explore all levels and benefits": "Utforska alla nivåer och fördelar",
|
||||||
|
"Find booking": "Hitta bokning",
|
||||||
|
"Get inspired": "Bli inspirerad",
|
||||||
|
"Go back to overview": "Gå tillbaka till översikten",
|
||||||
|
"How it works": "Hur det fungerar",
|
||||||
|
"Join Scandic Friends": "Gå med i Scandic Friends",
|
||||||
|
"Level": "Nivå",
|
||||||
|
"Level up to unlock": "Nivå upp för att låsa upp",
|
||||||
|
"Log in": "Logga in",
|
||||||
|
"Log out": "Logga ut",
|
||||||
|
"Membership cards": "Medlemskort",
|
||||||
|
"Membership ID": "Medlems-ID",
|
||||||
|
"Month": "Månad",
|
||||||
|
"My communication preferences": "Mina kommunikationspreferenser",
|
||||||
|
"My credit cards": "Mina kreditkort",
|
||||||
|
"My pages": "Mina sidor",
|
||||||
|
"My wishes": "Mina önskningar",
|
||||||
|
"Next": "Nästa",
|
||||||
|
"Next level": "Nästa nivå",
|
||||||
|
"No content published": "Inget innehåll publicerat",
|
||||||
|
"No transactions available": "Inga transaktioner tillgängliga",
|
||||||
|
"Not found": "Hittades inte",
|
||||||
|
"night": "natt",
|
||||||
|
"nights": "nätter",
|
||||||
|
"On your journey": "På din resa",
|
||||||
|
"Open": "Öppna",
|
||||||
|
"or": "eller",
|
||||||
|
"Password": "Lösenord",
|
||||||
|
"Phone": "Telefon",
|
||||||
|
"Phone is required": "Telefonnummer är obligatorisk",
|
||||||
|
"Phone number": "Telefonnummer",
|
||||||
|
"Please enter a valid phone number": "Var vänlig och ange ett giltigt telefonnummer",
|
||||||
|
"Points": "Poäng",
|
||||||
|
"Points may take up to 10 days to be displayed.": "Det kan ta upp till 10 dagar innan poäng visas.",
|
||||||
|
"Points until next level": "Poäng till nästa nivå",
|
||||||
|
"Previous victories": "Tidigare segrar",
|
||||||
|
"points until next level": "poäng till nästa nivå",
|
||||||
|
"Read more": "Läs mer",
|
||||||
|
"Save": "Spara",
|
||||||
|
"Select a country": "Välj ett land",
|
||||||
|
"Select country of residence": "Välj bosättningsland",
|
||||||
|
"Select date of birth": "Välj födelsedatum",
|
||||||
|
"Show more": "Visa mer",
|
||||||
|
"Skip to main content": "Fortsätt till huvudinnehåll",
|
||||||
|
"Something went wrong!": "Något gick fel!",
|
||||||
|
"Street": "Gata",
|
||||||
|
"Total Points": "Total poäng",
|
||||||
|
"Transaction date": "Transaktionsdatum",
|
||||||
|
"Transactions": "Transaktioner",
|
||||||
|
"Visiting address": "Besöksadress",
|
||||||
|
"Where should you go next?": "Vart ska du gå härnäst?",
|
||||||
|
"Year": "År",
|
||||||
|
"You have no previous stays.": "Du har inga tidigare vistelser.",
|
||||||
|
"You have no upcoming stays.": "Du har inga kommande vistelser.",
|
||||||
|
"Your Challenges Conquer & Earn!": "Dina utmaningar Erövra och tjäna!",
|
||||||
|
"Zip code": "Postnummer"
|
||||||
|
}
|
||||||
33
i18n/index.ts
Normal file
33
i18n/index.ts
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
import "server-only"
|
||||||
|
|
||||||
|
import { createIntl, createIntlCache } from "@formatjs/intl"
|
||||||
|
import { headers } from "next/headers"
|
||||||
|
|
||||||
|
import { Lang } from "@/constants/languages"
|
||||||
|
|
||||||
|
const cache = createIntlCache()
|
||||||
|
|
||||||
|
async function initIntl(lang: Lang) {
|
||||||
|
return createIntl(
|
||||||
|
{
|
||||||
|
defaultLocale: Lang.en,
|
||||||
|
locale: lang,
|
||||||
|
messages: (await import(`./dictionaries/${lang}.json`)).default,
|
||||||
|
},
|
||||||
|
cache
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function getIntl(forceLang?: Lang) {
|
||||||
|
const h = headers()
|
||||||
|
let lang = h.get("x-lang") as Lang
|
||||||
|
|
||||||
|
if (!lang) {
|
||||||
|
lang = Lang.en
|
||||||
|
}
|
||||||
|
|
||||||
|
if (forceLang) {
|
||||||
|
return await initIntl(forceLang)
|
||||||
|
}
|
||||||
|
return await initIntl(lang)
|
||||||
|
}
|
||||||
@@ -1,3 +0,0 @@
|
|||||||
export function _(str: string) {
|
|
||||||
return str
|
|
||||||
}
|
|
||||||
@@ -1,4 +1,3 @@
|
|||||||
import { headers } from "next/headers"
|
|
||||||
import { NextResponse } from "next/server"
|
import { NextResponse } from "next/server"
|
||||||
|
|
||||||
import { findLang } from "@/constants/languages"
|
import { findLang } from "@/constants/languages"
|
||||||
|
|||||||
143
package-lock.json
generated
143
package-lock.json
generated
@@ -35,6 +35,7 @@
|
|||||||
"react-feather": "^2.0.10",
|
"react-feather": "^2.0.10",
|
||||||
"react-hook-form": "^7.51.2",
|
"react-hook-form": "^7.51.2",
|
||||||
"react-international-phone": "^4.2.6",
|
"react-international-phone": "^4.2.6",
|
||||||
|
"react-intl": "^6.6.8",
|
||||||
"server-only": "^0.0.1",
|
"server-only": "^0.0.1",
|
||||||
"superjson": "^2.2.1",
|
"superjson": "^2.2.1",
|
||||||
"zod": "^3.22.4",
|
"zod": "^3.22.4",
|
||||||
@@ -2374,10 +2375,10 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@formatjs/ecma402-abstract": {
|
"node_modules/@formatjs/ecma402-abstract": {
|
||||||
"version": "1.18.2",
|
"version": "2.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/@formatjs/ecma402-abstract/-/ecma402-abstract-1.18.2.tgz",
|
"resolved": "https://registry.npmjs.org/@formatjs/ecma402-abstract/-/ecma402-abstract-2.0.0.tgz",
|
||||||
"integrity": "sha512-+QoPW4csYALsQIl8GbN14igZzDbuwzcpWrku9nyMXlaqAlwRBgl5V+p0vWMGFqHOw37czNXaP/lEk4wbLgcmtA==",
|
"integrity": "sha512-rRqXOqdFmk7RYvj4khklyqzcfQl9vEL/usogncBHRZfZBDOwMGuSRNFl02fu5KGHXdbinju+YXyuR+Nk8xlr/g==",
|
||||||
"peer": true,
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@formatjs/intl-localematcher": "0.5.4",
|
"@formatjs/intl-localematcher": "0.5.4",
|
||||||
"tslib": "^2.4.0"
|
"tslib": "^2.4.0"
|
||||||
@@ -2387,29 +2388,73 @@
|
|||||||
"version": "2.2.0",
|
"version": "2.2.0",
|
||||||
"resolved": "https://registry.npmjs.org/@formatjs/fast-memoize/-/fast-memoize-2.2.0.tgz",
|
"resolved": "https://registry.npmjs.org/@formatjs/fast-memoize/-/fast-memoize-2.2.0.tgz",
|
||||||
"integrity": "sha512-hnk/nY8FyrL5YxwP9e4r9dqeM6cAbo8PeU9UjyXojZMNvVad2Z06FAVHyR3Ecw6fza+0GH7vdJgiKIVXTMbSBA==",
|
"integrity": "sha512-hnk/nY8FyrL5YxwP9e4r9dqeM6cAbo8PeU9UjyXojZMNvVad2Z06FAVHyR3Ecw6fza+0GH7vdJgiKIVXTMbSBA==",
|
||||||
"peer": true,
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"tslib": "^2.4.0"
|
"tslib": "^2.4.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@formatjs/icu-messageformat-parser": {
|
"node_modules/@formatjs/icu-messageformat-parser": {
|
||||||
"version": "2.7.6",
|
"version": "2.7.8",
|
||||||
"resolved": "https://registry.npmjs.org/@formatjs/icu-messageformat-parser/-/icu-messageformat-parser-2.7.6.tgz",
|
"resolved": "https://registry.npmjs.org/@formatjs/icu-messageformat-parser/-/icu-messageformat-parser-2.7.8.tgz",
|
||||||
"integrity": "sha512-etVau26po9+eewJKYoiBKP6743I1br0/Ie00Pb/S/PtmYfmjTcOn2YCh2yNkSZI12h6Rg+BOgQYborXk46BvkA==",
|
"integrity": "sha512-nBZJYmhpcSX0WeJ5SDYUkZ42AgR3xiyhNCsQweFx3cz/ULJjym8bHAzWKvG5e2+1XO98dBYC0fWeeAECAVSwLA==",
|
||||||
"peer": true,
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@formatjs/ecma402-abstract": "1.18.2",
|
"@formatjs/ecma402-abstract": "2.0.0",
|
||||||
"@formatjs/icu-skeleton-parser": "1.8.0",
|
"@formatjs/icu-skeleton-parser": "1.8.2",
|
||||||
"tslib": "^2.4.0"
|
"tslib": "^2.4.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@formatjs/icu-skeleton-parser": {
|
"node_modules/@formatjs/icu-skeleton-parser": {
|
||||||
"version": "1.8.0",
|
"version": "1.8.2",
|
||||||
"resolved": "https://registry.npmjs.org/@formatjs/icu-skeleton-parser/-/icu-skeleton-parser-1.8.0.tgz",
|
"resolved": "https://registry.npmjs.org/@formatjs/icu-skeleton-parser/-/icu-skeleton-parser-1.8.2.tgz",
|
||||||
"integrity": "sha512-QWLAYvM0n8hv7Nq5BEs4LKIjevpVpbGLAJgOaYzg9wABEoX1j0JO1q2/jVkO6CVlq0dbsxZCngS5aXbysYueqA==",
|
"integrity": "sha512-k4ERKgw7aKGWJZgTarIcNEmvyTVD9FYh0mTrrBMHZ1b8hUu6iOJ4SzsZlo3UNAvHYa+PnvntIwRPt1/vy4nA9Q==",
|
||||||
"peer": true,
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@formatjs/ecma402-abstract": "1.18.2",
|
"@formatjs/ecma402-abstract": "2.0.0",
|
||||||
|
"tslib": "^2.4.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@formatjs/intl": {
|
||||||
|
"version": "2.10.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/@formatjs/intl/-/intl-2.10.4.tgz",
|
||||||
|
"integrity": "sha512-56483O+HVcL0c7VucAS2tyH020mt9XTozZO67cwtGg0a7KWDukS/FzW3OnvaHmTHDuYsoPIzO+ZHVfU6fT/bJw==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@formatjs/ecma402-abstract": "2.0.0",
|
||||||
|
"@formatjs/fast-memoize": "2.2.0",
|
||||||
|
"@formatjs/icu-messageformat-parser": "2.7.8",
|
||||||
|
"@formatjs/intl-displaynames": "6.6.8",
|
||||||
|
"@formatjs/intl-listformat": "7.5.7",
|
||||||
|
"intl-messageformat": "10.5.14",
|
||||||
|
"tslib": "^2.4.0"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"typescript": "^4.7 || 5"
|
||||||
|
},
|
||||||
|
"peerDependenciesMeta": {
|
||||||
|
"typescript": {
|
||||||
|
"optional": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@formatjs/intl-displaynames": {
|
||||||
|
"version": "6.6.8",
|
||||||
|
"resolved": "https://registry.npmjs.org/@formatjs/intl-displaynames/-/intl-displaynames-6.6.8.tgz",
|
||||||
|
"integrity": "sha512-Lgx6n5KxN16B3Pb05z3NLEBQkGoXnGjkTBNCZI+Cn17YjHJ3fhCeEJJUqRlIZmJdmaXQhjcQVDp6WIiNeRYT5g==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@formatjs/ecma402-abstract": "2.0.0",
|
||||||
|
"@formatjs/intl-localematcher": "0.5.4",
|
||||||
|
"tslib": "^2.4.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@formatjs/intl-listformat": {
|
||||||
|
"version": "7.5.7",
|
||||||
|
"resolved": "https://registry.npmjs.org/@formatjs/intl-listformat/-/intl-listformat-7.5.7.tgz",
|
||||||
|
"integrity": "sha512-MG2TSChQJQT9f7Rlv+eXwUFiG24mKSzmF144PLb8m8OixyXqn4+YWU+5wZracZGCgVTVmx8viCf7IH3QXoiB2g==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@formatjs/ecma402-abstract": "2.0.0",
|
||||||
|
"@formatjs/intl-localematcher": "0.5.4",
|
||||||
"tslib": "^2.4.0"
|
"tslib": "^2.4.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -2417,7 +2462,6 @@
|
|||||||
"version": "0.5.4",
|
"version": "0.5.4",
|
||||||
"resolved": "https://registry.npmjs.org/@formatjs/intl-localematcher/-/intl-localematcher-0.5.4.tgz",
|
"resolved": "https://registry.npmjs.org/@formatjs/intl-localematcher/-/intl-localematcher-0.5.4.tgz",
|
||||||
"integrity": "sha512-zTwEpWOzZ2CiKcB93BLngUX59hQkuZjT2+SAQEscSm52peDW/getsawMcWF1rGRpMCX6D7nSJA3CzJ8gn13N/g==",
|
"integrity": "sha512-zTwEpWOzZ2CiKcB93BLngUX59hQkuZjT2+SAQEscSm52peDW/getsawMcWF1rGRpMCX6D7nSJA3CzJ8gn13N/g==",
|
||||||
"peer": true,
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"tslib": "^2.4.0"
|
"tslib": "^2.4.0"
|
||||||
}
|
}
|
||||||
@@ -4887,6 +4931,16 @@
|
|||||||
"resolved": "https://registry.npmjs.org/@types/cookie/-/cookie-0.6.0.tgz",
|
"resolved": "https://registry.npmjs.org/@types/cookie/-/cookie-0.6.0.tgz",
|
||||||
"integrity": "sha512-4Kh9a6B2bQciAhf7FSuMRRkUWecJgJu9nPnx3yzpsfXX/c50REIqpHY4C82bXP90qrLtXtkDxTZosYO3UpOwlA=="
|
"integrity": "sha512-4Kh9a6B2bQciAhf7FSuMRRkUWecJgJu9nPnx3yzpsfXX/c50REIqpHY4C82bXP90qrLtXtkDxTZosYO3UpOwlA=="
|
||||||
},
|
},
|
||||||
|
"node_modules/@types/hoist-non-react-statics": {
|
||||||
|
"version": "3.3.5",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/hoist-non-react-statics/-/hoist-non-react-statics-3.3.5.tgz",
|
||||||
|
"integrity": "sha512-SbcrWzkKBw2cdwRTwQAswfpB9g9LJWfjtUeW/jvNwbhC8cpmmNYVePa+ncbUe0rGTQ7G3Ff6mYUN2VMfLVr+Sg==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@types/react": "*",
|
||||||
|
"hoist-non-react-statics": "^3.3.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@types/http-cache-semantics": {
|
"node_modules/@types/http-cache-semantics": {
|
||||||
"version": "4.0.4",
|
"version": "4.0.4",
|
||||||
"resolved": "https://registry.npmjs.org/@types/http-cache-semantics/-/http-cache-semantics-4.0.4.tgz",
|
"resolved": "https://registry.npmjs.org/@types/http-cache-semantics/-/http-cache-semantics-4.0.4.tgz",
|
||||||
@@ -4920,14 +4974,12 @@
|
|||||||
"node_modules/@types/prop-types": {
|
"node_modules/@types/prop-types": {
|
||||||
"version": "15.7.12",
|
"version": "15.7.12",
|
||||||
"resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.12.tgz",
|
"resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.12.tgz",
|
||||||
"integrity": "sha512-5zvhXYtRNRluoE/jAp4GVsSduVUzNWKkOZrCDBWYtE7biZywwdC2AcEzg+cSMLFRfVgeAFqpfNabiPjxFddV1Q==",
|
"integrity": "sha512-5zvhXYtRNRluoE/jAp4GVsSduVUzNWKkOZrCDBWYtE7biZywwdC2AcEzg+cSMLFRfVgeAFqpfNabiPjxFddV1Q=="
|
||||||
"devOptional": true
|
|
||||||
},
|
},
|
||||||
"node_modules/@types/react": {
|
"node_modules/@types/react": {
|
||||||
"version": "18.2.75",
|
"version": "18.2.75",
|
||||||
"resolved": "https://registry.npmjs.org/@types/react/-/react-18.2.75.tgz",
|
"resolved": "https://registry.npmjs.org/@types/react/-/react-18.2.75.tgz",
|
||||||
"integrity": "sha512-+DNnF7yc5y0bHkBTiLKqXFe+L4B3nvOphiMY3tuA5X10esmjqk7smyBZzbGTy2vsiy/Bnzj8yFIBL8xhRacoOg==",
|
"integrity": "sha512-+DNnF7yc5y0bHkBTiLKqXFe+L4B3nvOphiMY3tuA5X10esmjqk7smyBZzbGTy2vsiy/Bnzj8yFIBL8xhRacoOg==",
|
||||||
"devOptional": true,
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@types/prop-types": "*",
|
"@types/prop-types": "*",
|
||||||
"csstype": "^3.0.2"
|
"csstype": "^3.0.2"
|
||||||
@@ -6633,8 +6685,7 @@
|
|||||||
"node_modules/csstype": {
|
"node_modules/csstype": {
|
||||||
"version": "3.1.3",
|
"version": "3.1.3",
|
||||||
"resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz",
|
"resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz",
|
||||||
"integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==",
|
"integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw=="
|
||||||
"devOptional": true
|
|
||||||
},
|
},
|
||||||
"node_modules/cypress": {
|
"node_modules/cypress": {
|
||||||
"version": "13.7.2",
|
"version": "13.7.2",
|
||||||
@@ -8582,6 +8633,15 @@
|
|||||||
"node": ">= 0.4"
|
"node": ">= 0.4"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/hoist-non-react-statics": {
|
||||||
|
"version": "3.3.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz",
|
||||||
|
"integrity": "sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw==",
|
||||||
|
"license": "BSD-3-Clause",
|
||||||
|
"dependencies": {
|
||||||
|
"react-is": "^16.7.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/http-assert": {
|
"node_modules/http-assert": {
|
||||||
"version": "1.5.0",
|
"version": "1.5.0",
|
||||||
"resolved": "https://registry.npmjs.org/http-assert/-/http-assert-1.5.0.tgz",
|
"resolved": "https://registry.npmjs.org/http-assert/-/http-assert-1.5.0.tgz",
|
||||||
@@ -8815,14 +8875,14 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/intl-messageformat": {
|
"node_modules/intl-messageformat": {
|
||||||
"version": "10.5.11",
|
"version": "10.5.14",
|
||||||
"resolved": "https://registry.npmjs.org/intl-messageformat/-/intl-messageformat-10.5.11.tgz",
|
"resolved": "https://registry.npmjs.org/intl-messageformat/-/intl-messageformat-10.5.14.tgz",
|
||||||
"integrity": "sha512-eYq5fkFBVxc7GIFDzpFQkDOZgNayNTQn4Oufe8jw6YY6OHVw70/4pA3FyCsQ0Gb2DnvEJEMmN2tOaXUGByM+kg==",
|
"integrity": "sha512-IjC6sI0X7YRjjyVH9aUgdftcmZK7WXdHeil4KwbjDnRWjnVitKpAx3rr6t6di1joFp5188VqKcobOPA6mCLG/w==",
|
||||||
"peer": true,
|
"license": "BSD-3-Clause",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@formatjs/ecma402-abstract": "1.18.2",
|
"@formatjs/ecma402-abstract": "2.0.0",
|
||||||
"@formatjs/fast-memoize": "2.2.0",
|
"@formatjs/fast-memoize": "2.2.0",
|
||||||
"@formatjs/icu-messageformat-parser": "2.7.6",
|
"@formatjs/icu-messageformat-parser": "2.7.8",
|
||||||
"tslib": "^2.4.0"
|
"tslib": "^2.4.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -12127,6 +12187,33 @@
|
|||||||
"react": "^16.8.0 || ^17.0.0 || ^18.0.0"
|
"react": "^16.8.0 || ^17.0.0 || ^18.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/react-intl": {
|
||||||
|
"version": "6.6.8",
|
||||||
|
"resolved": "https://registry.npmjs.org/react-intl/-/react-intl-6.6.8.tgz",
|
||||||
|
"integrity": "sha512-M0pkhzcgV31h++2901BiRXWl69hp2zPyLxRrSwRjd1ErXbNoubz/f4M6DrRTd4OiSUrT4ajRQzrmtS5plG4FtA==",
|
||||||
|
"license": "BSD-3-Clause",
|
||||||
|
"dependencies": {
|
||||||
|
"@formatjs/ecma402-abstract": "2.0.0",
|
||||||
|
"@formatjs/icu-messageformat-parser": "2.7.8",
|
||||||
|
"@formatjs/intl": "2.10.4",
|
||||||
|
"@formatjs/intl-displaynames": "6.6.8",
|
||||||
|
"@formatjs/intl-listformat": "7.5.7",
|
||||||
|
"@types/hoist-non-react-statics": "^3.3.1",
|
||||||
|
"@types/react": "16 || 17 || 18",
|
||||||
|
"hoist-non-react-statics": "^3.3.2",
|
||||||
|
"intl-messageformat": "10.5.14",
|
||||||
|
"tslib": "^2.4.0"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"react": "^16.6.0 || 17 || 18",
|
||||||
|
"typescript": "^4.7 || 5"
|
||||||
|
},
|
||||||
|
"peerDependenciesMeta": {
|
||||||
|
"typescript": {
|
||||||
|
"optional": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/react-is": {
|
"node_modules/react-is": {
|
||||||
"version": "16.13.1",
|
"version": "16.13.1",
|
||||||
"resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz",
|
"resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz",
|
||||||
|
|||||||
@@ -49,6 +49,7 @@
|
|||||||
"react-feather": "^2.0.10",
|
"react-feather": "^2.0.10",
|
||||||
"react-hook-form": "^7.51.2",
|
"react-hook-form": "^7.51.2",
|
||||||
"react-international-phone": "^4.2.6",
|
"react-international-phone": "^4.2.6",
|
||||||
|
"react-intl": "^6.6.8",
|
||||||
"server-only": "^0.0.1",
|
"server-only": "^0.0.1",
|
||||||
"superjson": "^2.2.1",
|
"superjson": "^2.2.1",
|
||||||
"zod": "^3.22.4",
|
"zod": "^3.22.4",
|
||||||
|
|||||||
@@ -3,7 +3,6 @@ import {
|
|||||||
GetLoyaltyPageRefs,
|
GetLoyaltyPageRefs,
|
||||||
} from "@/lib/graphql/Query/LoyaltyPage.graphql"
|
} from "@/lib/graphql/Query/LoyaltyPage.graphql"
|
||||||
import { request } from "@/lib/graphql/request"
|
import { request } from "@/lib/graphql/request"
|
||||||
import { _ } from "@/lib/translation"
|
|
||||||
import { internalServerError, notFound } from "@/server/errors/trpc"
|
import { internalServerError, notFound } from "@/server/errors/trpc"
|
||||||
import { contentstackProcedure, router } from "@/server/trpc"
|
import { contentstackProcedure, router } from "@/server/trpc"
|
||||||
|
|
||||||
@@ -12,6 +11,7 @@ import {
|
|||||||
generateTag,
|
generateTag,
|
||||||
generateTags,
|
generateTags,
|
||||||
} from "@/utils/generateTag"
|
} from "@/utils/generateTag"
|
||||||
|
import { removeMultipleSlashes } from "@/utils/url"
|
||||||
|
|
||||||
import { removeEmptyObjects } from "../../utils"
|
import { removeEmptyObjects } from "../../utils"
|
||||||
import {
|
import {
|
||||||
@@ -35,7 +35,9 @@ function makeButtonObject(button: any) {
|
|||||||
href:
|
href:
|
||||||
button.is_contentstack_link && button.linkConnection.edges.length
|
button.is_contentstack_link && button.linkConnection.edges.length
|
||||||
? button.linkConnection.edges[0].node.web?.original_url ||
|
? button.linkConnection.edges[0].node.web?.original_url ||
|
||||||
`/${button.linkConnection.edges[0].node.system.locale}${button.linkConnection.edges[0].node.url}`
|
removeMultipleSlashes(
|
||||||
|
`/${button.linkConnection.edges[0].node.system.locale}/${button.linkConnection.edges[0].node.url}`
|
||||||
|
)
|
||||||
: button.external_link.href,
|
: button.external_link.href,
|
||||||
isExternal: !button.is_contentstack_link,
|
isExternal: !button.is_contentstack_link,
|
||||||
}
|
}
|
||||||
@@ -106,7 +108,9 @@ export const loyaltyPageQueryRouter = router({
|
|||||||
link: block.dynamic_content.link.pageConnection.edges.length
|
link: block.dynamic_content.link.pageConnection.edges.length
|
||||||
? {
|
? {
|
||||||
text: block.dynamic_content.link.text,
|
text: block.dynamic_content.link.text,
|
||||||
href: `/${block.dynamic_content.link.pageConnection.edges[0].node.system.locale}${block.dynamic_content.link.pageConnection.edges[0].node.url}`,
|
href: removeMultipleSlashes(
|
||||||
|
`/${block.dynamic_content.link.pageConnection.edges[0].node.system.locale}/${block.dynamic_content.link.pageConnection.edges[0].node.url}`
|
||||||
|
),
|
||||||
title:
|
title:
|
||||||
block.dynamic_content.link.pageConnection.edges[0]
|
block.dynamic_content.link.pageConnection.edges[0]
|
||||||
.node.title,
|
.node.title,
|
||||||
@@ -125,7 +129,9 @@ export const loyaltyPageQueryRouter = router({
|
|||||||
...shortcut.linkConnection.edges[0].node,
|
...shortcut.linkConnection.edges[0].node,
|
||||||
url:
|
url:
|
||||||
shortcut.linkConnection.edges[0].node.web?.original_url ||
|
shortcut.linkConnection.edges[0].node.web?.original_url ||
|
||||||
`/${shortcut.linkConnection.edges[0].node.system.locale}${shortcut.linkConnection.edges[0].node.url}`,
|
removeMultipleSlashes(
|
||||||
|
`/${shortcut.linkConnection.edges[0].node.system.locale}/${shortcut.linkConnection.edges[0].node.url}`
|
||||||
|
),
|
||||||
})),
|
})),
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import { Lang } from "@/constants/languages"
|
||||||
import {
|
import {
|
||||||
Block,
|
Block,
|
||||||
CardsGrid,
|
CardsGrid,
|
||||||
@@ -5,6 +6,8 @@ import {
|
|||||||
RteBlockContent,
|
RteBlockContent,
|
||||||
} from "@/server/routers/contentstack/loyaltyPage/output"
|
} from "@/server/routers/contentstack/loyaltyPage/output"
|
||||||
|
|
||||||
|
import type { IntlFormatters } from "@formatjs/intl"
|
||||||
|
|
||||||
export type BlocksProps = {
|
export type BlocksProps = {
|
||||||
blocks: Block[]
|
blocks: Block[]
|
||||||
}
|
}
|
||||||
@@ -40,6 +43,8 @@ export type Level = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export type LevelCardProps = {
|
export type LevelCardProps = {
|
||||||
|
formatMessage: IntlFormatters["formatMessage"]
|
||||||
|
lang: Lang
|
||||||
level: {
|
level: {
|
||||||
tier: number
|
tier: number
|
||||||
name: string
|
name: string
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
export type ShowMoreButtonParams = {
|
export type ShowMoreButtonParams = {
|
||||||
|
disabled?: boolean
|
||||||
loadMoreData: () => void
|
loadMoreData: () => void
|
||||||
disabled: boolean
|
|
||||||
}
|
}
|
||||||
|
|||||||
5
types/i18n.ts
Normal file
5
types/i18n.ts
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
import type { IntlConfig } from "react-intl"
|
||||||
|
|
||||||
|
export type ServerIntlProviderProps = React.PropsWithChildren<{
|
||||||
|
intl: IntlConfig
|
||||||
|
}>
|
||||||
Reference in New Issue
Block a user