Preview
Full width desktop view
Code
bills-7.tsx
1"use client";
2import React, { useState, useEffect } from "react";
3import {
4 Card,
5 CardContent,
6 CardDescription,
7 CardHeader,
8 CardTitle,
9} from "@/components/ui/card";
10import { Button } from "@/components/ui/button";
11import { Badge } from "@/components/ui/badge";
12import { Input } from "@/components/ui/input";
13import { Switch } from "@/components/ui/switch";
14import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
15import {
16 Table,
17 TableBody,
18 TableCell,
19 TableHead,
20 TableHeader,
21 TableRow,
22} from "@/components/ui/table";
23import {
24 Select,
25 SelectContent,
26 SelectItem,
27 SelectTrigger,
28 SelectValue,
29} from "@/components/ui/select";
30import { Separator } from "@/components/ui/separator";
31import { Progress } from "@/components/ui/progress";
32import { Alert, AlertDescription } from "@/components/ui/alert";
33import {
34 Tooltip,
35 TooltipContent,
36 TooltipProvider,
37 TooltipTrigger,
38} from "@/components/ui/tooltip";
39import {
40 CreditCard,
41 Download,
42 Search,
43 Filter,
44 Calendar,
45 DollarSign,
46 TrendingUp,
47 AlertTriangle,
48 CheckCircle,
49 Clock,
50 Users,
51 Database,
52 Shield,
53 Zap,
54 Plus,
55 Minus,
56 Info,
57} from "lucide-react";
58
59interface Plan {
60 id: string;
61 name: string;
62 description: string;
63 price: number;
64 billing: "monthly" | "yearly";
65 features: string[];
66 popular?: boolean;
67 current?: boolean;
68}
69
70interface AddOn {
71 id: string;
72 name: string;
73 description: string;
74 price: number;
75 icon: React.ReactNode;
76 enabled: boolean;
77}
78
79interface BillingRecord {
80 id: string;
81 date: string;
82 description: string;
83 amount: number;
84 status: "paid" | "pending" | "overdue" | "failed";
85 invoiceUrl?: string;
86}
87
88interface PaymentMethod {
89 id: string;
90 type: "card" | "bank";
91 last4: string;
92 brand?: string;
93 expiryDate?: string;
94 isDefault: boolean;
95}
96
97const B2BCustomerAdminDashboard: React.FC = () => {
98 const [selectedPlan, setSelectedPlan] = useState<string>("pro");
99 const [billingCycle, setBillingCycle] = useState<"monthly" | "yearly">(
100 "monthly"
101 );
102 const [addOns, setAddOns] = useState<AddOn[]>([
103 {
104 id: "storage",
105 name: "Extra Storage",
106 description: "1TB additional storage",
107 price: 10,
108 icon: <Database className="h-4 w-4" />,
109 enabled: false,
110 },
111 {
112 id: "users",
113 name: "Additional Users",
114 description: "10 extra team members",
115 price: 25,
116 icon: <Users className="h-4 w-4" />,
117 enabled: true,
118 },
119 {
120 id: "security",
121 name: "Advanced Security",
122 description: "SSO and advanced permissions",
123 price: 15,
124 icon: <Shield className="h-4 w-4" />,
125 enabled: false,
126 },
127 {
128 id: "priority",
129 name: "Priority Support",
130 description: "24/7 priority customer support",
131 price: 20,
132 icon: <Zap className="h-4 w-4" />,
133 enabled: true,
134 },
135 ]);
136
137 const [searchTerm, setSearchTerm] = useState("");
138 const [statusFilter, setStatusFilter] = useState<string>("all");
139 const [isLoading, setIsLoading] = useState(false);
140
141 const plans: Plan[] = [
142 {
143 id: "starter",
144 name: "Starter",
145 description: "Perfect for small teams",
146 price: billingCycle === "monthly" ? 29 : 290,
147 billing: billingCycle,
148 features: [
149 "Up to 5 users",
150 "10GB storage",
151 "Basic support",
152 "Core features",
153 ],
154 },
155 {
156 id: "pro",
157 name: "Professional",
158 description: "Best for growing businesses",
159 price: billingCycle === "monthly" ? 79 : 790,
160 billing: billingCycle,
161 features: [
162 "Up to 25 users",
163 "100GB storage",
164 "Priority support",
165 "Advanced features",
166 "API access",
167 ],
168 popular: true,
169 current: true,
170 },
171 {
172 id: "enterprise",
173 name: "Enterprise",
174 description: "For large organizations",
175 price: billingCycle === "monthly" ? 199 : 1990,
176 billing: billingCycle,
177 features: [
178 "Unlimited users",
179 "1TB storage",
180 "24/7 support",
181 "All features",
182 "Custom integrations",
183 "SLA guarantee",
184 ],
185 },
186 ];
187
188 const billingHistory: BillingRecord[] = [
189 {
190 id: "1",
191 date: "2024-01-15",
192 description: "Professional Plan - January 2024",
193 amount: 79.0,
194 status: "paid",
195 invoiceUrl: "#",
196 },
197 {
198 id: "2",
199 date: "2024-01-10",
200 description: "Additional Users Add-on",
201 amount: 25.0,
202 status: "paid",
203 invoiceUrl: "#",
204 },
205 {
206 id: "3",
207 date: "2024-01-01",
208 description: "Priority Support Add-on",
209 amount: 20.0,
210 status: "pending",
211 invoiceUrl: "#",
212 },
213 {
214 id: "4",
215 date: "2023-12-15",
216 description: "Professional Plan - December 2023",
217 amount: 79.0,
218 status: "overdue",
219 invoiceUrl: "#",
220 },
221 ];
222
223 const paymentMethods: PaymentMethod[] = [
224 {
225 id: "1",
226 type: "card",
227 last4: "4242",
228 brand: "Visa",
229 expiryDate: "12/25",
230 isDefault: true,
231 },
232 {
233 id: "2",
234 type: "card",
235 last4: "5555",
236 brand: "Mastercard",
237 expiryDate: "08/26",
238 isDefault: false,
239 },
240 ];
241
242 const toggleAddOn = (id: string) => {
243 setAddOns((prev) =>
244 prev.map((addon) =>
245 addon.id === id ? { ...addon, enabled: !addon.enabled } : addon
246 )
247 );
248 };
249
250 const calculateTotal = () => {
251 const currentPlan = plans.find((p) => p.id === selectedPlan);
252 const planPrice = currentPlan?.price || 0;
253 const addOnTotal = addOns
254 .filter((addon) => addon.enabled)
255 .reduce((sum, addon) => sum + addon.price, 0);
256 return planPrice + addOnTotal;
257 };
258
259 const getStatusBadge = (status: string) => {
260 const variants = {
261 paid: "default",
262 pending: "secondary",
263 overdue: "destructive",
264 failed: "destructive",
265 };
266 return (
267 <Badge variant={variants[status as keyof typeof variants] as any}>
268 {status}
269 </Badge>
270 );
271 };
272
273 const filteredBillingHistory = billingHistory.filter((record) => {
274 const matchesSearch = record.description
275 .toLowerCase()
276 .includes(searchTerm.toLowerCase());
277 const matchesStatus =
278 statusFilter === "all" || record.status === statusFilter;
279 return matchesSearch && matchesStatus;
280 });
281
282 const overdueAmount = billingHistory
283 .filter((record) => record.status === "overdue")
284 .reduce((sum, record) => sum + record.amount, 0);
285
286 return (
287 <TooltipProvider>
288 <div className="min-h-screen bg-background p-6 space-y-6">
289 {/* Header */}
290 <div className="flex flex-col lg:flex-row lg:items-center lg:justify-between gap-4">
291 <div>
292 <h1 className="text-3xl font-bold text-foreground">
293 Billing & Plans
294 </h1>
295 <p className="text-muted-foreground">
296 Manage your subscription and billing preferences
297 </p>
298 </div>
299 <div className="flex items-center gap-3">
300 <Button variant="outline" size="sm">
301 <Download className="h-4 w-4 mr-2" />
302 Export
303 </Button>
304 <Button size="sm">
305 <Plus className="h-4 w-4 mr-2" />
306 Add Payment Method
307 </Button>
308 </div>
309 </div>
310
311 {/* Alerts */}
312 {overdueAmount > 0 && (
313 <Alert variant="destructive">
314 <AlertTriangle className="h-4 w-4" />
315 <AlertDescription>
316 You have ${overdueAmount.toFixed(2)} in overdue payments. Please
317 update your payment method to avoid service interruption.
318 </AlertDescription>
319 </Alert>
320 )}
321
322 {/* Billing Overview Cards */}
323 <div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-6">
324 <Card>
325 <CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
326 <CardTitle className="text-sm font-medium">
327 Current Plan
328 </CardTitle>
329 <CreditCard className="h-4 w-4 text-muted-foreground" />
330 </CardHeader>
331 <CardContent>
332 <div className="text-2xl font-bold">Professional</div>
333 <p className="text-xs text-muted-foreground">
334 ${plans.find((p) => p.current)?.price}/month
335 </p>
336 </CardContent>
337 </Card>
338
339 <Card>
340 <CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
341 <CardTitle className="text-sm font-medium">
342 Monthly Total
343 </CardTitle>
344 <DollarSign className="h-4 w-4 text-muted-foreground" />
345 </CardHeader>
346 <CardContent>
347 <div className="text-2xl font-bold">${calculateTotal()}</div>
348 <p className="text-xs text-muted-foreground">
349 +$
350 {addOns
351 .filter((a) => a.enabled)
352 .reduce((s, a) => s + a.price, 0)}{" "}
353 add-ons
354 </p>
355 </CardContent>
356 </Card>
357
358 <Card>
359 <CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
360 <CardTitle className="text-sm font-medium">
361 Next Billing
362 </CardTitle>
363 <Calendar className="h-4 w-4 text-muted-foreground" />
364 </CardHeader>
365 <CardContent>
366 <div className="text-2xl font-bold">Feb 15</div>
367 <p className="text-xs text-muted-foreground">
368 Auto-renewal enabled
369 </p>
370 </CardContent>
371 </Card>
372
373 <Card>
374 <CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
375 <CardTitle className="text-sm font-medium">Usage</CardTitle>
376 <TrendingUp className="h-4 w-4 text-muted-foreground" />
377 </CardHeader>
378 <CardContent>
379 <div className="text-2xl font-bold">78%</div>
380 <Progress value={78} className="mt-2" />
381 </CardContent>
382 </Card>
383 </div>
384
385 <Tabs defaultValue="plans" className="space-y-6">
386 <TabsList className="grid w-full grid-cols-3">
387 <TabsTrigger value="plans">Plans & Add-ons</TabsTrigger>
388 <TabsTrigger value="billing">Billing History</TabsTrigger>
389 <TabsTrigger value="payment">Payment Methods</TabsTrigger>
390 </TabsList>
391
392 <TabsContent value="plans" className="space-y-6">
393 {/* Billing Cycle Toggle */}
394 <Card>
395 <CardHeader>
396 <div className="flex items-center justify-between">
397 <div>
398 <CardTitle>Choose Your Plan</CardTitle>
399 <CardDescription>
400 Select the perfect plan for your business needs
401 </CardDescription>
402 </div>
403 <div className="flex items-center space-x-2">
404 <span
405 className={
406 billingCycle === "monthly"
407 ? "font-medium"
408 : "text-muted-foreground"
409 }
410 >
411 Monthly
412 </span>
413 <Switch
414 checked={billingCycle === "yearly"}
415 onCheckedChange={(checked) =>
416 setBillingCycle(checked ? "yearly" : "monthly")
417 }
418 />
419 <span
420 className={
421 billingCycle === "yearly"
422 ? "font-medium"
423 : "text-muted-foreground"
424 }
425 >
426 Yearly{" "}
427 <Badge variant="secondary" className="ml-1">
428 Save 20%
429 </Badge>
430 </span>
431 </div>
432 </div>
433 </CardHeader>
434 <CardContent>
435 <div className="grid grid-cols-1 md:grid-cols-3 gap-6">
436 {plans.map((plan) => (
437 <Card
438 key={plan.id}
439 className={`relative cursor-pointer transition-all hover:shadow-lg ${
440 selectedPlan === plan.id ? "ring-2 ring-primary" : ""
441 } ${plan.popular ? "border-primary" : ""}`}
442 onClick={() => setSelectedPlan(plan.id)}
443 >
444 {plan.popular && (
445 <Badge className="absolute -top-2 left-1/2 transform -translate-x-1/2">
446 Most Popular
447 </Badge>
448 )}
449 {plan.current && (
450 <Badge
451 variant="secondary"
452 className="absolute -top-2 right-4"
453 >
454 Current Plan
455 </Badge>
456 )}
457 <CardHeader className="text-center">
458 <CardTitle className="text-xl">{plan.name}</CardTitle>
459 <CardDescription>{plan.description}</CardDescription>
460 <div className="text-3xl font-bold">
461 ${plan.price}
462 <span className="text-sm font-normal text-muted-foreground">
463 /{billingCycle === "monthly" ? "month" : "year"}
464 </span>
465 </div>
466 </CardHeader>
467 <CardContent>
468 <ul className="space-y-2">
469 {plan.features.map((feature, index) => (
470 <li key={index} className="flex items-center">
471 <CheckCircle className="h-4 w-4 text-green-500 mr-2" />
472 <span className="text-sm">{feature}</span>
473 </li>
474 ))}
475 </ul>
476 <Button
477 className="w-full mt-4"
478 variant={
479 selectedPlan === plan.id ? "default" : "outline"
480 }
481 disabled={plan.current}
482 >
483 {plan.current ? "Current Plan" : "Select Plan"}
484 </Button>
485 </CardContent>
486 </Card>
487 ))}
488 </div>
489 </CardContent>
490 </Card>
491
492 {/* Add-ons */}
493 <Card>
494 <CardHeader>
495 <CardTitle>Add-ons</CardTitle>
496 <CardDescription>
497 Enhance your plan with additional features
498 </CardDescription>
499 </CardHeader>
500 <CardContent>
501 <div className="space-y-4">
502 {addOns.map((addon) => (
503 <div
504 key={addon.id}
505 className="flex items-center justify-between p-4 border rounded-lg"
506 >
507 <div className="flex items-center space-x-3">
508 <div className="p-2 bg-muted rounded-md">
509 {addon.icon}
510 </div>
511 <div>
512 <div className="font-medium">{addon.name}</div>
513 <div className="text-sm text-muted-foreground">
514 {addon.description}
515 </div>
516 </div>
517 </div>
518 <div className="flex items-center space-x-3">
519 <span className="font-medium">
520 ${addon.price}/month
521 </span>
522 <Switch
523 checked={addon.enabled}
524 onCheckedChange={() => toggleAddOn(addon.id)}
525 />
526 </div>
527 </div>
528 ))}
529 </div>
530 </CardContent>
531 </Card>
532
533 {/* Billing Summary */}
534 <Card>
535 <CardHeader>
536 <CardTitle>Billing Summary</CardTitle>
537 <CardDescription>Your next invoice breakdown</CardDescription>
538 </CardHeader>
539 <CardContent>
540 <div className="space-y-3">
541 <div className="flex justify-between">
542 <span>
543 {plans.find((p) => p.id === selectedPlan)?.name} Plan
544 </span>
545 <span>
546 ${plans.find((p) => p.id === selectedPlan)?.price}
547 </span>
548 </div>
549 {addOns
550 .filter((addon) => addon.enabled)
551 .map((addon) => (
552 <div
553 key={addon.id}
554 className="flex justify-between text-sm"
555 >
556 <span>{addon.name}</span>
557 <span>${addon.price}</span>
558 </div>
559 ))}
560 <Separator />
561 <div className="flex justify-between font-medium text-lg">
562 <span>Total</span>
563 <span>${calculateTotal()}/month</span>
564 </div>
565 <Button className="w-full mt-4">Update Plan</Button>
566 </div>
567 </CardContent>
568 </Card>
569 </TabsContent>
570
571 <TabsContent value="billing" className="space-y-6">
572 <Card>
573 <CardHeader>
574 <CardTitle>Billing History</CardTitle>
575 <CardDescription>
576 View and manage your billing records
577 </CardDescription>
578 </CardHeader>
579 <CardContent>
580 {/* Filters */}
581 <div className="flex flex-col sm:flex-row gap-4 mb-6">
582 <div className="relative flex-1">
583 <Search className="absolute left-3 top-1/2 transform -translate-y-1/2 h-4 w-4 text-muted-foreground" />
584 <Input
585 placeholder="Search billing records..."
586 value={searchTerm}
587 onChange={(e) => setSearchTerm(e.target.value)}
588 className="pl-10"
589 />
590 </div>
591 <Select value={statusFilter} onValueChange={setStatusFilter}>
592 <SelectTrigger className="w-full sm:w-48">
593 <SelectValue placeholder="Filter by status" />
594 </SelectTrigger>
595 <SelectContent>
596 <SelectItem value="all">All Status</SelectItem>
597 <SelectItem value="paid">Paid</SelectItem>
598 <SelectItem value="pending">Pending</SelectItem>
599 <SelectItem value="overdue">Overdue</SelectItem>
600 <SelectItem value="failed">Failed</SelectItem>
601 </SelectContent>
602 </Select>
603 </div>
604
605 {/* Billing Table */}
606 <div className="rounded-md border">
607 <Table>
608 <TableHeader>
609 <TableRow>
610 <TableHead>Date</TableHead>
611 <TableHead>Description</TableHead>
612 <TableHead>Amount</TableHead>
613 <TableHead>Status</TableHead>
614 <TableHead className="text-right">Actions</TableHead>
615 </TableRow>
616 </TableHeader>
617 <TableBody>
618 {filteredBillingHistory.length === 0 ? (
619 <TableRow>
620 <TableCell
621 colSpan={5}
622 className="text-center py-8 text-muted-foreground"
623 >
624 No billing records found
625 </TableCell>
626 </TableRow>
627 ) : (
628 filteredBillingHistory.map((record) => (
629 <TableRow key={record.id}>
630 <TableCell>
631 {new Date(record.date).toLocaleDateString()}
632 </TableCell>
633 <TableCell>{record.description}</TableCell>
634 <TableCell>${record.amount.toFixed(2)}</TableCell>
635 <TableCell>
636 {getStatusBadge(record.status)}
637 </TableCell>
638 <TableCell className="text-right">
639 <Button variant="ghost" size="sm">
640 <Download className="h-4 w-4" />
641 </Button>
642 </TableCell>
643 </TableRow>
644 ))
645 )}
646 </TableBody>
647 </Table>
648 </div>
649 </CardContent>
650 </Card>
651 </TabsContent>
652
653 <TabsContent value="payment" className="space-y-6">
654 <Card>
655 <CardHeader>
656 <CardTitle>Payment Methods</CardTitle>
657 <CardDescription>
658 Manage your payment methods and billing information
659 </CardDescription>
660 </CardHeader>
661 <CardContent>
662 <div className="space-y-4">
663 {paymentMethods.map((method) => (
664 <div
665 key={method.id}
666 className="flex items-center justify-between p-4 border rounded-lg"
667 >
668 <div className="flex items-center space-x-3">
669 <CreditCard className="h-8 w-8 text-muted-foreground" />
670 <div>
671 <div className="font-medium">
672 {method.brand} ending in {method.last4}
673 {method.isDefault && (
674 <Badge variant="secondary" className="ml-2">
675 Default
676 </Badge>
677 )}
678 </div>
679 {method.expiryDate && (
680 <div className="text-sm text-muted-foreground">
681 Expires {method.expiryDate}
682 </div>
683 )}
684 </div>
685 </div>
686 <div className="flex items-center space-x-2">
687 <Button variant="outline" size="sm">
688 Edit
689 </Button>
690 <Button variant="ghost" size="sm">
691 <Minus className="h-4 w-4" />
692 </Button>
693 </div>
694 </div>
695 ))}
696 <Button variant="outline" className="w-full">
697 <Plus className="h-4 w-4 mr-2" />
698 Add New Payment Method
699 </Button>
700 </div>
701 </CardContent>
702 </Card>
703 </TabsContent>
704 </Tabs>
705 </div>
706 </TooltipProvider>
707 );
708};
709
710export default B2BCustomerAdminDashboard;
Dependencies
External Libraries
lucide-reactreact
Shadcn/UI Components
alertbadgebuttoncardinputprogressselectseparatorswitchtabletabstooltip