@umituz/web-dashboard 2.2.0 → 2.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@umituz/web-dashboard",
3
- "version": "2.2.0",
3
+ "version": "2.3.0",
4
4
  "description": "Dashboard Layout System - Customizable, themeable dashboard layouts and settings",
5
5
  "main": "./src/index.ts",
6
6
  "types": "./src/index.ts",
@@ -33,6 +33,11 @@
33
33
  "./analytics/hooks": "./src/domains/analytics/hooks/index.ts",
34
34
  "./analytics/utils": "./src/domains/analytics/utils/index.ts",
35
35
  "./analytics/types": "./src/domains/analytics/types/index.ts",
36
+ "./billing": "./src/domains/billing/index.ts",
37
+ "./billing/components": "./src/domains/billing/components/index.ts",
38
+ "./billing/hooks": "./src/domains/billing/hooks/index.ts",
39
+ "./billing/utils": "./src/domains/billing/utils/index.ts",
40
+ "./billing/types": "./src/domains/billing/types/index.ts",
36
41
  "./package.json": "./package.json"
37
42
  },
38
43
  "files": [
@@ -85,6 +90,12 @@
85
90
  "kpi",
86
91
  "visualization",
87
92
  "recharts",
93
+ "billing",
94
+ "subscription",
95
+ "payment",
96
+ "invoices",
97
+ "pricing",
98
+ "plans",
88
99
  "react",
89
100
  "typescript",
90
101
  "components",
@@ -0,0 +1,63 @@
1
+ /**
2
+ * Billing Layout Component
3
+ *
4
+ * Layout wrapper for billing pages
5
+ */
6
+
7
+ import { BrandLogo } from "../../layouts/components";
8
+ import type { BillingLayoutProps } from "../types/billing";
9
+
10
+ export const BillingLayout = ({ config, children }: BillingLayoutProps) => {
11
+ return (
12
+ <div className="min-h-screen bg-background flex flex-col">
13
+ {/* Header */}
14
+ <header className="border-b border-border px-6 py-4 flex items-center justify-between">
15
+ <div className="flex items-center gap-2">
16
+ <BrandLogo size={28} />
17
+ <span className="font-bold text-xl text-foreground">{config.brandName}</span>
18
+ </div>
19
+ <a
20
+ href="/dashboard"
21
+ className="text-sm text-muted-foreground hover:text-foreground transition-colors"
22
+ >
23
+ Back to Dashboard
24
+ </a>
25
+ </header>
26
+
27
+ {/* Main Content */}
28
+ <main className="flex-1 container max-w-6xl mx-auto px-4 py-12">
29
+ {/* Page Title */}
30
+ <div className="mb-8">
31
+ <h1 className="text-3xl font-bold text-foreground mb-2">
32
+ Billing & Subscription
33
+ </h1>
34
+ <p className="text-muted-foreground">
35
+ Manage your subscription, payment methods, and invoices
36
+ </p>
37
+ </div>
38
+
39
+ {/* Children Content */}
40
+ {children}
41
+ </main>
42
+
43
+ {/* Support Link */}
44
+ {config.supportEmail && (
45
+ <footer className="border-t border-border px-6 py-4">
46
+ <div className="container max-w-6xl mx-auto text-center">
47
+ <p className="text-sm text-muted-foreground">
48
+ Need help? Contact{" "}
49
+ <a
50
+ href={`mailto:${config.supportEmail}`}
51
+ className="text-primary hover:underline"
52
+ >
53
+ {config.supportEmail}
54
+ </a>
55
+ </p>
56
+ </div>
57
+ </footer>
58
+ )}
59
+ </div>
60
+ );
61
+ };
62
+
63
+ export default BillingLayout;
@@ -0,0 +1,198 @@
1
+ /**
2
+ * Billing Portal Component
3
+ *
4
+ * Main billing portal with tabs
5
+ */
6
+
7
+ import { CreditCard, FileText, BarChart3, Settings, Loader2, AlertCircle } from "lucide-react";
8
+ import { cn } from "@umituz/web-design-system/utils";
9
+ import { Button } from "@umituz/web-design-system/atoms";
10
+ import type { BillingPortalProps } from "../types/billing";
11
+ import { UsageCard } from "./UsageCard";
12
+ import { PaymentMethodsList } from "./PaymentMethodsList";
13
+ import { InvoiceCard } from "./InvoiceCard";
14
+ import { getDaysRemaining, getStatusColor, getStatusLabel, formatPrice } from "../utils/billing";
15
+
16
+ export const BillingPortal = ({
17
+ billing,
18
+ loading = false,
19
+ error,
20
+ showTabs = true,
21
+ activeTab = "overview",
22
+ onTabChange,
23
+ }: BillingPortalProps) => {
24
+ const tabs = [
25
+ { id: "overview", label: "Overview", icon: BarChart3 },
26
+ { id: "payment-methods", label: "Payment Methods", icon: CreditCard },
27
+ { id: "invoices", label: "Invoices", icon: FileText },
28
+ { id: "plan", label: "Plan", icon: Settings },
29
+ ];
30
+
31
+ if (loading) {
32
+ return (
33
+ <div className="flex items-center justify-center py-24">
34
+ <Loader2 className="h-12 w-12 animate-spin text-muted-foreground" />
35
+ </div>
36
+ );
37
+ }
38
+
39
+ if (error) {
40
+ return (
41
+ <div className="flex items-center justify-center py-24 gap-4 text-destructive">
42
+ <AlertCircle className="h-6 w-6" />
43
+ <p>{error}</p>
44
+ </div>
45
+ );
46
+ }
47
+
48
+ if (!billing) return null;
49
+
50
+ const renderContent = () => {
51
+ switch (activeTab) {
52
+ case "overview":
53
+ return (
54
+ <div className="space-y-6">
55
+ {/* Current Subscription */}
56
+ <div className="p-6 rounded-xl border border-border bg-background">
57
+ <h3 className="text-lg font-semibold text-foreground mb-4">
58
+ Current Subscription
59
+ </h3>
60
+ <div className="flex items-start justify-between">
61
+ <div>
62
+ <p className="text-2xl font-bold text-foreground">
63
+ {billing.subscription.plan.name}
64
+ </p>
65
+ <p className={cn("text-sm font-medium", getStatusColor(billing.subscription.status))}>
66
+ {getStatusLabel(billing.subscription.status)}
67
+ </p>
68
+ <p className="text-sm text-muted-foreground mt-1">
69
+ {formatPrice(
70
+ getPlanPrice(billing.subscription.plan, billing.subscription.cycle),
71
+ billing.subscription.plan.currency
72
+ )}
73
+ /{billing.subscription.cycle}
74
+ </p>
75
+ </div>
76
+ <div className="text-right">
77
+ {billing.subscription.status === "trialing" && billing.subscription.trialEnd && (
78
+ <p className="text-sm text-muted-foreground">
79
+ {getDaysRemaining(billing.subscription.trialEnd)} days left in trial
80
+ </p>
81
+ )}
82
+ {billing.upcomingInvoice && (
83
+ <p className="text-sm text-muted-foreground">
84
+ Next billing:{" "}
85
+ {new Date(billing.upcomingInvoice.date).toLocaleDateString()}
86
+ </p>
87
+ )}
88
+ </div>
89
+ </div>
90
+ </div>
91
+
92
+ {/* Usage Metrics */}
93
+ {billing.usage.length > 0 && (
94
+ <div className="grid grid-cols-1 md:grid-cols-2 gap-4">
95
+ {billing.usage.map((metric) => (
96
+ <UsageCard key={metric.id} metric={metric} />
97
+ ))}
98
+ </div>
99
+ )}
100
+
101
+ {/* Upcoming Invoice */}
102
+ {billing.upcomingInvoice && (
103
+ <div className="p-6 rounded-xl border border-border bg-background">
104
+ <h3 className="text-lg font-semibold text-foreground mb-2">
105
+ Upcoming Invoice
106
+ </h3>
107
+ <div className="flex items-center justify-between">
108
+ <div>
109
+ <p className="text-3xl font-bold text-foreground">
110
+ {formatPrice(billing.upcomingInvoice.amount, billing.upcomingInvoice.currency)}
111
+ </p>
112
+ <p className="text-sm text-muted-foreground">
113
+ Due {new Date(billing.upcomingInvoice.date).toLocaleDateString()}
114
+ </p>
115
+ </div>
116
+ </div>
117
+ </div>
118
+ )}
119
+ </div>
120
+ );
121
+
122
+ case "payment-methods":
123
+ return (
124
+ <PaymentMethodsList
125
+ paymentMethods={billing.paymentMethods}
126
+ onAddNew={() => console.log("Add payment method")}
127
+ onSetDefault={(id) => console.log("Set default:", id)}
128
+ onRemove={(id) => console.log("Remove:", id)}
129
+ />
130
+ );
131
+
132
+ case "invoices":
133
+ return (
134
+ <div className="space-y-4">
135
+ {billing.recentInvoices.length > 0 ? (
136
+ billing.recentInvoices.map((invoice) => (
137
+ <InvoiceCard
138
+ key={invoice.id}
139
+ invoice={invoice}
140
+ onClick={(inv) => console.log("View invoice:", inv)}
141
+ />
142
+ ))
143
+ ) : (
144
+ <div className="text-center py-12 text-muted-foreground">
145
+ <FileText className="h-12 w-12 mx-auto mb-4 opacity-50" />
146
+ <p>No invoices yet</p>
147
+ </div>
148
+ )}
149
+ </div>
150
+ );
151
+
152
+ case "plan":
153
+ return (
154
+ <div className="text-center py-12">
155
+ <p className="text-muted-foreground">Plan management coming soon...</p>
156
+ </div>
157
+ );
158
+
159
+ default:
160
+ return null;
161
+ }
162
+ };
163
+
164
+ return (
165
+ <div className="w-full">
166
+ {/* Tabs */}
167
+ {showTabs && (
168
+ <div className="border-b border-border mb-6">
169
+ <div className="flex gap-6">
170
+ {tabs.map((tab) => {
171
+ const Icon = tab.icon;
172
+ return (
173
+ <button
174
+ key={tab.id}
175
+ onClick={() => onTabChange?.(tab.id)}
176
+ className={cn(
177
+ "flex items-center gap-2 pb-4 border-b-2 transition-colors",
178
+ activeTab === tab.id
179
+ ? "border-primary text-foreground"
180
+ : "border-transparent text-muted-foreground hover:text-foreground"
181
+ )}
182
+ >
183
+ <Icon className="h-4 w-4" />
184
+ <span className="font-medium">{tab.label}</span>
185
+ </button>
186
+ );
187
+ })}
188
+ </div>
189
+ </div>
190
+ )}
191
+
192
+ {/* Content */}
193
+ {renderContent()}
194
+ </div>
195
+ );
196
+ };
197
+
198
+ export default BillingPortal;
@@ -0,0 +1,143 @@
1
+ /**
2
+ * Invoice Card Component
3
+ *
4
+ * Display invoice information
5
+ */
6
+
7
+ import { FileText, Download, ExternalLink } from "lucide-react";
8
+ import { cn } from "@umituz/web-design-system/utils";
9
+ import { Button } from "@umituz/web-design-system/atoms";
10
+ import type { InvoiceCardProps } from "../types/billing";
11
+ import { formatPrice, getInvoiceStatusColor, getInvoiceStatusLabel } from "../utils/billing";
12
+
13
+ export const InvoiceCard = ({
14
+ invoice,
15
+ compact = false,
16
+ onClick,
17
+ }: InvoiceCardProps) => {
18
+ const handleClick = () => {
19
+ onClick?.(invoice);
20
+ };
21
+
22
+ if (compact) {
23
+ return (
24
+ <div
25
+ onClick={handleClick}
26
+ className="flex items-center justify-between p-4 rounded-lg border border-border hover:border-primary/50 bg-background cursor-pointer transition-colors"
27
+ >
28
+ <div className="flex items-center gap-3">
29
+ <div className="w-10 h-10 rounded-lg bg-muted flex items-center justify-center">
30
+ <FileText className="h-5 w-5 text-muted-foreground" />
31
+ </div>
32
+ <div>
33
+ <p className="font-medium text-foreground text-sm">
34
+ {invoice.number}
35
+ </p>
36
+ <p className="text-xs text-muted-foreground">
37
+ {new Date(invoice.date).toLocaleDateString()}
38
+ </p>
39
+ </div>
40
+ </div>
41
+
42
+ <div className="text-right">
43
+ <p className="font-bold text-foreground">
44
+ {formatPrice(invoice.amount, invoice.currency)}
45
+ </p>
46
+ <p className={cn("text-xs font-medium", getInvoiceStatusColor(invoice.status))}>
47
+ {getInvoiceStatusLabel(invoice.status)}
48
+ </p>
49
+ </div>
50
+ </div>
51
+ );
52
+ }
53
+
54
+ return (
55
+ <div
56
+ onClick={handleClick}
57
+ className="p-6 rounded-xl border border-border hover:border-primary/50 bg-background cursor-pointer transition-colors"
58
+ >
59
+ {/* Header */}
60
+ <div className="flex items-start justify-between mb-4">
61
+ <div className="flex items-center gap-3">
62
+ <div className="w-12 h-12 rounded-lg bg-muted flex items-center justify-center">
63
+ <FileText className="h-6 w-6 text-muted-foreground" />
64
+ </div>
65
+ <div>
66
+ <p className="font-bold text-foreground">{invoice.number}</p>
67
+ <p className="text-sm text-muted-foreground">
68
+ {new Date(invoice.date).toLocaleDateString(undefined, {
69
+ year: "numeric",
70
+ month: "long",
71
+ day: "numeric",
72
+ })}
73
+ </p>
74
+ </div>
75
+ </div>
76
+
77
+ <div className="text-right">
78
+ <p className="text-2xl font-bold text-foreground">
79
+ {formatPrice(invoice.amount, invoice.currency)}
80
+ </p>
81
+ <p className={cn("text-sm font-medium", getInvoiceStatusColor(invoice.status))}>
82
+ {getInvoiceStatusLabel(invoice.status)}
83
+ </p>
84
+ </div>
85
+ </div>
86
+
87
+ {/* Items Preview */}
88
+ {invoice.items && invoice.items.length > 0 && (
89
+ <div className="space-y-2 mb-4">
90
+ {invoice.items.slice(0, 2).map((item, index) => (
91
+ <div key={index} className="flex justify-between text-sm">
92
+ <span className="text-muted-foreground">{item.description}</span>
93
+ <span className="text-foreground">
94
+ {formatPrice(item.amount, invoice.currency)}
95
+ </span>
96
+ </div>
97
+ ))}
98
+ {invoice.items.length > 2 && (
99
+ <p className="text-xs text-muted-foreground">
100
+ +{invoice.items.length - 2} more items
101
+ </p>
102
+ )}
103
+ </div>
104
+ )}
105
+
106
+ {/* Actions */}
107
+ <div className="flex items-center justify-between pt-4 border-t border-border">
108
+ <p className="text-xs text-muted-foreground">
109
+ Due {new Date(invoice.dueDate).toLocaleDateString()}
110
+ </p>
111
+
112
+ <div className="flex items-center gap-2">
113
+ {invoice.invoiceUrl && (
114
+ <Button
115
+ variant="ghost"
116
+ size="sm"
117
+ onClick={(e) => {
118
+ e.stopPropagation();
119
+ window.open(invoice.invoiceUrl, "_blank");
120
+ }}
121
+ >
122
+ <ExternalLink className="h-4 w-4" />
123
+ </Button>
124
+ )}
125
+ {invoice.pdfUrl && (
126
+ <Button
127
+ variant="ghost"
128
+ size="sm"
129
+ onClick={(e) => {
130
+ e.stopPropagation();
131
+ window.open(invoice.pdfUrl, "_blank");
132
+ }}
133
+ >
134
+ <Download className="h-4 w-4" />
135
+ </Button>
136
+ )}
137
+ </div>
138
+ </div>
139
+ </div>
140
+ );
141
+ };
142
+
143
+ export default InvoiceCard;
@@ -0,0 +1,116 @@
1
+ /**
2
+ * Payment Methods List Component
3
+ *
4
+ * Display and manage payment methods
5
+ */
6
+
7
+ import { CreditCard, Trash2, Check, Plus, Loader2 } from "lucide-react";
8
+ import { cn } from "@umituz/web-design-system/utils";
9
+ import { Button } from "@umituz/web-design-system/atoms";
10
+ import type { PaymentMethodsListProps } from "../types/billing";
11
+ import { formatCardNumber, formatExpiry } from "../utils/billing";
12
+
13
+ export const PaymentMethodsList = ({
14
+ paymentMethods,
15
+ loading = false,
16
+ onSetDefault,
17
+ onRemove,
18
+ onAddNew,
19
+ }: PaymentMethodsListProps) => {
20
+ if (loading) {
21
+ return (
22
+ <div className="flex items-center justify-center py-12">
23
+ <Loader2 className="h-8 w-8 animate-spin text-muted-foreground" />
24
+ </div>
25
+ );
26
+ }
27
+
28
+ if (paymentMethods.length === 0) {
29
+ return (
30
+ <div className="text-center py-12">
31
+ <CreditCard className="h-12 w-12 mx-auto text-muted-foreground mb-4" />
32
+ <p className="text-muted-foreground mb-6">No payment methods yet</p>
33
+ <Button onClick={onAddNew} variant="outline">
34
+ <Plus className="h-4 w-4 mr-2" />
35
+ Add Payment Method
36
+ </Button>
37
+ </div>
38
+ );
39
+ }
40
+
41
+ return (
42
+ <div className="space-y-4">
43
+ {paymentMethods.map((method) => (
44
+ <div
45
+ key={method.id}
46
+ className={cn(
47
+ "flex items-center justify-between p-4 rounded-xl border",
48
+ method.isDefault
49
+ ? "border-primary bg-primary/5"
50
+ : "border-border bg-background"
51
+ )}
52
+ >
53
+ <div className="flex items-center gap-4">
54
+ {/* Card Icon */}
55
+ <div className="w-12 h-8 rounded bg-gradient-to-br from-blue-600 to-purple-600 flex items-center justify-center">
56
+ {method.card && (
57
+ <CreditCard className="h-5 w-5 text-white" />
58
+ )}
59
+ </div>
60
+
61
+ {/* Card Details */}
62
+ <div>
63
+ <p className="font-medium text-foreground">
64
+ {method.card && formatCardNumber(method.card.last4, method.card.brand)}
65
+ </p>
66
+ <p className="text-sm text-muted-foreground">
67
+ Expires {method.card && formatExpiry(method.card.expiryMonth, method.card.expiryYear)}
68
+ </p>
69
+ </div>
70
+
71
+ {/* Default Badge */}
72
+ {method.isDefault && (
73
+ <span className="text-xs bg-primary text-primary-foreground px-2 py-1 rounded-full font-medium">
74
+ Default
75
+ </span>
76
+ )}
77
+ </div>
78
+
79
+ {/* Actions */}
80
+ <div className="flex items-center gap-2">
81
+ {!method.isDefault && onSetDefault && (
82
+ <Button
83
+ variant="ghost"
84
+ size="sm"
85
+ onClick={() => onSetDefault(method.id)}
86
+ className="text-muted-foreground hover:text-foreground"
87
+ >
88
+ <Check className="h-4 w-4" />
89
+ </Button>
90
+ )}
91
+ {onRemove && paymentMethods.length > 1 && (
92
+ <Button
93
+ variant="ghost"
94
+ size="sm"
95
+ onClick={() => onRemove(method.id)}
96
+ className="text-muted-foreground hover:text-destructive"
97
+ >
98
+ <Trash2 className="h-4 w-4" />
99
+ </Button>
100
+ )}
101
+ </div>
102
+ </div>
103
+ ))}
104
+
105
+ {/* Add New Button */}
106
+ {onAddNew && (
107
+ <Button onClick={onAddNew} variant="outline" className="w-full">
108
+ <Plus className="h-4 w-4 mr-2" />
109
+ Add Payment Method
110
+ </Button>
111
+ )}
112
+ </div>
113
+ );
114
+ };
115
+
116
+ export default PaymentMethodsList;