@umituz/web-dashboard 2.0.7 → 2.0.8

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 (171) hide show
  1. package/package.json +35 -77
  2. package/src/domains/layouts/components/BrandLogo.tsx +83 -0
  3. package/src/domains/layouts/components/DashboardHeader.tsx +240 -0
  4. package/src/domains/layouts/components/DashboardLayout.tsx +155 -0
  5. package/src/domains/layouts/components/DashboardSidebar.tsx +152 -0
  6. package/src/domains/layouts/components/index.ts +8 -0
  7. package/src/domains/layouts/hooks/dashboard.ts +81 -0
  8. package/src/domains/layouts/hooks/index.ts +8 -0
  9. package/src/domains/layouts/index.ts +11 -0
  10. package/{dist/layouts/theme/default.js → src/domains/layouts/theme/default.ts} +18 -11
  11. package/src/domains/layouts/theme/index.ts +18 -0
  12. package/src/domains/layouts/theme/presets.ts +96 -0
  13. package/src/domains/layouts/theme/utils.ts +67 -0
  14. package/src/domains/layouts/types/index.ts +9 -0
  15. package/src/domains/layouts/types/layout.ts +43 -0
  16. package/src/domains/layouts/types/notification.ts +19 -0
  17. package/src/domains/layouts/types/sidebar.ts +35 -0
  18. package/src/domains/layouts/types/theme.ts +64 -0
  19. package/src/domains/layouts/types/user.ts +35 -0
  20. package/src/domains/layouts/utils/dashboard.ts +96 -0
  21. package/src/domains/layouts/utils/index.ts +11 -0
  22. package/src/domains/onboarding/components/AppFocusStep.tsx +113 -0
  23. package/src/domains/onboarding/components/OnboardingWizard.tsx +262 -0
  24. package/src/domains/onboarding/components/PlanStep.tsx +208 -0
  25. package/src/domains/onboarding/components/PlatformsStep.tsx +109 -0
  26. package/src/domains/onboarding/components/UserTypeStep.tsx +135 -0
  27. package/src/domains/onboarding/components/index.ts +9 -0
  28. package/src/domains/onboarding/hooks/index.ts +5 -0
  29. package/{dist/onboarding/hooks/index.js → src/domains/onboarding/hooks/useOnboarding.ts} +65 -19
  30. package/src/domains/onboarding/index.ts +35 -0
  31. package/src/domains/onboarding/types/index.ts +16 -0
  32. package/src/domains/onboarding/types/onboarding.ts +214 -0
  33. package/src/domains/onboarding/utils/index.ts +15 -0
  34. package/src/domains/onboarding/utils/onboarding.ts +166 -0
  35. package/src/domains/settings/components/SettingsLayout.tsx +144 -0
  36. package/src/domains/settings/components/SettingsSection.tsx +106 -0
  37. package/src/domains/settings/components/index.ts +6 -0
  38. package/src/domains/settings/hooks/index.ts +7 -0
  39. package/src/domains/settings/hooks/useSettings.ts +80 -0
  40. package/src/domains/settings/index.ts +22 -0
  41. package/src/domains/settings/types/index.ts +11 -0
  42. package/src/domains/settings/types/settings.ts +81 -0
  43. package/src/domains/settings/utils/index.ts +11 -0
  44. package/src/domains/settings/utils/settings.ts +80 -0
  45. package/dist/layouts/components/BrandLogo.d.ts +0 -18
  46. package/dist/layouts/components/BrandLogo.js +0 -88
  47. package/dist/layouts/components/BrandLogo.js.map +0 -1
  48. package/dist/layouts/components/DashboardHeader.d.ts +0 -36
  49. package/dist/layouts/components/DashboardHeader.js +0 -225
  50. package/dist/layouts/components/DashboardHeader.js.map +0 -1
  51. package/dist/layouts/components/DashboardLayout.d.ts +0 -45
  52. package/dist/layouts/components/DashboardLayout.js +0 -501
  53. package/dist/layouts/components/DashboardLayout.js.map +0 -1
  54. package/dist/layouts/components/DashboardSidebar.d.ts +0 -29
  55. package/dist/layouts/components/DashboardSidebar.js +0 -189
  56. package/dist/layouts/components/DashboardSidebar.js.map +0 -1
  57. package/dist/layouts/components/index.d.ts +0 -10
  58. package/dist/layouts/components/index.js +0 -502
  59. package/dist/layouts/components/index.js.map +0 -1
  60. package/dist/layouts/hooks/dashboard.d.ts +0 -35
  61. package/dist/layouts/hooks/dashboard.js +0 -57
  62. package/dist/layouts/hooks/dashboard.js.map +0 -1
  63. package/dist/layouts/hooks/index.d.ts +0 -3
  64. package/dist/layouts/hooks/index.js +0 -57
  65. package/dist/layouts/hooks/index.js.map +0 -1
  66. package/dist/layouts/index.d.ts +0 -17
  67. package/dist/layouts/index.js +0 -756
  68. package/dist/layouts/index.js.map +0 -1
  69. package/dist/layouts/theme/default.d.ts +0 -18
  70. package/dist/layouts/theme/default.js.map +0 -1
  71. package/dist/layouts/theme/index.d.ts +0 -4
  72. package/dist/layouts/theme/index.js +0 -184
  73. package/dist/layouts/theme/index.js.map +0 -1
  74. package/dist/layouts/theme/presets.d.ts +0 -14
  75. package/dist/layouts/theme/presets.js +0 -137
  76. package/dist/layouts/theme/presets.js.map +0 -1
  77. package/dist/layouts/theme/utils.d.ts +0 -22
  78. package/dist/layouts/theme/utils.js +0 -181
  79. package/dist/layouts/theme/utils.js.map +0 -1
  80. package/dist/layouts/types/index.d.ts +0 -6
  81. package/dist/layouts/types/index.js +0 -2
  82. package/dist/layouts/types/index.js.map +0 -1
  83. package/dist/layouts/types/layout.d.ts +0 -45
  84. package/dist/layouts/types/layout.js +0 -2
  85. package/dist/layouts/types/layout.js.map +0 -1
  86. package/dist/layouts/types/notification.d.ts +0 -20
  87. package/dist/layouts/types/notification.js +0 -2
  88. package/dist/layouts/types/notification.js.map +0 -1
  89. package/dist/layouts/types/sidebar.d.ts +0 -36
  90. package/dist/layouts/types/sidebar.js +0 -2
  91. package/dist/layouts/types/sidebar.js.map +0 -1
  92. package/dist/layouts/types/theme.d.ts +0 -64
  93. package/dist/layouts/types/theme.js +0 -2
  94. package/dist/layouts/types/theme.js.map +0 -1
  95. package/dist/layouts/types/user.d.ts +0 -37
  96. package/dist/layouts/types/user.js +0 -2
  97. package/dist/layouts/types/user.js.map +0 -1
  98. package/dist/layouts/utils/dashboard.d.ts +0 -57
  99. package/dist/layouts/utils/dashboard.js +0 -44
  100. package/dist/layouts/utils/dashboard.js.map +0 -1
  101. package/dist/layouts/utils/index.d.ts +0 -1
  102. package/dist/layouts/utils/index.js +0 -44
  103. package/dist/layouts/utils/index.js.map +0 -1
  104. package/dist/onboarding/components/AppFocusStep.d.ts +0 -26
  105. package/dist/onboarding/components/AppFocusStep.js +0 -86
  106. package/dist/onboarding/components/AppFocusStep.js.map +0 -1
  107. package/dist/onboarding/components/OnboardingWizard.d.ts +0 -13
  108. package/dist/onboarding/components/OnboardingWizard.js +0 -332
  109. package/dist/onboarding/components/OnboardingWizard.js.map +0 -1
  110. package/dist/onboarding/components/PlanStep.d.ts +0 -21
  111. package/dist/onboarding/components/PlanStep.js +0 -167
  112. package/dist/onboarding/components/PlanStep.js.map +0 -1
  113. package/dist/onboarding/components/PlatformsStep.d.ts +0 -26
  114. package/dist/onboarding/components/PlatformsStep.js +0 -86
  115. package/dist/onboarding/components/PlatformsStep.js.map +0 -1
  116. package/dist/onboarding/components/UserTypeStep.d.ts +0 -30
  117. package/dist/onboarding/components/UserTypeStep.js +0 -93
  118. package/dist/onboarding/components/UserTypeStep.js.map +0 -1
  119. package/dist/onboarding/components/index.d.ts +0 -9
  120. package/dist/onboarding/components/index.js +0 -738
  121. package/dist/onboarding/components/index.js.map +0 -1
  122. package/dist/onboarding/hooks/index.d.ts +0 -4
  123. package/dist/onboarding/hooks/index.js.map +0 -1
  124. package/dist/onboarding/hooks/useOnboarding.d.ts +0 -50
  125. package/dist/onboarding/hooks/useOnboarding.js +0 -100
  126. package/dist/onboarding/hooks/useOnboarding.js.map +0 -1
  127. package/dist/onboarding/index.d.ts +0 -11
  128. package/dist/onboarding/index.js +0 -913
  129. package/dist/onboarding/index.js.map +0 -1
  130. package/dist/onboarding/types/index.d.ts +0 -3
  131. package/dist/onboarding/types/index.js +0 -2
  132. package/dist/onboarding/types/index.js.map +0 -1
  133. package/dist/onboarding/types/onboarding.d.ts +0 -209
  134. package/dist/onboarding/types/onboarding.js +0 -2
  135. package/dist/onboarding/types/onboarding.js.map +0 -1
  136. package/dist/onboarding/utils/index.d.ts +0 -4
  137. package/dist/onboarding/utils/index.js +0 -83
  138. package/dist/onboarding/utils/index.js.map +0 -1
  139. package/dist/onboarding/utils/onboarding.d.ts +0 -106
  140. package/dist/onboarding/utils/onboarding.js +0 -83
  141. package/dist/onboarding/utils/onboarding.js.map +0 -1
  142. package/dist/settings/components/SettingsLayout.d.ts +0 -19
  143. package/dist/settings/components/SettingsLayout.js +0 -170
  144. package/dist/settings/components/SettingsLayout.js.map +0 -1
  145. package/dist/settings/components/SettingsSection.d.ts +0 -24
  146. package/dist/settings/components/SettingsSection.js +0 -73
  147. package/dist/settings/components/SettingsSection.js.map +0 -1
  148. package/dist/settings/components/index.d.ts +0 -5
  149. package/dist/settings/components/index.js +0 -169
  150. package/dist/settings/components/index.js.map +0 -1
  151. package/dist/settings/hooks/index.d.ts +0 -3
  152. package/dist/settings/hooks/index.js +0 -59
  153. package/dist/settings/hooks/index.js.map +0 -1
  154. package/dist/settings/hooks/useSettings.d.ts +0 -25
  155. package/dist/settings/hooks/useSettings.js +0 -59
  156. package/dist/settings/hooks/useSettings.js.map +0 -1
  157. package/dist/settings/index.d.ts +0 -7
  158. package/dist/settings/index.js +0 -259
  159. package/dist/settings/index.js.map +0 -1
  160. package/dist/settings/types/index.d.ts +0 -2
  161. package/dist/settings/types/index.js +0 -2
  162. package/dist/settings/types/index.js.map +0 -1
  163. package/dist/settings/types/settings.d.ts +0 -79
  164. package/dist/settings/types/settings.js +0 -2
  165. package/dist/settings/types/settings.js.map +0 -1
  166. package/dist/settings/utils/index.d.ts +0 -3
  167. package/dist/settings/utils/index.js +0 -39
  168. package/dist/settings/utils/index.js.map +0 -1
  169. package/dist/settings/utils/settings.d.ts +0 -50
  170. package/dist/settings/utils/settings.js +0 -39
  171. package/dist/settings/utils/settings.js.map +0 -1
