ShadCN Vaults

Back to Blocks

B2B SaaS Billing Dashboard

Unknown Block

UnknownComponent

B2B SaaS Billing Dashboard

Advanced B2B dashboard for managing plans, add-ons, payment methods, and billing history.

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

LICENSE

MIT License

Copyright (c) 2025 Aldhaneka

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

Shadcn Vaults Project (CC BY-NC 4.0 with Internal Use Exception)

All user-submitted components in the '/blocks' directory are licensed under the Creative Commons Attribution-NonCommercial 4.0 International License (CC BY-NC 4.0),
with the following clarification and exception:

You are free to:
- Share — copy and redistribute the material in any medium or format
- Adapt — remix, transform, and build upon the material

Under these conditions:
- Attribution — You must give appropriate credit, provide a link to the license, and indicate if changes were made.
- NonCommercial — You may NOT use the material for commercial redistribution, resale, or monetization.

🚫 You MAY NOT:
- Sell or redistribute the components individually or as part of a product (e.g. a UI kit, template marketplace, SaaS component library)
- Offer the components or derivative works in any paid tool, theme pack, or design system

✅ You MAY:
- Use the components in internal company tools, dashboards, or applications that are not sold as products
- Remix or adapt components for private or enterprise projects
- Use them in open-source non-commercial projects

This license encourages sharing, learning, and internal innovation — but prohibits using these components as a basis for monetized products.

Full license text: https://creativecommons.org/licenses/by-nc/4.0/

By submitting a component, contributors agree to these terms.

Contributors

Ramiro Godoy@milogodoy

Review Form Block

Aldhaneka@Aldhanekaa

Project Creator

For questions about licensing, please contact the project maintainers.