mirror of
https://github.com/hanzoai/billing.git
synced 2026-10-30 18:46:54 +00:00
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]>
58 lines
1.4 KiB
TypeScript
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)
|
|
}
|