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,10 +0,0 @@
.container {
display: grid;
gap: var(--Spacing-x3);
max-width: 510px;
}
.content {
display: grid;
gap: var(--Spacing-x1);
}

View File

@@ -1,27 +0,0 @@
import ManagePreferencesButton from "@/components/Profile/ManagePreferencesButton"
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 type { LangParams, PageArgs } from "@/types/params"
export default async function CommunicationSlot({}: PageArgs<LangParams>) {
const intl = await getIntl()
return (
<section className={styles.container}>
<article className={styles.content}>
<Subtitle type="two" color="black">
{intl.formatMessage({ id: "My communication preferences" })}
</Subtitle>
<Body color="black">
{intl.formatMessage({
id: "Tell us what information and updates you'd like to receive, and how, by clicking the link below.",
})}
</Body>
</article>
<ManagePreferencesButton />
</section>
)
}

View File

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

View File

@@ -1,22 +0,0 @@
.container {
display: grid;
gap: var(--Spacing-x2);
justify-items: flex-start;
max-width: 510px;
}
.content {
display: grid;
gap: var(--Spacing-x1);
}
.cardContainer {
display: grid;
gap: var(--Spacing-x1);
}
@media screen and (min-width: 768px) {
.container {
gap: var(--Spacing-x3);
}
}

View File

@@ -1,33 +0,0 @@
import { serverClient } from "@/lib/trpc/server"
import AddCreditCardButton from "@/components/Profile/AddCreditCardButton"
import CreditCardList from "@/components/Profile/CreditCardList"
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 type { LangParams, PageArgs } from "@/types/params"
export default async function CreditCardSlot({}: PageArgs<LangParams>) {
const intl = await getIntl()
const creditCards = await serverClient().user.creditCards()
return (
<section className={styles.container}>
<article className={styles.content}>
<Subtitle type="two" color="black">
{intl.formatMessage({ id: "My payment cards" })}
</Subtitle>
<Body color="black">
{intl.formatMessage({
id: "Check out the credit cards saved to your profile. Pay with a saved card when signed in for a smoother web experience.",
})}
</Body>
</article>
<CreditCardList initialData={creditCards} />
<AddCreditCardButton />
</section>
)
}

View File

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

View File

@@ -1,22 +0,0 @@
.container {
display: grid;
gap: var(--Spacing-x3);
max-width: 510px;
}
.content {
display: grid;
gap: var(--Spacing-x1);
}
.card {
margin-top: 2rem;
display: grid;
grid-template-columns: repeat(2, 1fr);
grid-template-rows: repeat(3, auto);
gap: 0.5rem;
}
.subTitle {
grid-column: span 2;
}

View File

@@ -1,76 +0,0 @@
import { getMembershipCards } from "@/lib/trpc/memoizedRequests"
import { PlusCircleIcon } from "@/components/Icons"
import Link from "@/components/TempDesignSystem/Link"
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 type { LangParams, PageArgs } from "@/types/params"
export default async function MembershipCardSlot({}: PageArgs<LangParams>) {
const intl = await getIntl()
const membershipCards = await getMembershipCards()
return (
<section className={styles.container}>
<article className={styles.content}>
<Subtitle color="black">
{intl.formatMessage({ id: "My membership cards" })}
</Subtitle>
</article>
{membershipCards &&
membershipCards.length > 0 &&
membershipCards.map((card, idx) => (
<div className={styles.card} key={idx}>
<Subtitle className={styles.subTitle}>
{intl.formatMessage(
{ id: "Name: {cardMembershipType}" },
{
cardMembershipType: card.membershipType,
}
)}
</Subtitle>
<span>
{intl.formatMessage(
{ id: "Current Points: {points, number}" },
{ points: card.currentPoints }
)}
</span>
<span>
{intl.formatMessage(
{ id: "Member Since: {value}" },
{
value: card.memberSince,
}
)}
</span>
<span>
{intl.formatMessage(
{ id: "Number: {membershipNumber}" },
{
membershipNumber: card.membershipNumber,
}
)}
</span>
<span>
{intl.formatMessage(
{ id: "Expiration Date: {expirationDate}" },
{
expirationDate: card.expirationDate.split("T")[0],
}
)}
</span>
</div>
))}
<Link href="#" variant="icon">
<PlusCircleIcon color="burgundy" />
<Body color="burgundy" textTransform="underlined">
{intl.formatMessage({ id: "Add new card" })}
</Body>
</Link>
</section>
)
}

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,30 +0,0 @@
.profile {
display: flex;
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;
gap: var(--Spacing-x1);
grid-template-columns: auto auto 1fr;
justify-items: flex-end;
width: 100%;
}
@media screen and (min-width: 768px) {
.info {
grid-template-columns: repeat(3, auto);
}
.item {
justify-items: flex-start;
}
}

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;
}
}