refactor(subscription): migrate payment components to tailwind
This commit is contained in:
@@ -2,6 +2,8 @@ import { renderToStaticMarkup } from "react-dom/server";
|
|||||||
import { describe, expect, it } from "vitest";
|
import { describe, expect, it } from "vitest";
|
||||||
|
|
||||||
import { SubscriptionCtaButton } from "../subscription-cta-button";
|
import { SubscriptionCtaButton } from "../subscription-cta-button";
|
||||||
|
import { SubscriptionPaymentMethod } from "../subscription-payment-method";
|
||||||
|
import { SubscriptionPaymentSuccessDialog } from "../subscription-payment-success-dialog";
|
||||||
|
|
||||||
describe("subscription Tailwind components", () => {
|
describe("subscription Tailwind components", () => {
|
||||||
it("renders SubscriptionCtaButton with loading state", () => {
|
it("renders SubscriptionCtaButton with loading state", () => {
|
||||||
@@ -15,4 +17,55 @@ describe("subscription Tailwind components", () => {
|
|||||||
expect(html).toContain("disabled");
|
expect(html).toContain("disabled");
|
||||||
expect(html).toContain("Pay now");
|
expect(html).toContain("Pay now");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("renders SubscriptionPaymentSuccessDialog with topup content", () => {
|
||||||
|
expect(
|
||||||
|
renderToStaticMarkup(
|
||||||
|
<SubscriptionPaymentSuccessDialog
|
||||||
|
open={false}
|
||||||
|
subscriptionType="vip"
|
||||||
|
onClose={() => undefined}
|
||||||
|
/>,
|
||||||
|
),
|
||||||
|
).toBe("");
|
||||||
|
|
||||||
|
const html = renderToStaticMarkup(
|
||||||
|
<SubscriptionPaymentSuccessDialog
|
||||||
|
open
|
||||||
|
subscriptionType="topup"
|
||||||
|
onClose={() => undefined}
|
||||||
|
/>,
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(html).toContain('role="alertdialog"');
|
||||||
|
expect(html).toContain("z-80");
|
||||||
|
expect(html).toContain("Payment successful");
|
||||||
|
expect(html).toContain("Your coins have been added");
|
||||||
|
expect(html).toContain("bg-[linear-gradient(135deg,#ff67e0_0%,#f657a0_100%)]");
|
||||||
|
expect(html).toContain("OK");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("renders SubscriptionPaymentMethod with default ordering and selection", () => {
|
||||||
|
const html = renderToStaticMarkup(
|
||||||
|
<SubscriptionPaymentMethod
|
||||||
|
value="ezpay"
|
||||||
|
defaultChannel="ezpay"
|
||||||
|
caption="GCash by default"
|
||||||
|
onChange={() => undefined}
|
||||||
|
/>,
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(html).toContain('aria-labelledby="payment-method-title"');
|
||||||
|
expect(html).toContain("Payment Method");
|
||||||
|
expect(html).toContain("GCash by default");
|
||||||
|
expect(html.indexOf('aria-label="GCash"')).toBeLessThan(
|
||||||
|
html.indexOf('aria-label="Stripe"'),
|
||||||
|
);
|
||||||
|
expect(html).toContain('aria-pressed="true"');
|
||||||
|
expect(html).toContain("border-[#d92f7f]");
|
||||||
|
expect(html).toContain("bg-[#fff4f9]");
|
||||||
|
expect(html).toContain("0_0_0_3px_rgba(217,47,127,0.18)");
|
||||||
|
expect(html).toContain("-translate-y-0.5");
|
||||||
|
expect(html).toContain("/images/subscription/gcash-logo.svg");
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,87 +0,0 @@
|
|||||||
.root {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
gap: clamp(8px, 1.852vw, 10px);
|
|
||||||
}
|
|
||||||
|
|
||||||
.header {
|
|
||||||
display: flex;
|
|
||||||
align-items: baseline;
|
|
||||||
justify-content: space-between;
|
|
||||||
gap: var(--spacing-sm);
|
|
||||||
padding: 0 var(--spacing-xs);
|
|
||||||
}
|
|
||||||
|
|
||||||
.title {
|
|
||||||
margin: 0;
|
|
||||||
font-family: var(--font-athelas), var(--font-system);
|
|
||||||
font-size: var(--responsive-card-title, var(--font-size-lg));
|
|
||||||
font-weight: 700;
|
|
||||||
color: var(--color-auth-text-primary);
|
|
||||||
line-height: 1.2;
|
|
||||||
}
|
|
||||||
|
|
||||||
.caption {
|
|
||||||
color: var(--color-text-secondary);
|
|
||||||
font-size: var(--responsive-caption, var(--font-size-sm));
|
|
||||||
line-height: 1.2;
|
|
||||||
white-space: nowrap;
|
|
||||||
}
|
|
||||||
|
|
||||||
.options {
|
|
||||||
display: grid;
|
|
||||||
grid-template-columns: repeat(2, minmax(0, 1fr));
|
|
||||||
gap: var(--spacing-sm);
|
|
||||||
}
|
|
||||||
|
|
||||||
.option {
|
|
||||||
display: flex;
|
|
||||||
min-height: clamp(54px, 11.481vw, 62px);
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
padding: clamp(9px, 1.852vw, 10px) clamp(12px, 2.593vw, 14px);
|
|
||||||
border: 1px solid rgba(246, 87, 160, 0.18);
|
|
||||||
border-radius: var(--responsive-card-radius-sm, 18px);
|
|
||||||
background: #ffffff;
|
|
||||||
box-shadow: 0 5px 7px 0 rgba(247, 89, 168, 0.08);
|
|
||||||
color: #3c3b3b;
|
|
||||||
cursor: pointer;
|
|
||||||
font: inherit;
|
|
||||||
text-align: center;
|
|
||||||
transition: border-color 0.15s ease, box-shadow 0.15s ease,
|
|
||||||
transform 0.05s ease;
|
|
||||||
}
|
|
||||||
|
|
||||||
.option:focus-visible {
|
|
||||||
outline: 2px solid var(--color-accent);
|
|
||||||
outline-offset: 2px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.option:not(:disabled):active {
|
|
||||||
transform: scale(0.98);
|
|
||||||
}
|
|
||||||
|
|
||||||
.option:disabled {
|
|
||||||
cursor: not-allowed;
|
|
||||||
opacity: 0.72;
|
|
||||||
}
|
|
||||||
|
|
||||||
.selected {
|
|
||||||
border-color: #f657a0;
|
|
||||||
border-width: 2px;
|
|
||||||
box-shadow: 0 6px 12px 0 rgba(247, 89, 168, 0.14);
|
|
||||||
}
|
|
||||||
|
|
||||||
.optionTitle {
|
|
||||||
color: #1e1e1e;
|
|
||||||
font-size: var(--responsive-body, 16px);
|
|
||||||
font-weight: 700;
|
|
||||||
line-height: 1.1;
|
|
||||||
}
|
|
||||||
|
|
||||||
.optionLogo {
|
|
||||||
display: block;
|
|
||||||
width: min(100%, 92px);
|
|
||||||
max-height: 34px;
|
|
||||||
object-fit: contain;
|
|
||||||
}
|
|
||||||
@@ -4,8 +4,6 @@ import Image from "next/image";
|
|||||||
|
|
||||||
import type { PayChannel } from "@/data/dto/payment";
|
import type { PayChannel } from "@/data/dto/payment";
|
||||||
|
|
||||||
import styles from "./subscription-payment-method.module.css";
|
|
||||||
|
|
||||||
const PAYMENT_METHODS: Array<{
|
const PAYMENT_METHODS: Array<{
|
||||||
channel: PayChannel;
|
channel: PayChannel;
|
||||||
title: string;
|
title: string;
|
||||||
@@ -45,19 +43,30 @@ export function SubscriptionPaymentMethod({
|
|||||||
: PAYMENT_METHODS;
|
: PAYMENT_METHODS;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<section className={styles.root} aria-labelledby="payment-method-title">
|
<section
|
||||||
<div className={styles.header}>
|
className="flex flex-col gap-[clamp(8px,1.852vw,10px)]"
|
||||||
<h2 id="payment-method-title" className={styles.title}>
|
aria-labelledby="payment-method-title"
|
||||||
|
>
|
||||||
|
<div className="flex items-baseline justify-between gap-sm px-xs">
|
||||||
|
<h2
|
||||||
|
id="payment-method-title"
|
||||||
|
className="m-0 text-(length:--responsive-card-title,var(--font-size-lg)) font-bold leading-[1.2] text-auth-text-primary"
|
||||||
|
style={{ fontFamily: "var(--font-athelas), var(--font-system)" }}
|
||||||
|
>
|
||||||
Payment Method
|
Payment Method
|
||||||
</h2>
|
</h2>
|
||||||
<span className={styles.caption}>{caption}</span>
|
<span className="whitespace-nowrap text-(length:--responsive-caption,var(--font-size-sm)) leading-[1.2] text-text-secondary">
|
||||||
|
{caption}
|
||||||
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<div className={styles.options}>
|
<div className="grid grid-cols-2 gap-sm">
|
||||||
{paymentMethods.map((method) => {
|
{paymentMethods.map((method) => {
|
||||||
const selected = method.channel === value;
|
const selected = method.channel === value;
|
||||||
const classes = [
|
const classes = [
|
||||||
styles.option,
|
"flex min-h-[clamp(54px,11.481vw,62px)] cursor-pointer items-center justify-center rounded-(--responsive-card-radius-sm,18px) border border-[rgba(246,87,160,0.18)] bg-white px-[clamp(12px,2.593vw,14px)] py-[clamp(9px,1.852vw,10px)] text-center font-[inherit] text-[#3c3b3b] shadow-[0_5px_7px_0_rgba(247,89,168,0.08)] transition-[border-color,box-shadow,transform] duration-150 focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-(--color-accent) active:enabled:scale-98 disabled:cursor-not-allowed disabled:opacity-72",
|
||||||
selected ? styles.selected : "",
|
selected
|
||||||
|
? "border-2 border-[#d92f7f] bg-[#fff4f9] shadow-[0_8px_18px_rgba(217,47,127,0.24),0_0_0_3px_rgba(217,47,127,0.18)] -translate-y-0.5"
|
||||||
|
: "",
|
||||||
]
|
]
|
||||||
.filter(Boolean)
|
.filter(Boolean)
|
||||||
.join(" ");
|
.join(" ");
|
||||||
@@ -79,10 +88,12 @@ export function SubscriptionPaymentMethod({
|
|||||||
aria-hidden="true"
|
aria-hidden="true"
|
||||||
width={122}
|
width={122}
|
||||||
height={29}
|
height={29}
|
||||||
className={styles.optionLogo}
|
className="block max-h-[34px] w-[min(100%,92px)] object-contain"
|
||||||
/>
|
/>
|
||||||
) : (
|
) : (
|
||||||
<span className={styles.optionTitle}>{method.title}</span>
|
<span className="text-(length:--responsive-body,16px) font-bold leading-[1.1] text-[#1e1e1e]">
|
||||||
|
{method.title}
|
||||||
|
</span>
|
||||||
)}
|
)}
|
||||||
</button>
|
</button>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -1,79 +0,0 @@
|
|||||||
.overlay {
|
|
||||||
position: fixed;
|
|
||||||
inset: 0;
|
|
||||||
z-index: 80;
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
padding:
|
|
||||||
calc(var(--dialog-safe-margin, 20px) + var(--app-safe-top, 0px))
|
|
||||||
calc(var(--dialog-safe-margin, 20px) + var(--app-safe-right, 0px))
|
|
||||||
calc(var(--dialog-safe-margin, 20px) + var(--app-safe-bottom, 0px))
|
|
||||||
calc(var(--dialog-safe-margin, 20px) + var(--app-safe-left, 0px));
|
|
||||||
background: rgba(0, 0, 0, 0.45);
|
|
||||||
}
|
|
||||||
|
|
||||||
.dialog {
|
|
||||||
width: 100%;
|
|
||||||
max-width: var(--dialog-max-width, 380px);
|
|
||||||
border-radius: var(--responsive-card-radius, 32px);
|
|
||||||
background: var(--color-page-background, #ffffff);
|
|
||||||
box-shadow: 0 18px 40px rgba(0, 0, 0, 0.16);
|
|
||||||
padding:
|
|
||||||
var(--responsive-card-padding-lg, 28px)
|
|
||||||
var(--responsive-card-padding, 20px)
|
|
||||||
var(--responsive-card-padding, 20px);
|
|
||||||
text-align: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.badge {
|
|
||||||
display: inline-flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
width: clamp(52px, 10.741vw, 58px);
|
|
||||||
height: clamp(52px, 10.741vw, 58px);
|
|
||||||
margin-bottom: var(--page-section-gap, 16px);
|
|
||||||
border-radius: 999px;
|
|
||||||
background: linear-gradient(135deg, #ff67e0 0%, #f657a0 100%);
|
|
||||||
color: #ffffff;
|
|
||||||
font-size: clamp(28px, 5.926vw, 32px);
|
|
||||||
font-weight: 700;
|
|
||||||
line-height: 1;
|
|
||||||
box-shadow: 0 8px 18px rgba(247, 89, 168, 0.28);
|
|
||||||
}
|
|
||||||
|
|
||||||
.title {
|
|
||||||
margin: 0 0 clamp(7px, 1.481vw, 8px);
|
|
||||||
color: var(--color-text-foreground, #171717);
|
|
||||||
font-size: var(--responsive-page-title, 22px);
|
|
||||||
font-weight: 700;
|
|
||||||
line-height: 1.2;
|
|
||||||
}
|
|
||||||
|
|
||||||
.content {
|
|
||||||
margin: 0;
|
|
||||||
color: #393939;
|
|
||||||
font-size: var(--responsive-body, 14px);
|
|
||||||
line-height: 1.5;
|
|
||||||
}
|
|
||||||
|
|
||||||
.button {
|
|
||||||
width: 100%;
|
|
||||||
min-height: var(--responsive-control-height, 48px);
|
|
||||||
margin-top: var(--page-section-gap-lg, 22px);
|
|
||||||
border: 0;
|
|
||||||
border-radius: 999px;
|
|
||||||
background:
|
|
||||||
linear-gradient(269deg, #ff67e0 0%, rgba(254, 104, 224, 0.5) 20%, rgba(252, 105, 223, 0.79) 66%, #f96ade 100%),
|
|
||||||
linear-gradient(#f657a0, #f657a0);
|
|
||||||
background-blend-mode: normal, normal;
|
|
||||||
box-shadow: 0 5px 7px rgba(247, 89, 168, 0.31);
|
|
||||||
color: #ffffff;
|
|
||||||
cursor: pointer;
|
|
||||||
font-size: var(--responsive-body, 16px);
|
|
||||||
font-weight: 700;
|
|
||||||
}
|
|
||||||
|
|
||||||
.button:active {
|
|
||||||
transform: translateY(1px);
|
|
||||||
}
|
|
||||||
@@ -1,7 +1,5 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import styles from "./subscription-payment-success-dialog.module.css";
|
|
||||||
|
|
||||||
export interface SubscriptionPaymentSuccessDialogProps {
|
export interface SubscriptionPaymentSuccessDialogProps {
|
||||||
open: boolean;
|
open: boolean;
|
||||||
subscriptionType: "vip" | "topup";
|
subscriptionType: "vip" | "topup";
|
||||||
@@ -22,20 +20,32 @@ export function SubscriptionPaymentSuccessDialog({
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
className={styles.overlay}
|
className="fixed inset-0 z-80 flex items-center justify-center bg-[rgba(0,0,0,0.45)] pb-[calc(var(--dialog-safe-margin,20px)+var(--app-safe-bottom,0px))] pl-[calc(var(--dialog-safe-margin,20px)+var(--app-safe-left,0px))] pr-[calc(var(--dialog-safe-margin,20px)+var(--app-safe-right,0px))] pt-[calc(var(--dialog-safe-margin,20px)+var(--app-safe-top,0px))]"
|
||||||
role="alertdialog"
|
role="alertdialog"
|
||||||
aria-modal="true"
|
aria-modal="true"
|
||||||
aria-labelledby="subscription-payment-success-title"
|
aria-labelledby="subscription-payment-success-title"
|
||||||
>
|
>
|
||||||
<div className={styles.dialog}>
|
<div className="w-full max-w-(--dialog-max-width,380px) rounded-(--responsive-card-radius,32px) bg-(--color-page-background,#ffffff) px-(--responsive-card-padding,20px) pb-(--responsive-card-padding,20px) pt-(--responsive-card-padding-lg,28px) text-center shadow-[0_18px_40px_rgba(0,0,0,0.16)]">
|
||||||
<div className={styles.badge} aria-hidden="true">
|
<div
|
||||||
|
className="mb-(--page-section-gap,16px) inline-flex size-[clamp(52px,10.741vw,58px)] items-center justify-center rounded-full bg-[linear-gradient(135deg,#ff67e0_0%,#f657a0_100%)] text-[clamp(28px,5.926vw,32px)] font-bold leading-none text-white shadow-[0_8px_18px_rgba(247,89,168,0.28)]"
|
||||||
|
aria-hidden="true"
|
||||||
|
>
|
||||||
✓
|
✓
|
||||||
</div>
|
</div>
|
||||||
<h2 id="subscription-payment-success-title" className={styles.title}>
|
<h2
|
||||||
|
id="subscription-payment-success-title"
|
||||||
|
className="m-0 mb-[clamp(7px,1.481vw,8px)] text-(length:--responsive-page-title,22px) font-bold leading-[1.2] text-(--color-text-foreground,#171717)"
|
||||||
|
>
|
||||||
Payment successful
|
Payment successful
|
||||||
</h2>
|
</h2>
|
||||||
<p className={styles.content}>{content}</p>
|
<p className="m-0 text-(length:--responsive-body,14px) leading-[1.5] text-[#393939]">
|
||||||
<button type="button" className={styles.button} onClick={onClose}>
|
{content}
|
||||||
|
</p>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
className="mt-(--page-section-gap-lg,22px) min-h-(--responsive-control-height,48px) w-full cursor-pointer rounded-full border-0 bg-[linear-gradient(269deg,#ff67e0_0%,rgba(254,104,224,0.5)_20%,rgba(252,105,223,0.79)_66%,#f96ade_100%),linear-gradient(#f657a0,#f657a0)] text-(length:--responsive-body,16px) font-bold text-white shadow-[0_5px_7px_rgba(247,89,168,0.31)] active:translate-y-px"
|
||||||
|
onClick={onClose}
|
||||||
|
>
|
||||||
OK
|
OK
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
Reference in New Issue
Block a user