@umituz/web-dashboard 2.0.6 → 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.
- package/package.json +35 -77
- package/src/domains/layouts/components/BrandLogo.tsx +83 -0
- package/src/domains/layouts/components/DashboardHeader.tsx +240 -0
- package/src/domains/layouts/components/DashboardLayout.tsx +155 -0
- package/src/domains/layouts/components/DashboardSidebar.tsx +152 -0
- package/src/domains/layouts/components/index.ts +8 -0
- package/src/domains/layouts/hooks/dashboard.ts +81 -0
- package/src/domains/layouts/hooks/index.ts +8 -0
- package/src/domains/layouts/index.ts +11 -0
- package/{dist/layouts/theme/default.js → src/domains/layouts/theme/default.ts} +18 -11
- package/src/domains/layouts/theme/index.ts +18 -0
- package/src/domains/layouts/theme/presets.ts +96 -0
- package/src/domains/layouts/theme/utils.ts +67 -0
- package/src/domains/layouts/types/index.ts +9 -0
- package/src/domains/layouts/types/layout.ts +43 -0
- package/src/domains/layouts/types/notification.ts +19 -0
- package/src/domains/layouts/types/sidebar.ts +35 -0
- package/src/domains/layouts/types/theme.ts +64 -0
- package/src/domains/layouts/types/user.ts +35 -0
- package/src/domains/layouts/utils/dashboard.ts +96 -0
- package/src/domains/layouts/utils/index.ts +11 -0
- package/src/domains/onboarding/components/AppFocusStep.tsx +113 -0
- package/src/domains/onboarding/components/OnboardingWizard.tsx +262 -0
- package/src/domains/onboarding/components/PlanStep.tsx +208 -0
- package/src/domains/onboarding/components/PlatformsStep.tsx +109 -0
- package/src/domains/onboarding/components/UserTypeStep.tsx +135 -0
- package/src/domains/onboarding/components/index.ts +9 -0
- package/src/domains/onboarding/hooks/index.ts +5 -0
- package/{dist/onboarding/hooks/index.js → src/domains/onboarding/hooks/useOnboarding.ts} +65 -19
- package/src/domains/onboarding/index.ts +35 -0
- package/src/domains/onboarding/types/index.ts +16 -0
- package/src/domains/onboarding/types/onboarding.ts +214 -0
- package/src/domains/onboarding/utils/index.ts +15 -0
- package/src/domains/onboarding/utils/onboarding.ts +166 -0
- package/src/domains/settings/components/SettingsLayout.tsx +144 -0
- package/src/domains/settings/components/SettingsSection.tsx +106 -0
- package/src/domains/settings/components/index.ts +6 -0
- package/src/domains/settings/hooks/index.ts +7 -0
- package/src/domains/settings/hooks/useSettings.ts +80 -0
- package/src/domains/settings/index.ts +22 -0
- package/src/domains/settings/types/index.ts +11 -0
- package/src/domains/settings/types/settings.ts +81 -0
- package/src/domains/settings/utils/index.ts +11 -0
- package/src/domains/settings/utils/settings.ts +80 -0
- package/dist/layouts/components/BrandLogo.d.ts +0 -18
- package/dist/layouts/components/BrandLogo.js +0 -88
- package/dist/layouts/components/BrandLogo.js.map +0 -1
- package/dist/layouts/components/DashboardHeader.d.ts +0 -36
- package/dist/layouts/components/DashboardHeader.js +0 -225
- package/dist/layouts/components/DashboardHeader.js.map +0 -1
- package/dist/layouts/components/DashboardLayout.d.ts +0 -45
- package/dist/layouts/components/DashboardLayout.js +0 -501
- package/dist/layouts/components/DashboardLayout.js.map +0 -1
- package/dist/layouts/components/DashboardSidebar.d.ts +0 -29
- package/dist/layouts/components/DashboardSidebar.js +0 -189
- package/dist/layouts/components/DashboardSidebar.js.map +0 -1
- package/dist/layouts/components/index.d.ts +0 -10
- package/dist/layouts/components/index.js +0 -502
- package/dist/layouts/components/index.js.map +0 -1
- package/dist/layouts/hooks/dashboard.d.ts +0 -35
- package/dist/layouts/hooks/dashboard.js +0 -57
- package/dist/layouts/hooks/dashboard.js.map +0 -1
- package/dist/layouts/hooks/index.d.ts +0 -3
- package/dist/layouts/hooks/index.js +0 -57
- package/dist/layouts/hooks/index.js.map +0 -1
- package/dist/layouts/index.d.ts +0 -17
- package/dist/layouts/index.js +0 -756
- package/dist/layouts/index.js.map +0 -1
- package/dist/layouts/theme/default.d.ts +0 -18
- package/dist/layouts/theme/default.js.map +0 -1
- package/dist/layouts/theme/index.d.ts +0 -4
- package/dist/layouts/theme/index.js +0 -184
- package/dist/layouts/theme/index.js.map +0 -1
- package/dist/layouts/theme/presets.d.ts +0 -14
- package/dist/layouts/theme/presets.js +0 -137
- package/dist/layouts/theme/presets.js.map +0 -1
- package/dist/layouts/theme/utils.d.ts +0 -22
- package/dist/layouts/theme/utils.js +0 -181
- package/dist/layouts/theme/utils.js.map +0 -1
- package/dist/layouts/types/index.d.ts +0 -6
- package/dist/layouts/types/index.js +0 -2
- package/dist/layouts/types/index.js.map +0 -1
- package/dist/layouts/types/layout.d.ts +0 -45
- package/dist/layouts/types/layout.js +0 -2
- package/dist/layouts/types/layout.js.map +0 -1
- package/dist/layouts/types/notification.d.ts +0 -20
- package/dist/layouts/types/notification.js +0 -2
- package/dist/layouts/types/notification.js.map +0 -1
- package/dist/layouts/types/sidebar.d.ts +0 -36
- package/dist/layouts/types/sidebar.js +0 -2
- package/dist/layouts/types/sidebar.js.map +0 -1
- package/dist/layouts/types/theme.d.ts +0 -64
- package/dist/layouts/types/theme.js +0 -2
- package/dist/layouts/types/theme.js.map +0 -1
- package/dist/layouts/types/user.d.ts +0 -37
- package/dist/layouts/types/user.js +0 -2
- package/dist/layouts/types/user.js.map +0 -1
- package/dist/layouts/utils/dashboard.d.ts +0 -57
- package/dist/layouts/utils/dashboard.js +0 -44
- package/dist/layouts/utils/dashboard.js.map +0 -1
- package/dist/layouts/utils/index.d.ts +0 -1
- package/dist/layouts/utils/index.js +0 -44
- package/dist/layouts/utils/index.js.map +0 -1
- package/dist/onboarding/components/AppFocusStep.d.ts +0 -26
- package/dist/onboarding/components/AppFocusStep.js +0 -86
- package/dist/onboarding/components/AppFocusStep.js.map +0 -1
- package/dist/onboarding/components/OnboardingWizard.d.ts +0 -13
- package/dist/onboarding/components/OnboardingWizard.js +0 -332
- package/dist/onboarding/components/OnboardingWizard.js.map +0 -1
- package/dist/onboarding/components/PlanStep.d.ts +0 -21
- package/dist/onboarding/components/PlanStep.js +0 -167
- package/dist/onboarding/components/PlanStep.js.map +0 -1
- package/dist/onboarding/components/PlatformsStep.d.ts +0 -26
- package/dist/onboarding/components/PlatformsStep.js +0 -86
- package/dist/onboarding/components/PlatformsStep.js.map +0 -1
- package/dist/onboarding/components/UserTypeStep.d.ts +0 -30
- package/dist/onboarding/components/UserTypeStep.js +0 -93
- package/dist/onboarding/components/UserTypeStep.js.map +0 -1
- package/dist/onboarding/components/index.d.ts +0 -9
- package/dist/onboarding/components/index.js +0 -738
- package/dist/onboarding/components/index.js.map +0 -1
- package/dist/onboarding/hooks/index.d.ts +0 -4
- package/dist/onboarding/hooks/index.js.map +0 -1
- package/dist/onboarding/hooks/useOnboarding.d.ts +0 -50
- package/dist/onboarding/hooks/useOnboarding.js +0 -100
- package/dist/onboarding/hooks/useOnboarding.js.map +0 -1
- package/dist/onboarding/index.d.ts +0 -11
- package/dist/onboarding/index.js +0 -913
- package/dist/onboarding/index.js.map +0 -1
- package/dist/onboarding/types/index.d.ts +0 -3
- package/dist/onboarding/types/index.js +0 -2
- package/dist/onboarding/types/index.js.map +0 -1
- package/dist/onboarding/types/onboarding.d.ts +0 -209
- package/dist/onboarding/types/onboarding.js +0 -2
- package/dist/onboarding/types/onboarding.js.map +0 -1
- package/dist/onboarding/utils/index.d.ts +0 -4
- package/dist/onboarding/utils/index.js +0 -83
- package/dist/onboarding/utils/index.js.map +0 -1
- package/dist/onboarding/utils/onboarding.d.ts +0 -106
- package/dist/onboarding/utils/onboarding.js +0 -83
- package/dist/onboarding/utils/onboarding.js.map +0 -1
- package/dist/settings/components/SettingsLayout.d.ts +0 -19
- package/dist/settings/components/SettingsLayout.js +0 -170
- package/dist/settings/components/SettingsLayout.js.map +0 -1
- package/dist/settings/components/SettingsSection.d.ts +0 -24
- package/dist/settings/components/SettingsSection.js +0 -73
- package/dist/settings/components/SettingsSection.js.map +0 -1
- package/dist/settings/components/index.d.ts +0 -5
- package/dist/settings/components/index.js +0 -169
- package/dist/settings/components/index.js.map +0 -1
- package/dist/settings/hooks/index.d.ts +0 -3
- package/dist/settings/hooks/index.js +0 -59
- package/dist/settings/hooks/index.js.map +0 -1
- package/dist/settings/hooks/useSettings.d.ts +0 -25
- package/dist/settings/hooks/useSettings.js +0 -59
- package/dist/settings/hooks/useSettings.js.map +0 -1
- package/dist/settings/index.d.ts +0 -7
- package/dist/settings/index.js +0 -259
- package/dist/settings/index.js.map +0 -1
- package/dist/settings/types/index.d.ts +0 -2
- package/dist/settings/types/index.js +0 -2
- package/dist/settings/types/index.js.map +0 -1
- package/dist/settings/types/settings.d.ts +0 -79
- package/dist/settings/types/settings.js +0 -2
- package/dist/settings/types/settings.js.map +0 -1
- package/dist/settings/utils/index.d.ts +0 -3
- package/dist/settings/utils/index.js +0 -39
- package/dist/settings/utils/index.js.map +0 -1
- package/dist/settings/utils/settings.d.ts +0 -50
- package/dist/settings/utils/settings.js +0 -39
- package/dist/settings/utils/settings.js.map +0 -1
|
@@ -0,0 +1,214 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Onboarding Types
|
|
3
|
+
*
|
|
4
|
+
* Type definitions for onboarding system
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import type { LucideIcon } from "lucide-react";
|
|
8
|
+
import type { ComponentType, ReactElement, ReactNode } from "react";
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* User type option for first step
|
|
12
|
+
*/
|
|
13
|
+
export interface UserTypeOption {
|
|
14
|
+
/** Unique identifier */
|
|
15
|
+
id: string;
|
|
16
|
+
/** Display label */
|
|
17
|
+
label: string;
|
|
18
|
+
/** Description text */
|
|
19
|
+
description: string;
|
|
20
|
+
/** Icon component */
|
|
21
|
+
icon?: LucideIcon;
|
|
22
|
+
/** Badge text */
|
|
23
|
+
badge?: string;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Platform connection option
|
|
28
|
+
*/
|
|
29
|
+
export interface PlatformOption {
|
|
30
|
+
/** Unique identifier */
|
|
31
|
+
id: string;
|
|
32
|
+
/** Display name */
|
|
33
|
+
name: string;
|
|
34
|
+
/** Icon/emoji */
|
|
35
|
+
icon: string;
|
|
36
|
+
/** Color theme */
|
|
37
|
+
color?: string;
|
|
38
|
+
/** Connection status */
|
|
39
|
+
connected?: boolean;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Plan option for billing step
|
|
44
|
+
*/
|
|
45
|
+
export interface PlanOption {
|
|
46
|
+
/** Unique identifier */
|
|
47
|
+
id: string;
|
|
48
|
+
/** Plan name */
|
|
49
|
+
name: string;
|
|
50
|
+
/** Badge text */
|
|
51
|
+
badge?: string;
|
|
52
|
+
/** Badge color class */
|
|
53
|
+
badgeColor?: string;
|
|
54
|
+
/** Description text */
|
|
55
|
+
description: string;
|
|
56
|
+
/** Monthly price */
|
|
57
|
+
price: number;
|
|
58
|
+
/** Features list */
|
|
59
|
+
features: (string | { text: string; bold?: boolean })[];
|
|
60
|
+
/** Highlight style */
|
|
61
|
+
highlight?: boolean;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Props for onboarding step components
|
|
66
|
+
*/
|
|
67
|
+
export interface OnboardingStepContentProps {
|
|
68
|
+
/** Current onboarding state */
|
|
69
|
+
state: OnboardingState;
|
|
70
|
+
/** Update state function */
|
|
71
|
+
updateState: (updates: Partial<OnboardingState>) => void;
|
|
72
|
+
/** Go to next step */
|
|
73
|
+
goToNext: () => void;
|
|
74
|
+
/** Go to previous step */
|
|
75
|
+
goToPrev: () => void;
|
|
76
|
+
/** Go to specific step */
|
|
77
|
+
goToStep: (step: number) => void;
|
|
78
|
+
/** Onboarding configuration */
|
|
79
|
+
config: OnboardingConfig;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* Onboarding step configuration
|
|
84
|
+
*/
|
|
85
|
+
export interface OnboardingStep {
|
|
86
|
+
/** Step identifier */
|
|
87
|
+
id: string;
|
|
88
|
+
/** Step number */
|
|
89
|
+
order: number;
|
|
90
|
+
/** Step title */
|
|
91
|
+
title: string;
|
|
92
|
+
/** Step description */
|
|
93
|
+
description?: string;
|
|
94
|
+
/** Custom component - either a function component with props or a direct React node */
|
|
95
|
+
component?: ComponentType<OnboardingStepContentProps> | ReactElement;
|
|
96
|
+
/** Whether this step can be skipped */
|
|
97
|
+
skippable?: boolean;
|
|
98
|
+
/** Validation function */
|
|
99
|
+
validate?: (data: OnboardingState) => boolean;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* Onboarding configuration
|
|
104
|
+
*/
|
|
105
|
+
export interface OnboardingConfig {
|
|
106
|
+
/** Brand/application name */
|
|
107
|
+
brandName: string;
|
|
108
|
+
/** Brand tagline */
|
|
109
|
+
brandTagline?: string;
|
|
110
|
+
/** Onboarding steps */
|
|
111
|
+
steps: OnboardingStep[];
|
|
112
|
+
/** Route to navigate after completion */
|
|
113
|
+
completeRoute: string;
|
|
114
|
+
/** Route to navigate on cancel */
|
|
115
|
+
cancelRoute?: string;
|
|
116
|
+
/** Allow skipping steps */
|
|
117
|
+
allowSkip?: boolean;
|
|
118
|
+
/** Show progress indicator */
|
|
119
|
+
showProgress?: boolean;
|
|
120
|
+
/** Enable user menu in header */
|
|
121
|
+
showUserMenu?: boolean;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
/**
|
|
125
|
+
* Onboarding state
|
|
126
|
+
*/
|
|
127
|
+
export interface OnboardingState extends Record<string, unknown> {
|
|
128
|
+
/** Current step number */
|
|
129
|
+
currentStep: number;
|
|
130
|
+
/** Selected user type */
|
|
131
|
+
selectedUserType?: string;
|
|
132
|
+
/** Has mobile app */
|
|
133
|
+
hasMobileApp?: boolean;
|
|
134
|
+
/** Has web app */
|
|
135
|
+
hasWebApp?: boolean;
|
|
136
|
+
/** Connected platform IDs */
|
|
137
|
+
connectedPlatforms: string[];
|
|
138
|
+
/** Selected plan ID */
|
|
139
|
+
selectedPlan?: string;
|
|
140
|
+
/** Billing cycle */
|
|
141
|
+
billingCycle: "monthly" | "yearly";
|
|
142
|
+
/** Additional step data */
|
|
143
|
+
stepData: Record<string, unknown>;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
/**
|
|
147
|
+
* Onboarding actions
|
|
148
|
+
*/
|
|
149
|
+
export interface OnboardingActions {
|
|
150
|
+
/** Go to next step */
|
|
151
|
+
goToNext: () => void;
|
|
152
|
+
/** Go to previous step */
|
|
153
|
+
goToPrev: () => void;
|
|
154
|
+
/** Go to specific step */
|
|
155
|
+
goToStep: (step: number) => void;
|
|
156
|
+
/** Update state */
|
|
157
|
+
updateState: (updates: Partial<OnboardingState>) => void;
|
|
158
|
+
/** Complete onboarding */
|
|
159
|
+
complete: () => Promise<void>;
|
|
160
|
+
/** Cancel onboarding */
|
|
161
|
+
cancel: () => void;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
/**
|
|
165
|
+
* Onboarding wizard props
|
|
166
|
+
*/
|
|
167
|
+
export interface OnboardingWizardProps {
|
|
168
|
+
/** Onboarding configuration */
|
|
169
|
+
config: OnboardingConfig;
|
|
170
|
+
/** Initial state */
|
|
171
|
+
initialState?: Partial<OnboardingState>;
|
|
172
|
+
/** Completion callback */
|
|
173
|
+
onComplete?: (data: OnboardingState) => Promise<void>;
|
|
174
|
+
/** Cancel callback */
|
|
175
|
+
onCancel?: () => void;
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
/**
|
|
179
|
+
* Step progress props
|
|
180
|
+
*/
|
|
181
|
+
export interface StepProgressProps {
|
|
182
|
+
/** Current step number */
|
|
183
|
+
currentStep: number;
|
|
184
|
+
/** Total number of steps */
|
|
185
|
+
totalSteps: number;
|
|
186
|
+
/** Completed steps */
|
|
187
|
+
completedSteps?: number[];
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
/**
|
|
191
|
+
* Step navigation props
|
|
192
|
+
*/
|
|
193
|
+
export interface StepNavigationProps {
|
|
194
|
+
/** Current step number */
|
|
195
|
+
currentStep: number;
|
|
196
|
+
/** Total number of steps */
|
|
197
|
+
totalSteps: number;
|
|
198
|
+
/** Can proceed to next step */
|
|
199
|
+
canGoNext: boolean;
|
|
200
|
+
/** Is saving/completing */
|
|
201
|
+
isSaving?: boolean;
|
|
202
|
+
/** Next button label */
|
|
203
|
+
nextLabel?: string;
|
|
204
|
+
/** Previous button label */
|
|
205
|
+
prevLabel?: string;
|
|
206
|
+
/** On next */
|
|
207
|
+
onNext: () => void;
|
|
208
|
+
/** On previous */
|
|
209
|
+
onPrev: () => void;
|
|
210
|
+
/** Allow skipping */
|
|
211
|
+
allowSkip?: boolean;
|
|
212
|
+
/** On skip */
|
|
213
|
+
onSkip?: () => void;
|
|
214
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Onboarding Utils Export
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
export {
|
|
6
|
+
validateStep,
|
|
7
|
+
getStepTitle,
|
|
8
|
+
getStepDescription,
|
|
9
|
+
calculateProgress,
|
|
10
|
+
getCompletedSteps,
|
|
11
|
+
formatOnboardingData,
|
|
12
|
+
generateOnboardingEvent,
|
|
13
|
+
isValidEmail,
|
|
14
|
+
isValidPassword,
|
|
15
|
+
} from './onboarding';
|
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Onboarding Utilities
|
|
3
|
+
*
|
|
4
|
+
* Utility functions for onboarding operations
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import type { OnboardingState, OnboardingConfig } from "../types/onboarding";
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Validate onboarding step
|
|
11
|
+
*
|
|
12
|
+
* @param state - Current onboarding state
|
|
13
|
+
* @param stepNumber - Step number to validate
|
|
14
|
+
* @param config - Onboarding configuration
|
|
15
|
+
* @returns Whether the step is valid
|
|
16
|
+
*/
|
|
17
|
+
export function validateStep(
|
|
18
|
+
state: OnboardingState,
|
|
19
|
+
stepNumber: number,
|
|
20
|
+
config: OnboardingConfig
|
|
21
|
+
): boolean {
|
|
22
|
+
const stepConfig = config.steps[stepNumber - 1];
|
|
23
|
+
|
|
24
|
+
if (stepConfig?.validate) {
|
|
25
|
+
return stepConfig.validate(state);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
// Default validations
|
|
29
|
+
switch (stepNumber) {
|
|
30
|
+
case 1:
|
|
31
|
+
// User type step
|
|
32
|
+
return !!state.selectedUserType;
|
|
33
|
+
|
|
34
|
+
case 2:
|
|
35
|
+
// App focus step
|
|
36
|
+
return !!(state.hasMobileApp || state.hasWebApp);
|
|
37
|
+
|
|
38
|
+
case 3:
|
|
39
|
+
// Platforms step
|
|
40
|
+
return state.connectedPlatforms.length > 0;
|
|
41
|
+
|
|
42
|
+
case 4:
|
|
43
|
+
// Plan step
|
|
44
|
+
return !!state.selectedPlan;
|
|
45
|
+
|
|
46
|
+
default:
|
|
47
|
+
return true;
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Get step title
|
|
53
|
+
*
|
|
54
|
+
* @param stepNumber - Step number
|
|
55
|
+
* @param config - Onboarding configuration
|
|
56
|
+
* @returns Step title
|
|
57
|
+
*/
|
|
58
|
+
export function getStepTitle(stepNumber: number, config: OnboardingConfig): string {
|
|
59
|
+
const stepConfig = config.steps[stepNumber - 1];
|
|
60
|
+
return stepConfig?.title || `Step ${stepNumber}`;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Get step description
|
|
65
|
+
*
|
|
66
|
+
* @param stepNumber - Step number
|
|
67
|
+
* @param config - Onboarding configuration
|
|
68
|
+
* @returns Step description or undefined
|
|
69
|
+
*/
|
|
70
|
+
export function getStepDescription(stepNumber: number, config: OnboardingConfig): string | undefined {
|
|
71
|
+
const stepConfig = config.steps[stepNumber - 1];
|
|
72
|
+
return stepConfig?.description;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Calculate onboarding progress
|
|
77
|
+
*
|
|
78
|
+
* @param currentStep - Current step number
|
|
79
|
+
* @param totalSteps - Total number of steps
|
|
80
|
+
* @returns Progress percentage (0-100)
|
|
81
|
+
*/
|
|
82
|
+
export function calculateProgress(currentStep: number, totalSteps: number): number {
|
|
83
|
+
return Math.min((currentStep / totalSteps) * 100, 100);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* Get completed steps
|
|
88
|
+
*
|
|
89
|
+
* @param currentStep - Current step number
|
|
90
|
+
* @returns Array of completed step numbers
|
|
91
|
+
*/
|
|
92
|
+
export function getCompletedSteps(currentStep: number): number[] {
|
|
93
|
+
return Array.from({ length: currentStep - 1 }, (_, i) => i + 1);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* Format onboarding data for API submission
|
|
98
|
+
*
|
|
99
|
+
* @param state - Onboarding state
|
|
100
|
+
* @param userId - User ID
|
|
101
|
+
* @returns Formatted data object
|
|
102
|
+
*/
|
|
103
|
+
export function formatOnboardingData(state: OnboardingState, userId?: string) {
|
|
104
|
+
return {
|
|
105
|
+
userId,
|
|
106
|
+
userType: state.selectedUserType,
|
|
107
|
+
hasMobileApp: state.hasMobileApp,
|
|
108
|
+
hasWebApp: state.hasWebApp,
|
|
109
|
+
connectedPlatforms: state.connectedPlatforms,
|
|
110
|
+
selectedPlan: state.selectedPlan,
|
|
111
|
+
billingCycle: state.billingCycle,
|
|
112
|
+
stepData: state.stepData,
|
|
113
|
+
completedAt: new Date().toISOString(),
|
|
114
|
+
};
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
/**
|
|
118
|
+
* Generate onboarding analytics event
|
|
119
|
+
*
|
|
120
|
+
* @param event - Event name
|
|
121
|
+
* @param state - Onboarding state
|
|
122
|
+
* @param additionalData - Additional event data
|
|
123
|
+
* @returns Analytics event object
|
|
124
|
+
*/
|
|
125
|
+
export function generateOnboardingEvent(
|
|
126
|
+
event: string,
|
|
127
|
+
state: OnboardingState,
|
|
128
|
+
additionalData?: Record<string, unknown>
|
|
129
|
+
) {
|
|
130
|
+
return {
|
|
131
|
+
event,
|
|
132
|
+
properties: {
|
|
133
|
+
currentStep: state.currentStep,
|
|
134
|
+
userType: state.selectedUserType,
|
|
135
|
+
hasMobileApp: state.hasMobileApp,
|
|
136
|
+
hasWebApp: state.hasWebApp,
|
|
137
|
+
platformCount: state.connectedPlatforms.length,
|
|
138
|
+
selectedPlan: state.selectedPlan,
|
|
139
|
+
billingCycle: state.billingCycle,
|
|
140
|
+
...additionalData,
|
|
141
|
+
},
|
|
142
|
+
timestamp: new Date().toISOString(),
|
|
143
|
+
};
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
/**
|
|
147
|
+
* Validate email format
|
|
148
|
+
*
|
|
149
|
+
* @param email - Email address
|
|
150
|
+
* @returns Whether email is valid
|
|
151
|
+
*/
|
|
152
|
+
export function isValidEmail(email: string): boolean {
|
|
153
|
+
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
|
|
154
|
+
return emailRegex.test(email);
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
/**
|
|
158
|
+
* Validate password strength
|
|
159
|
+
*
|
|
160
|
+
* @param password - Password
|
|
161
|
+
* @param minLength - Minimum length (default: 8)
|
|
162
|
+
* @returns Whether password meets requirements
|
|
163
|
+
*/
|
|
164
|
+
export function isValidPassword(password: string, minLength: number = 8): boolean {
|
|
165
|
+
return password.length >= minLength;
|
|
166
|
+
}
|
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
import { useState } from "react";
|
|
2
|
+
import { Outlet, useLocation, useNavigate } from "react-router-dom";
|
|
3
|
+
import { SettingsSection } from "./SettingsSection";
|
|
4
|
+
import type { SettingsConfig } from "../types/settings";
|
|
5
|
+
import { Skeleton } from "@umituz/web-design-system/atoms";
|
|
6
|
+
import { ChevronLeft, Menu } from "lucide-react";
|
|
7
|
+
import { Button } from "@umituz/web-design-system/atoms";
|
|
8
|
+
|
|
9
|
+
interface SettingsLayoutProps {
|
|
10
|
+
/** Settings configuration */
|
|
11
|
+
config: SettingsConfig;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Settings Layout Component
|
|
16
|
+
*
|
|
17
|
+
* Main layout wrapper for settings pages.
|
|
18
|
+
* Provides sidebar navigation and content area.
|
|
19
|
+
*
|
|
20
|
+
* @param props - Settings layout props
|
|
21
|
+
*/
|
|
22
|
+
export const SettingsLayout = ({
|
|
23
|
+
config,
|
|
24
|
+
}: SettingsLayoutProps) => {
|
|
25
|
+
const location = useLocation();
|
|
26
|
+
const navigate = useNavigate();
|
|
27
|
+
const [collapsed, setCollapsed] = useState(false);
|
|
28
|
+
const [mobileOpen, setMobileOpen] = useState(false);
|
|
29
|
+
const [loading, setLoading] = useState(true);
|
|
30
|
+
|
|
31
|
+
// Simulate loading on route change
|
|
32
|
+
useState(() => {
|
|
33
|
+
setLoading(true);
|
|
34
|
+
const timer = setTimeout(() => setLoading(false), 200);
|
|
35
|
+
return () => clearTimeout(timer);
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
const handleNavigate = (path: string) => {
|
|
39
|
+
navigate(path);
|
|
40
|
+
setMobileOpen(false);
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
return (
|
|
44
|
+
<div className="flex h-screen w-full bg-background font-sans">
|
|
45
|
+
{/* Desktop Sidebar */}
|
|
46
|
+
<aside
|
|
47
|
+
className={`hidden md:flex flex-col shrink-0 border-r border-border bg-card transition-all duration-300 ${
|
|
48
|
+
collapsed ? "w-16" : "w-64"
|
|
49
|
+
}`}
|
|
50
|
+
>
|
|
51
|
+
<div className="flex h-14 items-center justify-between border-b border-border px-4">
|
|
52
|
+
{!collapsed && (
|
|
53
|
+
<h2 className="text-lg font-semibold text-foreground">
|
|
54
|
+
{config.brandName || "Settings"}
|
|
55
|
+
</h2>
|
|
56
|
+
)}
|
|
57
|
+
<Button
|
|
58
|
+
variant="ghost"
|
|
59
|
+
size="icon"
|
|
60
|
+
onClick={() => setCollapsed(!collapsed)}
|
|
61
|
+
className="ml-auto"
|
|
62
|
+
>
|
|
63
|
+
{collapsed ? <Menu className="h-4 w-4" /> : <ChevronLeft className="h-4 w-4" />}
|
|
64
|
+
</Button>
|
|
65
|
+
</div>
|
|
66
|
+
|
|
67
|
+
<nav className="flex-1 overflow-y-auto p-2">
|
|
68
|
+
{config.sections.map((section) => (
|
|
69
|
+
<SettingsSection
|
|
70
|
+
key={section.key}
|
|
71
|
+
section={section}
|
|
72
|
+
currentPath={location.pathname}
|
|
73
|
+
onNavigate={handleNavigate}
|
|
74
|
+
collapsed={collapsed}
|
|
75
|
+
/>
|
|
76
|
+
))}
|
|
77
|
+
</nav>
|
|
78
|
+
</aside>
|
|
79
|
+
|
|
80
|
+
{/* Mobile Menu Overlay */}
|
|
81
|
+
{mobileOpen && (
|
|
82
|
+
<div className="fixed inset-0 z-50 md:hidden">
|
|
83
|
+
<div
|
|
84
|
+
className="absolute inset-0 bg-background/80 backdrop-blur-sm"
|
|
85
|
+
onClick={() => setMobileOpen(false)}
|
|
86
|
+
/>
|
|
87
|
+
<aside className="absolute left-0 top-0 h-full w-64 border-r border-border bg-card shadow-xl">
|
|
88
|
+
<div className="flex h-14 items-center justify-between border-b border-border px-4">
|
|
89
|
+
<h2 className="text-lg font-semibold text-foreground">
|
|
90
|
+
{config.brandName || "Settings"}
|
|
91
|
+
</h2>
|
|
92
|
+
<Button variant="ghost" size="icon" onClick={() => setMobileOpen(false)}>
|
|
93
|
+
<Menu className="h-4 w-4" />
|
|
94
|
+
</Button>
|
|
95
|
+
</div>
|
|
96
|
+
|
|
97
|
+
<nav className="flex-1 overflow-y-auto p-2">
|
|
98
|
+
{config.sections.map((section) => (
|
|
99
|
+
<SettingsSection
|
|
100
|
+
key={section.key}
|
|
101
|
+
section={section}
|
|
102
|
+
currentPath={location.pathname}
|
|
103
|
+
onNavigate={handleNavigate}
|
|
104
|
+
/>
|
|
105
|
+
))}
|
|
106
|
+
</nav>
|
|
107
|
+
</aside>
|
|
108
|
+
</div>
|
|
109
|
+
)}
|
|
110
|
+
|
|
111
|
+
{/* Main Content Area */}
|
|
112
|
+
<div className="flex flex-1 flex-col overflow-hidden min-w-0">
|
|
113
|
+
{/* Mobile Header */}
|
|
114
|
+
<header className="flex h-14 items-center justify-between border-b border-border bg-card/50 backdrop-blur-md px-4 shrink-0 md:hidden">
|
|
115
|
+
<div className="flex items-center gap-3">
|
|
116
|
+
<Button variant="ghost" size="icon" onClick={() => setMobileOpen(true)}>
|
|
117
|
+
<Menu className="h-5 w-5" />
|
|
118
|
+
</Button>
|
|
119
|
+
<h2 className="text-sm font-semibold text-foreground">
|
|
120
|
+
{config.brandName || "Settings"}
|
|
121
|
+
</h2>
|
|
122
|
+
</div>
|
|
123
|
+
</header>
|
|
124
|
+
|
|
125
|
+
<main className="flex-1 overflow-y-auto p-4 md:p-8">
|
|
126
|
+
{loading ? (
|
|
127
|
+
<div className="mx-auto w-full max-w-4xl space-y-6">
|
|
128
|
+
<Skeleton className="h-8 w-1/3 rounded-xl" />
|
|
129
|
+
<div className="grid gap-4">
|
|
130
|
+
{Array.from({ length: 3 }).map((_, i) => (
|
|
131
|
+
<Skeleton key={i} className="h-24 rounded-xl" />
|
|
132
|
+
))}
|
|
133
|
+
</div>
|
|
134
|
+
</div>
|
|
135
|
+
) : (
|
|
136
|
+
<Outlet />
|
|
137
|
+
)}
|
|
138
|
+
</main>
|
|
139
|
+
</div>
|
|
140
|
+
</div>
|
|
141
|
+
);
|
|
142
|
+
};
|
|
143
|
+
|
|
144
|
+
export default SettingsLayout;
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
import { Link } from "react-router-dom";
|
|
2
|
+
import { ChevronRight } from "lucide-react";
|
|
3
|
+
import { cn } from "@umituz/web-design-system/utils";
|
|
4
|
+
import type { SettingsSection as SettingsSectionType } from "../types/settings";
|
|
5
|
+
|
|
6
|
+
interface SettingsSectionProps {
|
|
7
|
+
/** Section configuration */
|
|
8
|
+
section: SettingsSectionType;
|
|
9
|
+
/** Current path */
|
|
10
|
+
currentPath?: string;
|
|
11
|
+
/** On navigate callback */
|
|
12
|
+
onNavigate?: (path: string) => void;
|
|
13
|
+
/** Collapsed state */
|
|
14
|
+
collapsed?: boolean;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Settings Section Component
|
|
19
|
+
*
|
|
20
|
+
* Displays a section of settings items.
|
|
21
|
+
*
|
|
22
|
+
* @param props - Settings section props
|
|
23
|
+
*/
|
|
24
|
+
export const SettingsSection = ({
|
|
25
|
+
section,
|
|
26
|
+
currentPath,
|
|
27
|
+
onNavigate,
|
|
28
|
+
collapsed = false,
|
|
29
|
+
}: SettingsSectionProps) => {
|
|
30
|
+
const filteredItems = section.items.filter((item) => item.enabled !== false);
|
|
31
|
+
|
|
32
|
+
if (filteredItems.length === 0) return null;
|
|
33
|
+
|
|
34
|
+
return (
|
|
35
|
+
<div className="mb-6 last:mb-0">
|
|
36
|
+
{!collapsed && (
|
|
37
|
+
<h3 className="px-2 mb-2 text-xs font-bold uppercase tracking-wider text-muted-foreground">
|
|
38
|
+
{section.title}
|
|
39
|
+
</h3>
|
|
40
|
+
)}
|
|
41
|
+
|
|
42
|
+
<div className="space-y-1">
|
|
43
|
+
{filteredItems.map((item) => {
|
|
44
|
+
const isActive = currentPath === item.path;
|
|
45
|
+
|
|
46
|
+
const itemContent = (
|
|
47
|
+
<>
|
|
48
|
+
{item.icon && (
|
|
49
|
+
<item.icon
|
|
50
|
+
className={cn(
|
|
51
|
+
"h-4 w-4 shrink-0",
|
|
52
|
+
isActive && "scale-110"
|
|
53
|
+
)}
|
|
54
|
+
/>
|
|
55
|
+
)}
|
|
56
|
+
{!collapsed && (
|
|
57
|
+
<>
|
|
58
|
+
<span className="flex-1 text-left">{item.label}</span>
|
|
59
|
+
{item.badge !== undefined && item.badge > 0 && (
|
|
60
|
+
<span className="ml-auto flex h-5 min-w-5 items-center justify-center rounded-full bg-destructive px-1.5 text-[10px] font-bold text-destructive-foreground">
|
|
61
|
+
{item.badge > 99 ? "99+" : item.badge}
|
|
62
|
+
</span>
|
|
63
|
+
)}
|
|
64
|
+
{item.path && <ChevronRight className="h-4 w-4 text-muted-foreground" />}
|
|
65
|
+
</>
|
|
66
|
+
)}
|
|
67
|
+
</>
|
|
68
|
+
);
|
|
69
|
+
|
|
70
|
+
const itemClassName = cn(
|
|
71
|
+
"flex items-center gap-3 rounded-lg px-3 py-2 text-sm font-medium transition-all duration-200 w-full",
|
|
72
|
+
"hover:bg-accent hover:text-accent-foreground",
|
|
73
|
+
isActive && "bg-accent text-accent-foreground",
|
|
74
|
+
collapsed ? "justify-center" : "justify-start"
|
|
75
|
+
);
|
|
76
|
+
|
|
77
|
+
if (item.path) {
|
|
78
|
+
return (
|
|
79
|
+
<Link
|
|
80
|
+
key={item.key}
|
|
81
|
+
to={item.path}
|
|
82
|
+
onClick={() => onNavigate?.(item.path!)}
|
|
83
|
+
className={itemClassName}
|
|
84
|
+
title={collapsed ? item.label : undefined}
|
|
85
|
+
>
|
|
86
|
+
{itemContent}
|
|
87
|
+
</Link>
|
|
88
|
+
);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
return (
|
|
92
|
+
<button
|
|
93
|
+
key={item.key}
|
|
94
|
+
type="button"
|
|
95
|
+
onClick={() => {}}
|
|
96
|
+
className={itemClassName}
|
|
97
|
+
title={collapsed ? item.label : undefined}
|
|
98
|
+
>
|
|
99
|
+
{itemContent}
|
|
100
|
+
</button>
|
|
101
|
+
);
|
|
102
|
+
})}
|
|
103
|
+
</div>
|
|
104
|
+
</div>
|
|
105
|
+
);
|
|
106
|
+
};
|