@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.
@@ -0,0 +1,344 @@
1
+ /**
2
+ * Billing Utilities
3
+ *
4
+ * Helper functions for billing operations
5
+ */
6
+
7
+ import type {
8
+ BillingCycle,
9
+ Currency,
10
+ PlanTier,
11
+ Subscription,
12
+ PaymentMethod,
13
+ Invoice,
14
+ InvoiceStatus,
15
+ UsageMetric,
16
+ } from "../types/billing";
17
+
18
+ /**
19
+ * Format price with currency
20
+ *
21
+ * @param amount - Amount to format
22
+ * @param currency - Currency code (default: USD)
23
+ * @returns Formatted price string
24
+ */
25
+ export function formatPrice(amount: number, currency: Currency = "USD"): string {
26
+ const localeMap: Record<Currency, string> = {
27
+ USD: "en-US",
28
+ EUR: "de-DE",
29
+ GBP: "en-GB",
30
+ TRY: "tr-TR",
31
+ JPY: "ja-JP",
32
+ };
33
+
34
+ return new Intl.NumberFormat(localeMap[currency], {
35
+ style: "currency",
36
+ currency,
37
+ }).format(amount);
38
+ }
39
+
40
+ /**
41
+ * Calculate yearly discount
42
+ *
43
+ * @param monthlyPrice - Monthly price
44
+ * @param yearlyPrice - Yearly price
45
+ * @returns Discount percentage
46
+ */
47
+ export function calculateDiscount(monthlyPrice: number, yearlyPrice: number): number {
48
+ const yearlyMonthly = yearlyPrice / 12;
49
+ const discount = ((monthlyPrice - yearlyMonthly) / monthlyPrice) * 100;
50
+ return Math.round(discount);
51
+ }
52
+
53
+ /**
54
+ * Calculate prorated amount
55
+ *
56
+ * @param amount - Full amount
57
+ * @param daysUsed - Days used in period
58
+ * @param totalDays - Total days in period
59
+ * @returns Prorated amount
60
+ */
61
+ export function calculateProratedAmount(
62
+ amount: number,
63
+ daysUsed: number,
64
+ totalDays: number
65
+ ): number {
66
+ if (totalDays === 0) return 0;
67
+ return (amount / totalDays) * daysUsed;
68
+ }
69
+
70
+ /**
71
+ * Get plan price by cycle
72
+ *
73
+ * @param plan - Plan tier
74
+ * @param cycle - Billing cycle
75
+ * @returns Price for cycle
76
+ */
77
+ export function getPlanPrice(plan: PlanTier, cycle: BillingCycle): number {
78
+ return cycle === "monthly" ? plan.monthlyPrice : plan.yearlyPrice;
79
+ }
80
+
81
+ /**
82
+ * Calculate usage percentage
83
+ *
84
+ * @param metric - Usage metric
85
+ * @returns Usage percentage (0-100)
86
+ */
87
+ export function calculateUsagePercentage(metric: UsageMetric): number {
88
+ if (metric.limit === 0) return 0;
89
+ return Math.min((metric.current / metric.limit) * 100, 100);
90
+ }
91
+
92
+ /**
93
+ * Check if usage is near limit
94
+ *
95
+ * @param metric - Usage metric
96
+ * @param threshold - Warning threshold (default: 80)
97
+ * @returns Whether near limit
98
+ */
99
+ export function isNearLimit(metric: UsageMetric, threshold: number = 80): boolean {
100
+ return calculateUsagePercentage(metric) >= threshold;
101
+ }
102
+
103
+ /**
104
+ * Get subscription status color
105
+ *
106
+ * @param status - Subscription status
107
+ * @returns Color class
108
+ */
109
+ export function getStatusColor(status: SubscriptionStatus): string {
110
+ const colorMap: Record<SubscriptionStatus, string> = {
111
+ active: "text-green-600 dark:text-green-500",
112
+ trialing: "text-blue-600 dark:text-blue-500",
113
+ past_due: "text-orange-600 dark:text-orange-500",
114
+ canceled: "text-gray-600 dark:text-gray-500",
115
+ unpaid: "text-red-600 dark:text-red-500",
116
+ incomplete: "text-yellow-600 dark:text-yellow-500",
117
+ };
118
+
119
+ return colorMap[status] || "text-gray-600";
120
+ }
121
+
122
+ /**
123
+ * Get subscription status label
124
+ *
125
+ * @param status - Subscription status
126
+ * @returns Human readable label
127
+ */
128
+ export function getStatusLabel(status: SubscriptionStatus): string {
129
+ const labelMap: Record<SubscriptionStatus, string> = {
130
+ active: "Active",
131
+ trialing: "Trial",
132
+ past_due: "Past Due",
133
+ canceled: "Canceled",
134
+ unpaid: "Unpaid",
135
+ incomplete: "Incomplete",
136
+ };
137
+
138
+ return labelMap[status] || status;
139
+ }
140
+
141
+ /**
142
+ * Get invoice status color
143
+ *
144
+ * @param status - Invoice status
145
+ * @returns Color class
146
+ */
147
+ export function getInvoiceStatusColor(status: InvoiceStatus): string {
148
+ const colorMap: Record<InvoiceStatus, string> = {
149
+ draft: "text-gray-600 dark:text-gray-500",
150
+ open: "text-orange-600 dark:text-orange-500",
151
+ paid: "text-green-600 dark:text-green-500",
152
+ void: "text-gray-600 dark:text-gray-500",
153
+ uncollectible: "text-red-600 dark:text-red-500",
154
+ };
155
+
156
+ return colorMap[status] || "text-gray-600";
157
+ }
158
+
159
+ /**
160
+ * Get invoice status label
161
+ *
162
+ * @param status - Invoice status
163
+ * @returns Human readable label
164
+ */
165
+ export function getInvoiceStatusLabel(status: InvoiceStatus): string {
166
+ const labelMap: Record<InvoiceStatus, string> = {
167
+ draft: "Draft",
168
+ open: "Open",
169
+ paid: "Paid",
170
+ void: "Void",
171
+ uncollectible: "Uncollectible",
172
+ };
173
+
174
+ return labelMap[status] || status;
175
+ }
176
+
177
+ /**
178
+ * Format card number
179
+ *
180
+ * @param last4 - Last 4 digits
181
+ * @param brand - Card brand
182
+ * @returns Formatted card display
183
+ */
184
+ export function formatCardNumber(last4: string, brand: string): string {
185
+ return `${brand.toUpperCase()} •••• ${last4}`;
186
+ }
187
+
188
+ /**
189
+ * Format expiry date
190
+ *
191
+ * @param month - Expiry month
192
+ * @param year - Expiry year
193
+ * @returns Formatted expiry (MM/YY)
194
+ */
195
+ export function formatExpiry(month: number, year: number): string {
196
+ return `${String(month).padStart(2, "0")}/${String(year).slice(-2)}`;
197
+ }
198
+
199
+ /**
200
+ * Calculate remaining days
201
+ *
202
+ * @param endDate - End date string
203
+ * @returns Days remaining
204
+ */
205
+ export function getDaysRemaining(endDate: string): number {
206
+ const end = new Date(endDate);
207
+ const now = new Date();
208
+ const diff = end.getTime() - now.getTime();
209
+ return Math.max(0, Math.ceil(diff / (1000 * 60 * 60 * 24)));
210
+ }
211
+
212
+ /**
213
+ * Check if trial is expiring soon
214
+ *
215
+ * @param trialEnd - Trial end date
216
+ * @param daysThreshold - Days threshold (default: 7)
217
+ * @returns Whether trial is expiring soon
218
+ */
219
+ export function isTrialExpiringSoon(trialEnd: string, daysThreshold: number = 7): boolean {
220
+ const daysRemaining = getDaysRemaining(trialEnd);
221
+ return daysRemaining > 0 && daysRemaining <= daysThreshold;
222
+ }
223
+
224
+ /**
225
+ * Calculate next billing date
226
+ *
227
+ * @param currentPeriodEnd - Current period end
228
+ * @param cycle - Billing cycle
229
+ * @returns Next billing date
230
+ */
231
+ export function getNextBillingDate(
232
+ currentPeriodEnd: string,
233
+ cycle: BillingCycle
234
+ ): Date {
235
+ const nextDate = new Date(currentPeriodEnd);
236
+
237
+ if (cycle === "monthly") {
238
+ nextDate.setMonth(nextDate.getMonth() + 1);
239
+ } else {
240
+ nextDate.setFullYear(nextDate.getFullYear() + 1);
241
+ }
242
+
243
+ return nextDate;
244
+ }
245
+
246
+ /**
247
+ * Group invoices by status
248
+ *
249
+ * @param invoices - Array of invoices
250
+ * @returns Grouped invoices map
251
+ */
252
+ export function groupInvoicesByStatus(
253
+ invoices: Invoice[]
254
+ ): Record<InvoiceStatus, Invoice[]> {
255
+ const grouped: Record<string, Invoice[]> = {
256
+ draft: [],
257
+ open: [],
258
+ paid: [],
259
+ void: [],
260
+ uncollectible: [],
261
+ };
262
+
263
+ invoices.forEach((invoice) => {
264
+ if (grouped[invoice.status]) {
265
+ grouped[invoice.status].push(invoice);
266
+ }
267
+ });
268
+
269
+ return grouped as Record<InvoiceStatus, Invoice[]>;
270
+ }
271
+
272
+ /**
273
+ * Calculate total invoice amount
274
+ *
275
+ * @param invoices - Array of invoices
276
+ * @param status - Optional status filter
277
+ * @returns Total amount
278
+ */
279
+ export function calculateInvoiceTotal(
280
+ invoices: Invoice[],
281
+ status?: InvoiceStatus
282
+ ): number {
283
+ const filtered = status ? invoices.filter((inv) => inv.status === status) : invoices;
284
+ return filtered.reduce((sum, inv) => sum + inv.amount, 0);
285
+ }
286
+
287
+ /**
288
+ * Sort invoices by date
289
+ *
290
+ * @param invoices - Array of invoices
291
+ * @param order - Sort order (default: desc)
292
+ * @returns Sorted invoices
293
+ */
294
+ export function sortInvoicesByDate(
295
+ invoices: Invoice[],
296
+ order: "asc" | "desc" = "desc"
297
+ ): Invoice[] {
298
+ return [...invoices].sort((a, b) => {
299
+ const dateA = new Date(a.date).getTime();
300
+ const dateB = new Date(b.date).getTime();
301
+ return order === "asc" ? dateA - dateB : dateB - dateB;
302
+ });
303
+ }
304
+
305
+ /**
306
+ * Format feature list item
307
+ *
308
+ * @param feature - Feature item (string or object)
309
+ * @returns Formatted feature
310
+ */
311
+ export function formatFeature(
312
+ feature: string | { text: string; bold?: boolean; included?: boolean }
313
+ ): { text: string; bold?: boolean; included?: boolean } {
314
+ if (typeof feature === "string") {
315
+ return { text: feature, included: true };
316
+ }
317
+ return feature;
318
+ }
319
+
320
+ /**
321
+ * Check if plan is popular
322
+ *
323
+ * @param plan - Plan tier
324
+ * @param plans - All available plans
325
+ * @returns Whether plan is popular (middle tier)
326
+ */
327
+ export function isPopularPlan(plan: PlanTier, plans: PlanTier[]): boolean {
328
+ const middleIndex = Math.floor(plans.length / 2);
329
+ return plans[middleIndex]?.id === plan.id;
330
+ }
331
+
332
+ /**
333
+ * Generate trial days text
334
+ *
335
+ * @param trialEnd - Trial end date
336
+ * @returns Days remaining text
337
+ */
338
+ export function getTrialDaysText(trialEnd: string): string {
339
+ const days = getDaysRemaining(trialEnd);
340
+
341
+ if (days === 0) return "Trial ends today";
342
+ if (days === 1) return "1 day left";
343
+ return `${days} days left`;
344
+ }
@@ -0,0 +1,29 @@
1
+ /**
2
+ * Billing Utilities
3
+ *
4
+ * Export all billing utilities
5
+ */
6
+
7
+ export {
8
+ formatPrice,
9
+ calculateDiscount,
10
+ calculateProratedAmount,
11
+ getPlanPrice,
12
+ calculateUsagePercentage,
13
+ isNearLimit,
14
+ getStatusColor,
15
+ getStatusLabel,
16
+ getInvoiceStatusColor,
17
+ getInvoiceStatusLabel,
18
+ formatCardNumber,
19
+ formatExpiry,
20
+ getDaysRemaining,
21
+ isTrialExpiringSoon,
22
+ getNextBillingDate,
23
+ groupInvoicesByStatus,
24
+ calculateInvoiceTotal,
25
+ sortInvoicesByDate,
26
+ formatFeature,
27
+ isPopularPlan,
28
+ getTrialDaysText,
29
+ } from "./billing";