@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.
Files changed (125) hide show
  1. package/README.md +202 -14
  2. package/package.json +21 -29
  3. package/src/domains/analytics/components/AnalyticsCard.tsx +23 -21
  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 +42 -64
  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 +87 -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/settings.ts +11 -4
  93. package/src/i18n/I18nContext.tsx +33 -0
  94. package/src/i18n/defaultT.test.ts +43 -0
  95. package/src/i18n/defaultT.ts +34 -0
  96. package/src/i18n/index.ts +7 -0
  97. package/src/i18n/types.ts +13 -0
  98. package/src/index.ts +1 -7
  99. package/src/primitives/Button.tsx +30 -0
  100. package/src/primitives/Checkbox.tsx +40 -0
  101. package/src/primitives/Input.tsx +19 -0
  102. package/src/primitives/Skeleton.tsx +26 -0
  103. package/src/primitives/cn.ts +15 -0
  104. package/src/primitives/index.ts +23 -0
  105. package/src/styles/dashboard.css +3045 -0
  106. package/src/styles/tokens.contract.test.ts +66 -0
  107. package/src/styles/tokens.css +124 -0
  108. package/src/domains/billing/components/BillingLayout.tsx +0 -63
  109. package/src/domains/billing/components/BillingPage.tsx +0 -49
  110. package/src/domains/billing/components/BillingPortal.tsx +0 -198
  111. package/src/domains/billing/components/InvoiceCard.tsx +0 -143
  112. package/src/domains/billing/components/PaymentMethodsList.tsx +0 -116
  113. package/src/domains/billing/components/PlanComparison.tsx +0 -199
  114. package/src/domains/billing/components/UsageCard.tsx +0 -103
  115. package/src/domains/billing/components/index.ts +0 -13
  116. package/src/domains/billing/constants/billing.ts +0 -69
  117. package/src/domains/billing/hooks/index.ts +0 -7
  118. package/src/domains/billing/hooks/useBilling.ts +0 -385
  119. package/src/domains/billing/index.ts +0 -73
  120. package/src/domains/billing/types/billing.ts +0 -342
  121. package/src/domains/billing/types/index.ts +0 -30
  122. package/src/domains/billing/utils/billing.ts +0 -360
  123. package/src/domains/billing/utils/index.ts +0 -31
  124. package/src/domains/billing/utils/polar.ts +0 -63
  125. package/src/domains/calendar/components/index.ts +0 -7
@@ -1,47 +1,64 @@
1
1
  /**
2
2
  * Auth Layout Component
3
3
  *
4
- * Configurable layout wrapper for authentication pages
4
+ * Configurable layout wrapper for authentication pages.
5
+ * Strings stay localizable via the optional `translate` prop; when it
6
+ * is not provided the embedded English fallbacks are rendered.
7
+ * Styling is entirely `udb-auth*` classes from the package stylesheet.
5
8
  */
6
9
 
7
10
  import { BrandLogo } from "../../layouts/components";
8
- import { cn } from "@umituz/web-design-system/utils";
9
- import type { AuthLayoutProps } from "../types/auth";
11
+ import { cn } from "../../../primitives";
12
+ import { AUTH_KEYS } from "../utils/i18nKeys";
13
+ import type { AuthLayoutProps, Translate } from "../types/auth";
14
+
15
+ /**
16
+ * Embedded fallbacks used when no `translate` prop is supplied.
17
+ * Keys mirror the AUTH_KEYS catalog so consumers can override
18
+ * every string via their own i18n setup.
19
+ */
20
+ const DEFAULT_STRINGS: Record<string, string> = {
21
+ [AUTH_KEYS.layout.backToHome]: "Back to home",
22
+ [AUTH_KEYS.layout.orContinueWith]: "Or continue with",
23
+ [AUTH_KEYS.layout.allRightsReserved]: "All rights reserved.",
24
+ };
10
25
 
