From 2ebf5b9e97c79816edf60faed350f9269af2b325 Mon Sep 17 00:00:00 2001 From: chenhang Date: Mon, 13 Jul 2026 14:12:57 +0800 Subject: [PATCH] refactor(subscription): migrate payment components to tailwind --- .../__tests__/tailwind-components.test.tsx | 53 +++++++++++ .../subscription-payment-method.module.css | 87 ------------------- .../subscription-payment-method.tsx | 33 ++++--- ...cription-payment-success-dialog.module.css | 79 ----------------- .../subscription-payment-success-dialog.tsx | 26 ++++-- 5 files changed, 93 insertions(+), 185 deletions(-) delete mode 100644 src/app/subscription/components/subscription-payment-method.module.css delete mode 100644 src/app/subscription/components/subscription-payment-success-dialog.module.css diff --git a/src/app/subscription/components/__tests__/tailwind-components.test.tsx b/src/app/subscription/components/__tests__/tailwind-components.test.tsx index 293c704e..de908f50 100644 --- a/src/app/subscription/components/__tests__/tailwind-components.test.tsx +++ b/src/app/subscription/components/__tests__/tailwind-components.test.tsx @@ -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( + undefined} + />, + ), + ).toBe(""); + + const html = renderToStaticMarkup( + 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( + 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"); + }); }); diff --git a/src/app/subscription/components/subscription-payment-method.module.css b/src/app/subscription/components/subscription-payment-method.module.css deleted file mode 100644 index c3b6ecee..00000000 --- a/src/app/subscription/components/subscription-payment-method.module.css +++ /dev/null @@ -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; -} diff --git a/src/app/subscription/components/subscription-payment-method.tsx b/src/app/subscription/components/subscription-payment-method.tsx index 4087a4e0..88f9ebe3 100644 --- a/src/app/subscription/components/subscription-payment-method.tsx +++ b/src/app/subscription/components/subscription-payment-method.tsx @@ -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 ( -
-
-

+
+
+

Payment Method

- {caption} + + {caption} +
-
+
{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" /> ) : ( - {method.title} + + {method.title} + )} ); diff --git a/src/app/subscription/components/subscription-payment-success-dialog.module.css b/src/app/subscription/components/subscription-payment-success-dialog.module.css deleted file mode 100644 index c4fadce2..00000000 --- a/src/app/subscription/components/subscription-payment-success-dialog.module.css +++ /dev/null @@ -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); -} diff --git a/src/app/subscription/components/subscription-payment-success-dialog.tsx b/src/app/subscription/components/subscription-payment-success-dialog.tsx index 67f45240..7fa649b8 100644 --- a/src/app/subscription/components/subscription-payment-success-dialog.tsx +++ b/src/app/subscription/components/subscription-payment-success-dialog.tsx @@ -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 (
-
-