learning_ai_common_plat/packages/dashboard-shell/src/BillingPage.tsx
saravanakumardb1 1fda345d38 feat(dashboard-shell): add @bytelyst/dashboard-shell package (4.3) — 41 tests
Components:
- DashboardShell — main layout combining sidebar + topbar + content area
- Sidebar — collapsible nav with sections, badges, active state, auto-settings link
- TopBar — user avatar/menu, notifications bell, sign out, custom actions
- ProfilePage — avatar, name/email form, loading/error/success states
- BillingPage — current plan card, status badge, trial info, plan comparison grid
- SettingsPage — section-based layout with empty state

Features:
- NavItem[] or NavSection[] for flat or grouped navigation
- ShellFeatures toggle: profile, billing, settings, notifications, themeToggle
- ShellUser with avatar, role, initials fallback
- onNavigate callback for SPA routers (Next.js, etc.)
- Collapsible sidebar with toggle button
- All styled via --bl-shell-* CSS custom properties with fallbacks
- 41 tests covering all components
2026-03-19 20:54:28 -07:00

190 lines
5.7 KiB
TypeScript

import type { ReactNode } from 'react';
import type { BillingPageProps } from './types.js';
const statusColors: Record<string, string> = {
active: 'var(--color-success, #16a34a)',
trialing: 'var(--color-warning, #d97706)',
past_due: 'var(--color-destructive, #dc2626)',
canceled: 'var(--color-muted-foreground, #6b7280)',
};
export function BillingPage({
currentPlan = 'Free',
status = 'active',
trialEndsAt,
onManageBilling,
plans = [],
}: BillingPageProps): ReactNode {
return (
<div data-testid="bl-shell-billing-page" style={{ maxWidth: 800 }}>
<h1
style={{
fontSize: 24,
fontWeight: 700,
marginBottom: 24,
color: 'var(--color-foreground, #111827)',
}}
>
Billing
</h1>
{/* Current plan card */}
<div
data-testid="bl-billing-current"
style={{
padding: 24,
borderRadius: 12,
border: '1px solid var(--bl-shell-border, var(--color-border, #e5e7eb))',
marginBottom: 32,
background: 'var(--color-surface, #fff)',
}}
>
<div
style={{
display: 'flex',
justifyContent: 'space-between',
alignItems: 'center',
marginBottom: 16,
}}
>
<div>
<div
style={{
fontSize: 14,
color: 'var(--color-muted-foreground, #6b7280)',
marginBottom: 4,
}}
>
Current Plan
</div>
<div
style={{ fontSize: 24, fontWeight: 700, color: 'var(--color-foreground, #111827)' }}
>
{currentPlan}
</div>
</div>
<span
data-testid="bl-billing-status"
style={{
padding: '4px 12px',
borderRadius: 20,
fontSize: 12,
fontWeight: 600,
color: statusColors[status] || statusColors.active,
background: `color-mix(in srgb, ${statusColors[status] || statusColors.active} 10%, transparent)`,
border: `1px solid color-mix(in srgb, ${statusColors[status] || statusColors.active} 30%, transparent)`,
}}
>
{status.replace('_', ' ')}
</span>
</div>
{trialEndsAt && (
<div
data-testid="bl-billing-trial"
style={{ fontSize: 14, color: 'var(--color-warning, #d97706)', marginBottom: 12 }}
>
Trial ends: {trialEndsAt}
</div>
)}
{onManageBilling && (
<button
data-testid="bl-billing-manage"
onClick={onManageBilling}
style={{
padding: '10px 20px',
borderRadius: 8,
border: '1px solid var(--bl-shell-border, var(--color-border, #e5e7eb))',
fontSize: 14,
fontWeight: 500,
cursor: 'pointer',
background: 'var(--color-surface, #fff)',
color: 'var(--color-foreground, #111827)',
}}
>
Manage Billing
</button>
)}
</div>
{/* Plan comparison */}
{plans.length > 0 && (
<div>
<h2
style={{
fontSize: 18,
fontWeight: 600,
marginBottom: 16,
color: 'var(--color-foreground, #111827)',
}}
>
Available Plans
</h2>
<div
data-testid="bl-billing-plans"
style={{
display: 'grid',
gridTemplateColumns: `repeat(${Math.min(plans.length, 3)}, 1fr)`,
gap: 16,
}}
>
{plans.map(plan => (
<div
key={plan.name}
data-testid={`bl-billing-plan-${plan.name.toLowerCase()}`}
style={{
padding: 24,
borderRadius: 12,
border: plan.current
? '2px solid var(--bl-shell-accent, var(--color-primary, #2563eb))'
: '1px solid var(--bl-shell-border, var(--color-border, #e5e7eb))',
background: 'var(--color-surface, #fff)',
}}
>
<div style={{ fontSize: 18, fontWeight: 600, marginBottom: 4 }}>{plan.name}</div>
<div
style={{
fontSize: 24,
fontWeight: 700,
marginBottom: 16,
color: 'var(--bl-shell-accent, var(--color-primary, #2563eb))',
}}
>
{plan.price}
</div>
<ul style={{ listStyle: 'none', padding: 0, margin: 0 }}>
{plan.features.map(f => (
<li
key={f}
style={{
fontSize: 14,
padding: '4px 0',
color: 'var(--color-muted-foreground, #6b7280)',
}}
>
{f}
</li>
))}
</ul>
{plan.current && (
<div
style={{
marginTop: 16,
fontSize: 13,
fontWeight: 600,
color: 'var(--bl-shell-accent, var(--color-primary, #2563eb))',
}}
>
Current Plan
</div>
)}
</div>
))}
</div>
</div>
)}
</div>
);
}