Preview
Full width desktop view
Code
bills-10.tsx
1"use client";
2
3import * as React from "react";
4import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
5import { Button } from "@/components/ui/button";
6import { Badge } from "@/components/ui/badge";
7import { Separator } from "@/components/ui/separator";
8import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
9import { Switch } from "@/components/ui/switch";
10import {
11 Check,
12 CreditCard,
13 Calendar,
14 Building2,
15 Users,
16 Zap,
17 Shield,
18 Star,
19} from "lucide-react";
20import { cn } from "@/lib/utils";
21
22interface Plan {
23 id: string;
24 name: string;
25 description: string;
26 price: {
27 monthly: number;
28 yearly: number;
29 };
30 features: string[];
31 maxUsers: number;
32 storage: string;
33 support: string;
34 popular?: boolean;
35 highlighted?: boolean;
36}
37
38interface AddOn {
39 id: string;
40 name: string;
41 description: string;
42 price: {
43 monthly: number;
44 yearly: number;
45 };
46 unit: string;
47}
48
49interface Bill {
50 id: string;
51 description: string;
52 amount: number;
53 dueDate: string;
54 status: "pending" | "paid" | "overdue";
55 planName: string;
56}
57
58interface CustomerAdminDashboardProps {
59 companyName?: string;
60 currentPlan?: string;
61 plans?: Plan[];
62 addOns?: AddOn[];
63 bills?: Bill[];
64}
65
66const defaultPlans: Plan[] = [
67 {
68 id: "starter",
69 name: "Starter",
70 description: "Perfect for small teams getting started",
71 price: { monthly: 29, yearly: 290 },
72 features: [
73 "Up to 5 team members",
74 "10GB storage",
75 "Basic analytics",
76 "Email support",
77 "Standard integrations",
78 ],
79 maxUsers: 5,
80 storage: "10GB",
81 support: "Email",
82 },
83 {
84 id: "professional",
85 name: "Professional",
86 description: "Great for growing businesses",
87 price: { monthly: 79, yearly: 790 },
88 features: [
89 "Up to 25 team members",
90 "100GB storage",
91 "Advanced analytics",
92 "Priority support",
93 "All integrations",
94 "Custom workflows",
95 ],
96 maxUsers: 25,
97 storage: "100GB",
98 support: "Priority",
99 popular: true,
100 },
101 {
102 id: "enterprise",
103 name: "Enterprise",
104 description: "For large organizations with advanced needs",
105 price: { monthly: 199, yearly: 1990 },
106 features: [
107 "Unlimited team members",
108 "1TB storage",
109 "Custom analytics",
110 "24/7 phone support",
111 "Custom integrations",
112 "Advanced security",
113 "Dedicated account manager",
114 ],
115 maxUsers: -1,
116 storage: "1TB",
117 support: "24/7 Phone",
118 highlighted: true,
119 },
120];
121
122const defaultAddOns: AddOn[] = [
123 {
124 id: "extra-storage",
125 name: "Extra Storage",
126 description: "Additional storage for your data",
127 price: { monthly: 10, yearly: 100 },
128 unit: "per 50GB",
129 },
130 {
131 id: "advanced-security",
132 name: "Advanced Security",
133 description: "Enhanced security features and compliance",
134 price: { monthly: 25, yearly: 250 },
135 unit: "per account",
136 },
137 {
138 id: "premium-support",
139 name: "Premium Support",
140 description: "24/7 priority support with dedicated agent",
141 price: { monthly: 50, yearly: 500 },
142 unit: "per account",
143 },
144];
145
146const defaultBills: Bill[] = [
147 {
148 id: "bill-1",
149 description: "Professional Plan - Monthly",
150 amount: 79,
151 dueDate: "2024-02-15",
152 status: "pending",
153 planName: "Professional",
154 },
155 {
156 id: "bill-2",
157 description: "Extra Storage Add-on",
158 amount: 10,
159 dueDate: "2024-02-15",
160 status: "pending",
161 planName: "Add-on",
162 },
163 {
164 id: "bill-3",
165 description: "Professional Plan - Monthly",
166 amount: 79,
167 dueDate: "2024-01-15",
168 status: "paid",
169 planName: "Professional",
170 },
171];
172
173export function CustomerAdminDashboard({
174 companyName = "Acme Corp",
175 currentPlan = "professional",
176 plans = defaultPlans,
177 addOns = defaultAddOns,
178 bills = defaultBills,
179}: CustomerAdminDashboardProps) {
180 const [billingFrequency, setBillingFrequency] = React.useState<
181 "monthly" | "yearly"
182 >("monthly");
183 const [selectedPlan, setSelectedPlan] = React.useState(currentPlan);
184 const [selectedAddOns, setSelectedAddOns] = React.useState<string[]>([
185 "extra-storage",
186 ]);
187
188 const currentPlanData = plans.find((plan) => plan.id === currentPlan);
189 const pendingBills = bills.filter((bill) => bill.status === "pending");
190 const totalPendingAmount = pendingBills.reduce(
191 (sum, bill) => sum + bill.amount,
192 0
193 );
194
195 const calculateTotal = () => {
196 const plan = plans.find((p) => p.id === selectedPlan);
197 const planPrice = plan ? plan.price[billingFrequency] : 0;
198 const addOnPrice = selectedAddOns.reduce((sum, addOnId) => {
199 const addOn = addOns.find((a) => a.id === addOnId);
200 return sum + (addOn ? addOn.price[billingFrequency] : 0);
201 }, 0);
202 return planPrice + addOnPrice;
203 };
204
205 const toggleAddOn = (addOnId: string) => {
206 setSelectedAddOns((prev) =>
207 prev.includes(addOnId)
208 ? prev.filter((id) => id !== addOnId)
209 : [...prev, addOnId]
210 );
211 };
212
213 return (
214 <div className="min-h-screen bg-background p-6">
215 <div className="mx-auto max-w-7xl space-y-8">
216 {/* Header */}
217 <div className="flex items-center justify-between">
218 <div>
219 <h1 className="text-3xl font-bold tracking-tight">
220 Admin Dashboard
221 </h1>
222 <p className="text-muted-foreground">
223 Manage your subscription and billing for {companyName}
224 </p>
225 </div>
226 <div className="flex items-center gap-4">
227 <Badge variant="secondary" className="flex items-center gap-2">
228 <Building2 className="h-4 w-4" />
229 {companyName}
230 </Badge>
231 </div>
232 </div>
233
234 {/* Current Plan Overview */}
235 <Card>
236 <CardHeader>
237 <CardTitle className="flex items-center gap-2">
238 <Star className="h-5 w-5" />
239 Current Plan
240 </CardTitle>
241 </CardHeader>
242 <CardContent>
243 <div className="grid gap-6 md:grid-cols-3">
244 <div>
245 <h3 className="font-semibold text-lg">
246 {currentPlanData?.name}
247 </h3>
248 <p className="text-muted-foreground">
249 {currentPlanData?.description}
250 </p>
251 <div className="mt-2 flex items-center gap-2">
252 <span className="text-2xl font-bold">
253 ${currentPlanData?.price.monthly}
254 </span>
255 <span className="text-muted-foreground">/month</span>
256 </div>
257 </div>
258 <div className="space-y-2">
259 <div className="flex items-center gap-2">
260 <Users className="h-4 w-4 text-muted-foreground" />
261 <span className="text-sm">
262 {currentPlanData?.maxUsers === -1
263 ? "Unlimited"
264 : currentPlanData?.maxUsers}{" "}
265 users
266 </span>
267 </div>
268 <div className="flex items-center gap-2">
269 <Shield className="h-4 w-4 text-muted-foreground" />
270 <span className="text-sm">
271 {currentPlanData?.storage} storage
272 </span>
273 </div>
274 <div className="flex items-center gap-2">
275 <Zap className="h-4 w-4 text-muted-foreground" />
276 <span className="text-sm">
277 {currentPlanData?.support} support
278 </span>
279 </div>
280 </div>
281 <div className="flex items-center justify-end">
282 <Button variant="outline">Manage Plan</Button>
283 </div>
284 </div>
285 </CardContent>
286 </Card>
287
288 <div className="grid gap-8 lg:grid-cols-3">
289 {/* Plans and Add-ons */}
290 <div className="lg:col-span-2">
291 <Tabs defaultValue="plans" className="space-y-6">
292 <TabsList className="grid w-full grid-cols-2">
293 <TabsTrigger value="plans">Plans</TabsTrigger>
294 <TabsTrigger value="addons">Add-ons</TabsTrigger>
295 </TabsList>
296
297 <TabsContent value="plans" className="space-y-6">
298 {/* Billing Frequency Toggle */}
299 <Card>
300 <CardContent className="pt-6">
301 <div className="flex items-center justify-between">
302 <div>
303 <h3 className="font-semibold">Billing Frequency</h3>
304 <p className="text-sm text-muted-foreground">
305 Save up to 17% with yearly billing
306 </p>
307 </div>
308 <div className="flex items-center gap-3">
309 <span
310 className={cn(
311 "text-sm",
312 billingFrequency === "monthly"
313 ? "font-semibold"
314 : "text-muted-foreground"
315 )}
316 >
317 Monthly
318 </span>
319 <Switch
320 checked={billingFrequency === "yearly"}
321 onCheckedChange={(checked) =>
322 setBillingFrequency(checked ? "yearly" : "monthly")
323 }
324 />
325 <span
326 className={cn(
327 "text-sm",
328 billingFrequency === "yearly"
329 ? "font-semibold"
330 : "text-muted-foreground"
331 )}
332 >
333 Yearly
334 </span>
335 {billingFrequency === "yearly" && (
336 <Badge variant="secondary">Save 17%</Badge>
337 )}
338 </div>
339 </div>
340 </CardContent>
341 </Card>
342
343 {/* Plans Grid */}
344 <div className="grid gap-6 md:grid-cols-2 lg:grid-cols-3">
345 {plans.map((plan) => (
346 <Card
347 key={plan.id}
348 className={cn(
349 "relative cursor-pointer transition-all hover:shadow-md",
350 selectedPlan === plan.id && "ring-2 ring-primary",
351 plan.highlighted && "border-primary",
352 plan.popular && "border-orange-500"
353 )}
354 onClick={() => setSelectedPlan(plan.id)}
355 >
356 {plan.popular && (
357 <Badge className="absolute -top-2 left-1/2 -translate-x-1/2 bg-orange-500">
358 Most Popular
359 </Badge>
360 )}
361 {plan.highlighted && (
362 <Badge className="absolute -top-2 left-1/2 -translate-x-1/2">
363 Enterprise
364 </Badge>
365 )}
366 <CardHeader>
367 <CardTitle className="flex items-center justify-between">
368 {plan.name}
369 {selectedPlan === plan.id && (
370 <Check className="h-5 w-5 text-primary" />
371 )}
372 </CardTitle>
373 <p className="text-sm text-muted-foreground">
374 {plan.description}
375 </p>
376 </CardHeader>
377 <CardContent className="space-y-4">
378 <div>
379 <span className="text-3xl font-bold">
380 ${plan.price[billingFrequency]}
381 </span>
382 <span className="text-muted-foreground">
383 /{billingFrequency === "monthly" ? "mo" : "yr"}
384 </span>
385 {billingFrequency === "yearly" && (
386 <div className="text-sm text-muted-foreground">
387 ${Math.round(plan.price.yearly / 12)}/month billed
388 yearly
389 </div>
390 )}
391 </div>
392 <ul className="space-y-2">
393 {plan.features.map((feature, index) => (
394 <li
395 key={index}
396 className="flex items-center gap-2 text-sm"
397 >
398 <Check className="h-4 w-4 text-green-500" />
399 {feature}
400 </li>
401 ))}
402 </ul>
403 </CardContent>
404 </Card>
405 ))}
406 </div>
407 </TabsContent>
408
409 <TabsContent value="addons" className="space-y-6">
410 <div className="grid gap-4">
411 {addOns.map((addOn) => (
412 <Card
413 key={addOn.id}
414 className="cursor-pointer"
415 onClick={() => toggleAddOn(addOn.id)}
416 >
417 <CardContent className="pt-6">
418 <div className="flex items-center justify-between">
419 <div className="flex items-center gap-4">
420 <Switch
421 checked={selectedAddOns.includes(addOn.id)}
422 onCheckedChange={() => toggleAddOn(addOn.id)}
423 />
424 <div>
425 <h3 className="font-semibold">{addOn.name}</h3>
426 <p className="text-sm text-muted-foreground">
427 {addOn.description}
428 </p>
429 </div>
430 </div>
431 <div className="text-right">
432 <div className="font-semibold">
433 ${addOn.price[billingFrequency]}/
434 {billingFrequency === "monthly" ? "mo" : "yr"}
435 </div>
436 <div className="text-sm text-muted-foreground">
437 {addOn.unit}
438 </div>
439 </div>
440 </div>
441 </CardContent>
442 </Card>
443 ))}
444 </div>
445 </TabsContent>
446 </Tabs>
447 </div>
448
449 {/* Billing Summary and Current Bills */}
450 <div className="space-y-6">
451 {/* Billing Summary */}
452 <Card>
453 <CardHeader>
454 <CardTitle className="flex items-center gap-2">
455 <CreditCard className="h-5 w-5" />
456 Billing Summary
457 </CardTitle>
458 </CardHeader>
459 <CardContent className="space-y-4">
460 <div className="space-y-2">
461 <div className="flex justify-between text-sm">
462 <span>Selected Plan</span>
463 <span>
464 {plans.find((p) => p.id === selectedPlan)?.name}
465 </span>
466 </div>
467 <div className="flex justify-between text-sm">
468 <span>Plan Cost</span>
469 <span>
470 $
471 {
472 plans.find((p) => p.id === selectedPlan)?.price[
473 billingFrequency
474 ]
475 }
476 </span>
477 </div>
478 {selectedAddOns.map((addOnId) => {
479 const addOn = addOns.find((a) => a.id === addOnId);
480 return addOn ? (
481 <div
482 key={addOnId}
483 className="flex justify-between text-sm"
484 >
485 <span>{addOn.name}</span>
486 <span>${addOn.price[billingFrequency]}</span>
487 </div>
488 ) : null;
489 })}
490 </div>
491 <Separator />
492 <div className="flex justify-between font-semibold">
493 <span>Total</span>
494 <span>
495 ${calculateTotal()}/
496 {billingFrequency === "monthly" ? "mo" : "yr"}
497 </span>
498 </div>
499 <Button className="w-full">Update Subscription</Button>
500 </CardContent>
501 </Card>
502
503 {/* Current Bills */}
504 <Card>
505 <CardHeader>
506 <CardTitle className="flex items-center gap-2">
507 <Calendar className="h-5 w-5" />
508 Current Bills
509 </CardTitle>
510 </CardHeader>
511 <CardContent className="space-y-4">
512 {pendingBills.length > 0 ? (
513 <>
514 <div className="space-y-3">
515 {pendingBills.map((bill) => (
516 <div
517 key={bill.id}
518 className="flex items-center justify-between p-3 border rounded-lg"
519 >
520 <div>
521 <p className="font-medium text-sm">
522 {bill.description}
523 </p>
524 <p className="text-xs text-muted-foreground">
525 Due: {bill.dueDate}
526 </p>
527 </div>
528 <div className="text-right">
529 <p className="font-semibold">${bill.amount}</p>
530 <Badge
531 variant={
532 bill.status === "overdue"
533 ? "destructive"
534 : "secondary"
535 }
536 >
537 {bill.status}
538 </Badge>
539 </div>
540 </div>
541 ))}
542 </div>
543 <Separator />
544 <div className="flex justify-between font-semibold">
545 <span>Total Due</span>
546 <span>${totalPendingAmount}</span>
547 </div>
548 <Button className="w-full" variant="destructive">
549 Pay All Bills
550 </Button>
551 </>
552 ) : (
553 <div className="text-center py-6">
554 <p className="text-muted-foreground">No pending bills</p>
555 </div>
556 )}
557 </CardContent>
558 </Card>
559
560 {/* Recent Bills */}
561 <Card>
562 <CardHeader>
563 <CardTitle>Recent Bills</CardTitle>
564 </CardHeader>
565 <CardContent>
566 <div className="space-y-3">
567 {bills
568 .filter((bill) => bill.status === "paid")
569 .slice(0, 3)
570 .map((bill) => (
571 <div
572 key={bill.id}
573 className="flex items-center justify-between text-sm"
574 >
575 <div>
576 <p className="font-medium">{bill.description}</p>
577 <p className="text-xs text-muted-foreground">
578 {bill.dueDate}
579 </p>
580 </div>
581 <div className="text-right">
582 <p className="font-semibold">${bill.amount}</p>
583 <Badge variant="secondary">Paid</Badge>
584 </div>
585 </div>
586 ))}
587 </div>
588 </CardContent>
589 </Card>
590 </div>
591 </div>
592 </div>
593 </div>
594 );
595}
596
597export default function CustomerAdminDashboardDemo() {
598 return <CustomerAdminDashboard />;
599}
Dependencies
External Libraries
lucide-reactreact
Shadcn/UI Components
badgebuttoncardseparatorswitchtabs
Local Components
/lib/utils