@umituz/web-dashboard 3.2.2 → 4.0.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.
Files changed (125) hide show
  1. package/README.md +202 -14
  2. package/package.json +21 -29
  3. package/src/domains/analytics/components/AnalyticsCard.tsx +23 -21
  4. package/src/domains/analytics/components/AnalyticsChart.tsx +345 -149
  5. package/src/domains/analytics/components/AnalyticsLayout.tsx +93 -112
  6. package/src/domains/analytics/components/MetricCard.tsx +42 -64
  7. package/src/domains/analytics/hooks/index.ts +7 -1
  8. package/src/domains/analytics/hooks/useAnalytics.ts +144 -139
  9. package/src/domains/analytics/index.ts +11 -0
  10. package/src/domains/analytics/services/AnalyticsEngineService.ts +103 -55
  11. package/src/domains/analytics/services/PerformanceService.ts +117 -69
  12. package/src/domains/analytics/types/analytics.ts +20 -6
  13. package/src/domains/analytics/types/index.ts +3 -0
  14. package/src/domains/analytics/utils/analytics.test.ts +183 -0
  15. package/src/domains/analytics/utils/analytics.ts +26 -9
  16. package/src/domains/analytics/utils/i18nKeys.ts +40 -0
  17. package/src/domains/analytics/utils/index.ts +2 -0
  18. package/src/domains/auth/components/AuthLayout.tsx +63 -54
  19. package/src/domains/auth/components/ForgotPasswordForm.tsx +96 -98
  20. package/src/domains/auth/components/LoginForm.tsx +167 -226
  21. package/src/domains/auth/components/RegisterForm.tsx +202 -272
  22. package/src/domains/auth/components/ResetPasswordForm.tsx +105 -151
  23. package/src/domains/auth/components/shared/AuthErrorBanner.tsx +35 -0
  24. package/src/domains/auth/components/shared/EmailInput.tsx +91 -0
  25. package/src/domains/auth/components/shared/PasswordInput.tsx +119 -0
  26. package/src/domains/auth/components/shared/PasswordStrengthIndicator.tsx +75 -0
  27. package/src/domains/auth/components/shared/SocialLoginButtons.tsx +58 -0
  28. package/src/domains/auth/components/shared/SocialProviderIcons.tsx +48 -0
  29. package/src/domains/auth/components/shared/index.ts +20 -0
  30. package/src/domains/auth/hooks/index.ts +1 -0
  31. package/src/domains/auth/hooks/useAuth.ts +151 -232
  32. package/src/domains/auth/index.ts +8 -4
  33. package/src/domains/auth/types/auth.ts +26 -5
  34. package/src/domains/auth/types/index.ts +1 -0
  35. package/src/domains/auth/utils/auth.test.ts +182 -0
  36. package/src/domains/auth/utils/auth.ts +71 -130
  37. package/src/domains/auth/utils/i18nKeys.ts +127 -0
  38. package/src/domains/auth/utils/index.ts +7 -1
  39. package/src/domains/auth/utils/secureToken.ts +33 -0
  40. package/src/domains/auth/utils/validationKeys.ts +23 -0
  41. package/src/domains/calendar/hooks/useCalendar.ts +145 -151
  42. package/src/domains/calendar/index.ts +0 -1
  43. package/src/domains/calendar/services/CalendarService.ts +122 -179
  44. package/src/domains/calendar/utils/contentItemMapper.test.ts +108 -0
  45. package/src/domains/calendar/utils/contentItemMapper.ts +99 -0
  46. package/src/domains/calendar/utils/index.ts +33 -30
  47. package/src/domains/layouts/components/BrandLogo.tsx +12 -9
  48. package/src/domains/layouts/components/DashboardHeader.tsx +78 -80
  49. package/src/domains/layouts/components/DashboardLayout.tsx +45 -21
  50. package/src/domains/layouts/components/DashboardSidebar.tsx +48 -51
  51. package/src/domains/layouts/types/layout.ts +7 -1
  52. package/src/domains/layouts/types/user.ts +3 -1
  53. package/src/domains/modules/build.test.tsx +97 -0
  54. package/src/domains/modules/build.tsx +71 -0
  55. package/src/domains/modules/index.ts +2 -0
  56. package/src/domains/modules/types.ts +29 -0
  57. package/src/domains/onboarding/components/AppFocusStep.tsx +99 -68
  58. package/src/domains/onboarding/components/OnboardingWizard.tsx +109 -183
  59. package/src/domains/onboarding/components/PlanStep.tsx +156 -123
  60. package/src/domains/onboarding/components/PlatformsStep.tsx +74 -54
  61. package/src/domains/onboarding/components/StepNavigation.tsx +108 -0
  62. package/src/domains/onboarding/components/StepProgress.tsx +62 -0
  63. package/src/domains/onboarding/components/UserTypeStep.tsx +59 -80
  64. package/src/domains/onboarding/components/index.ts +4 -0
  65. package/src/domains/onboarding/hooks/useOnboarding.ts +76 -66
  66. package/src/domains/onboarding/index.ts +1 -0
  67. package/src/domains/onboarding/types/onboarding.ts +57 -17
  68. package/src/domains/onboarding/utils/i18nKeys.ts +72 -0
  69. package/src/domains/onboarding/utils/index.ts +2 -0
  70. package/src/domains/permissions/PermissionsContext.tsx +103 -0
  71. package/src/domains/permissions/hasPermission.test.ts +50 -0
  72. package/src/domains/permissions/hasPermission.ts +38 -0
  73. package/src/domains/permissions/index.ts +19 -0
  74. package/src/domains/permissions/types.ts +35 -0
  75. package/src/domains/permissions/utils/i18nKeys.ts +11 -0
  76. package/src/domains/session/DemoLogin.tsx +50 -0
  77. package/src/domains/session/OrgSwitcher.tsx +110 -0
  78. package/src/domains/session/SessionContext.tsx +150 -0
  79. package/src/domains/session/inMemoryAuthAdapter.ts +53 -0
  80. package/src/domains/session/index.ts +18 -0
  81. package/src/domains/session/types.ts +47 -0
  82. package/src/domains/session/utils/i18nKeys.ts +12 -0
  83. package/src/domains/settings/components/AuditLogList.tsx +59 -0
  84. package/src/domains/settings/components/PermissionMatrixScreen.tsx +80 -0
  85. package/src/domains/settings/components/SettingsLayout.tsx +75 -60
  86. package/src/domains/settings/components/SettingsSection.tsx +35 -22
  87. package/src/domains/settings/components/UserManagementTable.tsx +87 -0
  88. package/src/domains/settings/components/index.ts +10 -0
  89. package/src/domains/settings/hooks/useSettings.ts +77 -52
  90. package/src/domains/settings/types/settings.ts +2 -0
  91. package/src/domains/settings/utils/i18nKeys.ts +18 -0
  92. package/src/domains/settings/utils/settings.ts +11 -4
  93. package/src/i18n/I18nContext.tsx +33 -0
  94. package/src/i18n/defaultT.test.ts +43 -0
  95. package/src/i18n/defaultT.ts +34 -0
  96. package/src/i18n/index.ts +7 -0
  97. package/src/i18n/types.ts +13 -0
  98. package/src/index.ts +1 -7
  99. package/src/primitives/Button.tsx +30 -0
  100. package/src/primitives/Checkbox.tsx +40 -0
  101. package/src/primitives/Input.tsx +19 -0
  102. package/src/primitives/Skeleton.tsx +26 -0
  103. package/src/primitives/cn.ts +15 -0
  104. package/src/primitives/index.ts +23 -0
  105. package/src/styles/dashboard.css +3045 -0
  106. package/src/styles/tokens.contract.test.ts +66 -0
  107. package/src/styles/tokens.css +124 -0
  108. package/src/domains/billing/components/BillingLayout.tsx +0 -63
  109. package/src/domains/billing/components/BillingPage.tsx +0 -49
  110. package/src/domains/billing/components/BillingPortal.tsx +0 -198
  111. package/src/domains/billing/components/InvoiceCard.tsx +0 -143
  112. package/src/domains/billing/components/PaymentMethodsList.tsx +0 -116
  113. package/src/domains/billing/components/PlanComparison.tsx +0 -199
  114. package/src/domains/billing/components/UsageCard.tsx +0 -103
  115. package/src/domains/billing/components/index.ts +0 -13
  116. package/src/domains/billing/constants/billing.ts +0 -69
  117. package/src/domains/billing/hooks/index.ts +0 -7
  118. package/src/domains/billing/hooks/useBilling.ts +0 -385
  119. package/src/domains/billing/index.ts +0 -73
  120. package/src/domains/billing/types/billing.ts +0 -342
  121. package/src/domains/billing/types/index.ts +0 -30
  122. package/src/domains/billing/utils/billing.ts +0 -360
  123. package/src/domains/billing/utils/index.ts +0 -31
  124. package/src/domains/billing/utils/polar.ts +0 -63
  125. package/src/domains/calendar/components/index.ts +0 -7
