|
1 | 1 | 'use client' |
2 | 2 |
|
| 3 | +import { useInstances } from '@/hooks/use-instances' |
3 | 4 | import { useUsage } from '@/hooks/use-usage' |
4 | 5 | import { useCredits } from '@/hooks/use-credits' |
5 | 6 | import { CurrentSpend } from '@/components/billing/current-spend' |
6 | 7 | import { PlanCard } from '@/components/billing/plan-card' |
7 | | -import { UsageChart } from '@/components/billing/usage-chart' |
8 | 8 | import { UsageByModelTable } from '@/components/billing/usage-by-model' |
9 | 9 | import { BillingPortalButton } from '@/components/billing/billing-portal-button' |
10 | 10 | import { AddCreditsButton } from '@/components/billing/add-credits-button' |
11 | 11 | import { AutoTopupSettings } from '@/components/billing/auto-topup-settings' |
| 12 | +import { CreditActivity } from '@/components/billing/credit-activity' |
| 13 | +import { UsageAnalytics } from '@/components/billing/usage-analytics' |
12 | 14 | import { Loading } from '@/components/shared/loading' |
| 15 | +import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card' |
| 16 | +import { ArrowDownToLine, CreditCard, Sparkles } from 'lucide-react' |
13 | 17 |
|
14 | 18 | export default function BillingPage() { |
15 | 19 | const { usage, isLoading: usageLoading } = useUsage() |
16 | 20 | const { credits, isLoading: creditsLoading, mutate } = useCredits() |
| 21 | + const { instances, isLoading: instancesLoading } = useInstances() |
17 | 22 |
|
18 | | - if (usageLoading || creditsLoading) return <Loading /> |
| 23 | + if (usageLoading || creditsLoading || instancesLoading) return <Loading /> |
| 24 | + |
| 25 | + const topUpNowLabel = credits |
| 26 | + ? `Top Up €${credits.auto_topup_amount_eur} Now` |
| 27 | + : 'Top Up Now' |
19 | 28 |
|
20 | 29 | return ( |
21 | 30 | <div className="space-y-6"> |
22 | | - <div className="flex items-center justify-between"> |
23 | | - <h1 className="text-2xl font-bold">Billing</h1> |
| 31 | + <div className="flex flex-col gap-4 lg:flex-row lg:items-center lg:justify-between"> |
| 32 | + <div className="space-y-1"> |
| 33 | + <h1 className="text-2xl font-bold">Billing</h1> |
| 34 | + <p className="max-w-3xl text-sm text-muted-foreground"> |
| 35 | + Keep an eye on your AI credit balance, monthly usage, and the automatic refill setup that buys more credits when your balance gets low. |
| 36 | + </p> |
| 37 | + </div> |
24 | 38 | <div className="flex items-center gap-2"> |
25 | | - <AddCreditsButton onSuccess={() => mutate()} /> |
| 39 | + <AddCreditsButton |
| 40 | + amountEur={credits?.auto_topup_amount_eur ?? 20} |
| 41 | + label={topUpNowLabel} |
| 42 | + onSuccess={() => mutate()} |
| 43 | + /> |
26 | 44 | <BillingPortalButton /> |
27 | 45 | </div> |
28 | 46 | </div> |
29 | | - <div className="grid gap-4 sm:grid-cols-2"> |
| 47 | + |
| 48 | + <Card className="border border-border/70 bg-gradient-to-br from-muted/40 via-background to-background"> |
| 49 | + <CardHeader> |
| 50 | + <CardTitle>How billing works</CardTitle> |
| 51 | + </CardHeader> |
| 52 | + <CardContent className="grid gap-4 lg:grid-cols-3"> |
| 53 | + <div className="rounded-xl border border-border/70 bg-background/80 p-4"> |
| 54 | + <Sparkles className="h-5 w-5 text-muted-foreground" /> |
| 55 | + <p className="mt-3 font-medium">AI usage spends credits</p> |
| 56 | + <p className="mt-1 text-sm text-muted-foreground"> |
| 57 | + Every model request deducts from your credit balance, so the balance card tells you how much AI usage you can still run. |
| 58 | + </p> |
| 59 | + </div> |
| 60 | + <div className="rounded-xl border border-border/70 bg-background/80 p-4"> |
| 61 | + <ArrowDownToLine className="h-5 w-5 text-muted-foreground" /> |
| 62 | + <p className="mt-3 font-medium">Auto top-up refills low balances</p> |
| 63 | + <p className="mt-1 text-sm text-muted-foreground"> |
| 64 | + Choose a refill amount and a threshold. When the balance drops below that threshold, we charge your default card and add more credits automatically. |
| 65 | + </p> |
| 66 | + </div> |
| 67 | + <div className="rounded-xl border border-border/70 bg-background/80 p-4"> |
| 68 | + <CreditCard className="h-5 w-5 text-muted-foreground" /> |
| 69 | + <p className="mt-3 font-medium">Cards and invoices live in Stripe</p> |
| 70 | + <p className="mt-1 text-sm text-muted-foreground"> |
| 71 | + Use the billing portal to update payment methods, recover from failed top-ups, and review invoices in one place. |
| 72 | + </p> |
| 73 | + </div> |
| 74 | + </CardContent> |
| 75 | + </Card> |
| 76 | + |
| 77 | + <div className="grid gap-4 xl:grid-cols-[1.15fr_0.85fr]"> |
30 | 78 | <CurrentSpend |
31 | 79 | baseCost={usage?.base_cost ?? 0} |
32 | 80 | tokenCost={usage?.token_cost ?? 0} |
33 | 81 | totalCost={usage?.total_cost ?? 0} |
34 | 82 | creditBalance={credits?.credit_balance_eur} |
| 83 | + autoTopupEnabled={credits?.auto_topup_enabled} |
| 84 | + autoTopupAmountEur={credits?.auto_topup_amount_eur} |
| 85 | + autoTopupThresholdEur={credits?.auto_topup_threshold_eur} |
35 | 86 | autoTopupFailed={credits?.auto_topup_failed} |
36 | 87 | /> |
37 | 88 | <PlanCard currentPlan="starter" /> |
38 | 89 | </div> |
39 | | - {credits && ( |
40 | | - <AutoTopupSettings |
41 | | - enabled={credits.auto_topup_enabled} |
42 | | - amountEur={credits.auto_topup_amount_eur} |
43 | | - thresholdEur={credits.auto_topup_threshold_eur} |
44 | | - failed={credits.auto_topup_failed} |
45 | | - onUpdate={() => mutate()} |
46 | | - /> |
47 | | - )} |
48 | | - <UsageChart data={usage?.daily ?? []} /> |
| 90 | + |
| 91 | + <div className="grid gap-4 xl:grid-cols-[1.15fr_0.85fr]"> |
| 92 | + {credits && ( |
| 93 | + <AutoTopupSettings |
| 94 | + enabled={credits.auto_topup_enabled} |
| 95 | + amountEur={credits.auto_topup_amount_eur} |
| 96 | + thresholdEur={credits.auto_topup_threshold_eur} |
| 97 | + failed={credits.auto_topup_failed} |
| 98 | + onUpdate={() => mutate()} |
| 99 | + /> |
| 100 | + )} |
| 101 | + <CreditActivity transactions={credits?.recent_transactions ?? []} /> |
| 102 | + </div> |
| 103 | + |
| 104 | + <UsageAnalytics instances={instances} /> |
49 | 105 | <UsageByModelTable data={usage?.by_model ?? []} /> |
50 | 106 | </div> |
51 | 107 | ) |
|
0 commit comments