@umituz/web-dashboard 3.2.4 → 4.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.
Files changed (126) hide show
  1. package/README.md +228 -14
  2. package/package.json +23 -30
  3. package/src/domains/analytics/components/AnalyticsCard.tsx +43 -42
  4. package/src/domains/analytics/components/AnalyticsChart.tsx +345 -149
  5. package/src/domains/analytics/components/AnalyticsLayout.tsx +93 -112
  6. package/src/domains/analytics/components/MetricCard.tsx +29 -87
  7. package/src/domains/analytics/hooks/index.ts +7 -1
  8. package/src/domains/analytics/hooks/useAnalytics.ts +144 -139
  9. package/src/domains/analytics/index.ts +11 -0
  10. package/src/domains/analytics/services/AnalyticsEngineService.ts +103 -55
  11. package/src/domains/analytics/services/PerformanceService.ts +117 -69
  12. package/src/domains/analytics/types/analytics.ts +20 -6
  13. package/src/domains/analytics/types/index.ts +3 -0
  14. package/src/domains/analytics/utils/analytics.test.ts +183 -0
  15. package/src/domains/analytics/utils/analytics.ts +26 -9
  16. package/src/domains/analytics/utils/i18nKeys.ts +40 -0
  17. package/src/domains/analytics/utils/index.ts +2 -0
  18. package/src/domains/auth/components/AuthLayout.tsx +63 -54
  19. package/src/domains/auth/components/ForgotPasswordForm.tsx +96 -98
  20. package/src/domains/auth/components/LoginForm.tsx +167 -226
  21. package/src/domains/auth/components/RegisterForm.tsx +202 -272
  22. package/src/domains/auth/components/ResetPasswordForm.tsx +105 -151
  23. package/src/domains/auth/components/shared/AuthErrorBanner.tsx +35 -0
  24. package/src/domains/auth/components/shared/EmailInput.tsx +91 -0
  25. package/src/domains/auth/components/shared/PasswordInput.tsx +119 -0
  26. package/src/domains/auth/components/shared/PasswordStrengthIndicator.tsx +75 -0
  27. package/src/domains/auth/components/shared/SocialLoginButtons.tsx +58 -0
  28. package/src/domains/auth/components/shared/SocialProviderIcons.tsx +48 -0
  29. package/src/domains/auth/components/shared/index.ts +20 -0
  30. package/src/domains/auth/hooks/index.ts +1 -0
  31. package/src/domains/auth/hooks/useAuth.ts +151 -232
  32. package/src/domains/auth/index.ts +8 -4
  33. package/src/domains/auth/types/auth.ts +26 -5
  34. package/src/domains/auth/types/index.ts +1 -0
  35. package/src/domains/auth/utils/auth.test.ts +182 -0
  36. package/src/domains/auth/utils/auth.ts +71 -130
  37. package/src/domains/auth/utils/i18nKeys.ts +127 -0
  38. package/src/domains/auth/utils/index.ts +7 -1
  39. package/src/domains/auth/utils/secureToken.ts +33 -0
  40. package/src/domains/auth/utils/validationKeys.ts +23 -0
  41. package/src/domains/calendar/hooks/useCalendar.ts +145 -151
  42. package/src/domains/calendar/index.ts +0 -1
  43. package/src/domains/calendar/services/CalendarService.ts +122 -179
  44. package/src/domains/calendar/utils/contentItemMapper.test.ts +108 -0
  45. package/src/domains/calendar/utils/contentItemMapper.ts +99 -0
  46. package/src/domains/calendar/utils/index.ts +33 -30
  47. package/src/domains/layouts/components/BrandLogo.tsx +12 -9
  48. package/src/domains/layouts/components/DashboardHeader.tsx +78 -80
  49. package/src/domains/layouts/components/DashboardLayout.tsx +45 -21
  50. package/src/domains/layouts/components/DashboardSidebar.tsx +48 -51
  51. package/src/domains/layouts/types/layout.ts +7 -1
  52. package/src/domains/layouts/types/user.ts +3 -1
  53. package/src/domains/modules/build.test.tsx +97 -0
  54. package/src/domains/modules/build.tsx +71 -0
  55. package/src/domains/modules/index.ts +2 -0
  56. package/src/domains/modules/types.ts +29 -0
  57. package/src/domains/onboarding/components/AppFocusStep.tsx +99 -68
  58. package/src/domains/onboarding/components/OnboardingWizard.tsx +109 -183
  59. package/src/domains/onboarding/components/PlanStep.tsx +156 -123
  60. package/src/domains/onboarding/components/PlatformsStep.tsx +74 -54
  61. package/src/domains/onboarding/components/StepNavigation.tsx +108 -0
  62. package/src/domains/onboarding/components/StepProgress.tsx +62 -0
  63. package/src/domains/onboarding/components/UserTypeStep.tsx +59 -80
  64. package/src/domains/onboarding/components/index.ts +4 -0
  65. package/src/domains/onboarding/hooks/useOnboarding.ts +76 -66
  66. package/src/domains/onboarding/index.ts +1 -0
  67. package/src/domains/onboarding/types/onboarding.ts +57 -17
  68. package/src/domains/onboarding/utils/i18nKeys.ts +72 -0
  69. package/src/domains/onboarding/utils/index.ts +2 -0
  70. package/src/domains/permissions/PermissionsContext.tsx +103 -0
  71. package/src/domains/permissions/hasPermission.test.ts +50 -0
  72. package/src/domains/permissions/hasPermission.ts +38 -0
  73. package/src/domains/permissions/index.ts +19 -0
  74. package/src/domains/permissions/types.ts +35 -0
  75. package/src/domains/permissions/utils/i18nKeys.ts +11 -0
  76. package/src/domains/session/DemoLogin.tsx +50 -0
  77. package/src/domains/session/OrgSwitcher.tsx +110 -0
  78. package/src/domains/session/SessionContext.tsx +150 -0
  79. package/src/domains/session/inMemoryAuthAdapter.ts +53 -0
  80. package/src/domains/session/index.ts +18 -0
  81. package/src/domains/session/types.ts +47 -0
  82. package/src/domains/session/utils/i18nKeys.ts +12 -0
  83. package/src/domains/settings/components/AuditLogList.tsx +59 -0
  84. package/src/domains/settings/components/PermissionMatrixScreen.tsx +80 -0
  85. package/src/domains/settings/components/SettingsLayout.tsx +75 -60
  86. package/src/domains/settings/components/SettingsSection.tsx +35 -22
  87. package/src/domains/settings/components/UserManagementTable.tsx +69 -0
  88. package/src/domains/settings/components/index.ts +10 -0
  89. package/src/domains/settings/hooks/useSettings.ts +77 -52
  90. package/src/domains/settings/types/settings.ts +2 -0
  91. package/src/domains/settings/utils/i18nKeys.ts +18 -0
  92. package/src/domains/settings/utils/index.ts +1 -0
  93. package/src/domains/settings/utils/settings.ts +11 -4
  94. package/src/i18n/I18nContext.tsx +33 -0
  95. package/src/i18n/defaultT.test.ts +43 -0
  96. package/src/i18n/defaultT.ts +34 -0
  97. package/src/i18n/index.ts +7 -0
  98. package/src/i18n/types.ts +13 -0
  99. package/src/index.ts +1 -7
  100. package/src/primitives/Button.tsx +24 -0
  101. package/src/primitives/Checkbox.tsx +17 -0
  102. package/src/primitives/Input.tsx +23 -0
  103. package/src/primitives/Skeleton.tsx +6 -0
  104. package/src/primitives/cn.ts +10 -0
  105. package/src/primitives/index.ts +23 -0
  106. package/src/styles/dashboard.css +2731 -0
  107. package/src/styles/tokens.contract.test.ts +91 -0
  108. package/src/styles/tokens.css +116 -0
  109. package/src/domains/billing/components/BillingLayout.tsx +0 -63
  110. package/src/domains/billing/components/BillingPage.tsx +0 -49
  111. package/src/domains/billing/components/BillingPortal.tsx +0 -198
  112. package/src/domains/billing/components/InvoiceCard.tsx +0 -143
  113. package/src/domains/billing/components/PaymentMethodsList.tsx +0 -116
  114. package/src/domains/billing/components/PlanComparison.tsx +0 -199
  115. package/src/domains/billing/components/UsageCard.tsx +0 -103
  116. package/src/domains/billing/components/index.ts +0 -13
  117. package/src/domains/billing/constants/billing.ts +0 -69
  118. package/src/domains/billing/hooks/index.ts +0 -7
  119. package/src/domains/billing/hooks/useBilling.ts +0 -385
  120. package/src/domains/billing/index.ts +0 -73
  121. package/src/domains/billing/types/billing.ts +0 -342
  122. package/src/domains/billing/types/index.ts +0 -30
  123. package/src/domains/billing/utils/billing.ts +0 -360
  124. package/src/domains/billing/utils/index.ts +0 -31
  125. package/src/domains/billing/utils/polar.ts +0 -63
  126. package/src/domains/calendar/components/index.ts +0 -7
