Merged in feat/mypages-parallel-routes (pull request #1388)

feat: my profile - removed all parallel routes

* Removed all parallel routes on my-profile

* Fixed suspense

* Moved components into myprofile folder

* Turn off browser cache on myprofile

* Clear router cache when editing profile

* Clear route cache when adding new credit card

* PR fixes


Approved-by: Joakim Jäderberg
This commit is contained in:
Linus Flood
2025-02-21 11:24:46 +00:00
parent 9cd648fd65
commit 15dbeb9d46
25 changed files with 205 additions and 247 deletions

View File

@@ -1 +0,0 @@
export { default } from "../page"

View File

@@ -1 +0,0 @@
export { default } from "../page"

View File

@@ -1 +0,0 @@
export { default } from "../page"

View File

@@ -1,3 +0,0 @@
export default function Default() {
return null
}

View File

@@ -1,3 +0,0 @@
export default function DefaultEditProfileSlot() {
return null
}

View File

@@ -1,13 +0,0 @@
import { getProfile } from "@/lib/trpc/memoizedRequests"
import Form from "@/components/Forms/Edit/Profile"
import type { LangParams, PageArgs } from "@/types/params"
export default async function EditProfileSlot({}: PageArgs<LangParams>) {
const user = await getProfile()
if (!user || "error" in user) {
return null
}
return <Form user={user} />
}

View File

@@ -1,20 +0,0 @@
"use client"
import * as Sentry from "@sentry/nextjs"
import { useEffect } from "react"
import { useIntl } from "react-intl"
import type { ErrorPage } from "@/types/next/error"
export default function ProfileError({ error }: ErrorPage) {
const intl = useIntl()
useEffect(() => {
if (!error) return
console.error(error)
Sentry.captureException(error)
}, [error])
return <h1>{intl.formatMessage({ id: "Error happened, Profile" })}</h1>
}

View File

@@ -1,6 +0,0 @@
.container {
background-color: var(--Main-Grey-White);
border-radius: var(--Corner-radius-Large);
display: grid;
gap: var(--Spacing-x3);
}

View File

@@ -1,7 +0,0 @@
import styles from "./layout.module.css"
export default function ProfileSlotLayout({
children,
}: React.PropsWithChildren) {
return <section className={styles.container}>{children}</section>
}

View File

@@ -1,122 +0,0 @@
import { languages, languageSelect } from "@/constants/languages"
import { profileEdit } from "@/constants/routes/myPages"
import { getProfile } from "@/lib/trpc/memoizedRequests"
import {
CalendarIcon,
EmailIcon,
GlobeIcon,
LocationIcon,
LockIcon,
PhoneIcon,
} from "@/components/Icons"
import Header from "@/components/Profile/Header"
import Button from "@/components/TempDesignSystem/Button"
import Link from "@/components/TempDesignSystem/Link"
import Body from "@/components/TempDesignSystem/Text/Body"
import Title from "@/components/TempDesignSystem/Text/Title"
import { getIntl } from "@/i18n"
import styles from "./page.module.css"
import type { LangParams, PageArgs } from "@/types/params"
export default async function Profile({ params }: PageArgs<LangParams>) {
const intl = await getIntl()
const user = await getProfile()
if (!user || "error" in user) {
return null
}
const addressParts = []
if (user.address.streetAddress) {
addressParts.push(user.address.streetAddress)
}
if (user.address.city) {
addressParts.push(user.address.city)
}
if (user.address.country) {
addressParts.push(user.address.country)
}
const addressOutput =
addressParts.length > 0
? addressParts.join(", ")
: intl.formatMessage({ id: "N/A" })
const defaultLanguage = languages[params.lang]
const language = languageSelect.find((l) => l.value === user.language)
return (
<>
<Header>
<hgroup>
<Title as="h4" color="red" level="h1" textTransform="capitalize">
{intl.formatMessage({ id: "Welcome" })}
</Title>
<Title
data-hj-suppress
as="h4"
color="burgundy"
level="h2"
textTransform="capitalize"
>
{user.name}
</Title>
</hgroup>
<Button asChild intent="primary" size="small" theme="base">
<Link prefetch={false} color="none" href={profileEdit[params.lang]}>
{intl.formatMessage({ id: "Edit profile" })}
</Link>
</Button>
</Header>
<section className={styles.profile}>
<article className={styles.info}>
<div className={styles.item}>
<CalendarIcon color="burgundy" />
<Body color="burgundy" textTransform="bold">
{intl.formatMessage({ id: "Date of Birth" })}
</Body>
<Body color="burgundy">{user.dateOfBirth}</Body>
</div>
<div className={styles.item}>
<PhoneIcon color="burgundy" />
<Body color="burgundy" textTransform="bold">
{intl.formatMessage({ id: "Phone number" })}
</Body>
<Body color="burgundy">{user.phoneNumber}</Body>
</div>
<div className={styles.item}>
<GlobeIcon color="burgundy" />
<Body color="burgundy" textTransform="bold">
{intl.formatMessage({ id: "Language" })}
</Body>
<Body color="burgundy">{language?.label ?? defaultLanguage}</Body>
</div>
<div className={styles.item}>
<EmailIcon color="burgundy" />
<Body color="burgundy" textTransform="bold">
{intl.formatMessage({ id: "Email" })}
</Body>
<Body color="burgundy">{user.email}</Body>
</div>
<div className={styles.item}>
<LocationIcon color="burgundy" />
<Body color="burgundy" textTransform="bold">
{intl.formatMessage({ id: "Address" })}
</Body>
<Body color="burgundy">{addressOutput}</Body>
</div>
<div className={styles.item}>
<LockIcon color="burgundy" />
<Body color="burgundy" textTransform="bold">
{intl.formatMessage({ id: "Password" })}
</Body>
<Body color="burgundy">**********</Body>
</div>
</article>
</section>
</>
)
}

View File

@@ -0,0 +1,12 @@
.container {
background-color: var(--Main-Grey-White);
border-radius: var(--Corner-radius-Large);
display: grid;
gap: var(--Spacing-x3);
padding: var(--Spacing-x2) var(--Spacing-x2) var(--Spacing-x4);
}
@media screen and (min-width: 768px) {
.container {
padding: var(--Spacing-x3) var(--Spacing-x3) var(--Spacing-x4);
}
}

View File

@@ -1,5 +1,19 @@
import ProfilePage from "../page"
import { getProfile } from "@/lib/trpc/memoizedRequests"
export { generateMetadata } from "@/utils/generateMetadata"
import Form from "@/components/Forms/Edit/Profile"
export default ProfilePage
import styles from "./page.module.css"
export default async function EditProfileSlot() {
const user = await getProfile()
if (!user || "error" in user) {
return null
}
return (
<>
<div className={styles.container}>
<Form user={user} />
</div>
</>
)
}

View File

@@ -1,22 +1,3 @@
import Divider from "@/components/TempDesignSystem/Divider"
import type { ProfileLayoutProps } from "@/types/components/myPages/myProfile/layout"
export default function ProfileLayout({
children,
communication,
creditCards,
profile,
}: React.PropsWithChildren<ProfileLayoutProps>) {
return (
<main>
{children}
<section className="profile-layout">
{profile}
<Divider color="burgundy" opacity={8} />
{creditCards}
{communication}
</section>
</main>
)
export default function ProfileLayout({ children }: React.PropsWithChildren) {
return <main>{children}</main>
}

View File

@@ -1,7 +1,8 @@
import "./profileLayout.css"
import { Suspense } from "react"
import { serverClient } from "@/lib/trpc/server"
import Profile from "@/components/MyPages/myprofile/profile/profile"
import TrackingSDK from "@/components/TrackingSDK"
import type { LangParams, PageArgs } from "@/types/params"
@@ -15,5 +16,12 @@ export default async function ProfilePage({}: PageArgs<LangParams>) {
return null
}
return <TrackingSDK pageData={accountPage.tracking} />
return (
<>
<Profile />
<Suspense fallback={null}>
<TrackingSDK pageData={accountPage.tracking} />
</Suspense>
</>
)
}

View File

@@ -1,26 +0,0 @@
/**
* Due to css import issues with parallel routes we are forced to
* use a regular css file and import it in the page.tsx
* This is addressed in Next 15: https: //github.com/vercel/next.js/pull/66300
*/
.profile-layout {
background-color: var(--Main-Grey-White);
border-radius: var(--Corner-radius-xLarge);
display: grid;
gap: var(--Spacing-x4);
padding: var(--Spacing-x2) var(--Spacing-x2) var(--Spacing-x4);
margin: 0 calc(var(--Layout-Mobile-Margin-Margin-min) * -1);
}
@media screen and (min-width: 768px) {
.profile-layout {
padding: var(--Spacing-x3) var(--Spacing-x3) var(--Spacing-x4);
margin: 0 calc(var(--Layout-Tablet-Margin-Margin-min) * -1);
}
}
@media screen and (min-width: 1367px) {
.profile-layout {
margin: 0;
}
}

View File

@@ -95,6 +95,7 @@ export default function Form({ user }: EditFormProps) {
window.location.href = logout[lang]
} else {
router.push(profile[lang])
router.refresh() // Can be removed on NextJs 15
}
break
}
@@ -110,7 +111,7 @@ export default function Form({ user }: EditFormProps) {
return (
<section className={styles.container}>
<hgroup className={styles.title}>
<div className={styles.title}>
<Title as="h4" color="red" level="h1" textTransform="capitalize">
{intl.formatMessage({ id: "Welcome" })}
</Title>
@@ -123,7 +124,7 @@ export default function Form({ user }: EditFormProps) {
>
{user.name}
</Title>
</hgroup>
</div>
<div className={styles.btnContainer}>
<Dialog
bodyText={intl.formatMessage({

View File

@@ -3,11 +3,9 @@ import Body from "@/components/TempDesignSystem/Text/Body"
import Subtitle from "@/components/TempDesignSystem/Text/Subtitle"
import { getIntl } from "@/i18n"
import styles from "./page.module.css"
import styles from "./communication.module.css"
import type { LangParams, PageArgs } from "@/types/params"
export default async function CommunicationSlot({}: PageArgs<LangParams>) {
export default async function CommunicationSlot() {
const intl = await getIntl()
return (
<section className={styles.container}>

View File

@@ -6,11 +6,9 @@ import Body from "@/components/TempDesignSystem/Text/Body"
import Subtitle from "@/components/TempDesignSystem/Text/Subtitle"
import { getIntl } from "@/i18n"
import styles from "./page.module.css"
import styles from "./creditCards.module.css"
import type { LangParams, PageArgs } from "@/types/params"
export default async function CreditCardSlot({}: PageArgs<LangParams>) {
export default async function CreditCardSlot() {
const intl = await getIntl()
const creditCards = await serverClient().user.creditCards()

View File

@@ -6,11 +6,9 @@ import Body from "@/components/TempDesignSystem/Text/Body"
import Subtitle from "@/components/TempDesignSystem/Text/Subtitle"
import { getIntl } from "@/i18n"
import styles from "./page.module.css"
import styles from "./membershipcards.module.css"
import type { LangParams, PageArgs } from "@/types/params"
export default async function MembershipCardSlot({}: PageArgs<LangParams>) {
export default async function MembershipCardSlot() {
const intl = await getIntl()
const membershipCards = await getMembershipCards()

View File

@@ -3,14 +3,12 @@
gap: var(--Spacing-x2);
justify-content: space-between;
}
.info {
display: grid;
gap: var(--Spacing-x-one-and-half) var(--Spacing-x7);
width: 100%;
justify-items: flex-start;
}
.item {
align-items: center;
display: grid;
@@ -19,7 +17,18 @@
justify-items: flex-end;
width: 100%;
}
.content {
display: grid;
padding-bottom: var(--Spacing-x9);
position: relative;
}
.container {
background-color: var(--Main-Grey-White);
border-radius: var(--Corner-radius-Large);
display: grid;
gap: var(--Spacing-x3);
padding: var(--Spacing-x2) var(--Spacing-x2) var(--Spacing-x4);
}
@media screen and (min-width: 768px) {
.info {
grid-template-columns: repeat(3, auto);
@@ -27,4 +36,13 @@
.item {
justify-items: flex-start;
}
.container {
padding: var(--Spacing-x3) var(--Spacing-x3) var(--Spacing-x4);
}
}
@media screen and (min-width: 1367px) {
.content {
gap: var(--Spacing-x5);
grid-template-columns: max(340px) 1fr;
}
}

View File

@@ -0,0 +1,132 @@
import { languages, languageSelect } from "@/constants/languages"
import { profileEdit } from "@/constants/routes/myPages"
import { getProfile } from "@/lib/trpc/memoizedRequests"
import {
CalendarIcon,
EmailIcon,
GlobeIcon,
LocationIcon,
LockIcon,
PhoneIcon,
} from "@/components/Icons"
import CommunicationSlot from "@/components/MyPages/myprofile/communication/communication"
import CreditCardSlot from "@/components/MyPages/myprofile/creditCards/creditCards"
import MembershipCardSlot from "@/components/MyPages/myprofile/membershipCards/membershipcards"
import Header from "@/components/Profile/Header"
import Button from "@/components/TempDesignSystem/Button"
import Divider from "@/components/TempDesignSystem/Divider"
import Link from "@/components/TempDesignSystem/Link"
import Body from "@/components/TempDesignSystem/Text/Body"
import Title from "@/components/TempDesignSystem/Text/Title"
import { getIntl } from "@/i18n"
import { getLang } from "@/i18n/serverContext"
import styles from "./profile.module.css"
export default async function Profile() {
const intl = await getIntl()
const lang = getLang()
const user = await getProfile()
if (!user || "error" in user) {
return null
}
const addressParts = []
if (user.address.streetAddress) {
addressParts.push(user.address.streetAddress)
}
if (user.address.city) {
addressParts.push(user.address.city)
}
if (user.address.country) {
addressParts.push(user.address.country)
}
const addressOutput =
addressParts.length > 0
? addressParts.join(", ")
: intl.formatMessage({ id: "N/A" })
const defaultLanguage = languages[lang]
const language = languageSelect.find((l) => l.value === user.language)
return (
<>
<section className={styles.container}>
<Header>
<div>
<Title as="h4" color="red" level="h1" textTransform="capitalize">
{intl.formatMessage({ id: "Welcome" })}
</Title>
<Title
data-hj-suppress
as="h4"
color="burgundy"
level="h2"
textTransform="capitalize"
>
{user.name}
</Title>
</div>
<Button asChild intent="primary" size="small" theme="base">
<Link prefetch={false} color="none" href={profileEdit[lang]}>
{intl.formatMessage({ id: "Edit profile" })}
</Link>
</Button>
</Header>
<div className={styles.profile}>
<div className={styles.info}>
<div className={styles.item}>
<CalendarIcon color="burgundy" />
<Body color="burgundy" textTransform="bold">
{intl.formatMessage({ id: "Date of Birth" })}
</Body>
<Body color="burgundy">{user.dateOfBirth}</Body>
</div>
<div className={styles.item}>
<PhoneIcon color="burgundy" />
<Body color="burgundy" textTransform="bold">
{intl.formatMessage({ id: "Phone number" })}
</Body>
<Body color="burgundy">{user.phoneNumber}</Body>
</div>
<div className={styles.item}>
<GlobeIcon color="burgundy" />
<Body color="burgundy" textTransform="bold">
{intl.formatMessage({ id: "Language" })}
</Body>
<Body color="burgundy">{language?.label ?? defaultLanguage}</Body>
</div>
<div className={styles.item}>
<EmailIcon color="burgundy" />
<Body color="burgundy" textTransform="bold">
{intl.formatMessage({ id: "Email" })}
</Body>
<Body color="burgundy">{user.email}</Body>
</div>
<div className={styles.item}>
<LocationIcon color="burgundy" />
<Body color="burgundy" textTransform="bold">
{intl.formatMessage({ id: "Address" })}
</Body>
<Body color="burgundy">{addressOutput}</Body>
</div>
<div className={styles.item}>
<LockIcon color="burgundy" />
<Body color="burgundy" textTransform="bold">
{intl.formatMessage({ id: "Password" })}
</Body>
<Body color="burgundy">**********</Body>
</div>
</div>
</div>
<Divider color="burgundy" opacity={8} />
<CreditCardSlot />
<MembershipCardSlot />
<CommunicationSlot />
</section>
</>
)
}

View File

@@ -67,6 +67,7 @@ export default function AddCreditCardButton() {
onSuccess: (result) => {
if (result?.attribute.link) {
router.push(result.attribute.link)
router.refresh() // / Could be removed on NextJs 15
} else {
toast.error(
intl.formatMessage({