mirror of
https://github.com/hanzoai/billing.git
synced 2026-10-30 18:46:54 +00:00
- app/commerce/page.tsx: server-rendered landing page; fetches plans from pricing.hanzo.ai/v1/pricing/subscriptions (ISR 1h), falls back to hardcoded plans; renders hero, stats row, plan cards, feature grid, referral section, and footer - app/commerce/components/plan-card.tsx: plan card with monthly/annual pricing, popular badge, feature list, idle-resale payout callout, CTA linking to billing.hanzo.ai or cal.com/hanzo for Enterprise - app/commerce/components/billing-toggle.tsx: client toggle for monthly/annual interval; renders PlanCard grid - app/commerce/components/coupon-input.tsx: client coupon field with optimistic validation against commerce API - app/commerce/components/referral-section.tsx: affiliate sign-up form (20% recurring commission copy + email capture) - k8s/deployment.yaml: add commerce.hanzo.ai host to ingress TLS + rules pointing to existing billing service
77 lines
2.3 KiB
TypeScript
77 lines
2.3 KiB
TypeScript
'use client'
|
|
|
|
import { useState } from 'react'
|
|
import { PlanCard, type PricingPlan } from './plan-card'
|
|
|
|
interface BillingToggleProps {
|
|
plans: PricingPlan[]
|
|
}
|
|
|
|
export function BillingToggle({ plans }: BillingToggleProps) {
|
|
const [interval, setInterval] = useState<'monthly' | 'annual'>('monthly')
|
|
|
|
return (
|
|
<>
|
|
{/* Toggle */}
|
|
<div style={{ display: 'flex', justifyContent: 'center', marginBottom: '40px' }}>
|
|
<div style={{
|
|
display: 'inline-flex',
|
|
background: 'rgba(255,255,255,0.04)',
|
|
border: '1px solid rgba(255,255,255,0.1)',
|
|
borderRadius: '10px',
|
|
padding: '4px',
|
|
gap: '4px',
|
|
}}>
|
|
{(['monthly', 'annual'] as const).map((iv) => (
|
|
<button
|
|
key={iv}
|
|
type="button"
|
|
onClick={() => setInterval(iv)}
|
|
style={{
|
|
background: interval === iv ? 'rgba(255,255,255,0.1)' : 'transparent',
|
|
border: interval === iv ? '1px solid rgba(255,255,255,0.15)' : '1px solid transparent',
|
|
borderRadius: '7px',
|
|
padding: '6px 16px',
|
|
color: interval === iv ? '#fafafa' : 'rgba(255,255,255,0.4)',
|
|
fontSize: '13px',
|
|
fontWeight: 500,
|
|
cursor: 'pointer',
|
|
transition: 'all 0.15s',
|
|
display: 'flex',
|
|
alignItems: 'center',
|
|
gap: '6px',
|
|
}}
|
|
>
|
|
{iv === 'monthly' ? 'Monthly' : 'Annual'}
|
|
{iv === 'annual' && (
|
|
<span style={{
|
|
background: 'rgba(74,222,128,0.15)',
|
|
color: '#4ade80',
|
|
fontSize: '10px',
|
|
fontWeight: 700,
|
|
padding: '1px 5px',
|
|
borderRadius: '4px',
|
|
letterSpacing: '0.03em',
|
|
}}>
|
|
Save 20%
|
|
</span>
|
|
)}
|
|
</button>
|
|
))}
|
|
</div>
|
|
</div>
|
|
|
|
{/* Plans grid */}
|
|
<div style={{
|
|
display: 'grid',
|
|
gridTemplateColumns: 'repeat(auto-fit, minmax(240px, 1fr))',
|
|
gap: '20px',
|
|
}}>
|
|
{plans.map((plan) => (
|
|
<PlanCard key={plan.id} plan={plan} billingInterval={interval} />
|
|
))}
|
|
</div>
|
|
</>
|
|
)
|
|
}
|