@umituz/web-dashboard 3.2.4 → 4.1.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 (126) hide show
  1. package/README.md +228 -14
  2. package/package.json +23 -30
  3. package/src/domains/analytics/components/AnalyticsCard.tsx +43 -42
  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 +29 -87
  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 +69 -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/index.ts +1 -0
  93. package/src/domains/settings/utils/settings.ts +11 -4
  94. package/src/i18n/I18nContext.tsx +33 -0
  95. package/src/i18n/defaultT.test.ts +43 -0
  96. package/src/i18n/defaultT.ts +34 -0
  97. package/src/i18n/index.ts +7 -0
  98. package/src/i18n/types.ts +13 -0
  99. package/src/index.ts +1 -7
  100. package/src/primitives/Button.tsx +24 -0
  101. package/src/primitives/Checkbox.tsx +17 -0
  102. package/src/primitives/Input.tsx +23 -0
  103. package/src/primitives/Skeleton.tsx +6 -0
  104. package/src/primitives/cn.ts +10 -0
  105. package/src/primitives/index.ts +23 -0
  106. package/src/styles/dashboard.css +2731 -0
  107. package/src/styles/tokens.contract.test.ts +91 -0
  108. package/src/styles/tokens.css +116 -0
  109. package/src/domains/billing/components/BillingLayout.tsx +0 -63
  110. package/src/domains/billing/components/BillingPage.tsx +0 -49
  111. package/src/domains/billing/components/BillingPortal.tsx +0 -198
  112. package/src/domains/billing/components/InvoiceCard.tsx +0 -143
  113. package/src/domains/billing/components/PaymentMethodsList.tsx +0 -116
  114. package/src/domains/billing/components/PlanComparison.tsx +0 -199
  115. package/src/domains/billing/components/UsageCard.tsx +0 -103
  116. package/src/domains/billing/components/index.ts +0 -13
  117. package/src/domains/billing/constants/billing.ts +0 -69
  118. package/src/domains/billing/hooks/index.ts +0 -7
  119. package/src/domains/billing/hooks/useBilling.ts +0 -385
  120. package/src/domains/billing/index.ts +0 -73
  121. package/src/domains/billing/types/billing.ts +0 -342
  122. package/src/domains/billing/types/index.ts +0 -30
  123. package/src/domains/billing/utils/billing.ts +0 -360
  124. package/src/domains/billing/utils/index.ts +0 -31
  125. package/src/domains/billing/utils/polar.ts +0 -63
  126. package/src/domains/calendar/components/index.ts +0 -7
@@ -1,16 +1,33 @@
1
1
  /**
2
- * Analytics Layout Component
2
+ * AnalyticsLayout
3
3
  *
4
- * Configurable analytics page layout with KPIs and charts
4
+ * Top-level analytics page shell. All actions delegate to the
5
+ * consumer via callbacks — no console.log, no no-op handlers.
6
+ * The legacy "kpis" prop has been removed in favor of the typed
7
+ * `metrics` array (breaking change called out in changelog).
5
8
  */
6
9
 
