@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,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,17 @@
1
1
  /**
2
- * Metric Card Component
2
+ * MetricCard
3
3
  *
4
- * Displays a single metric with trend indicator
4
+ * Single KPI tile with a trend indicator. Colors and sizes come
5
+ * from `--u-*` tokens via the `udb-metric-*` classes (no hardcoded
6
+ * green/orange) and a11y role=button when interactive.
5
7
  */
6
8
 
7
9
  import { ArrowUp, ArrowDown, Minus } from "lucide-react";
8
- import { cn } from "@umituz/web-design-system/utils";
10
+ import { useT } from "../../../i18n";
11
+ import { cn } from "../../../primitives";
9
12
  import type { MetricCardProps } from "../types/analytics";
10
- import { formatMetricValue, calculateGrowth } from "../utils/analytics";
13
+ import { formatMetricValue, calculateGrowth, getTrend } from "../utils/analytics";
14
+ import { ANALYTICS_KEYS } from "../utils/i18nKeys";
11
15
 
12
16
  export const MetricCard = ({
13
17
  metric,
@@ -17,87 +21,61 @@ export const MetricCard = ({
17
21
  className,
18
22
  onClick,
19
23
  }: MetricCardProps) => {
20
- const growth =
21
- metric.previousValue !== undefined
22
- ? calculateGrowth(metric.value, metric.previousValue)
23
- : 0;
24
+ const t = useT();
24
25
 
25
- const sizeClasses = {
26
- sm: "p-4",
27
- md: "p-6",
28
- lg: "p-8",
29
- };
26
+ const hasPrevious = metric.previousValue !== undefined;
27
+ const growth = hasPrevious ? calculateGrowth(metric.value, metric.previousValue as number) : null;
28
+ const trend = growth !== null ? getTrend(growth) : 'stable';
30
29
 
31
- const titleSizeClasses = {
32
- sm: "text-sm",
33
- md: "text-base",
34
- lg: "text-lg",
35
- };
30
+ const TrendIcon = trend === 'up' ? ArrowUp : trend === 'down' ? ArrowDown : Minus;
36
31
 
37
- const valueSizeClasses = {
38
- sm: "text-xl",
39
- md: "text-3xl",
40
- lg: "text-4xl",
32
+ const handleKeyDown = (e: React.KeyboardEvent<HTMLDivElement>) => {
33
+ if (!onClick) return;
34
+ if (e.key === 'Enter' || e.key === ' ') {
35
+ e.preventDefault();
36
+ onClick();
37
+ }
41
38
  };
42
39
 
43
40
  return (
44
41
  <div
42
+ role={onClick ? 'button' : undefined}
43
+ tabIndex={onClick ? 0 : undefined}
44
+ aria-label={onClick ? `${metric.name} — ${formatMetricValue(metric)}` : undefined}
45
45
  onClick={onClick}
46
+ onKeyDown={onClick ? handleKeyDown : undefined}
46
47
  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
48
+ 'udb-metric',
49
+ `udb-metric--${size}`,
50
+ onClick && 'udb-metric--interactive',
51
+ className,
51
52
  )}
52
53
  >
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>
54
+ <div className="udb-metric__head">
55
+ <div className="udb-metric__body">
56
+ <p className="udb-metric__name">{metric.name}</p>
64
57
 
65
- {/* Value */}
66
- <div className={cn("font-bold text-foreground", valueSizeClasses[size])}>
67
- {formatMetricValue(metric)}
68
- </div>
58
+ <div className="udb-metric__value">{formatMetricValue(metric)}</div>
69
59
 
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
- >
60
+ {showTrend && hasPrevious && growth !== null && (
61
+ <div
62
+ className={cn('udb-metric__trend', `udb-metric__trend--${trend}`)}
63
+ aria-label={t(ANALYTICS_KEYS.metric.change)}
64
+ >
65
+ <TrendIcon className="udb-metric__trend-icon" aria-hidden="true" />
66
+ <span className="udb-metric__trend-value">
88
67
  {Math.abs(growth).toFixed(1)}%
89
68
  </span>
90
- <span className="text-sm text-muted-foreground">
91
- vs last period
69
+ <span className="udb-metric__trend-period">
70
+ {t(ANALYTICS_KEYS.common.lastPeriod)}
92
71
  </span>
93
72
  </div>
94
73
  )}
95
74
  </div>
96
75
 
97
- {/* Icon */}
98
76
  {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" />
77
+ <div className="udb-metric__icon-box">
78
+ <metric.icon className="udb-metric__icon" aria-hidden="true" />
101
79
  </div>
102
80
  )}
103
81
  </div>
@@ -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";