@umituz/web-dashboard 5.0.1 → 6.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.
- package/package.json +19 -17
- package/src/domains/onboarding/calc/calc.test.ts +181 -0
- package/src/domains/onboarding/calc/card-stack-motion-curve.ts +33 -0
- package/src/domains/onboarding/calc/count-with-locale.ts +16 -0
- package/src/domains/onboarding/calc/duration-equal-split.ts +24 -0
- package/src/domains/onboarding/calc/elapsed-millis.ts +12 -0
- package/src/domains/onboarding/calc/index.ts +36 -0
- package/src/domains/onboarding/calc/progress-percentage.ts +37 -0
- package/src/domains/onboarding/calc/slider-value-bounds.ts +44 -0
- package/src/domains/onboarding/calc/step-index-clamp.ts +28 -0
- package/src/domains/onboarding/calc/swipe-gesture-curve.ts +67 -0
- package/src/domains/onboarding/components/OnboardingFlow.test.tsx +481 -0
- package/src/domains/onboarding/components/OnboardingFlow.tsx +361 -0
- package/src/domains/onboarding/components/OnboardingIntl.tsx +108 -0
- package/src/domains/onboarding/components/hooks/index.ts +7 -0
- package/src/domains/onboarding/components/hooks/useOnboardingAnalytics.test.tsx +74 -0
- package/src/domains/onboarding/components/hooks/useOnboardingAnalytics.ts +62 -0
- package/src/domains/onboarding/components/hooks/useOnboardingProgress.test.tsx +150 -0
- package/src/domains/onboarding/components/hooks/useOnboardingProgress.ts +260 -0
- package/src/domains/onboarding/components/hooks/usePermissionPriming.ts +52 -0
- package/src/domains/onboarding/components/hooks/useReducedMotion.ts +24 -0
- package/src/domains/onboarding/components/screens/AccountGateScreen.tsx +89 -0
- package/src/domains/onboarding/components/screens/AppDemoScreen.tsx +39 -0
- package/src/domains/onboarding/components/screens/ComparisonScreen.tsx +59 -0
- package/src/domains/onboarding/components/screens/GoalQuestionScreen.tsx +56 -0
- package/src/domains/onboarding/components/screens/PainPointsScreen.tsx +65 -0
- package/src/domains/onboarding/components/screens/PaywallScreen.tsx +138 -0
- package/src/domains/onboarding/components/screens/PermissionPrimingScreen.tsx +113 -0
- package/src/domains/onboarding/components/screens/PersonalizedSolutionScreen.tsx +53 -0
- package/src/domains/onboarding/components/screens/PreferencesScreen.tsx +194 -0
- package/src/domains/onboarding/components/screens/ProcessingScreen.tsx +79 -0
- package/src/domains/onboarding/components/screens/SocialProofScreen.tsx +116 -0
- package/src/domains/onboarding/components/screens/TinderCardsScreen.tsx +108 -0
- package/src/domains/onboarding/components/screens/ValueDeliveryScreen.tsx +85 -0
- package/src/domains/onboarding/components/screens/WelcomeScreen.tsx +52 -0
- package/src/domains/onboarding/components/screens/index.ts +28 -0
- package/src/domains/onboarding/components/shared/AnimationWrapper.tsx +73 -0
- package/src/domains/onboarding/components/shared/OnboardingErrorBoundary.tsx +81 -0
- package/src/domains/onboarding/components/shared/OptionCard.tsx +73 -0
- package/src/domains/onboarding/components/shared/PrimaryButton.tsx +58 -0
- package/src/domains/onboarding/components/shared/ProgressBar.tsx +51 -0
- package/src/domains/onboarding/components/shared/QuestionWrapper.tsx +36 -0
- package/src/domains/onboarding/components/shared/ScreenContainer.tsx +29 -0
- package/src/domains/onboarding/components/shared/SwipeCard.test.tsx +51 -0
- package/src/domains/onboarding/components/shared/SwipeCard.tsx +106 -0
- package/src/domains/onboarding/components/shared/index.ts +16 -0
- package/src/domains/onboarding/components/shared/renderBundledIcon.tsx +20 -0
- package/src/domains/onboarding/index.ts +182 -0
- package/src/domains/onboarding/onboarding.css +1 -0
- package/src/domains/onboarding/predicates/has-handler.ts +25 -0
- package/src/domains/onboarding/predicates/icon-name-resolves.ts +113 -0
- package/src/domains/onboarding/predicates/index.ts +26 -0
- package/src/domains/onboarding/predicates/permission-granted.ts +26 -0
- package/src/domains/onboarding/predicates/permission-supports.ts +66 -0
- package/src/domains/onboarding/predicates/predicates.test.ts +202 -0
- package/src/domains/onboarding/predicates/screen-emits-data.ts +17 -0
- package/src/domains/onboarding/predicates/storage-available.ts +22 -0
- package/src/domains/onboarding/templates/TemplateDemoFallback.tsx +112 -0
- package/src/domains/onboarding/templates/art-onboarding.ts +81 -0
- package/src/domains/onboarding/templates/health-onboarding.ts +97 -0
- package/src/domains/onboarding/templates/index.ts +6 -0
- package/src/domains/onboarding/templates/platform-onboarding.ts +210 -0
- package/src/domains/onboarding/templates/video-onboarding.ts +100 -0
- package/src/domains/onboarding/test/setup.ts +30 -0
- package/src/domains/onboarding/theme/cn.ts +10 -0
- package/src/domains/onboarding/theme/index.css +131 -0
- package/src/domains/onboarding/theme/index.ts +12 -0
- package/src/domains/onboarding/theme/tokens.ts +122 -0
- package/src/domains/onboarding/types/config.ts +68 -0
- package/src/domains/onboarding/types/index.ts +10 -0
- package/src/domains/onboarding/types/labels.ts +83 -0
- package/src/domains/onboarding/types/screens.ts +193 -0
- package/src/domains/onboarding/utils/analytics-dispatch.test.ts +93 -0
- package/src/domains/onboarding/utils/analytics-dispatch.ts +92 -0
- package/src/domains/onboarding/utils/default-storage-key.ts +2 -0
- package/src/domains/onboarding/utils/index.ts +13 -0
- package/src/domains/onboarding/utils/local-storage-bridge.test.ts +93 -0
- package/src/domains/onboarding/utils/local-storage-bridge.ts +104 -0
- package/src/domains/onboarding/utils/permission-bridge.test.ts +64 -0
- package/src/domains/onboarding/utils/permission-bridge.ts +95 -0
- package/src/domains/onboarding/validation/flow-structure-check.ts +28 -0
- package/src/domains/onboarding/validation/index.ts +9 -0
- package/src/domains/onboarding/validation/permission-status-check.ts +23 -0
- package/src/domains/onboarding/validation/screen-config-check.ts +105 -0
- package/src/domains/onboarding/validation/stored-progress-check.ts +68 -0
- package/src/domains/onboarding/validation/validation.test.ts +175 -0
- package/src/index.ts +0 -10
- package/src/styles/dashboard.contract.test.ts +1 -0
- package/src/styles/dashboard.css +0 -460
- package/src/domains/auth/components/AuthLayout.tsx +0 -99
- package/src/domains/auth/components/ForgotPasswordForm.tsx +0 -167
- package/src/domains/auth/components/LoginForm.tsx +0 -222
- package/src/domains/auth/components/RegisterForm.tsx +0 -281
- package/src/domains/auth/components/ResetPasswordForm.tsx +0 -173
- package/src/domains/auth/components/index.ts +0 -11
- package/src/domains/auth/components/shared/AuthErrorBanner.tsx +0 -25
- package/src/domains/auth/components/shared/EmailInput.tsx +0 -73
- package/src/domains/auth/components/shared/PasswordInput.tsx +0 -100
- package/src/domains/auth/components/shared/PasswordStrengthIndicator.tsx +0 -63
- package/src/domains/auth/components/shared/SocialLoginButtons.tsx +0 -44
- package/src/domains/auth/components/shared/SocialProviderIcons.tsx +0 -48
- package/src/domains/auth/components/shared/index.ts +0 -20
- package/src/domains/auth/hooks/index.ts +0 -8
- package/src/domains/auth/hooks/useAuth.ts +0 -230
- package/src/domains/auth/index.ts +0 -62
- package/src/domains/auth/types/auth.ts +0 -302
- package/src/domains/auth/types/index.ts +0 -25
- package/src/domains/auth/utils/auth.test.ts +0 -188
- package/src/domains/auth/utils/auth.ts +0 -221
- package/src/domains/auth/utils/i18nKeys.ts +0 -109
- package/src/domains/auth/utils/index.ts +0 -29
- package/src/domains/auth/utils/secureToken.ts +0 -33
- package/src/domains/auth/utils/validationKeys.ts +0 -23
- package/src/domains/session/DemoLogin.tsx +0 -42
- package/src/domains/session/OrgSwitcher.tsx +0 -102
- package/src/domains/session/SessionContext.tsx +0 -150
- package/src/domains/session/inMemoryAuthAdapter.ts +0 -53
- package/src/domains/session/index.ts +0 -18
- package/src/domains/session/types.ts +0 -47
- package/src/domains/session/utils/i18nKeys.ts +0 -12
|
@@ -1,221 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Auth Utilities
|
|
3
|
-
*
|
|
4
|
-
* Validation and helper functions for authentication.
|
|
5
|
-
* Validation messages are i18n keys, not hardcoded strings —
|
|
6
|
-
* the consumer renders the localized text in its UI layer.
|
|
7
|
-
*/
|
|
8
|
-
|
|
9
|
-
import type { LoginCredentials, RegisterData, User } from "../types/auth";
|
|
10
|
-
import { AUTH_VALIDATION_KEYS } from './validationKeys';
|
|
11
|
-
|
|
12
|
-
export { generateResetToken } from './secureToken';
|
|
13
|
-
export { AUTH_VALIDATION_KEYS } from './validationKeys';
|
|
14
|
-
export type { AuthValidationKey } from './validationKeys';
|
|
15
|
-
|
|
16
|
-
/**
|
|
17
|
-
* Validation result type — error is now an i18n key.
|
|
18
|
-
*/
|
|
19
|
-
export type ValidationResult =
|
|
20
|
-
| { valid: true }
|
|
21
|
-
| { valid: false; error: string };
|
|
22
|
-
|
|
23
|
-
/**
|
|
24
|
-
* Validate email format
|
|
25
|
-
*/
|
|
26
|
-
export function isValidEmail(email: string): boolean {
|
|
27
|
-
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
|
|
28
|
-
return emailRegex.test(email);
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
/**
|
|
32
|
-
* Validate password length
|
|
33
|
-
*/
|
|
34
|
-
export function isValidPassword(password: string, minLength: number = 8): boolean {
|
|
35
|
-
return password.length >= minLength;
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
/**
|
|
39
|
-
* Validate login credentials
|
|
40
|
-
*/
|
|
41
|
-
export function validateLogin(credentials: LoginCredentials): ValidationResult {
|
|
42
|
-
if (!credentials.email) {
|
|
43
|
-
return { valid: false, error: AUTH_VALIDATION_KEYS.emailRequired };
|
|
44
|
-
}
|
|
45
|
-
if (!isValidEmail(credentials.email)) {
|
|
46
|
-
return { valid: false, error: AUTH_VALIDATION_KEYS.emailInvalid };
|
|
47
|
-
}
|
|
48
|
-
if (!credentials.password) {
|
|
49
|
-
return { valid: false, error: AUTH_VALIDATION_KEYS.passwordRequired };
|
|
50
|
-
}
|
|
51
|
-
return { valid: true };
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
/**
|
|
55
|
-
* Validate registration data
|
|
56
|
-
*/
|
|
57
|
-
export function validateRegister(
|
|
58
|
-
data: RegisterData & { confirmPassword?: string },
|
|
59
|
-
requireName: boolean = false,
|
|
60
|
-
requirePasswordConfirm?: boolean,
|
|
61
|
-
): ValidationResult {
|
|
62
|
-
if (!data.email) {
|
|
63
|
-
return { valid: false, error: AUTH_VALIDATION_KEYS.emailRequired };
|
|
64
|
-
}
|
|
65
|
-
if (!isValidEmail(data.email)) {
|
|
66
|
-
return { valid: false, error: AUTH_VALIDATION_KEYS.emailInvalid };
|
|
67
|
-
}
|
|
68
|
-
if (requireName && !data.name) {
|
|
69
|
-
return { valid: false, error: AUTH_VALIDATION_KEYS.nameRequired };
|
|
70
|
-
}
|
|
71
|
-
if (!data.password) {
|
|
72
|
-
return { valid: false, error: AUTH_VALIDATION_KEYS.passwordRequired };
|
|
73
|
-
}
|
|
74
|
-
if (!isValidPassword(data.password)) {
|
|
75
|
-
return { valid: false, error: AUTH_VALIDATION_KEYS.passwordTooShort };
|
|
76
|
-
}
|
|
77
|
-
if (requirePasswordConfirm && data.password !== data.confirmPassword) {
|
|
78
|
-
return { valid: false, error: AUTH_VALIDATION_KEYS.passwordsDoNotMatch };
|
|
79
|
-
}
|
|
80
|
-
return { valid: true };
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
/**
|
|
84
|
-
* Validate forgot-password request
|
|
85
|
-
*/
|
|
86
|
-
export function validateForgotPassword(data: { email: string }): ValidationResult {
|
|
87
|
-
if (!data.email) {
|
|
88
|
-
return { valid: false, error: AUTH_VALIDATION_KEYS.emailRequired };
|
|
89
|
-
}
|
|
90
|
-
if (!isValidEmail(data.email)) {
|
|
91
|
-
return { valid: false, error: AUTH_VALIDATION_KEYS.emailInvalid };
|
|
92
|
-
}
|
|
93
|
-
return { valid: true };
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
/**
|
|
97
|
-
* Validate password reset confirmation
|
|
98
|
-
*/
|
|
99
|
-
export function validateResetPassword(data: {
|
|
100
|
-
token: string;
|
|
101
|
-
password: string;
|
|
102
|
-
confirmPassword: string;
|
|
103
|
-
}): ValidationResult {
|
|
104
|
-
if (!data.token) {
|
|
105
|
-
return { valid: false, error: AUTH_VALIDATION_KEYS.invalidResetToken };
|
|
106
|
-
}
|
|
107
|
-
if (!data.password) {
|
|
108
|
-
return { valid: false, error: AUTH_VALIDATION_KEYS.passwordRequired };
|
|
109
|
-
}
|
|
110
|
-
if (!isValidPassword(data.password)) {
|
|
111
|
-
return { valid: false, error: AUTH_VALIDATION_KEYS.passwordTooShort };
|
|
112
|
-
}
|
|
113
|
-
if (data.password !== data.confirmPassword) {
|
|
114
|
-
return { valid: false, error: AUTH_VALIDATION_KEYS.passwordsDoNotMatch };
|
|
115
|
-
}
|
|
116
|
-
return { valid: true };
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
/**
|
|
120
|
-
* Get user display name
|
|
121
|
-
*
|
|
122
|
-
* Returns null when no usable identifier exists, letting the caller
|
|
123
|
-
* decide on a localized fallback. Avoids hardcoded "Guest"/"User" strings.
|
|
124
|
-
*/
|
|
125
|
-
export function getUserDisplayName(user: User | null): string | null {
|
|
126
|
-
if (!user) return null;
|
|
127
|
-
return user.name || user.email || null;
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
/**
|
|
131
|
-
* Get user initials
|
|
132
|
-
*
|
|
133
|
-
* Returns null when no name is available, so the caller can decide
|
|
134
|
-
* whether to render a placeholder or hide the avatar.
|
|
135
|
-
*/
|
|
136
|
-
export function getUserInitials(user: User | null): string | null {
|
|
137
|
-
if (!user) return null;
|
|
138
|
-
const name = user.name || user.email || "";
|
|
139
|
-
if (!name) return null;
|
|
140
|
-
const parts = name.trim().split(" ");
|
|
141
|
-
if (parts.length >= 2 && parts[0] && parts[parts.length - 1]) {
|
|
142
|
-
return (parts[0][0] + parts[parts.length - 1][0]).toUpperCase();
|
|
143
|
-
}
|
|
144
|
-
return name.slice(0, 2).toUpperCase();
|
|
145
|
-
}
|
|
146
|
-
|
|
147
|
-
/**
|
|
148
|
-
* Check if user has verified email
|
|
149
|
-
*/
|
|
150
|
-
export function isEmailVerified(user: User | null): boolean {
|
|
151
|
-
return user?.emailVerified === true;
|
|
152
|
-
}
|
|
153
|
-
|
|
154
|
-
/**
|
|
155
|
-
* Format user creation date
|
|
156
|
-
*
|
|
157
|
-
* Returns null when the user has no createdAt; the caller decides
|
|
158
|
-
* whether to display "—" or hide the field.
|
|
159
|
-
*/
|
|
160
|
-
export function formatUserCreatedAt(
|
|
161
|
-
user: User | null,
|
|
162
|
-
locale: string = "en-US",
|
|
163
|
-
): string | null {
|
|
164
|
-
if (!user?.createdAt) return null;
|
|
165
|
-
return new Date(user.createdAt).toLocaleDateString(locale, {
|
|
166
|
-
year: "numeric",
|
|
167
|
-
month: "long",
|
|
168
|
-
day: "numeric",
|
|
169
|
-
});
|
|
170
|
-
}
|
|
171
|
-
|
|
172
|
-
/**
|
|
173
|
-
* Mask email for privacy (e.g., u***@example.com)
|
|
174
|
-
*
|
|
175
|
-
* Returns null when the email is invalid so the caller can show
|
|
176
|
-
* a "—" placeholder rather than the raw input.
|
|
177
|
-
*/
|
|
178
|
-
export function maskEmail(email: string): string | null {
|
|
179
|
-
const [local, domain] = email.split("@");
|
|
180
|
-
if (!local || !domain) return null;
|
|
181
|
-
const maskedLocal = local[0] + "***";
|
|
182
|
-
return `${maskedLocal}@${domain}`;
|
|
183
|
-
}
|
|
184
|
-
|
|
185
|
-
/**
|
|
186
|
-
* Password strength score (0-100)
|
|
187
|
-
*/
|
|
188
|
-
export function calculatePasswordStrength(password: string): number {
|
|
189
|
-
let score = 0;
|
|
190
|
-
|
|
191
|
-
if (password.length >= 8) score += 25;
|
|
192
|
-
if (password.length >= 12) score += 25;
|
|
193
|
-
|
|
194
|
-
if (/[a-z]/.test(password)) score += 12.5;
|
|
195
|
-
if (/[A-Z]/.test(password)) score += 12.5;
|
|
196
|
-
if (/[0-9]/.test(password)) score += 12.5;
|
|
197
|
-
if (/[^a-zA-Z0-9]/.test(password)) score += 12.5;
|
|
198
|
-
|
|
199
|
-
return Math.min(score, 100);
|
|
200
|
-
}
|
|
201
|
-
|
|
202
|
-
/**
|
|
203
|
-
* Password strength band. Numeric so the UI can map it to a label
|
|
204
|
-
* via its own translation catalog.
|
|
205
|
-
*/
|
|
206
|
-
export type PasswordStrengthBand = 'weak' | 'fair' | 'good' | 'strong';
|
|
207
|
-
|
|
208
|
-
export function getPasswordStrengthBand(password: string): PasswordStrengthBand {
|
|
209
|
-
const score = calculatePasswordStrength(password);
|
|
210
|
-
if (score < 25) return 'weak';
|
|
211
|
-
if (score < 50) return 'fair';
|
|
212
|
-
if (score < 75) return 'good';
|
|
213
|
-
return 'strong';
|
|
214
|
-
}
|
|
215
|
-
|
|
216
|
-
/**
|
|
217
|
-
* Sanitize user input for display
|
|
218
|
-
*/
|
|
219
|
-
export function sanitizeInput(input: string, maxLength: number = 1000): string {
|
|
220
|
-
return input.trim().slice(0, maxLength);
|
|
221
|
-
}
|
|
@@ -1,109 +0,0 @@
|
|
|
1
|
-
export const AUTH_KEYS = {
|
|
2
|
-
login: {
|
|
3
|
-
welcomeBack: 'auth.login.welcomeBack',
|
|
4
|
-
signInTo: 'auth.login.signInTo',
|
|
5
|
-
submit: 'auth.login.submit',
|
|
6
|
-
submitting: 'auth.login.submitting',
|
|
7
|
-
rememberMe: 'auth.login.rememberMe',
|
|
8
|
-
forgotPassword: 'auth.login.forgotPassword',
|
|
9
|
-
noAccount: 'auth.login.noAccount',
|
|
10
|
-
signUp: 'auth.login.signUp',
|
|
11
|
-
orContinueWith: 'auth.login.orContinueWith',
|
|
12
|
-
},
|
|
13
|
-
|
|
14
|
-
register: {
|
|
15
|
-
createAccount: 'auth.register.createAccount',
|
|
16
|
-
join: 'auth.register.join',
|
|
17
|
-
fullName: 'auth.register.fullName',
|
|
18
|
-
fullNamePlaceholder: 'auth.placeholders.fullName',
|
|
19
|
-
confirmPassword: 'auth.register.confirmPassword',
|
|
20
|
-
confirmPasswordPlaceholder: 'auth.placeholders.confirmPassword',
|
|
21
|
-
acceptTerms: 'auth.register.acceptTerms',
|
|
22
|
-
termsOfService: 'auth.register.termsOfService',
|
|
23
|
-
privacyPolicy: 'auth.register.privacyPolicy',
|
|
24
|
-
submit: 'auth.register.submit',
|
|
25
|
-
submitting: 'auth.register.submitting',
|
|
26
|
-
haveAccount: 'auth.register.haveAccount',
|
|
27
|
-
signIn: 'auth.register.signIn',
|
|
28
|
-
mustAcceptTerms: 'auth.register.mustAcceptTerms',
|
|
29
|
-
passwordsDoNotMatch: 'auth.register.passwordsDoNotMatch',
|
|
30
|
-
},
|
|
31
|
-
|
|
32
|
-
forgotPassword: {
|
|
33
|
-
title: 'auth.forgotPassword.title',
|
|
34
|
-
description: 'auth.forgotPassword.description',
|
|
35
|
-
submit: 'auth.forgotPassword.submit',
|
|
36
|
-
submitting: 'auth.forgotPassword.submitting',
|
|
37
|
-
backToSignIn: 'auth.forgotPassword.backToSignIn',
|
|
38
|
-
successTitle: 'auth.forgotPassword.successTitle',
|
|
39
|
-
successMessage: 'auth.forgotPassword.successMessage',
|
|
40
|
-
sentTo: 'auth.forgotPassword.sentTo',
|
|
41
|
-
checkEmail: 'auth.forgotPassword.checkEmail',
|
|
42
|
-
step1: 'auth.forgotPassword.steps.step1',
|
|
43
|
-
step2: 'auth.forgotPassword.steps.step2',
|
|
44
|
-
step3: 'auth.forgotPassword.steps.step3',
|
|
45
|
-
},
|
|
46
|
-
|
|
47
|
-
resetPassword: {
|
|
48
|
-
title: 'auth.resetPassword.title',
|
|
49
|
-
description: 'auth.resetPassword.description',
|
|
50
|
-
newPassword: 'auth.resetPassword.newPassword',
|
|
51
|
-
confirmNewPassword: 'auth.resetPassword.confirmNewPassword',
|
|
52
|
-
submit: 'auth.resetPassword.submit',
|
|
53
|
-
submitting: 'auth.resetPassword.submitting',
|
|
54
|
-
successTitle: 'auth.resetPassword.successTitle',
|
|
55
|
-
successMessage: 'auth.resetPassword.successMessage',
|
|
56
|
-
goToSignIn: 'auth.resetPassword.goToSignIn',
|
|
57
|
-
},
|
|
58
|
-
|
|
59
|
-
fields: {
|
|
60
|
-
email: 'auth.fields.email',
|
|
61
|
-
password: 'auth.fields.password',
|
|
62
|
-
name: 'auth.fields.name',
|
|
63
|
-
},
|
|
64
|
-
|
|
65
|
-
placeholders: {
|
|
66
|
-
emailExample: 'auth.placeholders.emailExample',
|
|
67
|
-
password: 'auth.placeholders.password',
|
|
68
|
-
fullName: 'auth.placeholders.fullName',
|
|
69
|
-
},
|
|
70
|
-
|
|
71
|
-
errors: {
|
|
72
|
-
invalidCredentials: 'auth.errors.invalidCredentials',
|
|
73
|
-
loginFailed: 'auth.errors.loginFailed',
|
|
74
|
-
noAuthProvider: 'auth.errors.noAuthProvider',
|
|
75
|
-
registrationFailed: 'auth.errors.registrationFailed',
|
|
76
|
-
emailRequired: 'auth.errors.emailRequired',
|
|
77
|
-
emailInvalid: 'auth.errors.emailInvalid',
|
|
78
|
-
passwordRequired: 'auth.errors.passwordRequired',
|
|
79
|
-
passwordTooShort: 'auth.errors.passwordTooShort',
|
|
80
|
-
passwordsDoNotMatch: 'auth.errors.passwordsDoNotMatch',
|
|
81
|
-
sendResetFailed: 'auth.errors.sendResetFailed',
|
|
82
|
-
resetFailed: 'auth.errors.resetFailed',
|
|
83
|
-
},
|
|
84
|
-
|
|
85
|
-
passwordStrength: {
|
|
86
|
-
weak: 'auth.passwordStrength.weak',
|
|
87
|
-
fair: 'auth.passwordStrength.fair',
|
|
88
|
-
good: 'auth.passwordStrength.good',
|
|
89
|
-
strong: 'auth.passwordStrength.strong',
|
|
90
|
-
},
|
|
91
|
-
|
|
92
|
-
social: {
|
|
93
|
-
continueWithGoogle: 'auth.social.continueWithGoogle',
|
|
94
|
-
continueWithApple: 'auth.social.continueWithApple',
|
|
95
|
-
},
|
|
96
|
-
|
|
97
|
-
layout: {
|
|
98
|
-
backToHome: 'auth.layout.backToHome',
|
|
99
|
-
orContinueWith: 'auth.layout.orContinueWith',
|
|
100
|
-
allRightsReserved: 'auth.layout.allRightsReserved',
|
|
101
|
-
},
|
|
102
|
-
|
|
103
|
-
a11y: {
|
|
104
|
-
showPassword: 'auth.a11y.showPassword',
|
|
105
|
-
hidePassword: 'auth.a11y.hidePassword',
|
|
106
|
-
},
|
|
107
|
-
} as const;
|
|
108
|
-
|
|
109
|
-
export type AuthI18nKey = string;
|
|
@@ -1,29 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Auth Utilities
|
|
3
|
-
*
|
|
4
|
-
* Export all auth utilities
|
|
5
|
-
*/
|
|
6
|
-
|
|
7
|
-
export {
|
|
8
|
-
isValidEmail,
|
|
9
|
-
isValidPassword,
|
|
10
|
-
validateLogin,
|
|
11
|
-
validateRegister,
|
|
12
|
-
validateForgotPassword,
|
|
13
|
-
validateResetPassword,
|
|
14
|
-
getUserDisplayName,
|
|
15
|
-
getUserInitials,
|
|
16
|
-
isEmailVerified,
|
|
17
|
-
formatUserCreatedAt,
|
|
18
|
-
maskEmail,
|
|
19
|
-
generateResetToken,
|
|
20
|
-
calculatePasswordStrength,
|
|
21
|
-
getPasswordStrengthBand,
|
|
22
|
-
sanitizeInput,
|
|
23
|
-
type ValidationResult,
|
|
24
|
-
type PasswordStrengthBand,
|
|
25
|
-
} from "./auth";
|
|
26
|
-
|
|
27
|
-
export { AUTH_KEYS } from "./i18nKeys";
|
|
28
|
-
export { AUTH_VALIDATION_KEYS } from "./validationKeys";
|
|
29
|
-
export type { AuthValidationKey } from "./validationKeys";
|
|
@@ -1,33 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Auth Utilities - Secure Token
|
|
3
|
-
*
|
|
4
|
-
* Cryptographically secure token generation utilities
|
|
5
|
-
*/
|
|
6
|
-
|
|
7
|
-
/**
|
|
8
|
-
* Generate a cryptographically secure random token.
|
|
9
|
-
*
|
|
10
|
-
* Requires the Web Crypto API (`crypto.getRandomValues`), available in
|
|
11
|
-
* all modern browsers, Node.js >= 19 (and >= 15 via `globalThis.crypto`),
|
|
12
|
-
* and React Native with a crypto polyfill. Throws instead of silently
|
|
13
|
-
* degrading to an insecure PRNG — predictable tokens are worse than
|
|
14
|
-
* a loud failure.
|
|
15
|
-
*
|
|
16
|
-
* @param length - Token length in bytes (default: 32)
|
|
17
|
-
* @returns Hex-encoded random token
|
|
18
|
-
* @throws Error when no CSPRNG is available in the runtime
|
|
19
|
-
*/
|
|
20
|
-
export function generateResetToken(length: number = 32): string {
|
|
21
|
-
if (typeof crypto === 'undefined' || typeof crypto.getRandomValues !== 'function') {
|
|
22
|
-
throw new Error(
|
|
23
|
-
'generateResetToken: no cryptographic random source available in this runtime ' +
|
|
24
|
-
'(requires crypto.getRandomValues — Web Crypto, Node >= 19, or a polyfill)',
|
|
25
|
-
);
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
const byteLength = Math.max(1, Math.floor(length));
|
|
29
|
-
const bytes = new Uint8Array(byteLength);
|
|
30
|
-
crypto.getRandomValues(bytes);
|
|
31
|
-
|
|
32
|
-
return Array.from(bytes, (b) => b.toString(16).padStart(2, '0')).join('');
|
|
33
|
-
}
|
|
@@ -1,23 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Auth Validation Error Keys
|
|
3
|
-
*
|
|
4
|
-
* Stable i18n keys for validation errors. The auth UI maps these to
|
|
5
|
-
* localized messages via the consumer's translation system.
|
|
6
|
-
*
|
|
7
|
-
* Why keys instead of literal strings:
|
|
8
|
-
* - No hardcoded user-facing strings in source code
|
|
9
|
-
* - Centralized catalog: easier to audit and translate
|
|
10
|
-
* - Validators stay pure and testable
|
|
11
|
-
*/
|
|
12
|
-
|
|
13
|
-
export const AUTH_VALIDATION_KEYS = {
|
|
14
|
-
emailRequired: 'auth.validation.emailRequired',
|
|
15
|
-
emailInvalid: 'auth.validation.emailInvalid',
|
|
16
|
-
passwordRequired: 'auth.validation.passwordRequired',
|
|
17
|
-
passwordTooShort: 'auth.validation.passwordTooShort',
|
|
18
|
-
passwordsDoNotMatch: 'auth.validation.passwordsDoNotMatch',
|
|
19
|
-
nameRequired: 'auth.validation.nameRequired',
|
|
20
|
-
invalidResetToken: 'auth.validation.invalidResetToken',
|
|
21
|
-
} as const;
|
|
22
|
-
|
|
23
|
-
export type AuthValidationKey = (typeof AUTH_VALIDATION_KEYS)[keyof typeof AUTH_VALIDATION_KEYS];
|
|
@@ -1,42 +0,0 @@
|
|
|
1
|
-
import { UserRound } from "lucide-react";
|
|
2
|
-
import { Button } from "@umituz/web-design-system/atoms";
|
|
3
|
-
import { useT } from "@umituz/web-localization/react";
|
|
4
|
-
import { SESSION_KEYS } from "./utils/i18nKeys";
|
|
5
|
-
|
|
6
|
-
export interface DemoLoginProps {
|
|
7
|
-
onDemo: (userId: string) => void | Promise<void>;
|
|
8
|
-
userId: string;
|
|
9
|
-
label?: string;
|
|
10
|
-
variant?: "default" | "outline" | "ghost";
|
|
11
|
-
size?: "default" | "sm";
|
|
12
|
-
disabled?: boolean;
|
|
13
|
-
className?: string;
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
export const DemoLogin = ({
|
|
17
|
-
onDemo,
|
|
18
|
-
userId,
|
|
19
|
-
label,
|
|
20
|
-
variant = "outline",
|
|
21
|
-
size = "default",
|
|
22
|
-
disabled = false,
|
|
23
|
-
className,
|
|
24
|
-
}: DemoLoginProps) => {
|
|
25
|
-
const t = useT();
|
|
26
|
-
|
|
27
|
-
return (
|
|
28
|
-
<Button
|
|
29
|
-
type="button"
|
|
30
|
-
variant={variant}
|
|
31
|
-
size={size}
|
|
32
|
-
disabled={disabled}
|
|
33
|
-
className={className}
|
|
34
|
-
onClick={() => void onDemo(userId)}
|
|
35
|
-
>
|
|
36
|
-
<UserRound aria-hidden="true" />
|
|
37
|
-
{label ?? t(SESSION_KEYS.demoLogin.action)}
|
|
38
|
-
</Button>
|
|
39
|
-
);
|
|
40
|
-
};
|
|
41
|
-
|
|
42
|
-
export default DemoLogin;
|
|
@@ -1,102 +0,0 @@
|
|
|
1
|
-
import { useState } from "react";
|
|
2
|
-
import { Building2, Check, ChevronDown } from "lucide-react";
|
|
3
|
-
import { cn } from "@umituz/web-design-system/utils/cn";
|
|
4
|
-
import { useT } from "@umituz/web-localization/react";
|
|
5
|
-
import { SESSION_KEYS } from "./utils/i18nKeys";
|
|
6
|
-
import type { Tenant } from "./types";
|
|
7
|
-
|
|
8
|
-
export interface OrgSwitcherProps {
|
|
9
|
-
tenants: Tenant[];
|
|
10
|
-
activeTenantId?: string | null;
|
|
11
|
-
onSwitch: (tenantId: string) => void;
|
|
12
|
-
disabled?: boolean;
|
|
13
|
-
className?: string;
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
const initialsOf = (tenant: Tenant): string =>
|
|
17
|
-
tenant.initials ??
|
|
18
|
-
tenant.name
|
|
19
|
-
.split(/\s+/)
|
|
20
|
-
.slice(0, 2)
|
|
21
|
-
.map((word) => word.charAt(0).toUpperCase())
|
|
22
|
-
.join("");
|
|
23
|
-
|
|
24
|
-
export const OrgSwitcher = ({
|
|
25
|
-
tenants,
|
|
26
|
-
activeTenantId,
|
|
27
|
-
onSwitch,
|
|
28
|
-
disabled = false,
|
|
29
|
-
className,
|
|
30
|
-
}: OrgSwitcherProps) => {
|
|
31
|
-
const t = useT();
|
|
32
|
-
const [open, setOpen] = useState(false);
|
|
33
|
-
|
|
34
|
-
if (tenants.length === 0) return null;
|
|
35
|
-
|
|
36
|
-
const active = tenants.find((tenant) => tenant.id === activeTenantId) ?? tenants[0];
|
|
37
|
-
|
|
38
|
-
return (
|
|
39
|
-
<div className={cn("udb-header__anchor", className)}>
|
|
40
|
-
<button
|
|
41
|
-
type="button"
|
|
42
|
-
className="udb-profile-btn"
|
|
43
|
-
disabled={disabled}
|
|
44
|
-
aria-haspopup="menu"
|
|
45
|
-
aria-expanded={open}
|
|
46
|
-
aria-label={t(SESSION_KEYS.orgSwitcher.label)}
|
|
47
|
-
onClick={() => setOpen((prev) => !prev)}
|
|
48
|
-
>
|
|
49
|
-
<span className="udb-avatar">{initialsOf(active)}</span>
|
|
50
|
-
<span className="udb-org">
|
|
51
|
-
<span className="udb-org__name">{active.name}</span>
|
|
52
|
-
{active.plan && <span className="udb-org__meta">{active.plan}</span>}
|
|
53
|
-
</span>
|
|
54
|
-
<ChevronDown
|
|
55
|
-
className={cn(
|
|
56
|
-
"udb-profile-btn__chevron",
|
|
57
|
-
open && "udb-profile-btn__chevron--open",
|
|
58
|
-
)}
|
|
59
|
-
aria-hidden="true"
|
|
60
|
-
/>
|
|
61
|
-
</button>
|
|
62
|
-
|
|
63
|
-
{open && (
|
|
64
|
-
<>
|
|
65
|
-
<button
|
|
66
|
-
type="button"
|
|
67
|
-
className="udb-overlay"
|
|
68
|
-
aria-label="close tenant menu"
|
|
69
|
-
tabIndex={-1}
|
|
70
|
-
onClick={() => setOpen(false)}
|
|
71
|
-
/>
|
|
72
|
-
<div className="udb-menu udb-org-menu" role="menu">
|
|
73
|
-
{tenants.map((tenant) => {
|
|
74
|
-
const isActive = tenant.id === active.id;
|
|
75
|
-
return (
|
|
76
|
-
<button
|
|
77
|
-
key={tenant.id}
|
|
78
|
-
type="button"
|
|
79
|
-
role="menuitem"
|
|
80
|
-
className={cn(
|
|
81
|
-
"udb-menu__item",
|
|
82
|
-
isActive && "udb-menu__item--active",
|
|
83
|
-
)}
|
|
84
|
-
onClick={() => {
|
|
85
|
-
onSwitch(tenant.id);
|
|
86
|
-
setOpen(false);
|
|
87
|
-
}}
|
|
88
|
-
>
|
|
89
|
-
<Building2 className="udb-menu__item-icon" aria-hidden="true" />
|
|
90
|
-
<span className="udb-org-menu__label">{tenant.name}</span>
|
|
91
|
-
{isActive && <Check className="udb-org-menu__check" aria-hidden="true" />}
|
|
92
|
-
</button>
|
|
93
|
-
);
|
|
94
|
-
})}
|
|
95
|
-
</div>
|
|
96
|
-
</>
|
|
97
|
-
)}
|
|
98
|
-
</div>
|
|
99
|
-
);
|
|
100
|
-
};
|
|
101
|
-
|
|
102
|
-
export default OrgSwitcher;
|