10
+ import { useMemo } from "react";
7
11
  import { RefreshCw, Download, Calendar, 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 { AnalyticsLayoutProps } from "../types/analytics";
12
+ import { useT } from "../../../i18n";
13
+ import { cn } from "../../../primitives";
14
+ import { Button } from "../../../primitives";
15
+ import type { AnalyticsLayoutProps, AnalyticsPeriod } from "../types/analytics";
11
16
  import { AnalyticsCard } from "./AnalyticsCard";
12
17
  import { MetricCard } from "./MetricCard";
13
- import { getDateRangePresets } from "../utils/analytics";
18
+ import { ANALYTICS_KEYS } from "../utils/i18nKeys";
19
+
20
+ /**
21
+ * Map analytics period tokens to localized display labels.
22
+ * Periods not in this map fall back to the raw token — better
23
+ * to show the key than an empty select option.
24
+ */
25
+ const PERIOD_LABEL: Record<AnalyticsPeriod, string> = {
26
+ '7d': ANALYTICS_KEYS.common.last7Days,
27
+ '30d': ANALYTICS_KEYS.common.last30Days,
28
+ '90d': ANALYTICS_KEYS.common.last90Days,
29
+ '1y': ANALYTICS_KEYS.common.lastYear,
30
+ };
14
31
 
15
32
  export const AnalyticsLayout = ({
16
33
  config,
@@ -21,71 +38,83 @@ export const AnalyticsLayout = ({
21
38
  period,
22
39
  onPeriodChange,
23
40
  showDateRange = true,
24
- showRefresh,
25
- showExport,
26
- kpis,
41
+ showRefresh = true,
42
+ showExport = true,
27
43
  charts,
28
44
  headerContent,
45
+ onRefresh,
46
+ onExport,
29
47
  children,
30
48
  }: AnalyticsLayoutProps) => {
31
- const dateRangePresets = getDateRangePresets();
49
+ const t = useT();
32
50
 
33
- const handleExport = async () => {
34
- // In production, implement export functionality
35
- console.log("Exporting analytics data...");
36
- };
51
+ const visibleMetrics = useMemo(() => metrics ?? [], [metrics]);
37
52
 
38
- const handleRefresh = async () => {
39
- // In production, refresh data
40
- console.log("Refreshing analytics data...");
41
- };
42
-
43
- // Use config settings if props not provided
44
- const _showRefresh = showRefresh ?? config?.showRefresh ?? true;
45
- const _showExport = showExport ?? config?.showExport ?? true;
53
+ const showRefreshAction = showRefresh && Boolean(onRefresh);
54
+ const showExportAction = showExport && Boolean(onExport);
46
55
 
47
56
  return (
48
- <div className="w-full space-y-6">
49
- {/* Header */}
50
- <div className="flex items-center justify-between">
51
- <div>
57
+ <div className="udb-analytics">
58
+ <div className="udb-analytics__header">
59
+ <div className="udb-analytics__heading">
52
60
  {(title || config?.brandName) && (
53
- <h1 className="text-3xl font-bold text-foreground">
54
- {title || `${config?.brandName || ''} Analytics`}
61
+ <h1 className="udb-analytics__title">
62
+ {title ??
63
+ (config?.brandName
64
+ ? `${config.brandName} ${t(ANALYTICS_KEYS.layout.title)}`
65
+ : t(ANALYTICS_KEYS.layout.title))}
55
66
  </h1>
56
67
  )}
57
- {description && <p className="text-muted-foreground mt-1">{description}</p>}
68
+ {description && (
69
+ <p className="udb-analytics__description">{description}</p>
70
+ )}
58
71
  </div>
59
72
 
60
- {/* Actions */}
61
- <div className="flex items-center gap-3">
62
- {showDateRange && onPeriodChange && (
63
- <div className="flex items-center gap-2 bg-background border border-border rounded-lg px-3 py-2">
64
- <Calendar className="h-4 w-4 text-muted-foreground" />
73
+ <div className="udb-analytics__actions">
74
+ {showDateRange && onPeriodChange && period && (
75
+ <label className="udb-period-select">
76
+ <Calendar className="udb-period-select__icon" aria-hidden="true" />
77
+ <span className="udb-sr-only">{t(ANALYTICS_KEYS.common.periodSelector)}</span>
65
78
  <select
66
- className="bg-transparent text-sm text-foreground outline-none"
79
+ className="udb-period-select__select"
67
80
  value={period}
68
- onChange={(e) => onPeriodChange(e.target.value)}
81
+ onChange={(e) => onPeriodChange(e.target.value as AnalyticsPeriod)}
82
+ aria-label={t(ANALYTICS_KEYS.common.periodSelector)}
69
83
  >
70
- {config?.availablePeriods?.map((p) => (
84
+ {(config?.availablePeriods ?? Object.keys(PERIOD_LABEL) as AnalyticsPeriod[]).map((p) => (
71
85
  <option key={p} value={p}>
72
- {p === "7d" ? "7 Days" : p === "30d" ? "30 Days" : p === "90d" ? "90 Days" : p}
86
+ {t(PERIOD_LABEL[p] ?? p)}
73
87
  </option>
74
88
  ))}
75
89
  </select>
76
- </div>
90
+ </label>
77
91
  )}
78
92
 
79
- {_showRefresh && (
80
- <Button variant="ghost" size="sm" onClick={handleRefresh} disabled={loading}>
81
- <RefreshCw className={cn("h-4 w-4", loading && "animate-spin")} />
93
+ {showRefreshAction && (
94
+ <Button
95
+ variant="ghost"
96
+ size="sm"
97
+ onClick={onRefresh}
98
+ disabled={loading}
99
+ aria-label={t(ANALYTICS_KEYS.common.refresh)}
100
+ >
101
+ <RefreshCw
102
+ className={cn("udb-analytics__action-icon", loading && "udb-spin")}
103
+ aria-hidden="true"
104
+ />
82
105
  </Button>
83
106
  )}
84
107
 
85
- {_showExport && (
86
- <Button variant="ghost" size="sm" onClick={handleExport} disabled={loading}>
87
- <Download className="h-4 w-4" />
88
- Export
108
+ {showExportAction && (
109
+ <Button
110
+ variant="ghost"
111
+ size="sm"
112
+ onClick={onExport}
113
+ disabled={loading}
114
+ aria-label={t(ANALYTICS_KEYS.common.export)}
115
+ >
116
+ <Download className="udb-analytics__action-icon" aria-hidden="true" />
117
+ <span>{t(ANALYTICS_KEYS.common.export)}</span>
89
118
  </Button>
90
119
  )}
91
120
 
@@ -93,82 +122,35 @@ export const AnalyticsLayout = ({
93
122
  </div>
94
123
  </div>
95
124
 
96
- {/* Loading State */}
97
125
  {loading && (
98
- <div className="flex items-center justify-center py-24">
99
- <Loader2 className="h-12 w-12 animate-spin text-muted-foreground" />
126
+ <div
127
+ className="udb-analytics__loading"
128
+ role="status"
129
+ aria-live="polite"
130
+ >
131
+ <Loader2 className="udb-spin udb-analytics__loading-spinner" aria-hidden="true" />
132
+ <span className="udb-sr-only">{t(ANALYTICS_KEYS.common.loading)}</span>
100
133
  </div>
101
134
  )}
102
135
 
103
- {/* Metrics / KPI Cards */}
104
- {!loading && (metrics || kpis) && (
105
- <div className={cn(
106
- "grid gap-4",
107
- (metrics || kpis) && "grid-cols-1 sm:grid-cols-2 lg:grid-cols-5"
108
- )}>
109
- {metrics?.map((metric) => (
136
+ {!loading && visibleMetrics.length > 0 && (
137
+ <div
138
+ className={cn(
139
+ "udb-analytics__metrics",
140
+ visibleMetrics.length >= 4 && "udb-analytics__metrics--wide",
141
+ )}
142
+ >
143
+ {visibleMetrics.map((metric) => (
110
144
  <MetricCard key={metric.id} metric={metric} />
111
145
  ))}
112
-
113
- {/* Legacy kpis support */}
114
- {kpis && !metrics && (
115
- <>
116
- <MetricCard
117
- metric={{
118
- id: "downloads",
119
- name: "Downloads",
120
- value: kpis.downloads.current,
121
- previousValue: kpis.downloads.previous,
122
- unit: "K",
123
- }}
124
- />
125
- <MetricCard
126
- metric={{
127
- id: "engagement",
128
- name: "Engagement",
129
- value: kpis.engagement.current,
130
- previousValue: kpis.engagement.previous,
131
- unit: "%",
132
- }}
133
- />
134
- <MetricCard
135
- metric={{
136
- id: "users",
137
- name: "Users",
138
- value: kpis.users.current,
139
- previousValue: kpis.users.previous,
140
- unit: "K",
141
- }}
142
- />
143
- <MetricCard
144
- metric={{
145
- id: "revenue",
146
- name: "Revenue",
147
- value: kpis.revenue.current,
148
- previousValue: kpis.revenue.previous,
149
- unit: "$",
150
- }}
151
- />
152
- <MetricCard
153
- metric={{
154
- id: "retention",
155
- name: "Retention",
156
- value: kpis.retention.current,
157
- previousValue: kpis.retention.previous,
158
- unit: "%",
159
- }}
160
- />
161
- </>
162
- )}
163
146
  </div>
164
147
  )}
165
148
 
166
- {/* Charts */}
167
149
  {!loading && charts && charts.length > 0 && (
168
- <div className="grid grid-cols-1 lg:grid-cols-2 gap-6">
169
- {charts.map((chartConfig, index) => (
150
+ <div className="udb-analytics__charts">
151
+ {charts.map((chartConfig) => (
170
152
  <AnalyticsCard
171
- key={index}
153
+ key={chartConfig.id}
172
154
  title={chartConfig.title}
173
155
  description={chartConfig.description}
174
156
  chart={chartConfig}
@@ -177,7 +159,6 @@ export const AnalyticsLayout = ({
177
159
  </div>
178
160
  )}
179
161
 
180
- {/* Custom Content */}
181
162
  {!loading && children}
182
163
  </div>
183
164
  );
@@ -1,13 +1,15 @@
1
1
  /**
2
- * Metric Card Component
3
- *
4
- * Displays a single metric with trend indicator
2
+ * MetricCard — dashboard logic (growth, i18n, a11y) on top of the
3
+ * design-system MetricCard presentation.
5
4
  */
6
5
 
7
- import { ArrowUp, ArrowDown, Minus } from "lucide-react";
8
- import { cn } from "@umituz/web-design-system/utils";
6
+ import {
7
+ MetricCard as DSMetricCard,
8
+ } from "@umituz/web-design-system/organisms";
9
+ import { useT } from "../../../i18n";
9
10
  import type { MetricCardProps } from "../types/analytics";
10
- import { formatMetricValue, calculateGrowth } from "../utils/analytics";
11
+ import { formatMetricValue, calculateGrowth, getTrend } from "../utils/analytics";
12
+ import { ANALYTICS_KEYS } from "../utils/i18nKeys";
11
13
 
12
14
  export const MetricCard = ({
13
15
  metric,
@@ -17,91 +19,31 @@ export const MetricCard = ({
17
19
  className,
18
20
  onClick,
19
21
  }: MetricCardProps) => {
20
- const growth =
21
- metric.previousValue !== undefined
22
- ? calculateGrowth(metric.value, metric.previousValue)
23
- : 0;
22
+ const t = useT();
24
23
 
25
- const sizeClasses = {
26
- sm: "p-4",
27
- md: "p-6",
28
- lg: "p-8",
29
- };
30
-
31
- const titleSizeClasses = {
32
- sm: "text-sm",
33
- md: "text-base",
34
- lg: "text-lg",
35
- };
36
-
37
- const valueSizeClasses = {
38
- sm: "text-xl",
39
- md: "text-3xl",
40
- lg: "text-4xl",
41
- };
24
+ const hasPrevious = metric.previousValue !== undefined;
25
+ const growth = hasPrevious ? calculateGrowth(metric.value, metric.previousValue as number) : null;
26
+ const trend = growth !== null ? getTrend(growth) : "stable";
42
27
 
43
28
  return (
44
- <div
29
+ <DSMetricCard
30
+ className={className}
31
+ title={metric.name}
32
+ value={formatMetricValue(metric)}
33
+ icon={showIcon ? metric.icon : undefined}
34
+ iconColor="text-primary"
35
+ size={size}
36
+ trend={
37
+ showTrend && growth !== null && trend !== "stable"
38
+ ? {
39
+ value: growth,
40
+ label: t(ANALYTICS_KEYS.common.lastPeriod),
41
+ }
42
+ : undefined
43
+ }
44
+ trendValueFormatter={(v) => `${Math.abs(v).toFixed(1)}%`}
45
45
  onClick={onClick}
46
- className={cn(
47
- "bg-background border border-border rounded-xl",
48
- sizeClasses[size],
49
- onClick && "cursor-pointer hover:border-primary/50 transition-colors",
50
- className
51
- )}
52
- >
53
- <div className="flex items-start justify-between">
54
- <div className="flex-1">
55
- {/* Title */}
56
- <p
57
- className={cn(
58
- "text-muted-foreground font-medium mb-1",
59
- titleSizeClasses[size]
60
- )}
61
- >
62
- {metric.name}
63
- </p>
64
-
65
- {/* Value */}
66
- <div className={cn("font-bold text-foreground", valueSizeClasses[size])}>
67
- {formatMetricValue(metric)}
68
- </div>
69
-
70
- {/* Trend */}
71
- {showTrend && metric.previousValue !== undefined && (
72
- <div className="flex items-center gap-1 mt-2">
73
- {growth > 0 ? (
74
- <ArrowUp className="h-4 w-4 text-green-600 dark:text-green-500" />
75
- ) : growth < 0 ? (
76
- <ArrowDown className="h-4 w-4 text-destructive" />
77
- ) : (
78
- <Minus className="h-4 w-4 text-muted-foreground" />
79
- )}
80
- <span
81
- className={cn(
82
- "text-sm font-medium",
83
- growth > 0 && "text-green-600 dark:text-green-500",
84
- growth < 0 && "text-destructive",
85
- growth === 0 && "text-muted-foreground"
86
- )}
87
- >
88
- {Math.abs(growth).toFixed(1)}%
89
- </span>
90
- <span className="text-sm text-muted-foreground">
91
- vs last period
92
- </span>
93
- </div>
94
- )}
95
- </div>
96
-
97
- {/* Icon */}
98
- {showIcon && metric.icon && (
99
- <div className="w-12 h-12 rounded-lg bg-primary/10 flex items-center justify-center shrink-0">
100
- <metric.icon className="h-6 w-6 text-primary" />
101
- </div>
102
- )}
103
- </div>
104
- </div>
46
+ />
105
47
  );
106
48
  };
107
49
 
@@ -4,4 +4,10 @@
4
4
  * Export all analytics hooks
5
5
  */
6
6
 
7
- export { useAnalytics } from "./useAnalytics";
7
+ export { useAnalytics, createStubAnalyticsApiClient } from "./useAnalytics";
8
+ export type {
9
+ AnalyticsApiClient,
10
+ AnalyticsDataPayload,
11
+ UseAnalyticsOptions,
12
+ UseAnalyticsReturn,
13
+ } from "./useAnalytics";