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 { SubscriptionCtaButton } from "../subscription-cta-button";
|
||||
import { SubscriptionPaymentMethod } from "../subscription-payment-method";
|
||||
import { SubscriptionPaymentSuccessDialog } from "../subscription-payment-success-dialog";
|
||||
|
||||
describe("subscription Tailwind components", () => {
|
||||
it("renders SubscriptionCtaButton with loading state", () => {
|
||||
@@ -15,4 +17,55 @@ describe("subscription Tailwind components", () => {
|
||||
expect(html).toContain("disabled");
|
||||
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 styles from "./subscription-payment-method.module.css";
|
||||
|
||||
const PAYMENT_METHODS: Array<{
|
||||
channel: PayChannel;
|
||||
title: string;
|
||||
@@ -45,19 +43,30 @@ export function SubscriptionPaymentMethod({
|
||||
: PAYMENT_METHODS;
|
||||
|
||||
return (
|
||||
<section className={styles.root} aria-labelledby="payment-method-title">
|
||||
<div className={styles.header}>
|
||||
<h2 id="payment-method-title" className={styles.title}>
|
||||
<section
|
||||
className="flex flex-col gap-[clamp(8px,1.852vw,10px)]"
|
||||
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
|
||||
</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 className={styles.options}>
|
||||
<div className="grid grid-cols-2 gap-sm">
|
||||
{paymentMethods.map((method) => {
|
||||
const selected = method.channel === value;
|
||||
const classes = [
|
||||
styles.option,
|
||||
selected ? styles.selected : "",
|
||||
"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
|
||||
? "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)
|
||||
.join(" ");
|
||||
@@ -79,10 +88,12 @@ export function SubscriptionPaymentMethod({
|
||||
aria-hidden="true"
|
||||
width={122}
|
||||
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>
|
||||
);
|
||||
|
||||
@@ -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";
|
||||
|
||||
import styles from "./subscription-payment-success-dialog.module.css";
|
||||
|
||||
export interface SubscriptionPaymentSuccessDialogProps {
|
||||
open: boolean;
|
||||
subscriptionType: "vip" | "topup";
|
||||
@@ -22,20 +20,32 @@ export function SubscriptionPaymentSuccessDialog({
|
||||
|
||||
return (
|
||||
<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"
|
||||
aria-modal="true"
|
||||
aria-labelledby="subscription-payment-success-title"
|
||||
>
|
||||
<div className={styles.dialog}>
|
||||
<div className={styles.badge} aria-hidden="true">
|
||||
<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="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>
|
||||
<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
|
||||
</h2>
|
||||
<p className={styles.content}>{content}</p>
|
||||
<button type="button" className={styles.button} onClick={onClose}>
|
||||
<p className="m-0 text-(length:--responsive-body,14px) leading-[1.5] text-[#393939]">
|
||||
{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
|
||||
</button>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user