@umituz/web-dashboard 2.2.0 → 2.4.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.
@@ -0,0 +1,103 @@
1
+ /**
2
+ * Usage Card Component
3
+ *
4
+ * Display usage metrics with progress bar
5
+ */
6
+
7
+ import { AlertTriangle, TrendingUp } from "lucide-react";
8
+ import { cn } from "@umituz/web-design-system/utils";
9
+ import type { UsageCardProps } from "../types/billing";
10
+ import { calculateUsagePercentage, isNearLimit, formatNumber } from "../utils/billing";
11
+
12
+ export const UsageCard = ({
13
+ metric,
14
+ showProgress = true,
15
+ showLimit = true,
16
+ }: UsageCardProps) => {
17
+ const percentage = calculateUsagePercentage(metric);
18
+ const nearLimit = isNearLimit(metric);
19
+ const isOverLimit = metric.current > metric.limit;
20
+
21
+ return (
22
+ <div className="p-6 rounded-xl border border-border bg-background">
23
+ {/* Header */}
24
+ <div className="flex items-start justify-between mb-4">
25
+ <div>
26
+ <p className="text-sm text-muted-foreground mb-1">{metric.name}</p>
27
+ <p className="text-3xl font-bold text-foreground">
28
+ {formatNumber(metric.current)}
29
+ <span className="text-base font-normal text-muted-foreground ml-1">
30
+ {metric.unit}
31
+ </span>
32
+ </p>
33
+ </div>
34
+
35
+ {/* Warning Icon */}
36
+ {(nearLimit || isOverLimit) && (
37
+ <div
38
+ className={cn(
39
+ "w-10 h-10 rounded-lg flex items-center justify-center",
40
+ isOverLimit
41
+ ? "bg-destructive/10 text-destructive"
42
+ : "bg-orange-500/10 text-orange-600 dark:text-orange-500"
43
+ )}
44
+ >
45
+ {isOverLimit ? (
46
+ <AlertTriangle className="h-5 w-5" />
47
+ ) : (
48
+ <TrendingUp className="h-5 w-5" />
49
+ )}
50
+ </div>
51
+ )}
52
+ </div>
53
+
54
+ {/* Progress Bar */}
55
+ {showProgress && (
56
+ <div className="space-y-2">
57
+ <div className="flex items-center justify-between text-xs">
58
+ {showLimit && (
59
+ <span className="text-muted-foreground">
60
+ {formatNumber(metric.limit)} {metric.unit} limit
61
+ </span>
62
+ )}
63
+ <span
64
+ className={cn(
65
+ "font-medium",
66
+ isOverLimit
67
+ ? "text-destructive"
68
+ : nearLimit
69
+ ? "text-orange-600 dark:text-orange-500"
70
+ : "text-foreground"
71
+ )}
72
+ >
73
+ {percentage.toFixed(0)}%
74
+ </span>
75
+ </div>
76
+
77
+ <div className="w-full h-2 bg-muted rounded-full overflow-hidden">
78
+ <div
79
+ className={cn(
80
+ "h-full transition-all duration-500",
81
+ isOverLimit
82
+ ? "bg-destructive"
83
+ : nearLimit
84
+ ? "bg-orange-500"
85
+ : "bg-primary"
86
+ )}
87
+ style={{ width: `${Math.min(percentage, 100)}%` }}
88
+ />
89
+ </div>
90
+
91
+ {/* Reset Info */}
92
+ {metric.resetAt && (
93
+ <p className="text-xs text-muted-foreground mt-2">
94
+ Resets {new Date(metric.resetAt).toLocaleDateString()}
95
+ </p>
96
+ )}
97
+ </div>
98
+ )}
99
+ </div>
100
+ );
101
+ };
102
+
103
+ export default UsageCard;
@@ -0,0 +1,12 @@
1
+ /**
2
+ * Billing Components
3
+ *
4
+ * Export all billing components
5
+ */
6
+
7
+ export { PlanComparison } from "./PlanComparison";
8
+ export { PaymentMethodsList } from "./PaymentMethodsList";
9
+ export { InvoiceCard } from "./InvoiceCard";
10
+ export { UsageCard } from "./UsageCard";
11
+ export { BillingPortal } from "./BillingPortal";
12
+ export { BillingLayout } from "./BillingLayout";
@@ -0,0 +1,7 @@
1
+ /**
2
+ * Billing Hooks
3
+ *
4
+ * Export all billing hooks
5
+ */
6
+
7
+ export { useBilling } from "./useBilling";
@@ -0,0 +1,385 @@
1
+ /**
2
+ * useBilling Hook
3
+ *
4
+ * Core billing hook for managing subscription and payments
5
+ */
6
+
7
+ import { useState, useCallback, useEffect } from "react";
8
+ import type {
9
+ BillingSummary,
10
+ BillingCycle,
11
+ PlanTier,
12
+ PaymentMethod,
13
+ Invoice,
14
+ UsageMetric,
15
+ } from "../types/billing";
16
+ import { formatPrice } from "../utils/billing";
17
+
18
+ interface UseBillingOptions {
19
+ /** Billing API base URL */
20
+ apiUrl?: string;
21
+ /** Initial billing data */
22
+ initialData?: BillingSummary;
23
+ }
24
+
25
+ interface BillingActions {
26
+ /** Load billing summary */
27
+ loadBilling: () => Promise<void>;
28
+ /** Update subscription plan */
29
+ updatePlan: (planId: string) => Promise<void>;
30
+ /** Cancel subscription */
31
+ cancelSubscription: () => Promise<void>;
32
+ /** Update billing cycle */
33
+ updateCycle: (cycle: BillingCycle) => Promise<void>;
34
+ /** Add payment method */
35
+ addPaymentMethod: (paymentMethodDetails: any) => Promise<PaymentMethod>;
36
+ /** Remove payment method */
37
+ removePaymentMethod: (methodId: string) => Promise<void>;
38
+ /** Set default payment method */
39
+ setDefaultPaymentMethod: (methodId: string) => Promise<void>;
40
+ /** Get invoice URL */
41
+ getInvoiceUrl: (invoiceId: string) => Promise<string>;
42
+ }
43
+
44
+ /**
45
+ * useBilling hook
46
+ *
47
+ * Manages billing state and actions
48
+ *
49
+ * @param options - Hook options
50
+ * @returns Billing state and actions
51
+ */
52
+ export function useBilling(options: UseBillingOptions = {}) {
53
+ const { apiUrl = "/api/billing", initialData } = options;
54
+
55
+ // State
56
+ const [billing, setBilling] = useState<BillingSummary | null>(initialData || null);
57
+ const [isLoading, setIsLoading] = useState(false);
58
+ const [error, setError] = useState<string | null>(null);
59
+
60
+ // Load billing summary
61
+ const loadBilling = useCallback(async () => {
62
+ setIsLoading(true);
63
+ setError(null);
64
+
65
+ try {
66
+ // In production, call your billing API
67
+ // const response = await fetch(apiUrl);
68
+ // const data = await response.json();
69
+
70
+ // Mock data for demo
71
+ const mockBilling: BillingSummary = {
72
+ subscription: {
73
+ id: "sub_123",
74
+ planId: "pro",
75
+ plan: {
76
+ id: "pro",
77
+ type: "pro",
78
+ name: "Pro Plan",
79
+ description: "For growing teams",
80
+ monthlyPrice: 49,
81
+ yearlyPrice: 490,
82
+ currency: "USD",
83
+ features: [
84
+ "Up to 10 users",
85
+ "100GB storage",
86
+ "100K API calls/month",
87
+ "Priority support",
88
+ ],
89
+ },
90
+ status: "active",
91
+ cycle: "monthly",
92
+ currentPeriodStart: new Date().toISOString(),
93
+ currentPeriodEnd: new Date(Date.now() + 30 * 24 * 60 * 60 * 1000).toISOString(),
94
+ seats: 5,
95
+ },
96
+ paymentMethods: [
97
+ {
98
+ id: "pm_123",
99
+ type: "card",
100
+ isDefault: true,
101
+ card: {
102
+ last4: "4242",
103
+ brand: "Visa",
104
+ expiryMonth: 12,
105
+ expiryYear: 2025,
106
+ name: "John Doe",
107
+ },
108
+ createdAt: new Date().toISOString(),
109
+ },
110
+ ],
111
+ defaultPaymentMethod: {
112
+ id: "pm_123",
113
+ type: "card",
114
+ isDefault: true,
115
+ card: {
116
+ last4: "4242",
117
+ brand: "Visa",
118
+ expiryMonth: 12,
119
+ expiryYear: 2025,
120
+ name: "John Doe",
121
+ },
122
+ createdAt: new Date().toISOString(),
123
+ },
124
+ upcomingInvoice: {
125
+ amount: 49,
126
+ currency: "USD",
127
+ date: new Date(Date.now() + 30 * 24 * 60 * 60 * 1000).toISOString(),
128
+ },
129
+ usage: [
130
+ {
131
+ id: "storage",
132
+ name: "Storage",
133
+ current: 67.5,
134
+ limit: 100,
135
+ unit: "GB",
136
+ resetPeriod: "monthly",
137
+ resetAt: new Date(Date.now() + 30 * 24 * 60 * 60 * 1000).toISOString(),
138
+ },
139
+ {
140
+ id: "api",
141
+ name: "API Calls",
142
+ current: 75000,
143
+ limit: 100000,
144
+ unit: "calls",
145
+ resetPeriod: "monthly",
146
+ resetAt: new Date(Date.now() + 30 * 24 * 60 * 60 * 1000).toISOString(),
147
+ },
148
+ ],
149
+ recentInvoices: [],
150
+ };
151
+
152
+ setBilling(mockBilling);
153
+ } catch (err) {
154
+ const errorMessage = err instanceof Error ? err.message : "Failed to load billing";
155
+ setError(errorMessage);
156
+ throw err;
157
+ } finally {
158
+ setIsLoading(false);
159
+ }
160
+ }, [apiUrl]);
161
+
162
+ // Update subscription plan
163
+ const updatePlan = useCallback(async (planId: string) => {
164
+ if (!billing) return;
165
+
166
+ setIsLoading(true);
167
+ setError(null);
168
+
169
+ try {
170
+ // In production, call your billing API
171
+ // await fetch(`${apiUrl}/subscription`, {
172
+ // method: 'PATCH',
173
+ // body: JSON.stringify({ planId }),
174
+ // });
175
+
176
+ // Mock update
177
+ setBilling((prev) => {
178
+ if (!prev) return null;
179
+ return {
180
+ ...prev,
181
+ subscription: {
182
+ ...prev.subscription,
183
+ planId,
184
+ },
185
+ };
186
+ });
187
+ } catch (err) {
188
+ const errorMessage = err instanceof Error ? err.message : "Failed to update plan";
189
+ setError(errorMessage);
190
+ throw err;
191
+ } finally {
192
+ setIsLoading(false);
193
+ }
194
+ }, [apiUrl, billing]);
195
+
196
+ // Cancel subscription
197
+ const cancelSubscription = useCallback(async () => {
198
+ setIsLoading(true);
199
+ setError(null);
200
+
201
+ try {
202
+ // In production, call your billing API
203
+ // await fetch(`${apiUrl}/subscription/cancel`, { method: 'POST' });
204
+
205
+ // Mock cancel
206
+ setBilling((prev) => {
207
+ if (!prev) return null;
208
+ return {
209
+ ...prev,
210
+ subscription: {
211
+ ...prev.subscription,
212
+ status: "canceled",
213
+ cancelAtPeriodEnd: true,
214
+ },
215
+ };
216
+ });
217
+ } catch (err) {
218
+ const errorMessage = err instanceof Error ? err.message : "Failed to cancel subscription";
219
+ setError(errorMessage);
220
+ throw err;
221
+ } finally {
222
+ setIsLoading(false);
223
+ }
224
+ }, [apiUrl]);
225
+
226
+ // Update billing cycle
227
+ const updateCycle = useCallback(async (cycle: BillingCycle) => {
228
+ setIsLoading(true);
229
+ setError(null);
230
+
231
+ try {
232
+ // In production, call your billing API
233
+ // await fetch(`${apiUrl}/subscription/cycle`, {
234
+ // method: 'PATCH',
235
+ // body: JSON.stringify({ cycle }),
236
+ // });
237
+
238
+ // Mock update
239
+ setBilling((prev) => {
240
+ if (!prev) return null;
241
+ return {
242
+ ...prev,
243
+ subscription: {
244
+ ...prev.subscription,
245
+ cycle,
246
+ },
247
+ };
248
+ });
249
+ } catch (err) {
250
+ const errorMessage = err instanceof Error ? err.message : "Failed to update cycle";
251
+ setError(errorMessage);
252
+ throw err;
253
+ } finally {
254
+ setIsLoading(false);
255
+ }
256
+ }, [apiUrl]);
257
+
258
+ // Add payment method
259
+ const addPaymentMethod = useCallback(async (paymentMethodDetails: any) => {
260
+ setIsLoading(true);
261
+ setError(null);
262
+
263
+ try {
264
+ // In production, call your billing API
265
+ // const response = await fetch(`${apiUrl}/payment-methods`, {
266
+ // method: 'POST',
267
+ // body: JSON.stringify(paymentMethodDetails),
268
+ // });
269
+ // const newMethod = await response.json();
270
+
271
+ // Mock add
272
+ const newMethod: PaymentMethod = {
273
+ id: `pm_${Date.now()}`,
274
+ type: "card",
275
+ isDefault: false,
276
+ card: paymentMethodDetails,
277
+ createdAt: new Date().toISOString(),
278
+ };
279
+
280
+ setBilling((prev) => {
281
+ if (!prev) return null;
282
+ return {
283
+ ...prev,
284
+ paymentMethods: [...prev.paymentMethods, newMethod],
285
+ };
286
+ });
287
+
288
+ return newMethod;
289
+ } catch (err) {
290
+ const errorMessage = err instanceof Error ? err.message : "Failed to add payment method";
291
+ setError(errorMessage);
292
+ throw err;
293
+ } finally {
294
+ setIsLoading(false);
295
+ }
296
+ }, [apiUrl]);
297
+
298
+ // Remove payment method
299
+ const removePaymentMethod = useCallback(async (methodId: string) => {
300
+ setIsLoading(true);
301
+ setError(null);
302
+
303
+ try {
304
+ // In production, call your billing API
305
+ // await fetch(`${apiUrl}/payment-methods/${methodId}`, {
306
+ // method: 'DELETE',
307
+ // });
308
+
309
+ // Mock remove
310
+ setBilling((prev) => {
311
+ if (!prev) return null;
312
+ return {
313
+ ...prev,
314
+ paymentMethods: prev.paymentMethods.filter((pm) => pm.id !== methodId),
315
+ };
316
+ });
317
+ } catch (err) {
318
+ const errorMessage = err instanceof Error ? err.message : "Failed to remove payment method";
319
+ setError(errorMessage);
320
+ throw err;
321
+ } finally {
322
+ setIsLoading(false);
323
+ }
324
+ }, [apiUrl]);
325
+
326
+ // Set default payment method
327
+ const setDefaultPaymentMethod = useCallback(async (methodId: string) => {
328
+ setIsLoading(true);
329
+ setError(null);
330
+
331
+ try {
332
+ // In production, call your billing API
333
+ // await fetch(`${apiUrl}/payment-methods/${methodId}/default`, {
334
+ // method: 'PATCH',
335
+ // });
336
+
337
+ // Mock update
338
+ setBilling((prev) => {
339
+ if (!prev) return null;
340
+ return {
341
+ ...prev,
342
+ paymentMethods: prev.paymentMethods.map((pm) => ({
343
+ ...pm,
344
+ isDefault: pm.id === methodId,
345
+ })),
346
+ defaultPaymentMethod: prev.paymentMethods.find((pm) => pm.id === methodId),
347
+ };
348
+ });
349
+ } catch (err) {
350
+ const errorMessage = err instanceof Error ? err.message : "Failed to set default payment method";
351
+ setError(errorMessage);
352
+ throw err;
353
+ } finally {
354
+ setIsLoading(false);
355
+ }
356
+ }, [apiUrl]);
357
+
358
+ // Get invoice URL
359
+ const getInvoiceUrl = useCallback(async (invoiceId: string) => {
360
+ // In production, call your billing API
361
+ // const response = await fetch(`${apiUrl}/invoices/${invoiceId}`);
362
+ // const data = await response.json();
363
+ // return data.invoiceUrl;
364
+
365
+ return `#invoice-${invoiceId}`;
366
+ }, [apiUrl]);
367
+
368
+ const actions: BillingActions = {
369
+ loadBilling,
370
+ updatePlan,
371
+ cancelSubscription,
372
+ updateCycle,
373
+ addPaymentMethod,
374
+ removePaymentMethod,
375
+ setDefaultPaymentMethod,
376
+ getInvoiceUrl,
377
+ };
378
+
379
+ return {
380
+ billing,
381
+ isLoading,
382
+ error,
383
+ ...actions,
384
+ };
385
+ }
@@ -0,0 +1,69 @@
1
+ /**
2
+ * Billing Domain
3
+ *
4
+ * Main entry point for billing domain
5
+ */
6
+
7
+ // Components
8
+ export {
9
+ PlanComparison,
10
+ PaymentMethodsList,
11
+ InvoiceCard,
12
+ UsageCard,
13
+ BillingPortal,
14
+ BillingLayout,
15
+ } from "./components";
16
+
17
+ // Hooks
18
+ export {
19
+ useBilling,
20
+ } from "./hooks";
21
+
22
+ // Utils
23
+ export {
24
+ formatPrice,
25
+ calculateDiscount,
26
+ calculateProratedAmount,
27
+ getPlanPrice,
28
+ calculateUsagePercentage,
29
+ isNearLimit,
30
+ getStatusColor,
31
+ getStatusLabel,
32
+ getInvoiceStatusColor,
33
+ getInvoiceStatusLabel,
34
+ formatCardNumber,
35
+ formatExpiry,
36
+ getDaysRemaining,
37
+ isTrialExpiringSoon,
38
+ getNextBillingDate,
39
+ groupInvoicesByStatus,
40
+ calculateInvoiceTotal,
41
+ sortInvoicesByDate,
42
+ formatFeature,
43
+ isPopularPlan,
44
+ getTrialDaysText,
45
+ } from "./utils";
46
+
47
+ // Types
48
+ export type {
49
+ BillingCycle,
50
+ SubscriptionStatus,
51
+ PlanType,
52
+ Currency,
53
+ PlanTier,
54
+ Subscription,
55
+ PaymentMethodType,
56
+ PaymentMethod,
57
+ InvoiceStatus,
58
+ InvoiceItem,
59
+ Invoice,
60
+ UsageMetric,
61
+ BillingSummary,
62
+ PlanComparisonProps,
63
+ PaymentMethodsListProps,
64
+ InvoiceCardProps,
65
+ UsageCardProps,
66
+ BillingPortalProps,
67
+ BillingLayoutProps,
68
+ BillingConfig,
69
+ } from "./types";