Merged in feat/memberships-endpoint (pull request #247)
Feat(WEB-296): Add membership cards endpoint
This commit is contained in:
@@ -8,3 +8,15 @@
|
|||||||
display: grid;
|
display: grid;
|
||||||
gap: var(--Spacing-x1);
|
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;
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
import { serverClient } from "@/lib/trpc/server"
|
||||||
|
|
||||||
import { PlusCircleIcon } from "@/components/Icons"
|
import { PlusCircleIcon } from "@/components/Icons"
|
||||||
import Link from "@/components/TempDesignSystem/Link"
|
import Link from "@/components/TempDesignSystem/Link"
|
||||||
import Body from "@/components/TempDesignSystem/Text/Body"
|
import Body from "@/components/TempDesignSystem/Text/Body"
|
||||||
@@ -8,6 +10,8 @@ import styles from "./page.module.css"
|
|||||||
|
|
||||||
export default async function CreditCardSlot() {
|
export default async function CreditCardSlot() {
|
||||||
const { formatMessage } = await getIntl()
|
const { formatMessage } = await getIntl()
|
||||||
|
const creditCards = await serverClient().user.creditCards()
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<section className={styles.container}>
|
<section className={styles.container}>
|
||||||
<article className={styles.content}>
|
<article className={styles.content}>
|
||||||
@@ -20,6 +24,21 @@ export default async function CreditCardSlot() {
|
|||||||
})}
|
})}
|
||||||
</Body>
|
</Body>
|
||||||
</article>
|
</article>
|
||||||
|
{creditCards &&
|
||||||
|
creditCards.length > 0 &&
|
||||||
|
creditCards.map((card, idx) => (
|
||||||
|
<div className={styles.card} key={idx}>
|
||||||
|
<Subtitle className={styles.subTitle}>
|
||||||
|
Name: {card.attribute.cardName}
|
||||||
|
</Subtitle>
|
||||||
|
<span> Type: {card.attribute.cardType} </span>
|
||||||
|
<span> Alias: {card.attribute.alias}</span>
|
||||||
|
<span> Number: {card.attribute.truncatedNumber}</span>
|
||||||
|
<span>
|
||||||
|
Expiration Date: {card.attribute.expirationDate.split("T")[0]}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
<Link href="#" variant="icon">
|
<Link href="#" variant="icon">
|
||||||
<PlusCircleIcon color="burgundy" />
|
<PlusCircleIcon color="burgundy" />
|
||||||
<Body color="burgundy" textTransform="underlined">
|
<Body color="burgundy" textTransform="underlined">
|
||||||
|
|||||||
@@ -0,0 +1 @@
|
|||||||
|
export { default } from "../page"
|
||||||
@@ -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;
|
||||||
|
}
|
||||||
@@ -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 (
|
||||||
|
<section className={styles.container}>
|
||||||
|
<article className={styles.content}>
|
||||||
|
<Subtitle color="black">
|
||||||
|
{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}>
|
||||||
|
Name: {card.membershipType}
|
||||||
|
</Subtitle>
|
||||||
|
<span> Current Points: {card.currentPoints} </span>
|
||||||
|
<span> Member Since: {card.memberSince}</span>
|
||||||
|
<span> Number: {card.membershipNumber}</span>
|
||||||
|
<span>Expiration Date: {card.expirationDate.split("T")[0]}</span>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
<Link href="#" variant="icon">
|
||||||
|
<PlusCircleIcon color="burgundy" />
|
||||||
|
<Body color="burgundy" textTransform="underlined">
|
||||||
|
{formatMessage({ id: "Add new card" })}
|
||||||
|
</Body>
|
||||||
|
</Link>
|
||||||
|
</section>
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -6,6 +6,7 @@ export default function ProfileLayout({
|
|||||||
children,
|
children,
|
||||||
communication,
|
communication,
|
||||||
creditCards,
|
creditCards,
|
||||||
|
membershipCards,
|
||||||
profile,
|
profile,
|
||||||
}: React.PropsWithChildren<ProfileLayoutProps>) {
|
}: React.PropsWithChildren<ProfileLayoutProps>) {
|
||||||
return (
|
return (
|
||||||
@@ -14,6 +15,7 @@ export default function ProfileLayout({
|
|||||||
<section className="profile-layout">
|
<section className="profile-layout">
|
||||||
{profile}
|
{profile}
|
||||||
{creditCards}
|
{creditCards}
|
||||||
|
{membershipCards}
|
||||||
<Divider color="burgundy" opacity={8} />
|
<Divider color="burgundy" opacity={8} />
|
||||||
{communication}
|
{communication}
|
||||||
</section>
|
</section>
|
||||||
|
|||||||
14
package-lock.json
generated
14
package-lock.json
generated
@@ -6890,12 +6890,12 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/braces": {
|
"node_modules/braces": {
|
||||||
"version": "3.0.2",
|
"version": "3.0.3",
|
||||||
"resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz",
|
"resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz",
|
||||||
"integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==",
|
"integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==",
|
||||||
"devOptional": true,
|
"devOptional": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"fill-range": "^7.0.1"
|
"fill-range": "^7.1.1"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=8"
|
"node": ">=8"
|
||||||
@@ -9453,9 +9453,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/fill-range": {
|
"node_modules/fill-range": {
|
||||||
"version": "7.0.1",
|
"version": "7.1.1",
|
||||||
"resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz",
|
||||||
"integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==",
|
"integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==",
|
||||||
"devOptional": true,
|
"devOptional": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"to-regex-range": "^5.0.1"
|
"to-regex-range": "^5.0.1"
|
||||||
|
|||||||
@@ -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(),
|
||||||
|
})
|
||||||
|
)
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import { protectedProcedure, router } from "@/server/trpc"
|
|||||||
|
|
||||||
import { countries } from "@/components/TempDesignSystem/Form/Country/countries"
|
import { countries } from "@/components/TempDesignSystem/Form/Country/countries"
|
||||||
import * as maskValue from "@/utils/maskValue"
|
import * as maskValue from "@/utils/maskValue"
|
||||||
|
import { getMembershipCards } from "@/utils/user"
|
||||||
|
|
||||||
import {
|
import {
|
||||||
friendTransactionsInput,
|
friendTransactionsInput,
|
||||||
@@ -14,6 +15,7 @@ import {
|
|||||||
import {
|
import {
|
||||||
getCreditCardsSchema,
|
getCreditCardsSchema,
|
||||||
getFriendTransactionsSchema,
|
getFriendTransactionsSchema,
|
||||||
|
getMembershipCardsSchema,
|
||||||
getStaysSchema,
|
getStaysSchema,
|
||||||
getUserSchema,
|
getUserSchema,
|
||||||
} from "./output"
|
} from "./output"
|
||||||
@@ -348,4 +350,45 @@ export const userQueryRouter = router({
|
|||||||
|
|
||||||
return verifiedData.data.data
|
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
|
||||||
|
}),
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
export type ProfileLayoutProps = {
|
export type ProfileLayoutProps = {
|
||||||
communication: React.ReactNode
|
communication: React.ReactNode
|
||||||
creditCards: React.ReactNode
|
creditCards: React.ReactNode
|
||||||
|
membershipCards: React.ReactNode
|
||||||
profile: React.ReactNode
|
profile: React.ReactNode
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,12 +1,33 @@
|
|||||||
|
import { z } from "zod"
|
||||||
|
|
||||||
|
import { getMembershipCardsSchema } from "@/server/routers/user/output"
|
||||||
|
|
||||||
import { User } from "@/types/user"
|
import { User } from "@/types/user"
|
||||||
|
|
||||||
|
enum scandicMemberships {
|
||||||
|
guestpr = "guestpr",
|
||||||
|
scandicfriends = "scandicfriend's",
|
||||||
|
}
|
||||||
|
|
||||||
export function getMembership(memberships: User["memberships"]) {
|
export function getMembership(memberships: User["memberships"]) {
|
||||||
return memberships?.find(
|
return memberships?.find(
|
||||||
(membership) =>
|
(membership) =>
|
||||||
membership.membershipType.toLowerCase() === "guestpr" || "scandicfriend's"
|
membership.membershipType.toLowerCase() === scandicMemberships.guestpr
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function getMembershipCards(
|
||||||
|
memberships: z.infer<typeof getMembershipCardsSchema>
|
||||||
|
) {
|
||||||
|
return memberships.filter(function (membership) {
|
||||||
|
return (
|
||||||
|
membership.membershipType.toLowerCase() !== scandicMemberships.guestpr &&
|
||||||
|
membership.membershipType.toLowerCase() !==
|
||||||
|
scandicMemberships.scandicfriends
|
||||||
|
)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
export function getInitials(
|
export function getInitials(
|
||||||
firstName: User["firstName"],
|
firstName: User["firstName"],
|
||||||
lastName: User["lastName"]
|
lastName: User["lastName"]
|
||||||
|
|||||||
Reference in New Issue
Block a user