feat: list credit card according to design

This commit is contained in:
Christel Westerberg
2024-07-16 15:50:26 +02:00
parent d8f7e4157a
commit 09b1d322a7
12 changed files with 195 additions and 36 deletions

View File

@@ -1,6 +1,6 @@
.container {
display: grid;
gap: var(--Spacing-x3);
gap: var(--Spacing-x2);
max-width: 510px;
}
@@ -9,14 +9,24 @@
gap: var(--Spacing-x1);
}
.card {
margin-top: 2rem;
.cardContainer {
display: grid;
grid-template-columns: repeat(2, 1fr);
grid-template-rows: repeat(3, auto);
gap: 0.5rem;
gap: var(--Spacing-x1);
}
.subTitle {
grid-column: span 2;
.card {
display: grid;
align-items: center;
column-gap: var(--Spacing-x1);
grid-template-columns: auto auto auto 1fr;
justify-items: flex-end;
padding: var(--Spacing-x1) var(--Spacing-x-one-and-half,);
border-radius: var(--Corner-radius-Small);
background-color: var(--Base-Background-Primary-Normal);
}
@media screen and (min-width: 768px) {
.container {
gap: var(--Spacing-x2);
}
}

View File

@@ -1,8 +1,10 @@
import { serverClient } from "@/lib/trpc/server"
import { PlusCircleIcon } from "@/components/Icons"
import Link from "@/components/TempDesignSystem/Link"
import { CreditCard, Delete } from "@/components/Icons"
import AddCreditCardButton from "@/components/Profile/AddCreditCardButton"
import Button from "@/components/TempDesignSystem/Button"
import Body from "@/components/TempDesignSystem/Text/Body"
import Caption from "@/components/TempDesignSystem/Text/Caption"
import Subtitle from "@/components/TempDesignSystem/Text/Subtitle"
import { getIntl } from "@/i18n"
@@ -24,27 +26,38 @@ 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">
{formatMessage({ id: "Add new card" })}
</Body>
</Link>
{creditCards?.length ? (
<div className={styles.cardContainer}>
{creditCards.map((card, idx) => (
<CreditCardRow
key={idx}
cardType={card.attribute.cardType}
truncatedNumber={card.attribute.truncatedNumber}
/>
))}
</div>
) : null}
<AddCreditCardButton />
</section>
)
}
function CreditCardRow({
truncatedNumber,
cardType,
}: {
truncatedNumber: string
cardType: string
}) {
const maskedCardNumber = `**** ${truncatedNumber.slice(12, 16)}`
return (
<div className={styles.card}>
<CreditCard color="black" />
<Body textTransform="bold">{cardType}</Body>
<Caption color="textMediumContrast">{maskedCardNumber}</Caption>
<Button variant="icon" theme="base" intent="text">
<Delete color="burgundy" />
</Button>
</div>
)
}

View File

@@ -7,7 +7,7 @@
.info {
display: grid;
gap: var(--Spacing-x-one-and-half) var(--Spacing-x7);
grid-template-columns: repeat(3, auto);
width: 100%;
justify-items: flex-start;
}
@@ -16,4 +16,15 @@
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

@@ -7,5 +7,11 @@
border-radius: var(--Corner-radius-xLarge);
display: grid;
gap: var(--Spacing-x4);
padding: var(--Spacing-x3) var(--Spacing-x3) var(--Spacing-x4);
padding: var(--Spacing-x2) var(--Spacing-x2) var(--Spacing-x4);
}
@media screen and (min-width: 768px) {
.profile-layout {
padding: var(--Spacing-x3) var(--Spacing-x3) var(--Spacing-x4);
}
}

View File