@@ -1,342 +0,0 @@
1
- /**
2
- * Billing Types
3
- *
4
- * Type definitions for billing and subscription system
5
- */
6
-
7
- import type { ComponentType, ReactElement } from "react";
8
- import { SUBSCRIPTION_STATUS, INVOICE_STATUS, BILLING_CYCLE, CURRENCY } from "../constants/billing";
9
-
10
- /**
11
- * Billing cycle
12
- */
13
- export type BillingCycle = typeof BILLING_CYCLE[keyof typeof BILLING_CYCLE];
14
-
15
- /**
16
- * Subscription status
17
- */
18
- export type SubscriptionStatus = typeof SUBSCRIPTION_STATUS[keyof typeof SUBSCRIPTION_STATUS];
19
-
20
- /**
21
- * Invoice status
22
- */
23
- export type InvoiceStatus = typeof INVOICE_STATUS[keyof typeof INVOICE_STATUS];
24
-
25
- /**
26
- * Plan type - dynamic string from product provider
27
- */
28
- export type PlanType = string;
29
-
30
- /**
31
- * Currency
32
- */
33
- export type Currency = typeof CURRENCY[keyof typeof CURRENCY];
34
-
35
- /**
36
- * Plan pricing tier
37
- */
38
- export interface PlanTier {
39
- /** Plan ID */
40
- id: string;
41
- /** Plan type - optional, from product metadata */
42
- type?: PlanType;
43
- /** Plan name */
44
- name: string;
45
- /** Plan description */
46
- description: string;
47
- /** Badge text */
48
- badge?: string;
49
- /** Badge color */
50
- badgeColor?: string;
51
- /** Monthly price */
52
- monthlyPrice: number;
53
- /** Yearly price */
54
- yearlyPrice: number;
55
- /** Currency */
56
- currency: Currency;
57
- /** Features list */
58
- features: (string | { text: string; bold?: boolean; included?: boolean })[];
59
- /** Highlight/recommend */
60
- highlight?: boolean;
61
- /** Maximum users/seats */
62
- maxUsers?: number;
63
- /** Storage limit in GB */
64
- storageLimit?: number;
65
- /** API call limit */
66
- apiLimit?: number;
67
- }
68
-
69
- /**
70
- * Subscription info
71
- */
72
- export interface Subscription {
73
- /** Subscription ID */
74
- id: string;
75
- /** Plan ID */
76
- planId: string;
77
- /** Plan details */
78
- plan: PlanTier;
79
- /** Subscription status */
80
- status: SubscriptionStatus;
81
- /** Billing cycle */
82
- cycle: BillingCycle;
83
- /** Current period start */
84
- currentPeriodStart: string;
85
- /** Current period end */
86
- currentPeriodEnd: string;
87
- /** Cancel at period end */
88
- cancelAtPeriodEnd?: boolean;
89
- /** Trial end date */
90
- trialEnd?: string;
91
- /** User/seat count */
92
- seats?: number;
93
- }
94
-
95
- /**
96
- * Payment method type
97
- */
98
- export type PaymentMethodType = "card" | "bank_account" | "wallet";
99
-
100
- /**
101
- * Payment method
102
- */
103
- export interface PaymentMethod {
104
- /** Payment method ID */
105
- id: string;
106
- /** Type */
107
- type: PaymentMethodType;
108
- /** Is default */
109
- isDefault: boolean;
110
- /** Card details */
111
- card?: {
112
- /** Last 4 digits */
113
- last4: string;
114
- /** Brand (Visa, Mastercard) */
115
- brand: string;
116
- /** Expiry month */
117
- expiryMonth: number;
118
- /** Expiry year */
119
- expiryYear: number;
120
- /** Cardholder name */
121
- name?: string;
122
- };
123
- /** Bank account details */
124
- bankAccount?: {
125
- /** Last 4 digits */
126
- last4: string;
127
- /** Bank name */
128
- bankName: string;
129
- /** Account type */
130
- accountType?: "checking" | "savings";
131
- };
132
- /** Created date */
133
- createdAt: string;
134
- }
135
-
136
- /**
137
- * Invoice item
138
- */
139
- export interface InvoiceItem {
140
- /** Item description */
141
- description: string;
142
- /** Quantity */
143
- quantity: number;
144
- /** Unit price */
145
- unitPrice: number;
146
- /** Amount */
147
- amount: number;
148
- }
149
-
150
- /**
151
- * Invoice
152
- */
153
- export interface Invoice {
154
- /** Invoice ID */
155
- id: string;
156
- /** Invoice number */
157
- number: string;
158
- /** Amount */
159
- amount: number;
160
- /** Currency */
161
- currency: Currency;
162
- /** Status */
163
- status: InvoiceStatus;
164
- /** Invoice date */
165
- date: string;
166
- /** Due date */
167
- dueDate: string;
168
- /** Paid date */
169
- paidAt?: string;
170
- /** Invoice URL */
171
- invoiceUrl?: string;
172
- /** PDF download URL */
173
- pdfUrl?: string;
174
- /** Line items */
175
- items?: InvoiceItem[];
176
- /** Subtotal */
177
- subtotal?: number;
178
- /** Tax amount */
179
- tax?: number;
180
- /** Total amount */
181
- total?: number;
182
- }
183
-
184
- /**
185
- * Usage metric
186
- */
187
- export interface UsageMetric {
188
- /** Metric ID */
189
- id: string;
190
- /** Metric name */
191
- name: string;
192
- /** Current usage */
193
- current: number;
194
- /** Limit */
195
- limit: number;
196
- /** Unit */
197
- unit: string;
198
- /** Reset period */
199
- resetPeriod: "daily" | "monthly" | "yearly";
200
- /** Reset date */
201
- resetAt?: string;
202
- }
203
-
204
- /**
205
- * Billing summary
206
- */
207
- export interface BillingSummary {
208
- /** Current subscription */
209
- subscription: Subscription;
210
- /** Payment methods */
211
- paymentMethods: PaymentMethod[];
212
- /** Default payment method */
213
- defaultPaymentMethod?: PaymentMethod;
214
- /** Upcoming invoice */
215
- upcomingInvoice?: {
216
- amount: number;
217
- currency: Currency;
218
- date: string;
219
- };
220
- /** Usage metrics */
221
- usage: UsageMetric[];
222
- /** Recent invoices */
223
- recentInvoices: Invoice[];
224
- }
225
-
226
- /**
227
- * Plan comparison props
228
- */
229
- export interface PlanComparisonProps {
230
- /** Available plans */
231
- plans: PlanTier[];
232
- /** Selected plan ID */
233
- selectedPlan?: string;
234
- /** Billing cycle */
235
- cycle: BillingCycle;
236
- /** Show yearly toggle */
237
- showCycleToggle?: boolean;
238
- /** Show features */
239
- showFeatures?: boolean;
240
- /** On plan select */
241
- onPlanSelect?: (planId: string) => void;
242
- /** On cycle change */
243
- onCycleChange?: (cycle: BillingCycle) => void;
244
- /** Loading state */
245
- loading?: boolean;
246
- }
247
-
248
- /**
249
- * Payment methods list props
250
- */
251
- export interface PaymentMethodsListProps {
252
- /** Payment methods */
253
- paymentMethods: PaymentMethod[];
254
- /** Loading state */
255
- loading?: boolean;
256
- /** On set default */
257
- onSetDefault?: (methodId: string) => void;
258
- /** On remove */
259
- onRemove?: (methodId: string) => void;
260
- /** On add new */
261
- onAddNew?: () => void;
262
- }
263
-
264
- /**
265
- * Invoice card props
266
- */
267
- export interface InvoiceCardProps {
268
- /** Invoice data */
269
- invoice: Invoice;
270
- /** Compact view */
271
- compact?: boolean;
272
- /** On click */
273
- onClick?: (invoice: Invoice) => void;
274
- }
275
-
276
- /**
277
- * Usage card props
278
- */
279
- export interface UsageCardProps {
280
- /** Usage metric */
281
- metric: UsageMetric;
282
- /** Show progress bar */
283
- showProgress?: boolean;
284
- /** Show limit */
285
- showLimit?: boolean;
286
- }
287
-
288
- /**
289
- * Billing portal props
290
- */
291
- export interface BillingPortalProps {
292
- /** Billing summary */
293
- billing: BillingSummary;
294
- /** Loading state */
295
- loading?: boolean;
296
- /** Error message */
297
- error?: string;
298
- /** Show tabs */
299
- showTabs?: boolean;
300
- /** Active tab */
301
- activeTab?: string;
302
- /** On tab change */
303
- onTabChange?: (tab: string) => void;
304
- }
305
-
306
- /**
307
- * Billing layout props
308
- */
309
- export interface BillingLayoutProps {
310
- /** Billing configuration */
311
- config: BillingConfig;
312
- /** Children content */
313
- children?: React.ReactNode;
314
- }
315
-
316
- /**
317
- * Billing configuration
318
- */
319
- export interface BillingConfig {
320
- /** Brand name */
321
- brandName: string;
322
- /** Available plans */
323
- plans: PlanTier[];
324
- /** Supported currencies */
325
- currencies?: Currency[];
326
- /** Default currency */
327
- defaultCurrency?: Currency;
328
- /** Enable yearly discounts */
329
- enableYearlyDiscount?: boolean;
330
- /** Yearly discount percentage */
331
- yearlyDiscount?: number;
332
- /** Tax rate */
333
- taxRate?: number;
334
- /** Support email */
335
- supportEmail?: string;
336
- /** Cancel subscription route */
337
- cancelRoute?: string;
338
- /** Update payment route */
339
- updatePaymentRoute?: string;
340
- /** Invoice history route */
341
- invoiceHistoryRoute?: string;
342
- }
@@ -1,30 +0,0 @@
1
- /**
2
- * Billing Types
3
- *
4
- * Export all billing-related types
5
- */
6
-
7
- export type {
8
- BillingCycle,
9
- SubscriptionStatus,
10
- PlanType,
11
- Currency,
12
- PlanTier,
13
- Subscription,
14
- PaymentMethodType,
15
- PaymentMethod,
16
- InvoiceStatus,
17
- InvoiceItem,
18
- Invoice,
19
- UsageMetric,
20
- BillingSummary,
21
- PlanComparisonProps,
22
- PaymentMethodsListProps,
23
- InvoiceCardProps,
24
- UsageCardProps,
25
- BillingPortalProps,
26
- BillingLayoutProps,
27
- BillingConfig,
28
- } from "./billing";
29
-
30
- export type { BillingPageProps } from "../components/BillingPage";
@@ -1,360 +0,0 @@
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
- SubscriptionStatus,
13
- PaymentMethod,
14
- Invoice,
15
- InvoiceStatus,
16
- UsageMetric,
17
- } from "../types/billing";
18
-
19
- /**
20
- * Format number with thousand separators
21
- *
22
- * @param num - Number to format
23
- * @param locale - Locale (default: en-US)
24
- * @returns Formatted number string
25
- */
26
- export function formatNumber(num: number, locale: string = "en-US"): string {
27
- return new Intl.NumberFormat(locale).format(num);
28
- }
29
-
30
- /**
31
- * Format price with currency
32
- *
33
- * @param amount - Amount to format
34
- * @param currency - Currency code (default: USD)
35
- * @returns Formatted price string
36
- */
37
- export function formatPrice(amount: number, currency: Currency = "USD"): string {
38
- const localeMap: Record<Currency, string> = {
39
- USD: "en-US",
40
- EUR: "de-DE",
41
- GBP: "en-GB",
42
- TRY: "tr-TR",
43
- JPY: "ja-JP",
44
- };
45
-
46
- return new Intl.NumberFormat(localeMap[currency], {
47
- style: "currency",
48
- currency,
49
- }).format(amount);
50
- }
51
-
52
- /**
53
- * Calculate yearly discount
54
- *
55
- * @param monthlyPrice - Monthly price
56
- * @param yearlyPrice - Yearly price
57
- * @returns Discount percentage
58
- */
59
- export function calculateDiscount(monthlyPrice: number, yearlyPrice: number): number {
60
- const yearlyMonthly = yearlyPrice / 12;
61
- const discount = ((monthlyPrice - yearlyMonthly) / monthlyPrice) * 100;
62
- return Math.round(discount);
63
- }
64
-
65
- /**
66
- * Calculate prorated amount
67
- *
68
- * @param amount - Full amount
69
- * @param daysUsed - Days used in period
70
- * @param totalDays - Total days in period
71
- * @returns Prorated amount
72
- */
73
- export function calculateProratedAmount(
74
- amount: number,
75
- daysUsed: number,
76
- totalDays: number
77
- ): number {
78
- if (totalDays === 0) return 0;
79
- return (amount / totalDays) * daysUsed;
80
- }
81
-
82
- /**
83
- * Get plan price by cycle
84
- *
85
- * @param plan - Plan tier
86
- * @param cycle - Billing cycle
87
- * @returns Price for cycle
88
- */
89
- export function getPlanPrice(plan: PlanTier, cycle: BillingCycle): number {
90
- return cycle === "monthly" ? plan.monthlyPrice : plan.yearlyPrice;
91
- }
92
-
93
- /**
94
- * Calculate usage percentage
95
- *
96
- * @param metric - Usage metric
97
- * @returns Usage percentage (0-100)
98
- */
99
- export function calculateUsagePercentage(metric: UsageMetric): number {
100
- if (metric.limit === 0) return 0;
101
- return Math.min((metric.current / metric.limit) * 100, 100);
102
- }
103
-
104
- /**
105
- * Check if usage is near limit
106
- *
107
- * @param metric - Usage metric
108
- * @param threshold - Warning threshold (default: 80)
109
- * @returns Whether near limit
110
- */
111
- export function isNearLimit(metric: UsageMetric, threshold: number = 80): boolean {
112
- return calculateUsagePercentage(metric) >= threshold;
113
- }
114
-
115
- /**
116
- * Get subscription status color
117
- *
118
- * @param status - Subscription status
119
- * @returns Color class
120
- */
121
- export function getStatusColor(status: SubscriptionStatus): string {
122
- const colorMap: Record<SubscriptionStatus, string> = {
123
- active: "text-green-600 dark:text-green-500",
124
- trialing: "text-blue-600 dark:text-blue-500",
125
- past_due: "text-orange-600 dark:text-orange-500",
126
- canceled: "text-gray-600 dark:text-gray-500",
127
- unpaid: "text-red-600 dark:text-red-500",
128
- incomplete: "text-yellow-600 dark:text-yellow-500",
129
- revoked: "text-purple-600 dark:text-purple-500",
130
- };
131
-
132
- return colorMap[status] || "text-gray-600";
133
- }
134
-
135
- /**
136
- * Get subscription status label
137
- *
138
- * @param status - Subscription status
139
- * @returns Human readable label
140
- */
141
- export function getStatusLabel(status: SubscriptionStatus): string {
142
- const labelMap: Record<SubscriptionStatus, string> = {
143
- active: "Active",
144
- trialing: "Trial",
145
- past_due: "Past Due",
146
- canceled: "Canceled",
147
- unpaid: "Unpaid",
148
- incomplete: "Incomplete",
149
- revoked: "Revoked",
150
- };
151
-
152
- return labelMap[status] || status;
153
- }
154
-
155
- /**
156
- * Get invoice status color
157
- *
158
- * @param status - Invoice status
159
- * @returns Color class
160
- */
161
- export function getInvoiceStatusColor(status: InvoiceStatus): string {
162
- const colorMap: Record<InvoiceStatus, string> = {
163
- draft: "text-gray-600 dark:text-gray-500",
164
- open: "text-orange-600 dark:text-orange-500",
165
- paid: "text-green-600 dark:text-green-500",
166
- void: "text-gray-600 dark:text-gray-500",
167
- uncollectible: "text-red-600 dark:text-red-500",
168
- refunded: "text-blue-600 dark:text-blue-500",
169
- };
170
-
171
- return colorMap[status] || "text-gray-600";
172
- }
173
-
174
- /**
175
- * Get invoice status label
176
- *
177
- * @param status - Invoice status
178
- * @returns Human readable label
179
- */
180
- export function getInvoiceStatusLabel(status: InvoiceStatus): string {
181
- const labelMap: Record<InvoiceStatus, string> = {
182
- draft: "Draft",
183
- open: "Open",
184
- paid: "Paid",
185
- void: "Void",
186
- uncollectible: "Uncollectible",
187
- refunded: "Refunded",
188
- };
189
-
190
- return labelMap[status] || status;
191
- }
192
-
193
- /**
194
- * Format card number
195
- *
196
- * @param last4 - Last 4 digits
197
- * @param brand - Card brand
198
- * @returns Formatted card display
199
- */
200
- export function formatCardNumber(last4: string, brand: string): string {
201
- return `${brand.toUpperCase()} •••• ${last4}`;
202
- }
203
-
204
- /**
205
- * Format expiry date
206
- *
207
- * @param month - Expiry month
208
- * @param year - Expiry year
209
- * @returns Formatted expiry (MM/YY)
210
- */
211
- export function formatExpiry(month: number, year: number): string {
212
- return `${String(month).padStart(2, "0")}/${String(year).slice(-2)}`;
213
- }
214
-
215
- /**
216
- * Calculate remaining days
217
- *
218
- * @param endDate - End date string
219
- * @returns Days remaining
220
- */
221
- export function getDaysRemaining(endDate: string): number {
222
- const end = new Date(endDate);
223
- const now = new Date();
224
- const diff = end.getTime() - now.getTime();
225
- return Math.max(0, Math.ceil(diff / (1000 * 60 * 60 * 24)));
226
- }
227
-
228
- /**
229
- * Check if trial is expiring soon
230
- *
231
- * @param trialEnd - Trial end date
232
- * @param daysThreshold - Days threshold (default: 7)
233
- * @returns Whether trial is expiring soon
234
- */
235
- export function isTrialExpiringSoon(trialEnd: string, daysThreshold: number = 7): boolean {
236
- const daysRemaining = getDaysRemaining(trialEnd);
237
- return daysRemaining > 0 && daysRemaining <= daysThreshold;
238
- }
239
-
240
- /**
241
- * Calculate next billing date
242
- *
243
- * @param currentPeriodEnd - Current period end
244
- * @param cycle - Billing cycle
245
- * @returns Next billing date
246
- */
247
- export function getNextBillingDate(
248
- currentPeriodEnd: string,
249
- cycle: BillingCycle
250
- ): Date {
251
- const nextDate = new Date(currentPeriodEnd);
252
-
253
- if (cycle === "monthly") {
254
- nextDate.setMonth(nextDate.getMonth() + 1);
255
- } else {
256
- nextDate.setFullYear(nextDate.getFullYear() + 1);
257
- }
258
-
259
- return nextDate;
260
- }
261
-
262
- /**
263
- * Group invoices by status
264
- *
265
- * @param invoices - Array of invoices
266
- * @returns Grouped invoices map
267
- */
268
- export function groupInvoicesByStatus(
269
- invoices: Invoice[]
270
- ): Record<InvoiceStatus, Invoice[]> {
271
- const grouped: Record<string, Invoice[]> = {
272
- draft: [],
273
- open: [],
274
- paid: [],
275
- void: [],
276
- uncollectible: [],
277
- };
278
-
279
- invoices.forEach((invoice) => {
280
- if (grouped[invoice.status]) {
281
- grouped[invoice.status].push(invoice);
282
- }
283
- });
284
-
285
- return grouped as Record<InvoiceStatus, Invoice[]>;
286
- }
287
-
288
- /**
289
- * Calculate total invoice amount
290
- *
291
- * @param invoices - Array of invoices
292
- * @param status - Optional status filter
293
- * @returns Total amount
294
- */
295
- export function calculateInvoiceTotal(
296
- invoices: Invoice[],
297
- status?: InvoiceStatus
298
- ): number {
299
- const filtered = status ? invoices.filter((inv) => inv.status === status) : invoices;
300
- return filtered.reduce((sum, inv) => sum + inv.amount, 0);
301
- }
302
-
303
- /**
304
- * Sort invoices by date
305
- *
306
- * @param invoices - Array of invoices
307
- * @param order - Sort order (default: desc)
308
- * @returns Sorted invoices
309
- */
310
- export function sortInvoicesByDate(
311
- invoices: Invoice[],
312
- order: "asc" | "desc" = "desc"
313
- ): Invoice[] {
314
- return [...invoices].sort((a, b) => {
315
- const dateA = new Date(a.date).getTime();
316
- const dateB = new Date(b.date).getTime();
317
- return order === "asc" ? dateA - dateB : dateB - dateB;
318
- });
319
- }
320
-
321
- /**
322
- * Format feature list item
323
- *
324
- * @param feature - Feature item (string or object)
325
- * @returns Formatted feature
326
- */
327
- export function formatFeature(
328
- feature: string | { text: string; bold?: boolean; included?: boolean }
329
- ): { text: string; bold?: boolean; included?: boolean } {
330
- if (typeof feature === "string") {
331
- return { text: feature, included: true };
332
- }
333
- return feature;
334
- }
335
-
336
- /**
337
- * Check if plan is popular
338
- *
339
- * @param plan - Plan tier
340
- * @param plans - All available plans
341
- * @returns Whether plan is popular (middle tier)
342
- */
343
- export function isPopularPlan(plan: PlanTier, plans: PlanTier[]): boolean {
344
- const middleIndex = Math.floor(plans.length / 2);
345
- return plans[middleIndex]?.id === plan.id;
346
- }
347
-
348
- /**
349
- * Generate trial days text
350
- *
351
- * @param trialEnd - Trial end date
352
- * @returns Days remaining text
353
- */
354
- export function getTrialDaysText(trialEnd: string): string {
355
- const days = getDaysRemaining(trialEnd);
356
-
357
- if (days === 0) return "Trial ends today";
358
- if (days === 1) return "1 day left";
359
- return `${days} days left`;
360
- }