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;
|
||||
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 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 (
|
||||
<section className={styles.container}>
|
||||
<article className={styles.content}>
|
||||
@@ -20,6 +24,21 @@ export default async function CreditCardSlot() {
|
||||
})}
|
||||
</Body>
|
||||
</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">
|
||||
<PlusCircleIcon color="burgundy" />
|
||||
<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,
|
||||
communication,
|
||||
creditCards,
|
||||
membershipCards,
|
||||
profile,
|
||||
}: React.PropsWithChildren<ProfileLayoutProps>) {
|
||||
return (
|
||||
@@ -14,6 +15,7 @@ export default function ProfileLayout({
|
||||
<section className="profile-layout">
|
||||
{profile}
|
||||
{creditCards}
|
||||
{membershipCards}
|
||||
<Divider color="burgundy" opacity={8} />
|
||||
{communication}
|
||||
</section>
|
||||
|
||||
14
package-lock.json
generated
14
package-lock.json
generated
@@ -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"
|
||||
|
||||
@@ -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 * 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
|
||||
}),
|
||||
})
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
export type ProfileLayoutProps = {
|
||||
communication: React.ReactNode
|
||||
creditCards: React.ReactNode
|
||||
membershipCards: 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"
|
||||
|
||||
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<typeof getMembershipCardsSchema>
|
||||
) {
|
||||
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"]
|
||||
|
||||
Reference in New Issue
Block a user