diff --git a/app/[lang]/(live)/(protected)/my-pages/profile/@creditCards/page.module.css b/app/[lang]/(live)/(protected)/my-pages/profile/@creditCards/page.module.css index 76b9b1c13..3b9f6e70a 100644 --- a/app/[lang]/(live)/(protected)/my-pages/profile/@creditCards/page.module.css +++ b/app/[lang]/(live)/(protected)/my-pages/profile/@creditCards/page.module.css @@ -8,3 +8,15 @@ 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; +} diff --git a/app/[lang]/(live)/(protected)/my-pages/profile/@creditCards/page.tsx b/app/[lang]/(live)/(protected)/my-pages/profile/@creditCards/page.tsx index 9c650c8bc..c36fefabf 100644 --- a/app/[lang]/(live)/(protected)/my-pages/profile/@creditCards/page.tsx +++ b/app/[lang]/(live)/(protected)/my-pages/profile/@creditCards/page.tsx @@ -1,3 +1,5 @@ +import { serverClient } from "@/lib/trpc/server" + import { PlusCircleIcon } from "@/components/Icons" import Link from "@/components/TempDesignSystem/Link" import Body from "@/components/TempDesignSystem/Text/Body" @@ -8,6 +10,8 @@ import styles from "./page.module.css" export default async function CreditCardSlot() { const { formatMessage } = await getIntl() + const creditCards = await serverClient().user.creditCards() + return (
@@ -20,6 +24,21 @@ export default async function CreditCardSlot() { })}
+ {creditCards && + creditCards.length > 0 && + creditCards.map((card, idx) => ( +
+ + Name: {card.attribute.cardName} + + Type: {card.attribute.cardType} + Alias: {card.attribute.alias} + Number: {card.attribute.truncatedNumber} + + Expiration Date: {card.attribute.expirationDate.split("T")[0]} + +
+ ))} diff --git a/app/[lang]/(live)/(protected)/my-pages/profile/@membershipCards/[...catchAll]/page.tsx b/app/[lang]/(live)/(protected)/my-pages/profile/@membershipCards/[...catchAll]/page.tsx new file mode 100644 index 000000000..03a82e5f5 --- /dev/null +++ b/app/[lang]/(live)/(protected)/my-pages/profile/@membershipCards/[...catchAll]/page.tsx @@ -0,0 +1 @@ +export { default } from "../page" diff --git a/app/[lang]/(live)/(protected)/my-pages/profile/@membershipCards/page.module.css b/app/[lang]/(live)/(protected)/my-pages/profile/@membershipCards/page.module.css new file mode 100644 index 000000000..3b9f6e70a --- /dev/null +++ b/app/[lang]/(live)/(protected)/my-pages/profile/@membershipCards/page.module.css @@ -0,0 +1,22 @@ +.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; +} diff --git a/app/[lang]/(live)/(protected)/my-pages/profile/@membershipCards/page.tsx b/app/[lang]/(live)/(protected)/my-pages/profile/@membershipCards/page.tsx new file mode 100644 index 000000000..6da3bb6df --- /dev/null +++ b/app/[lang]/(live)/(protected)/my-pages/profile/@membershipCards/page.tsx @@ -0,0 +1,43 @@ +import { serverClient } from "@/lib/trpc/server" + +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" + +export default async function MembershipCardSlot() { + const { formatMessage } = await getIntl() + const membershipCards = await serverClient().user.membershipCards() + + return ( +
+
+ + {formatMessage({ id: "My membership cards" })} + +
+ {membershipCards && + membershipCards.length > 0 && + membershipCards.map((card, idx) => ( +
+ + Name: {card.membershipType} + + Current Points: {card.currentPoints} + Member Since: {card.memberSince} + Number: {card.membershipNumber} + Expiration Date: {card.expirationDate.split("T")[0]} +
+ ))} + + + + {formatMessage({ id: "Add new card" })} + + +
+ ) +} diff --git a/app/[lang]/(live)/(protected)/my-pages/profile/layout.tsx b/app/[lang]/(live)/(protected)/my-pages/profile/layout.tsx index a5983461d..d067f0277 100644 --- a/app/[lang]/(live)/(protected)/my-pages/profile/layout.tsx +++ b/app/[lang]/(live)/(protected)/my-pages/profile/layout.tsx @@ -6,6 +6,7 @@ export default function ProfileLayout({ children, communication, creditCards, + membershipCards, profile, }: React.PropsWithChildren) { return ( @@ -14,6 +15,7 @@ export default function ProfileLayout({
{profile} {creditCards} + {membershipCards} {communication}
diff --git a/package-lock.json b/package-lock.json index f6b074937..250fa8c2c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -6890,12 +6890,12 @@ } }, "node_modules/braces": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", - "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", + "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", "devOptional": true, "dependencies": { - "fill-range": "^7.0.1" + "fill-range": "^7.1.1" }, "engines": { "node": ">=8" @@ -9453,9 +9453,9 @@ } }, "node_modules/fill-range": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", - "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", + "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", "devOptional": true, "dependencies": { "to-regex-range": "^5.0.1" diff --git a/server/routers/user/output.ts b/server/routers/user/output.ts index 2e4b8db10..8a7f3b06b 100644 --- a/server/routers/user/output.ts +++ b/server/routers/user/output.ts @@ -181,3 +181,13 @@ export const getCreditCardsSchema = z.object({ }) ), }) + +export const getMembershipCardsSchema = z.array( + z.object({ + currentPoints: z.number(), + expirationDate: z.string(), + membershipNumber: z.string(), + memberSince: z.string(), + membershipType: z.string(), + }) +) diff --git a/server/routers/user/query.ts b/server/routers/user/query.ts index 732ec18a1..0a989b56d 100644 --- a/server/routers/user/query.ts +++ b/server/routers/user/query.ts @@ -5,6 +5,7 @@ import { protectedProcedure, router } from "@/server/trpc" import { countries } from "@/components/TempDesignSystem/Form/Country/countries" import * as maskValue from "@/utils/maskValue" +import { getMembershipCards } from "@/utils/user" import { friendTransactionsInput, @@ -14,6 +15,7 @@ import { import { getCreditCardsSchema, getFriendTransactionsSchema, + getMembershipCardsSchema, getStaysSchema, getUserSchema, } from "./output" @@ -348,4 +350,45 @@ export const userQueryRouter = router({ return verifiedData.data.data }), + + membershipCards: protectedProcedure.query(async function ({ ctx }) { + const apiResponse = await api.get(api.endpoints.v1.profile, { + cache: "no-store", + headers: { + Authorization: `Bearer ${ctx.session.token.access_token}`, + }, + }) + + if (!apiResponse.ok) { + // switch (apiResponse.status) { + // case 400: + // throw badRequestError() + // case 401: + // throw unauthorizedError() + // case 403: + // throw forbiddenError() + // default: + // throw internalServerError() + // } + console.info(`API Response Failed - Getting Membership Cards`) + console.info(`User: (${JSON.stringify(ctx.session.user)})`) + console.error(apiResponse) + } + + const apiJson = await apiResponse.json() + + const verifiedData = getMembershipCardsSchema.safeParse( + apiJson.data.attributes.memberships + ) + + if (!verifiedData.success) { + console.info(`Failed to validate Memberships Cards Data`) + console.info(`User: (${JSON.stringify(ctx.session.user)})`) + console.error(verifiedData.error) + return null + } + const cards = getMembershipCards(verifiedData.data) + + return cards + }), }) diff --git a/types/components/myPages/myProfile/layout.ts b/types/components/myPages/myProfile/layout.ts index be50c920a..3042210f1 100644 --- a/types/components/myPages/myProfile/layout.ts +++ b/types/components/myPages/myProfile/layout.ts @@ -1,5 +1,6 @@ export type ProfileLayoutProps = { communication: React.ReactNode creditCards: React.ReactNode + membershipCards: React.ReactNode profile: React.ReactNode } diff --git a/utils/user.ts b/utils/user.ts index 9260118b0..052826eaf 100644 --- a/utils/user.ts +++ b/utils/user.ts @@ -1,12 +1,33 @@ +import { z } from "zod" + +import { getMembershipCardsSchema } from "@/server/routers/user/output" + import { User } from "@/types/user" +enum scandicMemberships { + guestpr = "guestpr", + scandicfriends = "scandicfriend's", +} + export function getMembership(memberships: User["memberships"]) { return memberships?.find( (membership) => - membership.membershipType.toLowerCase() === "guestpr" || "scandicfriend's" + membership.membershipType.toLowerCase() === scandicMemberships.guestpr ) } +export function getMembershipCards( + memberships: z.infer +) { + return memberships.filter(function (membership) { + return ( + membership.membershipType.toLowerCase() !== scandicMemberships.guestpr && + membership.membershipType.toLowerCase() !== + scandicMemberships.scandicfriends + ) + }) +} + export function getInitials( firstName: User["firstName"], lastName: User["lastName"]