@@ -0,0 +1,40 @@
import * as variants from "./variants"
import type { IconProps } from "@/types/components/icon"
export default function CreditCardIcon({
className,
color,
...props
}: IconProps) {
const classNames = variants.iconVariants({ className, color })
return (
<svg
className={classNames}
width="24"
height="24"
viewBox="0 0 24 24"
fill="none"
xmlns="http://www.w3.org/2000/svg"
{...props}
>
<mask
id="mask0_7561_16317"
style={{ maskType: "alpha" }}
maskUnits="userSpaceOnUse"
x="0"
y="0"
width="24"
height="24"
>
<rect width="24" height="24" fill="#D9D9D9" />
</mask>
<svg mask="url(#mask0_7561_16317)">
<path
d="M21.75 6.1001V17.9001C21.75 18.4157 21.5664 18.8571 21.1992 19.2243C20.832 19.5915 20.3906 19.7751 19.875 19.7751H4.125C3.60937 19.7751 3.16796 19.5915 2.80077 19.2243C2.43359 18.8571 2.25 18.4157 2.25 17.9001V6.1001C2.25 5.58446 2.43359 5.14306 2.80077 4.77587C3.16796 4.40869 3.60937 4.2251 4.125 4.2251H19.875C20.3906 4.2251 20.832 4.40869 21.1992 4.77587C21.5664 5.14306 21.75 5.58446 21.75 6.1001ZM4.125 8.1251H19.875V6.1001H4.125V8.1251ZM4.125 11.9251V17.9001H19.875V11.9251H4.125Z"
fill="#26201E"
/>
</svg>
</svg>
)
}

View File

@@ -0,0 +1,36 @@
import { iconVariants } from "./variants"
import type { IconProps } from "@/types/components/icon"
export default function DeleteIcon({ className, color, ...props }: IconProps) {
const classNames = iconVariants({ className, color })
return (
<svg
className={classNames}
width="24"
height="24"
viewBox="0 0 24 24"
fill="none"
xmlns="http://www.w3.org/2000/svg"
{...props}
>
<mask
id="mask0_7561_16314"
style={{ maskType: "alpha" }}
maskUnits="userSpaceOnUse"
x="0"
y="0"
width="24"
height="24"
>
<rect width="24" height="24" fill="#D9D9D9" />
</mask>
<g mask="url(#mask0_7561_16314)">
<path
d="M7.07656 20.75C6.5599 20.75 6.11823 20.5667 5.75156 20.2C5.3849 19.8333 5.20156 19.3917 5.20156 18.875V6.075H5.16406C4.90573 6.075 4.6849 5.98333 4.50156 5.8C4.31823 5.61667 4.22656 5.39583 4.22656 5.1375C4.22656 4.87917 4.31823 4.65833 4.50156 4.475C4.6849 4.29167 4.90573 4.2 5.16406 4.2H9.05156V4.1875C9.05156 3.92917 9.14323 3.70833 9.32656 3.525C9.5099 3.34167 9.73073 3.25 9.98906 3.25H14.0391C14.2974 3.25 14.5182 3.34167 14.7016 3.525C14.8849 3.70833 14.9766 3.92917 14.9766 4.1875V4.2H18.8641C19.1224 4.2 19.3432 4.29167 19.5266 4.475C19.7099 4.65833 19.8016 4.87917 19.8016 5.1375C19.8016 5.39583 19.7099 5.61667 19.5266 5.8C19.3432 5.98333 19.1224 6.075 18.8641 6.075H18.8266V18.875C18.8266 19.3917 18.6432 19.8333 18.2766 20.2C17.9099 20.5667 17.4682 20.75 16.9516 20.75H7.07656ZM16.9516 6.075H7.07656V18.875H16.9516V6.075ZM10.0641 16.9125C10.3224 16.9125 10.5432 16.8208 10.7266 16.6375C10.9099 16.4542 11.0016 16.2333 11.0016 15.975V8.975C11.0016 8.71667 10.9099 8.49583 10.7266 8.3125C10.5432 8.12917 10.3224 8.0375 10.0641 8.0375C9.80573 8.0375 9.5849 8.12917 9.40156 8.3125C9.21823 8.49583 9.12656 8.71667 9.12656 8.975V15.975C9.12656 16.2333 9.21823 16.4542 9.40156 16.6375C9.5849 16.8208 9.80573 16.9125 10.0641 16.9125ZM13.9641 16.9125C14.2224 16.9125 14.4432 16.8208 14.6266 16.6375C14.8099 16.4542 14.9016 16.2333 14.9016 15.975V8.975C14.9016 8.71667 14.8099 8.49583 14.6266 8.3125C14.4432 8.12917 14.2224 8.0375 13.9641 8.0375C13.7057 8.0375 13.4849 8.12917 13.3016 8.3125C13.1182 8.49583 13.0266 8.71667 13.0266 8.975V15.975C13.0266 16.2333 13.1182 16.4542 13.3016 16.6375C13.4849 16.8208 13.7057 16.9125 13.9641 16.9125Z"
fill="#4D001B"
/>
</g>
</svg>
)
}

