Files
billing/lib/theme/theme-provider.tsx
d61b2220c3 feat(billing): enterprise billing portal with dark/light theme (#1)
Overhaul billing.hanzo.ai with dual-mode theming (dark default matching
docs site, light via toggle) and 4 new feature panels: credits with
starter credit countdown, transaction history with filtering, promotions
with discount code validation, and support tier upsell cards.

- Expand CSS custom properties for semantic tokens (bg, text, border,
  brand, success/warning/danger) with html.light class inversion
- Add ThemeProvider context + ThemeToggle component
- Retheme all 10 existing billing components from hardcoded zinc to
  semantic token classes
- Extend Commerce client with 5 new API methods (credit grants, credit
  balance, portal overview, meters, meter events summary)
- Add CreditGrant, TransactionRecord, SupportTier types
- New CreditsPanel with progress bars and expiry countdown
- New TransactionsPanel with color-coded types and pagination
- New PromotionsPanel with discount code entry/validation
- New SupportTiersPanel with 3-tier card layout
- Expand BillingWorkspace from 7 to 11 tabs
- Wire credit/transaction/support data through billing-shell
- Add "Add Funds" preset tiles with scale animations
- Add Account link to hanzo.id in nav header

Co-authored-by: Hanzo Dev <[email protected]>
2026-02-24 01:51:30 -08:00

58 lines
1.4 KiB
TypeScript

'use client'
import { createContext, useCallback, useContext, useEffect, useState, type ReactNode } from 'react'
type Theme = 'dark' | 'light'
interface ThemeContextValue {
theme: Theme
toggleTheme: () => void
}
const ThemeContext = createContext<ThemeContextValue>({
theme: 'dark',
toggleTheme: () => {},
})
const STORAGE_KEY = 'hanzo_billing_theme'
export function ThemeProvider({ children }: { children: ReactNode }) {
const [theme, setTheme] = useState<Theme>('dark')
useEffect(() => {
const stored = localStorage.getItem(STORAGE_KEY) as Theme | null
if (stored === 'light' || stored === 'dark') {
setTheme(stored)
if (stored === 'light') {
document.documentElement.classList.add('light')
}
}
}, [])
const toggleTheme = useCallback(() => {
setTheme((prev) => {
const next = prev === 'dark' ? 'light' : 'dark'
localStorage.setItem(STORAGE_KEY, next)
const el = document.documentElement
el.classList.add('transitioning')
if (next === 'light') {
el.classList.add('light')
} else {
el.classList.remove('light')
}
setTimeout(() => el.classList.remove('transitioning'), 200)
return next
})
}, [])
return (
<ThemeContext.Provider value={{ theme, toggleTheme }}>
{children}
</ThemeContext.Provider>
)
}
export function useTheme() {
return useContext(ThemeContext)
}