@@ -0,0 +1,35 @@
1
+ /**
2
+ * Dashboard Types - User
3
+ *
4
+ * Type definitions for user profile
5
+ */
6
+
7
+ /**
8
+ * User profile info for header
9
+ */
10
+ export interface DashboardUser {
11
+ /** User ID */
12
+ id: string;
13
+ /** Display name */
14
+ name?: string;
15
+ /** Email address */
16
+ email?: string;
17
+ /** Avatar URL */
18
+ avatar?: string;
19
+ /** Whether user has mobile app access */
20
+ hasMobileApp?: boolean;
21
+ /** Whether user has web app access */
22
+ hasWebApp?: boolean;
23
+ }
24
+
25
+ /**
26
+ * Navigation item for user menu
27
+ */
28
+ export interface UserNavMenuItem {
29
+ /** Display label */
30
+ label: string;
31
+ /** Icon component */
32
+ icon: React.ComponentType<{ className?: string }>;
33
+ /** Route path */
34
+ path: string;
35
+ }
@@ -0,0 +1,96 @@
1
+ /**
2
+ * Dashboard Utilities
3
+ *
4
+ * Utility functions for dashboard operations
5
+ */
6
+
7
+ /**
8
+ * Format notification timestamp to relative time
9
+ *
10
+ * @param createdAt - Notification creation timestamp
11
+ * @param t - i18n translation function
12
+ * @returns Formatted time string
13
+ */
14
+ export function formatNotificationTime(
15
+ createdAt: Date | string | number,
16
+ t: (key: string, params?: Record<string, unknown>) => string
17
+ ): string {
18
+ const date = new Date(createdAt);
19
+ const secs = Math.floor((Date.now() - date.getTime()) / 1000);
20
+
21
+ if (secs < 60) return t("dashboard.activityFeed.times.justNow");
22
+ if (secs < 3600) return t("dashboard.activityFeed.times.minutesAgo", { val: Math.floor(secs / 60) });
23
+ if (secs < 86400) return t("dashboard.activityFeed.times.hoursAgo", { val: Math.floor(secs / 3600) });
24
+ return t("dashboard.activityFeed.times.daysAgo", { val: Math.floor(secs / 86400) });
25
+ }
26
+
27
+ /**
28
+ * Check if a path is active
29
+ *
30
+ * @param currentPath - Current location pathname
31
+ * @param targetPath - Target path to check
32
+ * @returns True if paths match
33
+ */
34
+ export function isPathActive(currentPath: string, targetPath: string): boolean {
35
+ return currentPath === targetPath;
36
+ }
37
+
38
+ /**
39
+ * Get page title from sidebar configuration
40
+ *
41
+ * @param pathname - Current pathname
42
+ * @param sidebarGroups - Sidebar groups configuration
43
+ * @param extraTitleMap - Extra title mappings
44
+ * @returns Page title or null
45
+ */
46
+ export function getPageTitle(
47
+ pathname: string,
48
+ sidebarGroups: Array<{ items: Array<{ path: string; label: string }> }>,
49
+ extraTitleMap?: Record<string, string>
50
+ ): string | null {
51
+ // Search in sidebar groups
52
+ for (const group of sidebarGroups) {
53
+ const item = group.items.find((i) => i.path === pathname);
54
+ if (item) return item.label;
55
+ }
56
+
57
+ // Search in extra title map
58
+ if (extraTitleMap?.[pathname]) {
59
+ return extraTitleMap[pathname];
60
+ }
61
+
62
+ return null;
63
+ }
64
+
65
+ /**
66
+ * Filter sidebar items by app type and enabled status
67
+ *
68
+ * @param items - Sidebar items to filter
69
+ * @param user - Current user object
70
+ * @returns Filtered items
71
+ */
72
+ export function filterSidebarItems<T extends { enabled?: boolean; requiredApp?: "mobile" | "web" }>(
73
+ items: T[],
74
+ user?: { hasMobileApp?: boolean; hasWebApp?: boolean }
75
+ ): T[] {
76
+ return items.filter((item) => {
77
+ // Skip disabled items
78
+ if (item.enabled === false) return false;
79
+
80
+ // Skip items that require specific app types
81
+ if (!item.requiredApp) return true;
82
+ if (item.requiredApp === "mobile") return user?.hasMobileApp ?? false;
83
+ if (item.requiredApp === "web") return user?.hasWebApp ?? false;
84
+
85
+ return true;
86
+ });
87
+ }
88
+
89
+ /**
90
+ * Generate notification ID
91
+ *
92
+ * @returns Unique notification ID
93
+ */
94
+ export function generateNotificationId(): string {
95
+ return crypto.randomUUID();
96
+ }
@@ -0,0 +1,11 @@
1
+ /**
2
+ * Layout Utils Export
3
+ */
4
+
5
+ export {
6
+ formatNotificationTime,
7
+ isPathActive,
8
+ getPageTitle,
9
+ filterSidebarItems,
10
+ generateNotificationId,
11
+ } from './dashboard';
@@ -0,0 +1,113 @@
1
+ import { Check } from "lucide-react";
2
+ import { cn } from "@umituz/web-design-system/utils";
3
+ import type { OnboardingState } from "../types/onboarding";
4
+
5
+ interface AppFocusStepProps {
6
+ /** Current onboarding state */
7
+ state: OnboardingState;
8
+ /** Update state function */
9
+ updateState: (updates: Partial<OnboardingState>) => void;
10
+ /** Custom app type options */
11
+ appTypes?: Array<{
12
+ id: string;
13
+ label: string;
14
+ description: string;
15
+ icon: string;
16
+ }>;
17
+ }
18
+
19
+ /**
20
+ * App Focus Selection Step
21
+ *
22
+ * Second step - select which app types to focus on (mobile/web)
23
+ */
24
+ export const AppFocusStep = ({
25
+ state,
26
+ updateState,
27
+ appTypes,
28
+ }: AppFocusStepProps) => {
29
+ // Default app types
30
+ const defaultAppTypes = [
31
+ {
32
+ id: "mobile",
33
+ label: "Mobile App",
34
+ description: "iOS or Android application",
35
+ icon: "📱",
36
+ },
37
+ {
38
+ id: "web",
39
+ label: "Web App",
40
+ description: "Web application or website",
41
+ icon: "💻",
42
+ },
43
+ ];
44
+
45
+ const types = appTypes || defaultAppTypes;
46
+
47
+ return (
48
+ <div className="w-full max-w-xl">
49
+ <div className="text-center mb-10">
50
+ <h1 className="text-3xl md:text-4xl font-extrabold text-foreground mb-3">
51
+ What type of app are you building?
52
+ </h1>
53
+ <p className="text-muted-foreground">
54
+ Select all that apply - you can change this later
55
+ </p>
56
+ </div>
57
+
58
+ <div className="grid grid-cols-1 gap-4">
59
+ {types.map((type) => {
60
+ const isSelected = type.id === "mobile" ? state.hasMobileApp : state.hasWebApp;
61
+
62
+ return (
63
+ <button
64
+ key={type.id}
65
+ onClick={() => {
66
+ if (type.id === "mobile") {
67
+ updateState({ hasMobileApp: !state.hasMobileApp });
68
+ } else {
69
+ updateState({ hasWebApp: !state.hasWebApp });
70
+ }
71
+ }}
72
+ className={cn(
73
+ "w-full flex items-center gap-4 p-6 rounded-2xl border bg-background text-left transition-all",
74
+ isSelected ? "border-primary ring-2 ring-primary/20 bg-primary/5" : "border-border hover:border-primary/40"
75
+ )}
76
+ >
77
+ <div
78
+ className={cn(
79
+ "w-12 h-12 rounded-2xl bg-muted flex items-center justify-center text-2xl shrink-0 transition-transform",
80
+ isSelected && "scale-110"
81
+ )}
82
+ >
83
+ {type.icon}
84
+ </div>
85
+
86
+ <div className="flex-1">
87
+ <p className="font-bold text-foreground text-lg">{type.label}</p>
88
+ <p className="text-sm text-muted-foreground">{type.description}</p>
89
+ </div>
90
+
91
+ <div
92
+ className={cn(
93
+ "w-6 h-6 rounded-full border-2 flex items-center justify-center shrink-0 transition-colors",
94
+ isSelected ? "border-primary bg-primary text-white" : "border-muted-foreground/30"
95
+ )}
96
+ >
97
+ {isSelected && <Check className="h-4 w-4" />}
98
+ </div>
99
+ </button>
100
+ );
101
+ })}
102
+ </div>
103
+
104
+ {(state.hasMobileApp || state.hasWebApp) && (
105
+ <p className="mt-8 text-center text-sm text-muted-foreground animate-in fade-in slide-in-from-bottom-2 duration-500">
106
+ ✨ Great choice! We'll tailor your experience accordingly.
107
+ </p>
108
+ )}
109
+ </div>
110
+ );
111
+ };
112
+
113
+ export default AppFocusStep;
@@ -0,0 +1,262 @@
1
+ import { useState, useCallback } from "react";
2
+ import { useNavigate } from "react-router-dom";
3
+ import { Check, Loader2 } from "lucide-react";
4
+ import { cn } from "@umituz/web-design-system/utils";
5
+ import { Button } from "@umituz/web-design-system/atoms";
6
+ import { BrandLogo } from "../../layouts/components";
7
+ import type {
8
+ OnboardingConfig,
9
+ OnboardingState,
10
+ OnboardingWizardProps,
11
+ StepProgressProps,
12
+ StepNavigationProps,
13
+ } from "../types/onboarding";
14
+
15
+ /**
16
+ * Step Progress Component
17
+ */
18
+ const StepProgress = ({ currentStep, totalSteps, completedSteps = [] }: StepProgressProps) => {
19
+ return (
20
+ <div className="flex items-center gap-0 flex-1 justify-center">
21
+ {Array.from({ length: totalSteps }, (_, i) => i + 1).map((step) => {
22
+ const isCompleted = completedSteps.includes(step) || step < currentStep;
23
+ const isCurrent = step === currentStep;
24
+
25
+ return (
26
+ <div key={step} className="flex items-center">
27
+ <div
28
+ className={cn(
29
+ "w-8 h-8 rounded-full flex items-center justify-center text-sm font-semibold transition-colors",
30
+ isCompleted || isCurrent
31
+ ? "bg-primary text-primary-foreground"
32
+ : "bg-muted text-muted-foreground"
33
+ )}
34
+ >
35
+ {isCompleted ? <Check className="h-4 w-4" /> : step}
36
+ </div>
37
+ {step < totalSteps && (
38
+ <div
39
+ className={cn(
40
+ "w-12 h-0.5 mx-1",
41
+ step < currentStep ? "bg-primary" : "bg-border"
42
+ )}
43
+ />
44
+ )}
45
+ </div>
46
+ );
47
+ })}
48
+ </div>
49
+ );
50
+ };
51
+
52
+ /**
53
+ * Step Navigation Component
54
+ */
55
+ const StepNavigation = ({
56
+ currentStep,
57
+ totalSteps,
58
+ canGoNext,
59
+ isSaving = false,
60
+ nextLabel,
61
+ prevLabel,
62
+ onNext,
63
+ onPrev,
64
+ allowSkip = false,
65
+ onSkip,
66
+ }: StepNavigationProps) => {
67
+ const t = (key: string) => key; // Simple i18n fallback
68
+
69
+ return (
70
+ <footer className="bg-background border-t border-border px-8 py-6 flex items-center justify-between">
71
+ {currentStep > 1 ? (
72
+ <Button
73
+ variant="ghost"
74
+ onClick={onPrev}
75
+ className="rounded-full px-6"
76
+ disabled={isSaving}
77
+ >
78
+ ← {prevLabel || t("onboarding.buttons.back")}
79
+ </Button>
80
+ ) : (
81
+ <div />
82
+ )}
83
+
84
+ <div className="flex items-center gap-3">
85
+ {allowSkip && onSkip && currentStep < totalSteps && (
86
+ <Button
87
+ variant="ghost"
88
+ onClick={onSkip}
89
+ className="rounded-full px-6"
90
+ disabled={isSaving}
91
+ >
92
+ {t("onboarding.buttons.skip")}
93
+ </Button>
94
+ )}
95
+
96
+ <Button
97
+ onClick={onNext}
98
+ className="rounded-full px-12 h-12 text-base font-bold"
99
+ disabled={!canGoNext || isSaving}
100
+ >
101
+ {isSaving ? (
102
+ <>
103
+ <Loader2 className="h-4 w-4 mr-2 animate-spin" />
104
+ {t("onboarding.buttons.finalizing")}
105
+ </>
106
+ ) : currentStep === totalSteps ? (
107
+ nextLabel || t("onboarding.buttons.getStarted")
108
+ ) : (
109
+ t("onboarding.buttons.next")
110
+ )}
111
+ </Button>
112
+ </div>
113
+ </footer>
114
+ );
115
+ };
116
+
117
+ /**
118
+ * Onboarding Wizard Component
119
+ *
120
+ * Main onboarding container with step management
121
+ */
122
+ export const OnboardingWizard = ({
123
+ config,
124
+ initialState,
125
+ onComplete,
126
+ onCancel,
127
+ }: OnboardingWizardProps) => {
128
+ const navigate = useNavigate();
129
+ const [currentStep, setCurrentStep] = useState(1);
130
+ const [saving, setSaving] = useState(false);
131
+ const [completedSteps, setCompletedSteps] = useState<number[]>([]);
132
+
133
+ // State management
134
+ const [state, setState] = useState<OnboardingState>({
135
+ currentStep: 1,
136
+ connectedPlatforms: [],
137
+ billingCycle: "monthly",
138
+ stepData: {},
139
+ ...initialState,
140
+ });
141
+
142
+ const totalSteps = config.steps.length;
143
+
144
+ // Validate current step
145
+ const canGoNext = useCallback(() => {
146
+ const currentStepConfig = config.steps[currentStep - 1];
147
+ if (currentStepConfig?.validate) {
148
+ return currentStepConfig.validate(state);
149
+ }
150
+ return true;
151
+ }, [config.steps, currentStep, state]);
152
+
153
+ // Navigation handlers
154
+ const goToNext = useCallback(async () => {
155
+ if (currentStep < totalSteps) {
156
+ setCompletedSteps((prev) => [...prev, currentStep]);
157
+ setCurrentStep((prev) => prev + 1);
158
+ setState((prev) => ({ ...prev, currentStep: prev.currentStep + 1 }));
159
+ } else {
160
+ // Complete onboarding
161
+ setSaving(true);
162
+ try {
163
+ await onComplete?.(state);
164
+ navigate(config.completeRoute);
165
+ } catch (error) {
166
+ console.error("Onboarding completion error:", error);
167
+ setSaving(false);
168
+ }
169
+ }
170
+ }, [currentStep, totalSteps, state, onComplete, navigate, config.completeRoute]);
171
+
172
+ const goToPrev = useCallback(() => {
173
+ if (currentStep > 1) {
174
+ setCurrentStep((prev) => prev - 1);
175
+ setState((prev) => ({ ...prev, currentStep: prev.currentStep - 1 }));
176
+ }
177
+ }, [currentStep]);
178
+
179
+ const goToStep = useCallback((step: number) => {
180
+ if (step >= 1 && step <= totalSteps) {
181
+ setCurrentStep(step);
182
+ setState((prev) => ({ ...prev, currentStep: step }));
183
+ }
184
+ }, [totalSteps]);
185
+
186
+ const updateState = useCallback((updates: Partial<OnboardingState>) => {
187
+ setState((prev) => ({ ...prev, ...updates }));
188
+ }, []);
189
+
190
+ const handleCancel = useCallback(() => {
191
+ onCancel?.();
192
+ navigate(config.cancelRoute || "/");
193
+ }, [onCancel, navigate, config.cancelRoute]);
194
+
195
+ const handleSkip = useCallback(() => {
196
+ if (currentStep < totalSteps) {
197
+ goToNext();
198
+ }
199
+ }, [currentStep, totalSteps, goToNext]);
200
+
201
+ // Render current step content
202
+ const currentStepConfig = config.steps[currentStep - 1];
203
+ const StepContent = currentStepConfig?.component;
204
+
205
+ return (
206
+ <div className="min-h-screen bg-secondary flex flex-col">
207
+ {/* Header */}
208
+ <header className="bg-background border-b border-border px-6 py-3 flex items-center">
209
+ <div className="flex items-center gap-2 mr-8">
210
+ <BrandLogo size={28} />
211
+ <span className="font-bold text-foreground">{config.brandName}</span>
212
+ </div>
213
+
214
+ {config.showProgress !== false && (
215
+ <StepProgress
216
+ currentStep={currentStep}
217
+ totalSteps={totalSteps}
218
+ completedSteps={completedSteps}
219
+ />
220
+ )}
221
+
222
+ <div className="w-24" />
223
+ </header>
224
+
225
+ {/* Main Content */}
226
+ <main className="flex-1 flex flex-col items-center justify-center px-4 py-12">
227
+ <div className="w-full max-w-4xl">
228
+ {StepContent ? (
229
+ // Check if it's a function component (has call signature)
230
+ typeof StepContent === 'function' ? (
231
+ <StepContent
232
+ state={state}
233
+ updateState={updateState}
234
+ goToNext={goToNext}
235
+ goToPrev={goToPrev}
236
+ goToStep={goToStep}
237
+ config={config}
238
+ />
239
+ ) : (
240
+ // Otherwise it's a ReactElement
241
+ StepContent
242
+ )
243
+ ) : null}
244
+ </div>
245
+ </main>
246
+
247
+ {/* Footer Navigation */}
248
+ <StepNavigation
249
+ currentStep={currentStep}
250
+ totalSteps={totalSteps}
251
+ canGoNext={canGoNext()}
252
+ isSaving={saving}
253
+ onNext={goToNext}
254
+ onPrev={goToPrev}
255
+ allowSkip={config.allowSkip}
256
+ onSkip={handleSkip}
257
+ />
258
+ </div>
259
+ );
260
+ };
261
+
262
+ export default OnboardingWizard;