View File

@@ -13,6 +13,8 @@ export { default as ChevronRightIcon } from "./ChevronRight"
export { default as CloseIcon } from "./Close"
export { default as CoffeeIcon } from "./Coffee"
export { default as ConciergeIcon } from "./Concierge"
export { default as CreditCard } from "./CreditCard"
export { default as Delete } from "./Delete"
export { default as DoorOpenIcon } from "./DoorOpen"
export { default as ElectricBikeIcon } from "./ElectricBike"
export { default as EmailIcon } from "./Email"

View File

@@ -0,0 +1,31 @@
"use client"
import { useIntl } from "react-intl"
import { trpc } from "@/lib/trpc/client"
import { PlusCircleIcon } from "@/components/Icons"
import Button from "@/components/TempDesignSystem/Button"
export default function AddCreditCardButton() {
const { formatMessage } = useIntl()
async function handleAddCreditCard() {
// TODO: initiate add credit card flow and redirect user to planet:
// const { url } = trpc.user.creditCard.add.useMutation()
// router.redirect(url)
console.log("Credit card added!")
}
return (
<Button
variant="icon"
theme="base"
intent="text"
onClick={handleAddCreditCard}
wrapping
>
<PlusCircleIcon color="burgundy" />
{formatMessage({ id: "Add new card" })}
</Button>
)
}

View File

@@ -13,11 +13,16 @@
/* TODO: Waiting for variables for buttons from Design team */
font-family: var(--typography-Body-Bold-fontFamily);
font-size: var(--typography-Body-Bold-fontSize);
font-weight: var(--typography-Body-Bold-fontWeight);
font-weight: 500;
line-height: 24px;
letter-spacing: 0.6%;
}
.wrapping {
padding-left: 0 !important;
padding-right: 0 !important;
}
/* INTENT */
.primary,
a.primary {
@@ -69,7 +74,7 @@ a.default {
}
.medium {
gap: var(--Spacing-x-quarter);
gap: var(--Spacing-x-half);
height: 48px;
padding: var(--Spacing-x-one-and-half) var(--Spacing-x2);
}

View File

@@ -14,6 +14,7 @@ export default function Button({
intent,
size,
variant,
wrapping,
...props
}: ButtonProps) {
const Comp = asChild ? Slot : "button"
@@ -22,6 +23,7 @@ export default function Button({
intent,
size,
theme,
wrapping,
variant,
})
return <Comp className={classNames} disabled={disabled} {...props} />

View File

@@ -30,6 +30,9 @@ export const buttonVariants = cva(styles.btn, {
default: styles.default,
icon: styles.icon,
},
wrapping: {
true: styles.wrapping,
},
},
defaultVariants: {
intent: "primary",

View File

@@ -26,7 +26,7 @@ const config = {
defaultVariants: {
color: "burgundy",
textAlign: "left",
textTransform: "capitalize",
textTransform: "regular",
type: "one",
},
} as const