@@ -1,24 +1,57 @@
1
1
  /**
2
- * Login Form Component
2
+ * LoginForm
3
3
  *
4
- * Configurable login form with validation
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 { Eye, EyeOff, Loader2 } from "lucide-react";
10
- import { cn } from "@umituz/web-design-system/utils";
11
- import { Button } from "@umituz/web-design-system/atoms";
12
- import type { LoginFormProps } from "../types/auth";
13
- import { validateLogin, calculatePasswordStrength } from "../utils/auth";
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 [email, setEmail] = useState(defaultCredentials.email || "");
30
- const [password, setPassword] = useState(defaultCredentials.password || "");
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
- try {
51
- let user: { id: string; email: string; name?: string };
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
- // Use custom login handler if provided (real auth), otherwise use mock auth
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
- // Real authentication flow
56
- user = await onLoginAttempt({ email, password });
57
- } else {
58
- // Mock authentication flow (for development/demo)
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
- // Call success callback
68
- await onLoginSuccess?.(user);
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
- // Navigate to after-login route
71
- navigate(config.afterLoginRoute);
72
- } catch (err) {
73
- const errorMessage = err instanceof Error ? err.message : "Login failed";
74
- setError(errorMessage);
75
- onLoginError?.(errorMessage);
76
- } finally {
77
- setIsLoading(false);
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 passwordStrength = calculatePasswordStrength(password);
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="w-full max-w-md space-y-6">
85
- {/* Header */}
86
- <div className="text-center">
87
- <h1 className="text-3xl font-extrabold text-foreground mb-2">
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="text-muted-foreground">
91
- Sign in to your {config.brandName} account
137
+ <p className="udb-form__subtitle">
138
+ {translate(KEYS.signInTo, { brand: config.brandName })}
92
139
  </p>
