mirror of
https://github.com/hanzoai/billing.git
synced 2026-10-30 18:46:54 +00:00
The Tailwind strip left the colour *values* behind: markup still named literal whites and dark-theme hexes, so `html.light` moved the tokens and nothing followed. Light mode rendered near-black text on a near-black page. Same four facts, each with one home now: ground bg-[#09090b] · bg-[#141419] · bg-[#0d0d10] · bg-[#16161c] -> bg-bg · bg-bg-card · bg-bg-elevated ink the whole `*-white/α` family is really "foreground at α%", so it says so: `-ink/α`, mixed from var(--color-text) brand bg-white + text-black pills -> bg-brand + text-brand-foreground (+ .bg-brand-foreground, which the switch knob needed — it was painting white on the white ON-track and vanishing) selection one rule for both themes; the light override painted white text on a pale tint /commerce styled itself entirely with inline dark literals and ignored the flip wholesale — 82 unreadable elements. Its hexes and rgba(255,255,255,α) now read the same tokens, and the H-mark inherits currentColor instead of being permanently white. Also: every self-navigation goes through BASE_PATH. /plans, /transactions, /payment-methods and the auth callback jumped to a bare '/', which walks the browser out of the app whenever the SPA is mounted at /admin/billing inside the commerce binary. billing-shell already did this for /topup — now it is the only way. Verified: next build green; Playwright over the static export in both themes — light /commerce 82 unreadable -> 0, /plans -> /admin/billing/#pricing (was the server root); dark render byte-identical in intent (same alphas, now expressed in oklab). Co-authored-by: Hanzo Dev <[email protected]>
72 lines
2.4 KiB
TypeScript
72 lines
2.4 KiB
TypeScript
'use client'
|
|
|
|
import { useEffect, useState } from 'react'
|
|
import { SubscriptionPortal } from '@/lib/billing/subscription-portal'
|
|
import type { Subscription, SubscriptionPlan } from '@/lib/billing/types'
|
|
import { fetchPublicPlans } from '@/lib/plans'
|
|
|
|
// Logged-out prospect has no subscription — an empty one so the grid renders
|
|
// every plan as available.
|
|
const NO_SUBSCRIPTION: Subscription = {
|
|
id: '',
|
|
customerId: '',
|
|
planId: '',
|
|
status: 'active',
|
|
currentPeriodStart: new Date(),
|
|
currentPeriodEnd: new Date(Date.now() + 30 * 24 * 60 * 60 * 1000),
|
|
}
|
|
|
|
/**
|
|
* Public pricing page — renders logged-out with prices from the public catalog.
|
|
* Auth gates only the subscribe action: any plan CTA starts sign-in.
|
|
*/
|
|
export function PublicPricing({ onSignIn }: { onSignIn: () => void }) {
|
|
const [plans, setPlans] = useState<SubscriptionPlan[]>([])
|
|
const [loading, setLoading] = useState(true)
|
|
|
|
useEffect(() => {
|
|
let alive = true
|
|
fetchPublicPlans()
|
|
.then((p) => { if (alive) { setPlans(p); setLoading(false) } })
|
|
// A rejected fetch leaves plans empty; the portal shows an honest catalog
|
|
// error rather than a login wall.
|
|
.catch(() => { if (alive) setLoading(false) })
|
|
return () => { alive = false }
|
|
}, [])
|
|
|
|
return (
|
|
<div className="min-h-screen bg-bg">
|
|
<header className="flex items-center justify-between border-b border-ink/[0.07] px-6 py-4">
|
|
<div className="flex items-center gap-2">
|
|
<span className="text-[15px] font-semibold tracking-tight text-text">Hanzo</span>
|
|
<span className="text-[13px] text-ink/40">Billing</span>
|
|
</div>
|
|
<button
|
|
type="button"
|
|
onClick={onSignIn}
|
|
className="rounded-lg bg-brand px-4 py-2 text-[13px] font-semibold text-brand-foreground transition hover:bg-brand-hover"
|
|
>
|
|
Sign in
|
|
</button>
|
|
</header>
|
|
|
|
<main className="mx-auto max-w-5xl px-6 py-10">
|
|
<div className="mb-8">
|
|
<h1 className="text-2xl font-bold tracking-tight text-text">Plans & Pricing</h1>
|
|
<p className="mt-2 text-[14px] text-ink/50">
|
|
Pick a plan to get started. Sign in to subscribe.
|
|
</p>
|
|
</div>
|
|
|
|
<SubscriptionPortal
|
|
subscription={NO_SUBSCRIPTION}
|
|
availablePlans={plans}
|
|
plansLoading={loading}
|
|
defaultView="plans"
|
|
onRequireAuth={onSignIn}
|
|
/>
|
|
</main>
|
|
</div>
|
|
)
|
|
}
|