ShadcnUI Vaults

Back to Blocks

Ultimate SaaS Billing Suite

Unknown Block

UnknownComponent

Ultimate SaaS Billing Suite

Comprehensive SaaS billing suite with navigation, plan management, add-ons, and usage tracking.

Preview

Full width desktop view

Code

bills-12.tsx
1"use client";
2
3import React, { useState, useEffect } from "react";
4import { motion, AnimatePresence } from "framer-motion";
5import {
6  Home,
7  User,
8  Settings,
9  LogOut,
10  Menu,
11  X,
12  ChevronLeft,
13  ChevronRight,
14  BarChart3,
15  FileText,
16  Bell,
17  Search,
18  HelpCircle,
19  CreditCard,
20  DollarSign,
21  Calendar,
22  Check,
23  Plus,
24  Minus,
25  AlertCircle,
26  Building,
27  Users,
28  Zap,
29  Crown,
30  Rocket,
31  Star,
32  Shield,
33} from "lucide-react";
34
35interface NavigationItem {
36  id: string;
37  name: string;
38  icon: React.ComponentType<{ className?: string }>;
39  href: string;
40  badge?: string;
41}
42
43interface Plan {
44  id: string;
45  name: string;
46  description: string;
47  price: {
48    monthly: number;
49    yearly: number;
50  };
51  features: string[];
52  icon: React.ComponentType<{ className?: string }>;
53  popular?: boolean;
54  current?: boolean;
55}
56
57interface AddOn {
58  id: string;
59  name: string;
60  description: string;
61  price: {
62    monthly: number;
63    yearly: number;
64  };
65  enabled: boolean;
66}
67
68interface Bill {
69  id: string;
70  description: string;
71  amount: number;
72  dueDate: string;
73  status: "paid" | "pending" | "overdue";
74  type: "subscription" | "addon" | "usage";
75}
76
77const navigationItems: NavigationItem[] = [
78  { id: "dashboard", name: "Dashboard", icon: Home, href: "/dashboard" },
79  {
80    id: "billing",
81    name: "Billing & Plans",
82    icon: CreditCard,
83    href: "/billing",
84    badge: "2",
85  },
86  { id: "analytics", name: "Analytics", icon: BarChart3, href: "/analytics" },
87  { id: "users", name: "Team Members", icon: Users, href: "/users" },
88  { id: "settings", name: "Settings", icon: Settings, href: "/settings" },
89  { id: "help", name: "Help & Support", icon: HelpCircle, href: "/help" },
90];
91
92const plans: Plan[] = [
93  {
94    id: "starter",
95    name: "Starter",
96    description: "Perfect for small teams getting started",
97    price: { monthly: 29, yearly: 290 },
98    features: [
99      "Up to 5 team members",
100      "10GB storage",
101      "Basic analytics",
102      "Email support",
103      "Standard integrations",
104    ],
105    icon: Zap,
106    current: false,
107  },
108  {
109    id: "professional",
110    name: "Professional",
111    description: "Advanced features for growing businesses",
112    price: { monthly: 99, yearly: 990 },
113    features: [
114      "Up to 25 team members",
115      "100GB storage",
116      "Advanced analytics",
117      "Priority support",
118      "Premium integrations",
119      "Custom workflows",
120      "API access",
121    ],
122    icon: Crown,
123    popular: true,
124    current: true,
125  },
126  {
127    id: "enterprise",
128    name: "Enterprise",
129    description: "Complete solution for large organizations",
130    price: { monthly: 299, yearly: 2990 },
131    features: [
132      "Unlimited team members",
133      "1TB storage",
134      "Enterprise analytics",
135      "24/7 dedicated support",
136      "Custom integrations",
137      "Advanced security",
138      "SLA guarantee",
139      "White-label options",
140    ],
141    icon: Rocket,
142    current: false,
143  },
144];
145
146const addOns: AddOn[] = [
147  {
148    id: "extra-storage",
149    name: "Extra Storage",
150    description: "Additional 100GB storage space",
151    price: { monthly: 15, yearly: 150 },
152    enabled: true,
153  },
154  {
155    id: "advanced-security",
156    name: "Advanced Security",
157    description: "Enhanced security features and compliance",
158    price: { monthly: 25, yearly: 250 },
159    enabled: false,
160  },
161  {
162    id: "priority-support",
163    name: "Priority Support",
164    description: "24/7 priority customer support",
165    price: { monthly: 20, yearly: 200 },
166    enabled: true,
167  },
168];
169
170const bills: Bill[] = [
171  {
172    id: "1",
173    description: "Professional Plan - Monthly",
174    amount: 99,
175    dueDate: "2024-02-15",
176    status: "pending",
177    type: "subscription",
178  },
179  {
180    id: "2",
181    description: "Extra Storage Add-on",
182    amount: 15,
183    dueDate: "2024-02-15",
184    status: "pending",
185    type: "addon",
186  },
187  {
188    id: "3",
189    description: "Priority Support Add-on",
190    amount: 20,
191    dueDate: "2024-02-15",
192    status: "pending",
193    type: "addon",
194  },
195  {
196    id: "4",
197    description: "Professional Plan - January",
198    amount: 99,
199    dueDate: "2024-01-15",
200    status: "paid",
201    type: "subscription",
202  },
203];
204
205function Sidebar({
206  className = "",
207  activeItem,
208  setActiveItem,
209}: {
210  className?: string;
211  activeItem: string;
212  setActiveItem: (item: string) => void;
213}) {
214  const [isOpen, setIsOpen] = useState(false);
215  const [isCollapsed, setIsCollapsed] = useState(false);
216
217  useEffect(() => {
218    const handleResize = () => {
219      if (window.innerWidth >= 768) {
220        setIsOpen(true);
221      } else {
222        setIsOpen(false);
223      }
224    };
225
226    handleResize();
227    window.addEventListener("resize", handleResize);
228    return () => window.removeEventListener("resize", handleResize);
229  }, []);
230
231  const toggleSidebar = () => setIsOpen(!isOpen);
232  const toggleCollapse = () => setIsCollapsed(!isCollapsed);
233
234  const handleItemClick = (itemId: string) => {
235    setActiveItem(itemId);
236    if (window.innerWidth < 768) {
237      setIsOpen(false);
238    }
239  };
240
241  return (
242    <>
243      <button
244        onClick={toggleSidebar}
245        className="fixed top-6 left-6 z-50 p-3 rounded-lg bg-background shadow-md border border-border md:hidden hover:bg-accent transition-all duration-200"
246        aria-label="Toggle sidebar"
247      >
248        {isOpen ? (
249          <X className="h-5 w-5 text-foreground" />
250        ) : (
251          <Menu className="h-5 w-5 text-foreground" />
252        )}
253      </button>
254
255      {isOpen && (
256        <div
257          className="fixed inset-0 bg-black/40 backdrop-blur-sm z-30 md:hidden transition-opacity duration-300"
258          onClick={toggleSidebar}
259        />
260      )}
261
262      <div
263        className={`
264          fixed top-0 left-0 h-full bg-background border-r border-border z-40 transition-all duration-300 ease-in-out flex flex-col
265          ${isOpen ? "translate-x-0" : "-translate-x-full"}
266          ${isCollapsed ? "w-20" : "w-72"}
267          md:translate-x-0 md:static md:z-auto
268          ${className}
269        `}
270      >
271        <div className="flex items-center justify-between p-5 border-b border-border bg-muted/30">
272          {!isCollapsed && (
273            <div className="flex items-center space-x-2.5">
274              <div className="w-9 h-9 bg-primary rounded-lg flex items-center justify-center shadow-sm">
275                <Building className="text-primary-foreground h-5 w-5" />
276              </div>
277              <div className="flex flex-col">
278                <span className="font-semibold text-foreground text-base">
279                  B2B Admin
280                </span>
281                <span className="text-xs text-muted-foreground">
282                  Customer Portal
283                </span>
284              </div>
285            </div>
286          )}
287
288          {isCollapsed && (
289            <div className="w-9 h-9 bg-primary rounded-lg flex items-center justify-center mx-auto shadow-sm">
290              <Building className="text-primary-foreground h-5 w-5" />
291            </div>
292          )}
293
294          <button
295            onClick={toggleCollapse}
296            className="hidden md:flex p-1.5 rounded-md hover:bg-accent transition-all duration-200"
297            aria-label={isCollapsed ? "Expand sidebar" : "Collapse sidebar"}
298          >
299            {isCollapsed ? (
300              <ChevronRight className="h-4 w-4 text-muted-foreground" />
301            ) : (
302              <ChevronLeft className="h-4 w-4 text-muted-foreground" />
303            )}
304          </button>
305        </div>
306
307        {!isCollapsed && (
308          <div className="px-4 py-3">
309            <div className="relative">
310              <Search className="absolute left-3 top-1/2 transform -translate-y-1/2 h-3.5 w-3.5 text-muted-foreground" />
311              <input
312                type="text"
313                placeholder="Search..."
314                className="w-full pl-9 pr-4 py-2 bg-muted border border-border rounded-md text-sm placeholder-muted-foreground focus:outline-none focus:ring-2 focus:ring-ring focus:border-transparent transition-all duration-200"
315              />
316            </div>
317          </div>
318        )}
319
320        <nav className="flex-1 px-3 py-2 overflow-y-auto">
321          <ul className="space-y-0.5">
322            {navigationItems.map((item) => {
323              const Icon = item.icon;
324              const isActive = activeItem === item.id;
325
326              return (
327                <li key={item.id}>
328                  <button
329                    onClick={() => handleItemClick(item.id)}
330                    className={`
331                      w-full flex items-center space-x-2.5 px-3 py-2.5 rounded-md text-left transition-all duration-200 group relative
332                      ${
333                        isActive
334                          ? "bg-primary/10 text-primary"
335                          : "text-muted-foreground hover:bg-accent hover:text-foreground"
336                      }
337                      ${isCollapsed ? "justify-center px-2" : ""}
338                    `}
339                    title={isCollapsed ? item.name : undefined}
340                  >
341                    <div className="flex items-center justify-center min-w-[24px]">
342                      <Icon
343                        className={`
344                          h-4.5 w-4.5 flex-shrink-0
345                          ${
346                            isActive
347                              ? "text-primary"
348                              : "text-muted-foreground group-hover:text-foreground"
349                          }
350                        `}
351                      />
352                    </div>
353
354                    {!isCollapsed && (
355                      <div className="flex items-center justify-between w-full">
356                        <span
357                          className={`text-sm ${
358                            isActive ? "font-medium" : "font-normal"
359                          }`}
360                        >
361                          {item.name}
362                        </span>
363                        {item.badge && (
364                          <span
365                            className={`
366                            px-1.5 py-0.5 text-xs font-medium rounded-full
367                            ${
368                              isActive
369                                ? "bg-primary/20 text-primary"
370                                : "bg-muted text-muted-foreground"
371                            }
372                          `}
373                          >
374                            {item.badge}
375                          </span>
376                        )}
377                      </div>
378                    )}
379
380                    {isCollapsed && item.badge && (
381                      <div className="absolute top-1 right-1 w-4 h-4 flex items-center justify-center rounded-full bg-primary/20 border border-background">
382                        <span className="text-[10px] font-medium text-primary">
383                          {parseInt(item.badge) > 9 ? "9+" : item.badge}
384                        </span>
385                      </div>
386                    )}
387                  </button>
388                </li>
389              );
390            })}
391          </ul>
392        </nav>
393
394        <div className="mt-auto border-t border-border">
395          <div
396            className={`border-b border-border bg-muted/30 ${
397              isCollapsed ? "py-3 px-2" : "p-3"
398            }`}
399          >
400            {!isCollapsed ? (
401              <div className="flex items-center px-3 py-2 rounded-md bg-background hover:bg-accent transition-colors duration-200">
402                <div className="w-8 h-8 bg-muted rounded-full flex items-center justify-center">
403                  <span className="text-foreground font-medium text-sm">
404                    JD
405                  </span>
406                </div>
407                <div className="flex-1 min-w-0 ml-2.5">
408                  <p className="text-sm font-medium text-foreground truncate">
409                    John Doe
410                  </p>
411                  <p className="text-xs text-muted-foreground truncate">
412                    Admin User
413                  </p>
414                </div>
415                <div
416                  className="w-2 h-2 bg-green-500 rounded-full ml-2"
417                  title="Online"
418                />
419              </div>
420            ) : (
421              <div className="flex justify-center">
422                <div className="relative">
423                  <div className="w-9 h-9 bg-muted rounded-full flex items-center justify-center">
424                    <span className="text-foreground font-medium text-sm">
425                      JD
426                    </span>
427                  </div>
428                  <div className="absolute -bottom-1 -right-1 w-3 h-3 bg-green-500 rounded-full border-2 border-background" />
429                </div>
430              </div>
431            )}
432          </div>
433
434          <div className="p-3">
435            <button
436              className={`
437                w-full flex items-center rounded-md text-left transition-all duration-200 group
438                text-destructive hover:bg-destructive/10 hover:text-destructive
439                ${
440                  isCollapsed
441                    ? "justify-center p-2.5"
442                    : "space-x-2.5 px-3 py-2.5"
443                }
444              `}
445              title={isCollapsed ? "Logout" : undefined}
446            >
447              <div className="flex items-center justify-center min-w-[24px]">
448                <LogOut className="h-4.5 w-4.5 flex-shrink-0 text-destructive" />
449              </div>
450
451              {!isCollapsed && <span className="text-sm">Logout</span>}
452            </button>
453          </div>
454        </div>
455      </div>
456    </>
457  );
458}
459
460function BillingDashboard() {
461  const [isYearly, setIsYearly] = useState(false);
462  const [selectedPlan, setSelectedPlan] = useState("professional");
463  const [enabledAddOns, setEnabledAddOns] = useState<string[]>([
464    "extra-storage",
465    "priority-support",
466  ]);
467
468  const currentPlan = plans.find((plan) => plan.current);
469  const pendingBills = bills.filter((bill) => bill.status === "pending");
470  const totalPending = pendingBills.reduce((sum, bill) => sum + bill.amount, 0);
471
472  const toggleAddOn = (addOnId: string) => {
473    setEnabledAddOns((prev) =>
474      prev.includes(addOnId)
475        ? prev.filter((id) => id !== addOnId)
476        : [...prev, addOnId]
477    );
478  };
479
480  const calculateTotal = () => {
481    const plan = plans.find((p) => p.id === selectedPlan);
482    const planPrice = plan
483      ? isYearly
484        ? plan.price.yearly
485        : plan.price.monthly
486      : 0;
487    const addOnPrice = addOns
488      .filter((addon) => enabledAddOns.includes(addon.id))
489      .reduce(
490        (sum, addon) =>
491          sum + (isYearly ? addon.price.yearly : addon.price.monthly),
492        0
493      );
494    return planPrice + addOnPrice;
495  };
496
497  return (
498    <div className="p-6 space-y-8">
499      {/* Header */}
500      <div className="flex flex-col lg:flex-row lg:items-center lg:justify-between gap-4">
501        <div>
502          <h1 className="text-3xl font-bold text-foreground">
503            Billing & Plans
504          </h1>
505          <p className="text-muted-foreground mt-1">
506            Manage your subscription and billing information
507          </p>
508        </div>
509
510        <div className="flex items-center gap-4">
511          <div className="flex items-center gap-2 text-sm">
512            <span
513              className={
514                !isYearly
515                  ? "text-foreground font-medium"
516                  : "text-muted-foreground"
517              }
518            >
519              Monthly
520            </span>
521            <button
522              onClick={() => setIsYearly(!isYearly)}
523              className={`relative w-12 h-6 rounded-full transition-colors ${
524                isYearly ? "bg-primary" : "bg-muted"
525              }`}
526            >
527              <div
528                className={`absolute top-0.5 w-5 h-5 bg-background rounded-full shadow-sm transition-transform ${
529                  isYearly ? "translate-x-6" : "translate-x-0.5"
530                }`}
531              />
532            </button>
533            <span
534              className={
535                isYearly
536                  ? "text-foreground font-medium"
537                  : "text-muted-foreground"
538              }
539            >
540              Yearly
541            </span>
542            {isYearly && (
543              <span className="px-2 py-1 bg-green-100 text-green-700 text-xs rounded-full font-medium">
544                Save 20%
545              </span>
546            )}
547          </div>
548        </div>
549      </div>
550
551      {/* Current Plan & Bills Overview */}
552      <div className="grid grid-cols-1 lg:grid-cols-3 gap-6">
553        {/* Current Plan */}
554        <div className="lg:col-span-2">
555          <div className="bg-background border border-border rounded-lg p-6">
556            <div className="flex items-center justify-between mb-4">
557              <h2 className="text-xl font-semibold text-foreground">
558                Current Plan
559              </h2>
560              {currentPlan?.popular && (
561                <span className="px-3 py-1 bg-primary/10 text-primary text-sm rounded-full font-medium">
562                  Most Popular
563                </span>
564              )}
565            </div>
566
567            {currentPlan && (
568              <div className="space-y-4">
569                <div className="flex items-center gap-3">
570                  <div className="w-12 h-12 bg-primary/10 rounded-lg flex items-center justify-center">
571                    <currentPlan.icon className="w-6 h-6 text-primary" />
572                  </div>
573                  <div>
574                    <h3 className="text-lg font-semibold text-foreground">
575                      {currentPlan.name}
576                    </h3>
577                    <p className="text-muted-foreground">
578                      {currentPlan.description}
579                    </p>
580                  </div>
581                </div>
582
583                <div className="flex items-baseline gap-2">
584                  <span className="text-3xl font-bold text-foreground">
585                    $
586                    {isYearly
587                      ? currentPlan.price.yearly
588                      : currentPlan.price.monthly}
589                  </span>
590                  <span className="text-muted-foreground">
591                    /{isYearly ? "year" : "month"}
592                  </span>
593                </div>
594
595                <div className="grid grid-cols-1 md:grid-cols-2 gap-3">
596                  {currentPlan.features.map((feature, index) => (
597                    <div key={index} className="flex items-center gap-2">
598                      <Check className="w-4 h-4 text-green-500 flex-shrink-0" />
599                      <span className="text-sm text-muted-foreground">
600                        {feature}
601                      </span>
602                    </div>
603                  ))}
604                </div>
605              </div>
606            )}
607          </div>
608        </div>
609
610        {/* Pending Bills */}
611        <div className="bg-background border border-border rounded-lg p-6">
612          <h2 className="text-xl font-semibold text-foreground mb-4">
613            Pending Bills
614          </h2>
615
616          <div className="space-y-3">
617            <div className="flex items-center justify-between p-3 bg-orange-50 border border-orange-200 rounded-lg">
618              <div className="flex items-center gap-2">
619                <AlertCircle className="w-4 h-4 text-orange-500" />
620                <span className="text-sm font-medium text-orange-700">
621                  Total Due
622                </span>
623              </div>
624              <span className="text-lg font-bold text-orange-700">
625                ${totalPending}
626              </span>
627            </div>
628
629            {pendingBills.map((bill) => (
630              <div
631                key={bill.id}
632                className="flex items-center justify-between py-2"
633              >
634                <div>
635                  <p className="text-sm font-medium text-foreground">
636                    {bill.description}
637                  </p>
638                  <p className="text-xs text-muted-foreground">
639                    Due: {bill.dueDate}
640                  </p>
641                </div>
642                <span className="text-sm font-medium text-foreground">
643                  ${bill.amount}
644                </span>
645              </div>
646            ))}
647
648            <button className="w-full mt-4 bg-primary text-primary-foreground py-2 px-4 rounded-md hover:bg-primary/90 transition-colors">
649              Pay Now
650            </button>
651          </div>
652        </div>
653      </div>
654
655      {/* Available Plans */}
656      <div>
657        <h2 className="text-2xl font-semibold text-foreground mb-6">
658          Available Plans
659        </h2>
660
661        <div className="grid grid-cols-1 md:grid-cols-3 gap-6">
662          {plans.map((plan) => (
663            <motion.div
664              key={plan.id}
665              className={`relative bg-background border rounded-lg p-6 cursor-pointer transition-all ${
666                selectedPlan === plan.id
667                  ? "border-primary ring-2 ring-primary/20"
668                  : "border-border hover:border-primary/50"
669              } ${
670                plan.current ? "ring-2 ring-green-500/20 border-green-500" : ""
671              }`}
672              onClick={() => setSelectedPlan(plan.id)}
673              whileHover={{ scale: 1.02 }}
674              whileTap={{ scale: 0.98 }}
675            >
676              {plan.popular && (
677                <div className="absolute -top-3 left-1/2 transform -translate-x-1/2">
678                  <span className="bg-primary text-primary-foreground px-3 py-1 rounded-full text-xs font-medium">
679                    Most Popular
680                  </span>
681                </div>
682              )}
683
684              {plan.current && (
685                <div className="absolute -top-3 right-4">
686                  <span className="bg-green-500 text-white px-3 py-1 rounded-full text-xs font-medium">
687                    Current Plan
688                  </span>
689                </div>
690              )}
691
692              <div className="flex items-center gap-3 mb-4">
693                <div
694                  className={`w-10 h-10 rounded-lg flex items-center justify-center ${
695                    selectedPlan === plan.id ? "bg-primary/10" : "bg-muted"
696                  }`}
697                >
698                  <plan.icon
699                    className={`w-5 h-5 ${
700                      selectedPlan === plan.id
701                        ? "text-primary"
702                        : "text-muted-foreground"
703                    }`}
704                  />
705                </div>
706                <div>
707                  <h3 className="text-lg font-semibold text-foreground">
708                    {plan.name}
709                  </h3>
710                  <p className="text-sm text-muted-foreground">
711                    {plan.description}
712                  </p>
713                </div>
714              </div>
715
716              <div className="mb-4">
717                <div className="flex items-baseline gap-2">
718                  <span className="text-2xl font-bold text-foreground">
719                    ${isYearly ? plan.price.yearly : plan.price.monthly}
720                  </span>
721                  <span className="text-muted-foreground">
722                    /{isYearly ? "year" : "month"}
723                  </span>
724                </div>
725                {isYearly && (
726                  <p className="text-sm text-green-600 mt-1">
727                    Save ${plan.price.monthly * 12 - plan.price.yearly} per year
728                  </p>
729                )}
730              </div>
731
732              <div className="space-y-2">
733                {plan.features.slice(0, 4).map((feature, index) => (
734                  <div key={index} className="flex items-center gap-2">
735                    <Check className="w-3 h-3 text-green-500 flex-shrink-0" />
736                    <span className="text-xs text-muted-foreground">
737                      {feature}
738                    </span>
739                  </div>
740                ))}
741                {plan.features.length > 4 && (
742                  <p className="text-xs text-muted-foreground">
743                    +{plan.features.length - 4} more features
744                  </p>
745                )}
746              </div>
747
748              {!plan.current && (
749                <button
750                  className={`w-full mt-4 py-2 px-4 rounded-md transition-colors ${
751                    selectedPlan === plan.id
752                      ? "bg-primary text-primary-foreground hover:bg-primary/90"
753                      : "bg-muted text-muted-foreground hover:bg-muted/80"
754                  }`}
755                >
756                  {selectedPlan === plan.id ? "Selected" : "Select Plan"}
757                </button>
758              )}
759            </motion.div>
760          ))}
761        </div>
762      </div>
763
764      {/* Add-ons */}
765      <div>
766        <h2 className="text-2xl font-semibold text-foreground mb-6">Add-ons</h2>
767
768        <div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
769          {addOns.map((addon) => (
770            <div
771              key={addon.id}
772              className="bg-background border border-border rounded-lg p-6"
773            >
774              <div className="flex items-center justify-between mb-4">
775                <div>
776                  <h3 className="text-lg font-semibold text-foreground">
777                    {addon.name}
778                  </h3>
779                  <p className="text-sm text-muted-foreground">
780                    {addon.description}
781                  </p>
782                </div>
783                <button
784                  onClick={() => toggleAddOn(addon.id)}
785                  className={`relative w-10 h-6 rounded-full transition-colors ${
786                    enabledAddOns.includes(addon.id) ? "bg-primary" : "bg-muted"
787                  }`}
788                >
789                  <div
790                    className={`absolute top-0.5 w-5 h-5 bg-background rounded-full shadow-sm transition-transform ${
791                      enabledAddOns.includes(addon.id)
792                        ? "translate-x-4"
793                        : "translate-x-0.5"
794                    }`}
795                  />
796                </button>
797              </div>
798
799              <div className="flex items-baseline gap-2">
800                <span className="text-xl font-bold text-foreground">
801                  ${isYearly ? addon.price.yearly : addon.price.monthly}
802                </span>
803                <span className="text-muted-foreground">
804                  /{isYearly ? "year" : "month"}
805                </span>
806              </div>
807            </div>
808          ))}
809        </div>
810      </div>
811
812      {/* Total Cost Summary */}
813      <div className="bg-muted/50 border border-border rounded-lg p-6">
814        <h2 className="text-xl font-semibold text-foreground mb-4">
815          Cost Summary
816        </h2>
817
818        <div className="space-y-3">
819          <div className="flex items-center justify-between">
820            <span className="text-foreground">
821              {plans.find((p) => p.id === selectedPlan)?.name} Plan
822            </span>
823            <span className="font-medium text-foreground">
824              $
825              {plans.find((p) => p.id === selectedPlan)
826                ? isYearly
827                  ? plans.find((p) => p.id === selectedPlan)!.price.yearly
828                  : plans.find((p) => p.id === selectedPlan)!.price.monthly
829                : 0}
830            </span>
831          </div>
832
833          {addOns
834            .filter((addon) => enabledAddOns.includes(addon.id))
835            .map((addon) => (
836              <div key={addon.id} className="flex items-center justify-between">
837                <span className="text-muted-foreground">{addon.name}</span>
838                <span className="text-foreground">
839                  ${isYearly ? addon.price.yearly : addon.price.monthly}
840                </span>
841              </div>
842            ))}
843
844          <div className="border-t border-border pt-3">
845            <div className="flex items-center justify-between">
846              <span className="text-lg font-semibold text-foreground">
847                Total {isYearly ? "Yearly" : "Monthly"}
848              </span>
849              <span className="text-2xl font-bold text-foreground">
850                ${calculateTotal()}
851              </span>
852            </div>
853          </div>
854        </div>
855
856        <button className="w-full mt-6 bg-primary text-primary-foreground py-3 px-4 rounded-md hover:bg-primary/90 transition-colors font-medium">
857          Update Plan
858        </button>
859      </div>
860
861      {/* Recent Bills */}
862      <div>
863        <h2 className="text-2xl font-semibold text-foreground mb-6">
864          Recent Bills
865        </h2>
866
867        <div className="bg-background border border-border rounded-lg overflow-hidden">
868          <div className="overflow-x-auto">
869            <table className="w-full">
870              <thead className="bg-muted/50">
871                <tr>
872                  <th className="text-left py-3 px-4 font-medium text-foreground">
873                    Description
874                  </th>
875                  <th className="text-left py-3 px-4 font-medium text-foreground">
876                    Amount
877                  </th>
878                  <th className="text-left py-3 px-4 font-medium text-foreground">
879                    Due Date
880                  </th>
881                  <th className="text-left py-3 px-4 font-medium text-foreground">
882                    Status
883                  </th>
884                  <th className="text-left py-3 px-4 font-medium text-foreground">
885                    Type
886                  </th>
887                </tr>
888              </thead>
889              <tbody>
890                {bills.map((bill) => (
891                  <tr key={bill.id} className="border-t border-border">
892                    <td className="py-3 px-4 text-foreground">
893                      {bill.description}
894                    </td>
895                    <td className="py-3 px-4 text-foreground font-medium">
896                      ${bill.amount}
897                    </td>
898                    <td className="py-3 px-4 text-muted-foreground">
899                      {bill.dueDate}
900                    </td>
901                    <td className="py-3 px-4">
902                      <span
903                        className={`px-2 py-1 rounded-full text-xs font-medium ${
904                          bill.status === "paid"
905                            ? "bg-green-100 text-green-700"
906                            : bill.status === "pending"
907                            ? "bg-orange-100 text-orange-700"
908                            : "bg-red-100 text-red-700"
909                        }`}
910                      >
911                        {bill.status.charAt(0).toUpperCase() +
912                          bill.status.slice(1)}
913                      </span>
914                    </td>
915                    <td className="py-3 px-4 text-muted-foreground capitalize">
916                      {bill.type}
917                    </td>
918                  </tr>
919                ))}
920              </tbody>
921            </table>
922          </div>
923        </div>
924      </div>
925    </div>
926  );
927}
928
929function CustomerAdminDashboard() {
930  const [activeItem, setActiveItem] = useState("billing");
931
932  return (
933    <div className="flex h-screen bg-background">
934      <Sidebar activeItem={activeItem} setActiveItem={setActiveItem} />
935
936      <div className="flex-1 overflow-auto">
937        <div className="md:ml-72">
938          {activeItem === "billing" && <BillingDashboard />}
939          {activeItem === "dashboard" && (
940            <div className="p-6">
941              <h1 className="text-3xl font-bold text-foreground">Dashboard</h1>
942              <p className="text-muted-foreground mt-2">
943                Welcome to your admin dashboard
944              </p>
945            </div>
946          )}
947          {activeItem !== "billing" && activeItem !== "dashboard" && (
948            <div className="p-6">
949              <h1 className="text-3xl font-bold text-foreground capitalize">
950                {activeItem}
951              </h1>
952              <p className="text-muted-foreground mt-2">
953                This section is under development
954              </p>
955            </div>
956          )}
957        </div>
958      </div>
959    </div>
960  );
961}
962
963export default CustomerAdminDashboard;

Dependencies

External Libraries

framer-motionlucide-reactreact

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.