11
26
  export const AuthLayout = ({
12
27
  config,
13
- authState,
14
- authActions,
28
+ translate,
29
+ onSocialLogin,
15
30
  children,
16
31
  }: AuthLayoutProps) => {
32
+ const t: Translate = translate ?? ((key) => DEFAULT_STRINGS[key] ?? key);
33
+ // Prefer an explicit home route; fall back to afterLoginRoute for backwards compatibility.
34
+ const homeRoute = config.homeRoute ?? config.afterLoginRoute;
35
+ const footerLinks = config.footerLinks ?? [];
36
+
17
37
  return (
18
- <div className="min-h-screen bg-background flex flex-col">
38
+ <div className="udb-root udb-auth">
19
39
  {/* Header */}
20
- <header className="border-b border-border px-6 py-4 flex items-center justify-between">
21
- <div className="flex items-center gap-2">
40
+ <header className="udb-auth__header">
41
+ <div className="udb-auth__brand">
22
42
  <BrandLogo size={32} />
23
- <span className="font-bold text-xl text-foreground">{config.brandName}</span>
43
+ <span className="udb-auth__brand-name">{config.brandName}</span>
24
44
  </div>
25
45
 
26
46
  {/* Additional Header Content */}
27
- {config.afterLoginRoute && (
28
- <a
29
- href={config.afterLoginRoute}
30
- className="text-sm text-muted-foreground hover:text-foreground transition-colors"
31
- >
32
- Back to home
47
+ {homeRoute && (
48
+ <a href={homeRoute} className="udb-auth__home-link">
49
+ {t(AUTH_KEYS.layout.backToHome)}
33
50
  </a>
34
51
  )}
35
52
  </header>
36
53
 
37
54
  {/* Main Content */}
38
- <main className="flex-1 flex items-center justify-center px-4 py-12 bg-secondary/30">
39
- <div className="w-full max-w-md">
55
+ <main className="udb-auth__main">
56
+ <div className="udb-auth__column">
40
57
  {/* Card Container */}
41
- <div className="bg-background border border-border rounded-2xl shadow-sm p-8">
58
+ <div className="udb-auth__card">
42
59
  {/* Tagline */}
43
60
  {config.brandTagline && (
44
- <p className="text-center text-sm text-muted-foreground mb-6">
61
+ <p className="udb-auth__tagline">
45
62
  {config.brandTagline}
46
63
  </p>
47
64
  )}
@@ -52,36 +69,24 @@ export const AuthLayout = ({
52
69
 
53
70
  {/* Social Login */}
54
71
  {config.showSocialLogin && config.socialProviders && config.socialProviders.length > 0 && (
55
- <div className="mt-6 space-y-4">
72
+ <div className="udb-auth__social">
56
73
  {/* Divider */}
57
- <div className="relative">
58
- <div className="absolute inset-0 flex items-center">
59
- <div className="w-full border-t border-border" />
60
- </div>
61
- <div className="relative flex justify-center text-sm">
62
- <span className="px-2 bg-secondary/30 text-muted-foreground">
63
- Or continue with
64
- </span>
65
- </div>
74
+ <div className="udb-divider-row">
75
+ <span className="udb-divider-row__label">
76
+ {t(AUTH_KEYS.layout.orContinueWith)}
77
+ </span>
66
78
  </div>
67
79
 
68
80
  {/* Social Buttons */}
69
- <div className="grid grid-cols-2 gap-3">
81
+ <div className="udb-social-grid">
70
82
  {config.socialProviders.map((provider) => (
71
83
  <button
72
84
  key={provider.id}
73
85
  type="button"
74
- className={cn(
75
- "flex items-center justify-center gap-2 px-4 py-3 rounded-lg border",
76
- "bg-background hover:bg-muted transition-colors",
77
- "text-sm font-medium text-foreground"
78
- )}
79
- onClick={() => {
80
- // Handle social login
81
- console.log(`Social login with ${provider.id}`);
82
- }}
86
+ className={cn("udb-social-btn")}
87
+ onClick={() => onSocialLogin?.(provider.id)}
83
88
  >
84
- <span className="text-lg">{provider.icon}</span>
89
+ <span className="udb-social-btn__icon">{provider.icon}</span>
85
90
  <span>{provider.name}</span>
86
91
  </button>
87
92
  ))}
@@ -92,20 +97,24 @@ export const AuthLayout = ({
92
97
  </main>
93
98
 
94
99
  {/* Footer */}
95
- <footer className="border-t border-border px-6 py-4">
96
- <div className="max-w-4xl mx-auto flex items-center justify-between text-xs text-muted-foreground">
97
- <p>© {new Date().getFullYear()} {config.brandName}. All rights reserved.</p>
98
- <div className="flex items-center gap-4">
99
- <a href="/terms" className="hover:text-foreground transition-colors">
100
- Terms
101
- </a>
102
- <a href="/privacy" className="hover:text-foreground transition-colors">
103
- Privacy
104
- </a>
105
- <a href="/contact" className="hover:text-foreground transition-colors">
106
- Contact
107
- </a>
108
- </div>
100
+ <footer className="udb-auth__footer">
101
+ <div className="udb-auth__footer-inner">
102
+ <p>
103
+ © {new Date().getFullYear()} {config.brandName}. {t(AUTH_KEYS.layout.allRightsReserved)}
104
+ </p>
105
+ {footerLinks.length > 0 && (
106
+ <div className="udb-auth__footer-links">
107
+ {footerLinks.map((link) => (
108
+ <a
109
+ key={link.href}
110
+ href={link.href}
111
+ className="udb-auth__footer-link"
112
+ >
113
+ {link.label}
114
+ </a>
115
+ ))}
116
+ </div>
117
+ )}
109
118
  </div>
110
119
  </footer>
111
120
  </div>
@@ -1,16 +1,39 @@
1
1
  /**
2
- * Forgot Password Form Component
2
+ * ForgotPasswordForm
3
3
  *
4
- * Password reset request form
4
+ * Composed of shared auth primitives (EmailInput, AuthErrorBanner).
5
+ * All copy is i18n-keyed; the success state uses lucide icons
6
+ * instead of the previous `✓` Unicode glyphs.
5
7
  */
6
8
 
7
9
  import { useState } from "react";
8
10
  import { useNavigate } from "react-router-dom";
9
- import { ArrowLeft, Loader2, CheckCircle } from "lucide-react";
10
- import { cn } from "@umituz/web-design-system/utils";
11
- import { Button } from "@umituz/web-design-system/atoms";
12
- import type { ForgotPasswordFormProps } from "../types/auth";
11
+ import { useT } from "../../../i18n";
12
+ import { ArrowLeft, Loader2, CheckCircle2, Check } from "lucide-react";
13
+ import { Button } from "../../../primitives";
14
+ import type { ForgotPasswordFormProps, Translate } from "../types/auth";
13
15
  import { validateForgotPassword } from "../utils/auth";
16
+ import { AUTH_KEYS } from "../utils/i18nKeys";
17
+ import { AUTH_VALIDATION_KEYS } from "../utils/validationKeys";
18
+ import { AuthErrorBanner, EmailInput } from "./shared";
19
+
20
+ const KEYS = {
21
+ title: 'auth.forgotPassword.title',
22
+ description: 'auth.forgotPassword.description',
23
+ emailLabel: AUTH_KEYS.fields.email,
24
+ emailPlaceholder: AUTH_KEYS.placeholders.emailExample,
25
+ submitting: 'auth.forgotPassword.submitting',
26
+ submit: 'auth.forgotPassword.submit',
27
+ backToSignIn: 'auth.forgotPassword.backToSignIn',
28
+ successTitle: 'auth.forgotPassword.successTitle',
29
+ successMessage: 'auth.forgotPassword.successMessage',
30
+ sentTo: 'auth.forgotPassword.sentTo',
31
+ step1: 'auth.forgotPassword.steps.step1',
32
+ step2: 'auth.forgotPassword.steps.step2',
33
+ step3: 'auth.forgotPassword.steps.step3',
34
+ invalidEmail: AUTH_VALIDATION_KEYS.emailInvalid,
35
+ sendResetFailed: 'auth.errors.sendResetFailed',
36
+ } as const;
14
37
 
15
38
  export const ForgotPasswordForm = ({
16
39
  config,
@@ -19,6 +42,9 @@ export const ForgotPasswordForm = ({
19
42
  showBackLink = true,
20
43
  }: ForgotPasswordFormProps) => {
21
44
  const navigate = useNavigate();
45
+ const t = useT();
46
+ const translate: Translate = t;
47
+
22
48
  const [email, setEmail] = useState("");
23
49
  const [isLoading, setIsLoading] = useState(false);
24
50
  const [error, setError] = useState<string | null>(null);
@@ -28,29 +54,26 @@ export const ForgotPasswordForm = ({
28
54
  e.preventDefault();
29
55
  setError(null);
30
56
 
31
- // Validate
32
57
  const validation = validateForgotPassword({ email });
33
58
  if (!validation.valid) {
34
- setError(validation.error || "Invalid email");
35
- onError?.(validation.error || "Invalid email");
59
+ const message = validation.error ?? KEYS.invalidEmail;
60
+ setError(message);
61
+ onError?.(message);
36
62
  return;
37
63
  }
38
64
 
39
65
  setIsLoading(true);
40
-
41
66
  try {
42
- // In production, call your auth API
43
- // await forgotPassword({ email });
44
-
45
- // Simulate API call
46
- await new Promise((resolve) => setTimeout(resolve, 1000));
47
-
67
+ // Hand off to the consumer's auth provider. The form has
68
+ // no network knowledge of its own — it's pure orchestration.
69
+ // The submitted email is passed through so the consumer can
70
+ // actually trigger the reset email.
71
+ await onSuccess?.({ email });
48
72
  setSuccess(true);
49
- await onSuccess?.();
50
73
  } catch (err) {
51
- const errorMessage = err instanceof Error ? err.message : "Failed to send reset email";
52
- setError(errorMessage);
53
- onError?.(errorMessage);
74
+ const message = err instanceof Error ? err.message : KEYS.sendResetFailed;
75
+ setError(message);
76
+ onError?.(message);
54
77
  } finally {
55
78
  setIsLoading(false);
56
79
  }
@@ -58,48 +81,44 @@ export const ForgotPasswordForm = ({
58
81
 
59
82
  if (success) {
60
83
  return (
61
- <div className="w-full max-w-md space-y-6">
62
- {/* Success Message */}
63
- <div className="text-center space-y-4">
64
- <div className="flex justify-center">
65
- <div className="w-16 h-16 bg-green-100 dark:bg-green-900/20 rounded-full flex items-center justify-center">
66
- <CheckCircle className="h-8 w-8 text-green-600 dark:text-green-500" />
67
- </div>
84
+ <div className="udb-form" role="status" aria-live="polite">
85
+ <div className="udb-form__header">
86
+ <div className="udb-success-badge">
87
+ <CheckCircle2 className="udb-success-badge__icon" aria-hidden="true" />
68
88
  </div>
69
- <div>
70
- <h1 className="text-2xl font-extrabold text-foreground mb-2">
71
- Check your email
72
- </h1>
73
- <p className="text-muted-foreground">
74
- We sent a password reset link to{" "}
75
- <span className="font-medium text-foreground">{email}</span>
76
- </p>
77
- </div>
78
- </div>
79
-
80
- {/* Instructions */}
81
- <div className="bg-muted/50 border border-border rounded-lg p-4 space-y-2">
82
- <p className="text-sm text-muted-foreground">
83
- ✓ Click the link in your email
84
- </p>
85
- <p className="text-sm text-muted-foreground">
86
- ✓ Create a new password
87
- </p>
88
- <p className="text-sm text-muted-foreground">
89
- ✓ Sign in with your new password
89
+ <h1 className="udb-form__title">
90
+ {translate(KEYS.successTitle)}
91
+ </h1>
92
+ <p className="udb-form__subtitle">
93
+ {translate(KEYS.sentTo)}{' '}
94
+ <span className="udb-form__subtitle-strong">{email}</span>
90
95
  </p>
91
96
  </div>
92
97
 
93
- {/* Back to Login */}
98
+ <ol className="udb-steps-list">
99
+ <li className="udb-steps-list__item">
100
+ <Check className="udb-steps-list__icon" aria-hidden="true" />
101
+ {translate(KEYS.step1)}
102
+ </li>
103
+ <li className="udb-steps-list__item">
104
+ <Check className="udb-steps-list__icon" aria-hidden="true" />
105
+ {translate(KEYS.step2)}
106
+ </li>
107
+ <li className="udb-steps-list__item">
108
+ <Check className="udb-steps-list__icon" aria-hidden="true" />
109
+ {translate(KEYS.step3)}
110
+ </li>
111
+ </ol>
112
+
94
113
  {showBackLink && config.loginRoute && (
95
114
  <Button
96
115
  type="button"
97
116
  variant="ghost"
98
- onClick={() => navigate(config.loginRoute!)}
99
- className="w-full"
117
+ onClick={() => navigate(config.loginRoute)}
118
+ className="udb-submit-btn"
100
119
  >
101
- <ArrowLeft className="h-4 w-4 mr-2" />
102
- Back to sign in
120
+ <ArrowLeft className="udb-btn__spinner" aria-hidden="true" />
121
+ {translate(KEYS.backToSignIn)}
103
122
  </Button>
104
123
  )}
105
124
  </div>
@@ -107,71 +126,50 @@ export const ForgotPasswordForm = ({
107
126
  }
108
127
 
109
128
  return (
110
- <form onSubmit={handleSubmit} className="w-full max-w-md space-y-6">
111
- {/* Header */}
112
- <div className="text-center">
113
- <h1 className="text-3xl font-extrabold text-foreground mb-2">
114
- Forgot password?
129
+ <form onSubmit={handleSubmit} className="udb-form" noValidate>
130
+ <div className="udb-form__header">
131
+ <h1 className="udb-form__title udb-form__title--lg">
132
+ {translate(KEYS.title)}
115
133
  </h1>
116
- <p className="text-muted-foreground">
117
- No worries, we'll send you reset instructions
118
- </p>
134
+ <p className="udb-form__subtitle">{translate(KEYS.description)}</p>
119
135
  </div>
120
136
 
121
- {/* Error Message */}
122
- {error && (
123
- <div className="bg-destructive/10 border border-destructive/20 text-destructive px-4 py-3 rounded-lg text-sm">
124
- {error}
125
- </div>
126
- )}
137
+ <AuthErrorBanner message={error} translate={translate} />
127
138
 
128
- {/* Email Field */}
129
- <div className="space-y-2">
130
- <label htmlFor="email" className="text-sm font-medium text-foreground">
131
- Email
132
- </label>
133
- <input
134
- id="email"
135
- type="email"
136
- value={email}
137
- onChange={(e) => setEmail(e.target.value)}
138
- placeholder="you@example.com"
139
- className={cn(
140
- "w-full px-4 py-3 rounded-lg border bg-background",
141
- "focus:outline-none focus:ring-2 focus:ring-primary focus:border-transparent",
142
- "placeholder:text-muted-foreground",
143
- error && "border-destructive"
144
- )}
145
- disabled={isLoading}
146
- autoComplete="email"
147
- />
148
- </div>
139
+ <EmailInput
140
+ id="forgot-email"
141
+ labelKey={KEYS.emailLabel}
142
+ placeholderKey={KEYS.emailPlaceholder}
143
+ value={email}
144
+ onChange={setEmail}
145
+ translate={translate}
146
+ disabled={isLoading}
147
+ required
148
+ />
149
149
 
150
- {/* Submit Button */}
151
150
  <Button
152
151
  type="submit"
153
- className="w-full h-12 text-base font-bold rounded-full"
152
+ className="udb-submit-btn udb-submit-btn--pill"
154
153
  disabled={isLoading}
155
154
  >
156
155
  {isLoading ? (
157
156
  <>
158
- <Loader2 className="h-4 w-4 mr-2 animate-spin" />
159
- Sending...
157
+ <Loader2 className="udb-btn__spinner" aria-hidden="true" />
158
+ {translate(KEYS.submitting)}
160
159
  </>
161
160
  ) : (
162
- "Send reset link"
161
+ translate(KEYS.submit)
163
162
  )}
164
163
  </Button>
165
164
 
166
- {/* Back to Login */}
167
165
  {showBackLink && config.loginRoute && (
168
166
  <button
169
167
  type="button"
170
- onClick={() => navigate(config.loginRoute!)}
171
- className="w-full flex items-center justify-center gap-2 text-sm text-muted-foreground hover:text-foreground transition-colors"
168
+ onClick={() => navigate(config.loginRoute)}
169
+ className="udb-back-link"
172
170
  >
173
- <ArrowLeft className="h-4 w-4" />
174
- Back to sign in
171
+ <ArrowLeft className="udb-back-link__icon" aria-hidden="true" />
172
+ {translate(KEYS.backToSignIn)}
175
173
  </button>
176
174
  )}
177
175
  </form>