93
140
  </div>
94
141
 
95
- {/* Error Message */}
96
- {error && (
97
- <div className="bg-destructive/10 border border-destructive/20 text-destructive px-4 py-3 rounded-lg text-sm">
98
- {error}
99
- </div>
100
- )}
101
-
102
- {/* Email Field */}
103
- <div className="space-y-2">
104
- <label htmlFor="email" className="text-sm font-medium text-foreground">
105
- Email
106
- </label>
107
- <input
108
- id="email"
109
- type="email"
110
- value={email}
111
- onChange={(e) => setEmail(e.target.value)}
112
- placeholder="you@example.com"
113
- className={cn(
114
- "w-full px-4 py-3 rounded-lg border bg-background",
115
- "focus:outline-none focus:ring-2 focus:ring-primary focus:border-transparent",
116
- "placeholder:text-muted-foreground",
117
- error && "border-destructive"
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="flex items-center justify-between">
168
+ <div className="udb-form__row">
182
169
  {showRememberMe && (
183
- <label className="flex items-center gap-2 cursor-pointer">
184
- <input
185
- type="checkbox"
186
- checked={rememberMe}
187
- onChange={(e) => setRememberMe(e.target.checked)}
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
- <span className="text-sm text-muted-foreground">Remember me</span>
176
+ {translate(KEYS.rememberMe)}
191
177
  </label>
192
178
  )}
193
- {showForgotPassword && config.forgotPasswordRoute && (
179
+ {showForgotPassword && (
194
180
  <button
195
181
  type="button"
196
- onClick={() => navigate(config.forgotPasswordRoute!)}
197
- className="text-sm text-primary hover:underline font-medium"
182
+ onClick={() => navigate(config.forgotPasswordRoute ?? '/forgot-password')}
183
+ className="udb-link-btn"
184
+ disabled={loading}
198
185
  >
199
- Forgot password?
186
+ {translate(KEYS.forgotPassword)}
200
187
  </button>
201
188
  )}
202
189
  </div>
203
190
  )}
204
191
 
205
- {/* Submit Button */}
206
- <Button
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="h-4 w-4 mr-2 animate-spin" />
214
- Signing in...
195
+ <Loader2 className="udb-btn__spinner" aria-hidden="true" />
196
+ {translate(KEYS.submitting)}
215
197
  </>
216
198
  ) : (
217
- "Sign in"
199
+ translate(KEYS.submit)
218
200
  )}
219
201
  </Button>
220
202
 
221
- {/* Register Link */}
222
- {showRegisterLink && config.registerRoute && (
223
- <p className="text-center text-sm text-muted-foreground">
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="text-primary hover:underline font-medium"
209
+ className="udb-link-btn udb-link-btn--strong"
210
+ disabled={loading}
229
211
  >
230
- Sign up
212
+ {translate(KEYS.signUp)}
231
213
  </button>
232
214
  </p>
233
215
  )}
234
216
 
235
- {/* Social Login */}
236
- {showSocialLogin && config.showSocialLogin && config.socialProviders && config.socialProviders.length > 0 && (
217
+ {showSocialLogin && (
237
218
  <>
238
- {/* Divider */}
239
- <div className="relative">
240
- <div className="absolute inset-0 flex items-center">
241
- <div className="w-full border-t border-border" />
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>