From 6c329dd24157d311122bfb89502e09b0cbd778f6 Mon Sep 17 00:00:00 2001 From: WOPR Date: Tue, 24 Mar 2026 15:07:20 -0700 Subject: [PATCH] feat: wire CryptoCheckout 4-step flow into billing page MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Replaces flat token bar (BuyCryptoCreditPanel) with 4-step flow: amount → coin/chain picker → deposit address + QR → confirmation tracker. Fixes PaymentMethodPicker amountUsd prop and test ReactNode type. Co-Authored-By: Claude Opus 4.6 (1M context) --- src/__tests__/crypto-checkout.test.tsx | 26 +++++++++++++++--- src/app/(dashboard)/billing/credits/page.tsx | 4 +-- src/components/billing/crypto-checkout.tsx | 28 +++++++++++++++----- 3 files changed, 46 insertions(+), 12 deletions(-) diff --git a/src/__tests__/crypto-checkout.test.tsx b/src/__tests__/crypto-checkout.test.tsx index b14c526..80455ac 100644 --- a/src/__tests__/crypto-checkout.test.tsx +++ b/src/__tests__/crypto-checkout.test.tsx @@ -4,7 +4,9 @@ import { describe, expect, it, vi } from "vitest"; vi.mock("framer-motion", () => ({ motion: { - div: ({ children, ...props }: Record) =>
{children}
, + div: ({ children, ...props }: { children?: React.ReactNode; [key: string]: unknown }) => ( +
{children}
+ ), }, AnimatePresence: ({ children }: { children: React.ReactNode }) => <>{children}, })); @@ -15,8 +17,26 @@ vi.mock("qrcode.react", () => ({ vi.mock("@/lib/api", () => ({ getSupportedPaymentMethods: vi.fn().mockResolvedValue([ - { id: "BTC:mainnet", type: "native", token: "BTC", chain: "bitcoin", displayName: "Bitcoin", decimals: 8, displayOrder: 0, iconUrl: "" }, - { id: "USDT:tron", type: "erc20", token: "USDT", chain: "tron", displayName: "USDT on Tron", decimals: 6, displayOrder: 1, iconUrl: "" }, + { + id: "BTC:mainnet", + type: "native", + token: "BTC", + chain: "bitcoin", + displayName: "Bitcoin", + decimals: 8, + displayOrder: 0, + iconUrl: "", + }, + { + id: "USDT:tron", + type: "erc20", + token: "USDT", + chain: "tron", + displayName: "USDT on Tron", + decimals: 6, + displayOrder: 1, + iconUrl: "", + }, ]), createCheckout: vi.fn().mockResolvedValue({ depositAddress: "THwbQb1sPiRUpUYunVQxQKc6i4LCmpP1mj", diff --git a/src/app/(dashboard)/billing/credits/page.tsx b/src/app/(dashboard)/billing/credits/page.tsx index 9c5210e..69126dc 100644 --- a/src/app/(dashboard)/billing/credits/page.tsx +++ b/src/app/(dashboard)/billing/credits/page.tsx @@ -5,9 +5,9 @@ import { usePathname, useRouter, useSearchParams } from "next/navigation"; import { Suspense, useEffect, useState } from "react"; import { AutoTopupCard } from "@/components/billing/auto-topup-card"; import { BuyCreditsPanel } from "@/components/billing/buy-credits-panel"; -import { BuyCryptoCreditPanel } from "@/components/billing/buy-crypto-credits-panel"; import { CouponInput } from "@/components/billing/coupon-input"; import { CreditBalance } from "@/components/billing/credit-balance"; +import { CryptoCheckout } from "@/components/billing/crypto-checkout"; import { DividendBanner } from "@/components/billing/dividend-banner"; import { DividendEligibility } from "@/components/billing/dividend-eligibility"; import { DividendPoolStats } from "@/components/billing/dividend-pool-stats"; @@ -200,7 +200,7 @@ function CreditsContent() { - + diff --git a/src/components/billing/crypto-checkout.tsx b/src/components/billing/crypto-checkout.tsx index 0617d76..89b9b45 100644 --- a/src/components/billing/crypto-checkout.tsx +++ b/src/components/billing/crypto-checkout.tsx @@ -6,10 +6,10 @@ import { useCallback, useEffect, useState } from "react"; import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"; import { type CheckoutResult, - type SupportedPaymentMethod, createCheckout, getChargeStatus, getSupportedPaymentMethods, + type SupportedPaymentMethod, } from "@/lib/api"; import { AmountSelector } from "./amount-selector"; import { ConfirmationTracker } from "./confirmation-tracker"; @@ -30,7 +30,9 @@ export function CryptoCheckout() { const [loading, setLoading] = useState(false); useEffect(() => { - getSupportedPaymentMethods().then(setMethods).catch(() => {}); + getSupportedPaymentMethods() + .then(setMethods) + .catch(() => {}); }, []); useEffect(() => { @@ -47,7 +49,10 @@ export function CryptoCheckout() { } else if (res.status === "expired" || res.status === "failed") { setStatus(res.status as PaymentStatus); clearInterval(interval); - } else if (res.amountReceivedCents > 0 && res.amountReceivedCents >= res.amountExpectedCents) { + } else if ( + res.amountReceivedCents > 0 && + res.amountReceivedCents >= res.amountExpectedCents + ) { setStatus("confirming"); setStep("confirming"); } else if (res.amountReceivedCents > 0) { @@ -93,7 +98,11 @@ export function CryptoCheckout() { if (methods.length === 0) return null; return ( - + @@ -122,12 +131,13 @@ export function CryptoCheckout() { > setStep("amount")} /> {loading && ( -

Creating checkout...

+

+ Creating checkout... +

)}
)} @@ -155,7 +165,11 @@ export function CryptoCheckout() { credited={status === "credited"} /> {status === "credited" && ( - )}