@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.
- package/README.md +202 -14
- package/package.json +21 -29
- package/src/domains/analytics/components/AnalyticsCard.tsx +23 -21
- package/src/domains/analytics/components/AnalyticsChart.tsx +345 -149
- package/src/domains/analytics/components/AnalyticsLayout.tsx +93 -112
- package/src/domains/analytics/components/MetricCard.tsx +42 -64
- package/src/domains/analytics/hooks/index.ts +7 -1
- package/src/domains/analytics/hooks/useAnalytics.ts +144 -139
- package/src/domains/analytics/index.ts +11 -0
- package/src/domains/analytics/services/AnalyticsEngineService.ts +103 -55
- package/src/domains/analytics/services/PerformanceService.ts +117 -69
- package/src/domains/analytics/types/analytics.ts +20 -6
- package/src/domains/analytics/types/index.ts +3 -0
- package/src/domains/analytics/utils/analytics.test.ts +183 -0
- package/src/domains/analytics/utils/analytics.ts +26 -9
- package/src/domains/analytics/utils/i18nKeys.ts +40 -0
- package/src/domains/analytics/utils/index.ts +2 -0
- package/src/domains/auth/components/AuthLayout.tsx +63 -54
- package/src/domains/auth/components/ForgotPasswordForm.tsx +96 -98
- package/src/domains/auth/components/LoginForm.tsx +167 -226
- package/src/domains/auth/components/RegisterForm.tsx +202 -272
- package/src/domains/auth/components/ResetPasswordForm.tsx +105 -151
- package/src/domains/auth/components/shared/AuthErrorBanner.tsx +35 -0
- package/src/domains/auth/components/shared/EmailInput.tsx +91 -0
- package/src/domains/auth/components/shared/PasswordInput.tsx +119 -0
- package/src/domains/auth/components/shared/PasswordStrengthIndicator.tsx +75 -0
- package/src/domains/auth/components/shared/SocialLoginButtons.tsx +58 -0
- package/src/domains/auth/components/shared/SocialProviderIcons.tsx +48 -0
- package/src/domains/auth/components/shared/index.ts +20 -0
- package/src/domains/auth/hooks/index.ts +1 -0
- package/src/domains/auth/hooks/useAuth.ts +151 -232
- package/src/domains/auth/index.ts +8 -4
- package/src/domains/auth/types/auth.ts +26 -5
- package/src/domains/auth/types/index.ts +1 -0
- package/src/domains/auth/utils/auth.test.ts +182 -0
- package/src/domains/auth/utils/auth.ts +71 -130
- package/src/domains/auth/utils/i18nKeys.ts +127 -0
- package/src/domains/auth/utils/index.ts +7 -1
- package/src/domains/auth/utils/secureToken.ts +33 -0
- package/src/domains/auth/utils/validationKeys.ts +23 -0
- package/src/domains/calendar/hooks/useCalendar.ts +145 -151
- package/src/domains/calendar/index.ts +0 -1
- package/src/domains/calendar/services/CalendarService.ts +122 -179
- package/src/domains/calendar/utils/contentItemMapper.test.ts +108 -0
- package/src/domains/calendar/utils/contentItemMapper.ts +99 -0
- package/src/domains/calendar/utils/index.ts +33 -30
- package/src/domains/layouts/components/BrandLogo.tsx +12 -9
- package/src/domains/layouts/components/DashboardHeader.tsx +78 -80
- package/src/domains/layouts/components/DashboardLayout.tsx +45 -21
- package/src/domains/layouts/components/DashboardSidebar.tsx +48 -51
- package/src/domains/layouts/types/layout.ts +7 -1
- package/src/domains/layouts/types/user.ts +3 -1
- package/src/domains/modules/build.test.tsx +97 -0
- package/src/domains/modules/build.tsx +71 -0
- package/src/domains/modules/index.ts +2 -0
- package/src/domains/modules/types.ts +29 -0
- package/src/domains/onboarding/components/AppFocusStep.tsx +99 -68
- package/src/domains/onboarding/components/OnboardingWizard.tsx +109 -183
- package/src/domains/onboarding/components/PlanStep.tsx +156 -123
- package/src/domains/onboarding/components/PlatformsStep.tsx +74 -54
- package/src/domains/onboarding/components/StepNavigation.tsx +108 -0
- package/src/domains/onboarding/components/StepProgress.tsx +62 -0
- package/src/domains/onboarding/components/UserTypeStep.tsx +59 -80
- package/src/domains/onboarding/components/index.ts +4 -0
- package/src/domains/onboarding/hooks/useOnboarding.ts +76 -66
- package/src/domains/onboarding/index.ts +1 -0
- package/src/domains/onboarding/types/onboarding.ts +57 -17
- package/src/domains/onboarding/utils/i18nKeys.ts +72 -0
- package/src/domains/onboarding/utils/index.ts +2 -0
- package/src/domains/permissions/PermissionsContext.tsx +103 -0
- package/src/domains/permissions/hasPermission.test.ts +50 -0
- package/src/domains/permissions/hasPermission.ts +38 -0
- package/src/domains/permissions/index.ts +19 -0
- package/src/domains/permissions/types.ts +35 -0
- package/src/domains/permissions/utils/i18nKeys.ts +11 -0
- package/src/domains/session/DemoLogin.tsx +50 -0
- package/src/domains/session/OrgSwitcher.tsx +110 -0
- package/src/domains/session/SessionContext.tsx +150 -0
- package/src/domains/session/inMemoryAuthAdapter.ts +53 -0
- package/src/domains/session/index.ts +18 -0
- package/src/domains/session/types.ts +47 -0
- package/src/domains/session/utils/i18nKeys.ts +12 -0
- package/src/domains/settings/components/AuditLogList.tsx +59 -0
- package/src/domains/settings/components/PermissionMatrixScreen.tsx +80 -0
- package/src/domains/settings/components/SettingsLayout.tsx +75 -60
- package/src/domains/settings/components/SettingsSection.tsx +35 -22
- package/src/domains/settings/components/UserManagementTable.tsx +87 -0
- package/src/domains/settings/components/index.ts +10 -0
- package/src/domains/settings/hooks/useSettings.ts +77 -52
- package/src/domains/settings/types/settings.ts +2 -0
- package/src/domains/settings/utils/i18nKeys.ts +18 -0
- package/src/domains/settings/utils/settings.ts +11 -4
- package/src/i18n/I18nContext.tsx +33 -0
- package/src/i18n/defaultT.test.ts +43 -0
- package/src/i18n/defaultT.ts +34 -0
- package/src/i18n/index.ts +7 -0
- package/src/i18n/types.ts +13 -0
- package/src/index.ts +1 -7
- package/src/primitives/Button.tsx +30 -0
- package/src/primitives/Checkbox.tsx +40 -0
- package/src/primitives/Input.tsx +19 -0
- package/src/primitives/Skeleton.tsx +26 -0
- package/src/primitives/cn.ts +15 -0
- package/src/primitives/index.ts +23 -0
- package/src/styles/dashboard.css +3045 -0
- package/src/styles/tokens.contract.test.ts +66 -0
- package/src/styles/tokens.css +124 -0
- package/src/domains/billing/components/BillingLayout.tsx +0 -63
- package/src/domains/billing/components/BillingPage.tsx +0 -49
- package/src/domains/billing/components/BillingPortal.tsx +0 -198
- package/src/domains/billing/components/InvoiceCard.tsx +0 -143
- package/src/domains/billing/components/PaymentMethodsList.tsx +0 -116
- package/src/domains/billing/components/PlanComparison.tsx +0 -199
- package/src/domains/billing/components/UsageCard.tsx +0 -103
- package/src/domains/billing/components/index.ts +0 -13
- package/src/domains/billing/constants/billing.ts +0 -69
- package/src/domains/billing/hooks/index.ts +0 -7
- package/src/domains/billing/hooks/useBilling.ts +0 -385
- package/src/domains/billing/index.ts +0 -73
- package/src/domains/billing/types/billing.ts +0 -342
- package/src/domains/billing/types/index.ts +0 -30
- package/src/domains/billing/utils/billing.ts +0 -360
- package/src/domains/billing/utils/index.ts +0 -31
- package/src/domains/billing/utils/polar.ts +0 -63
- package/src/domains/calendar/components/index.ts +0 -7
|
@@ -1,24 +1,57 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
2
|
+
* LoginForm
|
|
3
3
|
*
|
|
4
|
-
*
|
|
4
|
+
* Composed from shared auth primitives (EmailInput, PasswordInput,
|
|
5
|
+
* AuthErrorBanner, SocialLoginButtons). The form's job is to
|
|
6
|
+
* orchestrate them — UI rendering and validation live elsewhere.
|
|
5
7
|
*/
|
|
6
8
|
|
|
7
|
-
import { useState } from "react";
|
|
9
|
+
import { useState, useCallback } from "react";
|
|
8
10
|
import { useNavigate } from "react-router-dom";
|
|
9
|
-
import {
|
|
10
|
-
import {
|
|
11
|
-
import {
|
|
12
|
-
import
|
|
13
|
-
import {
|
|
11
|
+
import { useT } from "../../../i18n";
|
|
12
|
+
import { Button, Checkbox } from "../../../primitives";
|
|
13
|
+
import { Loader2 } from "lucide-react";
|
|
14
|
+
import { validateLogin } from "../utils/auth";
|
|
15
|
+
import type {
|
|
16
|
+
LoginFormProps,
|
|
17
|
+
LoginCredentials,
|
|
18
|
+
User,
|
|
19
|
+
Translate,
|
|
20
|
+
} from "../types/auth";
|
|
21
|
+
import {
|
|
22
|
+
AuthErrorBanner,
|
|
23
|
+
EmailInput,
|
|
24
|
+
PasswordInput,
|
|
25
|
+
SocialLoginButtons,
|
|
26
|
+
} from "./shared";
|
|
27
|
+
|
|
28
|
+
const KEYS = {
|
|
29
|
+
welcomeBack: 'auth.login.welcomeBack',
|
|
30
|
+
signInTo: 'auth.login.signInTo',
|
|
31
|
+
emailLabel: 'auth.fields.email',
|
|
32
|
+
emailPlaceholder: 'auth.placeholders.emailExample',
|
|
33
|
+
passwordLabel: 'auth.fields.password',
|
|
34
|
+
passwordPlaceholder: 'auth.placeholders.password',
|
|
35
|
+
rememberMe: 'auth.login.rememberMe',
|
|
36
|
+
forgotPassword: 'auth.login.forgotPassword',
|
|
37
|
+
submit: 'auth.login.submit',
|
|
38
|
+
submitting: 'auth.login.submitting',
|
|
39
|
+
noAccount: 'auth.login.noAccount',
|
|
40
|
+
signUp: 'auth.login.signUp',
|
|
41
|
+
orContinueWith: 'auth.login.orContinueWith',
|
|
42
|
+
invalidCredentials: 'auth.errors.invalidCredentials',
|
|
43
|
+
loginFailed: 'auth.errors.loginFailed',
|
|
44
|
+
noAuthProvider: 'auth.errors.noAuthProvider',
|
|
45
|
+
} as const;
|
|
14
46
|
|
|
15
47
|
export const LoginForm = ({
|
|
16
48
|
config,
|
|
17
|
-
defaultCredentials
|
|
49
|
+
defaultCredentials,
|
|
18
50
|
showRememberMe = true,
|
|
19
51
|
showForgotPassword = true,
|
|
20
52
|
showRegisterLink = true,
|
|
21
|
-
showSocialLogin,
|
|
53
|
+
showSocialLogin = true,
|
|
54
|
+
enableMockAuth = false,
|
|
22
55
|
onLoginAttempt,
|
|
23
56
|
onLoginSuccess,
|
|
24
57
|
onLoginError,
|
|
@@ -26,265 +59,173 @@ export const LoginForm = ({
|
|
|
26
59
|
onAppleLogin,
|
|
27
60
|
}: LoginFormProps) => {
|
|
28
61
|
const navigate = useNavigate();
|
|
29
|
-
const
|
|
30
|
-
const
|
|
31
|
-
const [rememberMe, setRememberMe] = useState(false);
|
|
32
|
-
const [showPassword, setShowPassword] = useState(false);
|
|
33
|
-
const [isLoading, setIsLoading] = useState(false);
|
|
34
|
-
const [error, setError] = useState<string | null>(null);
|
|
35
|
-
|
|
36
|
-
const handleSubmit = async (e: React.FormEvent) => {
|
|
37
|
-
e.preventDefault();
|
|
38
|
-
setError(null);
|
|
39
|
-
|
|
40
|
-
// Validate
|
|
41
|
-
const validation = validateLogin({ email, password });
|
|
42
|
-
if (!validation.valid) {
|
|
43
|
-
setError(validation.error || "Invalid credentials");
|
|
44
|
-
onLoginError?.(validation.error || "Invalid credentials");
|
|
45
|
-
return;
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
setIsLoading(true);
|
|
62
|
+
const t = useT();
|
|
63
|
+
const translate: Translate = t;
|
|
49
64
|
|
|
50
|
-
|
|
51
|
-
|
|
65
|
+
const [email, setEmail] = useState<string>(defaultCredentials?.email ?? "");
|
|
66
|
+
const [password, setPassword] = useState<string>(defaultCredentials?.password ?? "");
|
|
67
|
+
const [remember, setRemember] = useState<boolean>(false);
|
|
68
|
+
const [loading, setLoading] = useState<boolean>(false);
|
|
69
|
+
const [error, setError] = useState<string | null>(null);
|
|
52
70
|
|
|
53
|
-
|
|
71
|
+
/**
|
|
72
|
+
* Auth execution. A real provider is supplied via onLoginAttempt.
|
|
73
|
+
* The mock path accepts ANY credentials and is therefore opt-in
|
|
74
|
+
* (`enableMockAuth`) — never a silent default in production builds.
|
|
75
|
+
*/
|
|
76
|
+
const performLogin = useCallback(
|
|
77
|
+
async (credentials: LoginCredentials): Promise<User> => {
|
|
54
78
|
if (onLoginAttempt) {
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
60
|
-
user = {
|
|
61
|
-
id: "1",
|
|
62
|
-
email,
|
|
63
|
-
name: email.split("@")[0],
|
|
64
|
-
};
|
|
79
|
+
return onLoginAttempt(credentials);
|
|
80
|
+
}
|
|
81
|
+
if (!enableMockAuth) {
|
|
82
|
+
throw new Error(KEYS.noAuthProvider);
|
|
65
83
|
}
|
|
84
|
+
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
85
|
+
return {
|
|
86
|
+
id: "1",
|
|
87
|
+
email: credentials.email,
|
|
88
|
+
name: credentials.email.split("@")[0],
|
|
89
|
+
};
|
|
90
|
+
},
|
|
91
|
+
[onLoginAttempt, enableMockAuth],
|
|
92
|
+
);
|
|
66
93
|
|
|
67
|
-
|
|
68
|
-
|
|
94
|
+
const handleSubmit = useCallback(
|
|
95
|
+
async (e: React.FormEvent) => {
|
|
96
|
+
e.preventDefault();
|
|
97
|
+
setError(null);
|
|
98
|
+
|
|
99
|
+
const validation = validateLogin({ email, password });
|
|
100
|
+
if (!validation.valid) {
|
|
101
|
+
const message = validation.error ?? KEYS.invalidCredentials;
|
|
102
|
+
setError(message);
|
|
103
|
+
onLoginError?.(message);
|
|
104
|
+
return;
|
|
105
|
+
}
|
|
69
106
|
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
107
|
+
setLoading(true);
|
|
108
|
+
try {
|
|
109
|
+
const user = await performLogin({ email, password });
|
|
110
|
+
await onLoginSuccess?.(user);
|
|
111
|
+
navigate(config.afterLoginRoute);
|
|
112
|
+
} catch (err) {
|
|
113
|
+
const message = err instanceof Error ? err.message : KEYS.loginFailed;
|
|
114
|
+
setError(message);
|
|
115
|
+
onLoginError?.(message);
|
|
116
|
+
} finally {
|
|
117
|
+
setLoading(false);
|
|
118
|
+
}
|
|
119
|
+
},
|
|
120
|
+
[email, password, performLogin, navigate, config.afterLoginRoute, onLoginSuccess, onLoginError],
|
|
121
|
+
);
|
|
80
122
|
|
|
81
|
-
const
|
|
123
|
+
const handleSocial = useCallback(
|
|
124
|
+
(provider: 'google' | 'apple') => {
|
|
125
|
+
if (provider === 'google') onGoogleLogin?.();
|
|
126
|
+
if (provider === 'apple') onAppleLogin?.();
|
|
127
|
+
},
|
|
128
|
+
[onGoogleLogin, onAppleLogin],
|
|
129
|
+
);
|
|
82
130
|
|
|
83
131
|
return (
|
|
84
|
-
<form onSubmit={handleSubmit} className="
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
Welcome back
|
|
132
|
+
<form onSubmit={handleSubmit} className="udb-form" noValidate>
|
|
133
|
+
<div className="udb-form__header">
|
|
134
|
+
<h1 className="udb-form__title">
|
|
135
|
+
{translate(KEYS.welcomeBack)}
|
|
89
136
|
</h1>
|
|
90
|
-
<p className="
|
|
91
|
-
|
|
137
|
+
<p className="udb-form__subtitle">
|
|
138
|
+
{translate(KEYS.signInTo, { brand: config.brandName })}
|
|
92
139
|
</p>
|
|
93
140
|
</div>
|
|
94
141
|
|
|
95
|
-
{
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
disabled={isLoading}
|
|
120
|
-
autoComplete="email"
|
|
121
|
-
/>
|
|
122
|
-
</div>
|
|
123
|
-
|
|
124
|
-
{/* Password Field */}
|
|
125
|
-
<div className="space-y-2">
|
|
126
|
-
<label htmlFor="password" className="text-sm font-medium text-foreground">
|
|
127
|
-
Password
|
|
128
|
-
</label>
|
|
129
|
-
<div className="relative">
|
|
130
|
-
<input
|
|
131
|
-
id="password"
|
|
132
|
-
type={showPassword ? "text" : "password"}
|
|
133
|
-
value={password}
|
|
134
|
-
onChange={(e) => setPassword(e.target.value)}
|
|
135
|
-
placeholder="••••••••"
|
|
136
|
-
className={cn(
|
|
137
|
-
"w-full px-4 py-3 rounded-lg border bg-background pr-12",
|
|
138
|
-
"focus:outline-none focus:ring-2 focus:ring-primary focus:border-transparent",
|
|
139
|
-
"placeholder:text-muted-foreground",
|
|
140
|
-
error && "border-destructive"
|
|
141
|
-
)}
|
|
142
|
-
disabled={isLoading}
|
|
143
|
-
autoComplete="current-password"
|
|
144
|
-
/>
|
|
145
|
-
<button
|
|
146
|
-
type="button"
|
|
147
|
-
onClick={() => setShowPassword(!showPassword)}
|
|
148
|
-
className="absolute right-3 top-1/2 -translate-y-1/2 text-muted-foreground hover:text-foreground transition-colors"
|
|
149
|
-
tabIndex={-1}
|
|
150
|
-
>
|
|
151
|
-
{showPassword ? <EyeOff className="h-5 w-5" /> : <Eye className="h-5 w-5" />}
|
|
152
|
-
</button>
|
|
153
|
-
</div>
|
|
154
|
-
{/* Password Strength Indicator */}
|
|
155
|
-
{password && (
|
|
156
|
-
<div className="flex items-center gap-2">
|
|
157
|
-
<div className="flex-1 h-1 bg-muted rounded-full overflow-hidden">
|
|
158
|
-
<div
|
|
159
|
-
className={cn(
|
|
160
|
-
"h-full transition-all duration-300",
|
|
161
|
-
passwordStrength < 25 && "bg-destructive",
|
|
162
|
-
passwordStrength >= 25 && passwordStrength < 50 && "bg-orange-500",
|
|
163
|
-
passwordStrength >= 50 && passwordStrength < 75 && "bg-yellow-500",
|
|
164
|
-
passwordStrength >= 75 && "bg-green-500"
|
|
165
|
-
)}
|
|
166
|
-
style={{ width: `${passwordStrength}%` }}
|
|
167
|
-
/>
|
|
168
|
-
</div>
|
|
169
|
-
<span className="text-xs text-muted-foreground">
|
|
170
|
-
{passwordStrength < 25 && "Weak"}
|
|
171
|
-
{passwordStrength >= 25 && passwordStrength < 50 && "Fair"}
|
|
172
|
-
{passwordStrength >= 50 && passwordStrength < 75 && "Good"}
|
|
173
|
-
{passwordStrength >= 75 && "Strong"}
|
|
174
|
-
</span>
|
|
175
|
-
</div>
|
|
176
|
-
)}
|
|
177
|
-
</div>
|
|
142
|
+
<AuthErrorBanner message={error} translate={translate} />
|
|
143
|
+
|
|
144
|
+
<EmailInput
|
|
145
|
+
id="login-email"
|
|
146
|
+
labelKey={KEYS.emailLabel}
|
|
147
|
+
placeholderKey={KEYS.emailPlaceholder}
|
|
148
|
+
value={email}
|
|
149
|
+
onChange={setEmail}
|
|
150
|
+
translate={translate}
|
|
151
|
+
disabled={loading}
|
|
152
|
+
required
|
|
153
|
+
/>
|
|
154
|
+
|
|
155
|
+
<PasswordInput
|
|
156
|
+
id="login-password"
|
|
157
|
+
labelKey={KEYS.passwordLabel}
|
|
158
|
+
placeholderKey={KEYS.passwordPlaceholder}
|
|
159
|
+
value={password}
|
|
160
|
+
onChange={setPassword}
|
|
161
|
+
translate={translate}
|
|
162
|
+
autoComplete="current-password"
|
|
163
|
+
disabled={loading}
|
|
164
|
+
required
|
|
165
|
+
/>
|
|
178
166
|
|
|
179
|
-
{/* Remember Me & Forgot Password */}
|
|
180
167
|
{(showRememberMe || showForgotPassword) && (
|
|
181
|
-
<div className="
|
|
168
|
+
<div className="udb-form__row">
|
|
182
169
|
{showRememberMe && (
|
|
183
|
-
<label className="
|
|
184
|
-
<
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
className="w-4 h-4 rounded border-border text-primary focus:ring-2 focus:ring-primary"
|
|
170
|
+
<label className="udb-check-label">
|
|
171
|
+
<Checkbox
|
|
172
|
+
checked={remember}
|
|
173
|
+
onCheckedChange={(checked) => setRemember(Boolean(checked))}
|
|
174
|
+
disabled={loading}
|
|
189
175
|
/>
|
|
190
|
-
|
|
176
|
+
{translate(KEYS.rememberMe)}
|
|
191
177
|
</label>
|
|
192
178
|
)}
|
|
193
|
-
{showForgotPassword &&
|
|
179
|
+
{showForgotPassword && (
|
|
194
180
|
<button
|
|
195
181
|
type="button"
|
|
196
|
-
onClick={() => navigate(config.forgotPasswordRoute
|
|
197
|
-
className="
|
|
182
|
+
onClick={() => navigate(config.forgotPasswordRoute ?? '/forgot-password')}
|
|
183
|
+
className="udb-link-btn"
|
|
184
|
+
disabled={loading}
|
|
198
185
|
>
|
|
199
|
-
|
|
186
|
+
{translate(KEYS.forgotPassword)}
|
|
200
187
|
</button>
|
|
201
188
|
)}
|
|
202
189
|
</div>
|
|
203
190
|
)}
|
|
204
191
|
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
type="submit"
|
|
208
|
-
className="w-full h-12 text-base font-bold rounded-full"
|
|
209
|
-
disabled={isLoading}
|
|
210
|
-
>
|
|
211
|
-
{isLoading ? (
|
|
192
|
+
<Button type="submit" disabled={loading} className="udb-submit-btn">
|
|
193
|
+
{loading ? (
|
|
212
194
|
<>
|
|
213
|
-
<Loader2 className="
|
|
214
|
-
|
|
195
|
+
<Loader2 className="udb-btn__spinner" aria-hidden="true" />
|
|
196
|
+
{translate(KEYS.submitting)}
|
|
215
197
|
</>
|
|
216
198
|
) : (
|
|
217
|
-
|
|
199
|
+
translate(KEYS.submit)
|
|
218
200
|
)}
|
|
219
201
|
</Button>
|
|
220
202
|
|
|
221
|
-
{
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
Don't have an account?{" "}
|
|
203
|
+
{showRegisterLink && (
|
|
204
|
+
<p className="udb-form__footer-text">
|
|
205
|
+
{translate(KEYS.noAccount)}{' '}
|
|
225
206
|
<button
|
|
226
207
|
type="button"
|
|
227
208
|
onClick={() => navigate(config.registerRoute)}
|
|
228
|
-
className="
|
|
209
|
+
className="udb-link-btn udb-link-btn--strong"
|
|
210
|
+
disabled={loading}
|
|
229
211
|
>
|
|
230
|
-
|
|
212
|
+
{translate(KEYS.signUp)}
|
|
231
213
|
</button>
|
|
232
214
|
</p>
|
|
233
215
|
)}
|
|
234
216
|
|
|
235
|
-
{
|
|
236
|
-
{showSocialLogin && config.showSocialLogin && config.socialProviders && config.socialProviders.length > 0 && (
|
|
217
|
+
{showSocialLogin && (
|
|
237
218
|
<>
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
</div>
|
|
243
|
-
<div className="relative flex justify-center text-sm">
|
|
244
|
-
<span className="px-2 bg-background text-muted-foreground">
|
|
245
|
-
Or continue with
|
|
246
|
-
</span>
|
|
247
|
-
</div>
|
|
248
|
-
</div>
|
|
249
|
-
|
|
250
|
-
{/* Social Buttons */}
|
|
251
|
-
<div className="grid grid-cols-2 gap-3">
|
|
252
|
-
{config.socialProviders.map((provider) => (
|
|
253
|
-
<button
|
|
254
|
-
key={provider.id}
|
|
255
|
-
type="button"
|
|
256
|
-
className={cn(
|
|
257
|
-
"flex items-center justify-center gap-2 px-4 py-3 rounded-lg border",
|
|
258
|
-
"bg-background hover:bg-muted transition-colors",
|
|
259
|
-
"text-sm font-medium text-foreground",
|
|
260
|
-
isLoading && "opacity-50 cursor-not-allowed"
|
|
261
|
-
)}
|
|
262
|
-
onClick={async () => {
|
|
263
|
-
if (provider.id === "google" && onGoogleLogin) {
|
|
264
|
-
await onGoogleLogin();
|
|
265
|
-
} else if (provider.id === "apple" && onAppleLogin) {
|
|
266
|
-
await onAppleLogin();
|
|
267
|
-
}
|
|
268
|
-
}}
|
|
269
|
-
disabled={isLoading}
|
|
270
|
-
>
|
|
271
|
-
{provider.icon === "google" && (
|
|
272
|
-
<svg width="18" height="18" viewBox="0 0 24 24">
|
|
273
|
-
<path d="M22.56 12.25c0-.78-.07-1.53-.2-2.25H12v4.26h5.92a5.06 5.06 0 01-2.2 3.32v2.77h3.57c2.08-1.92 3.28-4.74 3.28-8.1z" fill="#4285F4"/>
|
|
274
|
-
<path d="M12 23c2.97 0 5.46-.98 7.28-2.66l-3.57-2.77c-.98.66-2.23 1.06-3.71 1.06-2.86 0-5.29-1.93-6.16-4.53H2.18v2.84C3.99 20.53 7.7 23 12 23z" fill="#34A853"/>
|
|
275
|
-
<path d="M5.84 14.09c-.22-.66-.35-1.36-.35-2.09s.13-1.43.35-2.09V7.07H2.18C1.43 8.55 1 10.22 1 12s.43 3.45 1.18 4.93l2.85-2.22.81-.62z" fill="#FBBC05"/>
|
|
276
|
-
<path d="M12 5.38c1.62 0 3.06.56 4.21 1.64l3.15-3.15C17.45 2.09 14.97 1 12 1 7.7 1 3.99 3.47 2.18 7.07l3.66 2.84c.87-2.6 3.3-4.53 6.16-4.53z" fill="#EA4335"/>
|
|
277
|
-
</svg>
|
|
278
|
-
)}
|
|
279
|
-
{provider.icon === "apple" && (
|
|
280
|
-
<svg width="18" height="18" viewBox="0 0 24 24" fill="currentColor" className="text-foreground">
|
|
281
|
-
<path d="M17.05 20.28c-.98.95-2.05.8-3.08.35-1.09-.46-2.09-.48-3.24 0-1.44.62-2.2.44-3.06-.35C2.79 15.25 3.51 7.59 9.05 7.31c1.35.07 2.29.74 3.08.8 1.18-.24 2.31-.93 3.57-.84 1.51.12 2.65.72 3.4 1.8.3-3.12 1.38-5.98 0-8.1-3.12-1.87-3.12-3.28-8.1.3-8.1.8-1.06 0-2.29.44-3.06.35-2.09V7.07H2.18C1.43 8.55 1 10.22 1 12s.43 3.45 1.18 4.93l2.85-2.22.81-.62z"/>
|
|
282
|
-
</svg>
|
|
283
|
-
)}
|
|
284
|
-
<span>{provider.name}</span>
|
|
285
|
-
</button>
|
|
286
|
-
))}
|
|
219
|
+
<div className="udb-divider-row">
|
|
220
|
+
<span className="udb-divider-row__label">
|
|
221
|
+
{translate(KEYS.orContinueWith)}
|
|
222
|
+
</span>
|
|
287
223
|
</div>
|
|
224
|
+
<SocialLoginButtons
|
|
225
|
+
onProvider={handleSocial}
|
|
226
|
+
translate={translate}
|
|
227
|
+
disabled={loading}
|
|
228
|
+
/>
|
|
288
229
|
</>
|
|
289
230
|
)}
|
|
290
231
|
</form>
|