xertica-ui 2.2.0 → 2.2.1
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/CHANGELOG.md +1 -1
- package/README.md +1 -1
- package/dist/ThemeContext-BgclCB35.js +1856 -0
- package/dist/ThemeContext-DQUOeziy.cjs +1855 -0
- package/dist/VerifyEmailPage-RrUApqBN.js +3214 -0
- package/dist/VerifyEmailPage-VoMI7MYH.cjs +3213 -0
- package/dist/XerticaProvider-BSyFrmC0.js +49 -0
- package/dist/XerticaProvider-CiNKjMx1.cjs +48 -0
- package/dist/XerticaXLogo-B2svDGZh.cjs +251 -0
- package/dist/XerticaXLogo-CowGv7BC.js +252 -0
- package/dist/brand.cjs.js +2 -2
- package/dist/brand.es.js +2 -2
- package/dist/hooks.cjs.js +1 -1
- package/dist/hooks.es.js +1 -1
- package/dist/index.cjs.js +5 -5
- package/dist/index.es.js +5 -5
- package/dist/layout.cjs.js +1 -1
- package/dist/layout.es.js +1 -1
- package/dist/pages.cjs.js +1 -1
- package/dist/pages.es.js +1 -1
- package/dist/sidebar-CRMiBtAi.js +801 -0
- package/dist/sidebar-CZ2mWaMM.cjs +800 -0
- package/dist/xertica-ui.css +1 -1
- package/package.json +1 -1
- package/templates/CLAUDE.md +165 -180
- package/templates/package.json +2 -2
- package/templates/src/features/auth/ui/ForgotPasswordContent.tsx +9 -7
- package/templates/src/features/auth/ui/LoginContent.tsx +10 -8
- package/templates/src/features/auth/ui/ResetPasswordContent.tsx +179 -177
- package/templates/src/features/auth/ui/SocialLoginButtons.tsx +9 -4
- package/templates/src/features/auth/ui/VerifyEmailContent.tsx +84 -82
- package/templates/src/features/template/ui/TemplateContent.tsx +1 -1
- package/templates/src/locales/en/components/assistant.json +14 -0
- package/templates/src/locales/en/pages/forgotPassword.json +10 -0
- package/templates/src/locales/en/pages/templates.json +1 -1
- package/templates/src/locales/es/components/assistant.json +14 -0
- package/templates/src/locales/es/pages/forgotPassword.json +10 -0
- package/templates/src/locales/es/pages/templates.json +1 -1
- package/templates/src/locales/pt-BR/components/assistant.json +14 -0
- package/templates/src/locales/pt-BR/pages/forgotPassword.json +10 -0
- package/templates/src/locales/pt-BR/pages/templates.json +1 -1
- package/templates/src/pages/AssistantPage.tsx +464 -463
package/package.json
CHANGED
package/templates/CLAUDE.md
CHANGED
|
@@ -1,180 +1,165 @@
|
|
|
1
|
-
# Xertica UI — Project Rules
|
|
2
|
-
|
|
3
|
-
This project uses **xertica-ui**, an enterprise React design system built on Tailwind CSS v4, Radix UI, and Lucide Icons.
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
<
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
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
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
---
|
|
159
|
-
|
|
160
|
-
##
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
toast.error('Something went wrong');
|
|
167
|
-
toast.info('Processing...');
|
|
168
|
-
toast.warning('Check your input');
|
|
169
|
-
```
|
|
170
|
-
|
|
171
|
-
Never render `<Toaster>` manually — it is auto-injected by `<XerticaProvider>`.
|
|
172
|
-
|
|
173
|
-
---
|
|
174
|
-
|
|
175
|
-
## Before Writing Any Component
|
|
176
|
-
|
|
177
|
-
1. Check `node_modules/xertica-ui/llms-compact.txt` — the component may already exist.
|
|
178
|
-
2. Check `node_modules/xertica-ui/docs/decision-tree.md` — you may be choosing the wrong component.
|
|
179
|
-
3. Read the specific component doc at `node_modules/xertica-ui/docs/components/[name].md`.
|
|
180
|
-
4. Never create custom primitives that duplicate library components.
|
|
1
|
+
# Xertica UI — Project Rules
|
|
2
|
+
|
|
3
|
+
This project uses **xertica-ui**, an enterprise React design system built on Tailwind CSS v4, Radix UI, and Lucide Icons.
|
|
4
|
+
|
|
5
|
+
## Command Reference
|
|
6
|
+
|
|
7
|
+
- **Dev Server**: `npm run dev`
|
|
8
|
+
- **Build Production**: `npm run build`
|
|
9
|
+
- **Linter**: `npm run lint` / `npm run lint:fix`
|
|
10
|
+
- **Type Checker**: `npm run type-check`
|
|
11
|
+
- **All Validation Checks**: `npm run check` (type-check + lint)
|
|
12
|
+
- **Code Formatter**: `npm run format`
|
|
13
|
+
|
|
14
|
+
---
|
|
15
|
+
|
|
16
|
+
## Architecture — Feature-Sliced Design (FSD) + Feature-Driven Architecture (FDA)
|
|
17
|
+
|
|
18
|
+
This project follows **FSD (Feature-Sliced Design)** layered architecture combined with **FDA (Feature-Driven Architecture)** vertical slicing. Layers can only import from layers below them.
|
|
19
|
+
|
|
20
|
+
- **`src/app/`**: Application shell (BrowserRouter, AppLayout, AuthGuard, AuthContext). No business logic.
|
|
21
|
+
- **`src/shared/`**: Shared layer with no business domain (config/navigation, lib/auth, types). Importable by any layer.
|
|
22
|
+
- **`src/features/`**: Self-contained vertical slices by business capability (auth, home, template, assistant). Import only from the barrel `src/features/<name>/index.ts` — never from internal feature paths.
|
|
23
|
+
- **`src/pages/`**: Thin route wrappers composing `AppLayout` + feature content. No logic or API calls here.
|
|
24
|
+
|
|
25
|
+
### Adding a New Route
|
|
26
|
+
1. **Create the feature content**: `src/features/<name>/ui/<NameContent>.tsx`
|
|
27
|
+
2. **Export from the barrel**: `src/features/<name>/index.ts`
|
|
28
|
+
3. **Create the page**: `src/pages/<NamePage>.tsx` (thin AppLayout wrapper)
|
|
29
|
+
4. **Register the route**: Add route path, label, and icon to the `routes` array in `src/shared/config/navigation.ts`.
|
|
30
|
+
5. **Register the `<Route>`**: Add the route element inside `<Routes>` in `src/app/components/AuthGuard.tsx`.
|
|
31
|
+
|
|
32
|
+
---
|
|
33
|
+
|
|
34
|
+
## Import Rules
|
|
35
|
+
|
|
36
|
+
Always use the correct `xertica-ui` subpath:
|
|
37
|
+
- **UI Primitives**: `xertica-ui/ui` (e.g. `Button`, `Card`, `Input`, `Badge`, `Table`, `Dialog`, `Select`)
|
|
38
|
+
- **Providers & Brand**: `xertica-ui/brand` (e.g. `XerticaProvider`, `XerticaLogo`, `ThemeToggle`, `LanguageSelector`)
|
|
39
|
+
- **Navigation Shell**: `xertica-ui/layout` (e.g. `Sidebar`, `Header`)
|
|
40
|
+
- **AI Assistant**: `xertica-ui/assistant` (e.g. `XerticaAssistant`, `generateDemoResponse`)
|
|
41
|
+
- **Media Players**: `xertica-ui/media` (e.g. `VideoPlayer`, `AudioPlayer`)
|
|
42
|
+
- **Hooks & Contexts**: `xertica-ui/hooks` (e.g. `useLayout`, `useOptionalLayout`, `useTheme`, `useLanguage`)
|
|
43
|
+
- **Styles**: Import `xertica-ui/style.css` once in `src/styles/index.css` (never directly inside components).
|
|
44
|
+
|
|
45
|
+
Icons always come from `lucide-react` — never from `xertica-ui`.
|
|
46
|
+
|
|
47
|
+
---
|
|
48
|
+
|
|
49
|
+
## Non-Negotiable Coding Rules
|
|
50
|
+
|
|
51
|
+
### HTML & Radix Elements
|
|
52
|
+
Never use native HTML interactive elements where design system components exist:
|
|
53
|
+
- `<Button>` instead of `<button>`
|
|
54
|
+
- `<Input>` instead of `<input>`
|
|
55
|
+
- `<Select>` instead of `<select>`
|
|
56
|
+
- `<PageHeader>` + `<PageHeaderHeading>` instead of `<h1>`/`<h2>` for page titles
|
|
57
|
+
- `<Card>` instead of `<div className="card">`
|
|
58
|
+
- `<ScrollArea>` instead of custom scrollbars
|
|
59
|
+
|
|
60
|
+
### Color & Border Radius Styling
|
|
61
|
+
- **Never use raw color values** (`#hex`, `rgb()`, `hsl()`) or inline styles for theming.
|
|
62
|
+
- **Never use standard Tailwind color classes for semantic/status contexts** (e.g., do not use `bg-red-500` or `text-green-500` for status/feedback/errors). Use semantic tokens: `bg-destructive`/`text-destructive` for errors, `bg-success` for success, `bg-warning` for warnings, `bg-info` for info.
|
|
63
|
+
- **Tailwind colors** (`bg-blue-500`, `text-gray-700`) are allowed **only** for layout, spacing, and general non-semantic UI where no semantic token applies.
|
|
64
|
+
- **Borders**: Always use `rounded-[var(--radius)]` — never use fixed radius classes like `rounded-lg` or `rounded-xl`.
|
|
65
|
+
|
|
66
|
+
### Layout State
|
|
67
|
+
Never hardcode sidebar or layout widths (e.g. `256px`). Read them dynamically:
|
|
68
|
+
- `const { sidebarExpanded, sidebarWidth } = useLayout()`
|
|
69
|
+
- For components that might render outside the layout provider:
|
|
70
|
+
`const layout = useOptionalLayout(); const fallbackSidebarWidth = layout?.sidebarWidth ?? 80;`
|
|
71
|
+
|
|
72
|
+
### Confirm Destructive Actions
|
|
73
|
+
Always wrap destructive actions in `<AlertDialog>` for confirmation:
|
|
74
|
+
```tsx
|
|
75
|
+
<AlertDialog>
|
|
76
|
+
<AlertDialogTrigger asChild>
|
|
77
|
+
<Button variant="destructive">Delete</Button>
|
|
78
|
+
</AlertDialogTrigger>
|
|
79
|
+
<AlertDialogContent>
|
|
80
|
+
<AlertDialogHeader>
|
|
81
|
+
<AlertDialogTitle>Are you sure?</AlertDialogTitle>
|
|
82
|
+
<AlertDialogDescription>This action cannot be undone.</AlertDialogDescription>
|
|
83
|
+
</AlertDialogHeader>
|
|
84
|
+
<AlertDialogFooter>
|
|
85
|
+
<AlertDialogCancel>Cancel</AlertDialogCancel>
|
|
86
|
+
<AlertDialogAction onClick={handleDelete}>Delete</AlertDialogAction>
|
|
87
|
+
</AlertDialogFooter>
|
|
88
|
+
</AlertDialogContent>
|
|
89
|
+
</AlertDialog>
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
### Toast Notifications
|
|
93
|
+
Always use `toast` from `sonner` and translate messages via `t()`:
|
|
94
|
+
```tsx
|
|
95
|
+
toast.success(t('common.saveSuccess'));
|
|
96
|
+
toast.error(t('errors.somethingWentWrong'));
|
|
97
|
+
```
|
|
98
|
+
Never render `<Toaster>` manually — it is auto-injected by `<XerticaProvider>`.
|
|
99
|
+
|
|
100
|
+
---
|
|
101
|
+
|
|
102
|
+
## Internationalization (i18n)
|
|
103
|
+
|
|
104
|
+
Every user-facing string must go through `useTranslation()`:
|
|
105
|
+
- Translate all labels, placeholders, titles, aria-labels, tooltips, toasts, etc.
|
|
106
|
+
- Add translation keys to split JSON files in `src/locales/<lang>/` for all languages.
|
|
107
|
+
- **Monolingual Mode**: `<LanguageSelector>` auto-hides when a single language is configured. Use `<LanguageSelector showWhenMonolingual />` to override.
|
|
108
|
+
- **Dynamic mock data / outside React cycle**: Always use **factory functions** returning `i18n.t(...)` rather than static arrays, so translations update dynamically:
|
|
109
|
+
```tsx
|
|
110
|
+
export function getMockOptions() {
|
|
111
|
+
return [i18n.t('feedback.notWhatIWanted')];
|
|
112
|
+
}
|
|
113
|
+
```
|
|
114
|
+
- Inside components, wrap enum label maps in `useMemo([..., t])`.
|
|
115
|
+
|
|
116
|
+
---
|
|
117
|
+
|
|
118
|
+
## Server State (React Query)
|
|
119
|
+
|
|
120
|
+
- Place typed fetch functions in `data/mock.ts` and React Query wrappers in `hooks/use<Xxx>.ts`.
|
|
121
|
+
- **Language-aware queryKey is MANDATORY**: Every hook returning translated data must include `language` in its `queryKey` so switching languages invalidates the cache and triggers a refetch:
|
|
122
|
+
```tsx
|
|
123
|
+
const { language } = useLanguage();
|
|
124
|
+
return useQuery({
|
|
125
|
+
queryKey: ['home', 'feature-cards', language],
|
|
126
|
+
queryFn: fetchFeatureCards,
|
|
127
|
+
staleTime: 10 * 60 * 1000
|
|
128
|
+
});
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
---
|
|
132
|
+
|
|
133
|
+
## Loading States (Skeletons)
|
|
134
|
+
|
|
135
|
+
Always render matching skeletons instead of spinners for data loading:
|
|
136
|
+
- Wrap cards/lists with their matching FSD skeleton companions (`ActivityCardSkeleton`, `ProfileCardSkeleton`, `ProjectCardSkeleton`, `NotificationCardSkeleton`, `QuickActionCardSkeleton`, `FeatureCardSkeleton`, `StatsCardSkeleton`).
|
|
137
|
+
- Pass `rows` to skeletons (e.g. `<ActivityCardSkeleton rows={5} />`) to match the loaded state height.
|
|
138
|
+
- For tables, build loading rows using `<TableRow>` + `<TableCell>` + `<Skeleton className="h-3.5 w-28" />`.
|
|
139
|
+
|
|
140
|
+
---
|
|
141
|
+
|
|
142
|
+
## Theme & Token Customization
|
|
143
|
+
|
|
144
|
+
- Customize colors, fonts, and dark mode classes by editing `src/styles/xertica/tokens.css`.
|
|
145
|
+
- Update theme or languages using the CLI: `npx xertica-ui update`.
|
|
146
|
+
|
|
147
|
+
---
|
|
148
|
+
|
|
149
|
+
## Authentication Pattern
|
|
150
|
+
|
|
151
|
+
The auth flow is managed in `src/app/context/AuthContext.tsx` via the `useAuth()` hook:
|
|
152
|
+
- `getStoredUser()` / `storeUser()` / `clearStoredUser()` — from `src/shared/lib/auth.ts`
|
|
153
|
+
- Auth pages (`/login`, `/forgot-password`, etc.) redirect to `/home` when user is already logged in
|
|
154
|
+
- Protected routes use `<ProtectedRoute>` wrapper that redirects to `/login` if user is null
|
|
155
|
+
- `useAuth().login(email, password)` stores the user
|
|
156
|
+
- `useAuth().logout()` clears storage and navigates to `/login`
|
|
157
|
+
|
|
158
|
+
---
|
|
159
|
+
|
|
160
|
+
## Before Writing Any Component
|
|
161
|
+
|
|
162
|
+
1. Check `node_modules/xertica-ui/llms-compact.txt` — the component may already exist.
|
|
163
|
+
2. Check `node_modules/xertica-ui/docs/decision-tree.md` — you may be choosing the wrong component.
|
|
164
|
+
3. Read the specific component doc at `node_modules/xertica-ui/docs/components/[name].md`.
|
|
165
|
+
4. Never create custom primitives that duplicate library components.
|
package/templates/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "xertica-ui-template",
|
|
3
|
-
"version": "2.2.
|
|
3
|
+
"version": "2.2.1",
|
|
4
4
|
"private": true,
|
|
5
5
|
"type": "module",
|
|
6
6
|
"scripts": {
|
|
@@ -24,7 +24,7 @@
|
|
|
24
24
|
"react-i18next": "^17.0.8",
|
|
25
25
|
"react-router-dom": "^7.1.3",
|
|
26
26
|
"sonner": "^1.7.3",
|
|
27
|
-
"xertica-ui": "^2.2.
|
|
27
|
+
"xertica-ui": "^2.2.1",
|
|
28
28
|
"zustand": "^5.0.13"
|
|
29
29
|
},
|
|
30
30
|
"devDependencies": {
|
|
@@ -5,9 +5,11 @@ import { ArrowLeft } from 'lucide-react';
|
|
|
5
5
|
import { useNavigate } from 'react-router-dom';
|
|
6
6
|
import { AuthPageShell } from './AuthPageShell';
|
|
7
7
|
import { SocialLoginButtons } from './SocialLoginButtons';
|
|
8
|
+
import { useTranslation } from 'react-i18next';
|
|
8
9
|
|
|
9
10
|
export function ForgotPasswordContent() {
|
|
10
11
|
const navigate = useNavigate();
|
|
12
|
+
const { t } = useTranslation();
|
|
11
13
|
const [email, setEmail] = useState('');
|
|
12
14
|
const [isLoading, setIsLoading] = useState(false);
|
|
13
15
|
|
|
@@ -22,35 +24,35 @@ export function ForgotPasswordContent() {
|
|
|
22
24
|
return (
|
|
23
25
|
<AuthPageShell
|
|
24
26
|
imageSrc="https://images.unsplash.com/photo-1557804506-669a67965ba0?w=1200&h=800&fit=crop&auto=format"
|
|
25
|
-
imageAlt=
|
|
27
|
+
imageAlt={t('forgotPassword.heroImageAlt')}
|
|
26
28
|
>
|
|
27
29
|
<div className="text-center">
|
|
28
30
|
<div className="flex items-center justify-center mb-4">
|
|
29
31
|
<XerticaLogo className="h-12 w-auto text-primary dark:text-foreground" variant="theme" />
|
|
30
32
|
</div>
|
|
31
|
-
<h2 className="text-sm text-muted-foreground">
|
|
33
|
+
<h2 className="text-sm text-muted-foreground">{t('forgotPassword.heading')}</h2>
|
|
32
34
|
<p className="mt-2 text-muted-foreground">
|
|
33
|
-
|
|
35
|
+
{t('forgotPassword.subheading')}
|
|
34
36
|
</p>
|
|
35
37
|
</div>
|
|
36
38
|
|
|
37
39
|
<form className="space-y-6" onSubmit={handleSubmit}>
|
|
38
40
|
<div className="form-group space-y-2">
|
|
39
|
-
<Label htmlFor="email">
|
|
41
|
+
<Label htmlFor="email">{t('forgotPassword.emailLabel')}</Label>
|
|
40
42
|
<Input
|
|
41
43
|
id="email"
|
|
42
44
|
name="email"
|
|
43
45
|
type="email"
|
|
44
46
|
required
|
|
45
47
|
className="w-full"
|
|
46
|
-
placeholder=
|
|
48
|
+
placeholder={t('forgotPassword.emailPlaceholder')}
|
|
47
49
|
value={email}
|
|
48
50
|
onChange={e => setEmail(e.target.value)}
|
|
49
51
|
/>
|
|
50
52
|
</div>
|
|
51
53
|
<div className="space-y-3">
|
|
52
54
|
<Button type="submit" className="w-full" disabled={isLoading}>
|
|
53
|
-
{isLoading ? '
|
|
55
|
+
{isLoading ? t('forgotPassword.submitting') : t('forgotPassword.submit')}
|
|
54
56
|
</Button>
|
|
55
57
|
<Button
|
|
56
58
|
type="button"
|
|
@@ -59,7 +61,7 @@ export function ForgotPasswordContent() {
|
|
|
59
61
|
className="w-full text-muted-foreground hover:text-foreground"
|
|
60
62
|
>
|
|
61
63
|
<ArrowLeft className="w-4 h-4 mr-2" />
|
|
62
|
-
|
|
64
|
+
{t('forgotPassword.backToLogin')}
|
|
63
65
|
</Button>
|
|
64
66
|
</div>
|
|
65
67
|
</form>
|
|
@@ -4,6 +4,7 @@ import { XerticaLogo } from 'xertica-ui/brand';
|
|
|
4
4
|
import { useNavigate } from 'react-router-dom';
|
|
5
5
|
import { AuthPageShell } from './AuthPageShell';
|
|
6
6
|
import { SocialLoginButtons } from './SocialLoginButtons';
|
|
7
|
+
import { useTranslation } from 'react-i18next';
|
|
7
8
|
|
|
8
9
|
interface LoginContentProps {
|
|
9
10
|
onLogin: (email: string, password: string) => boolean;
|
|
@@ -11,6 +12,7 @@ interface LoginContentProps {
|
|
|
11
12
|
|
|
12
13
|
export function LoginContent({ onLogin }: LoginContentProps) {
|
|
13
14
|
const navigate = useNavigate();
|
|
15
|
+
const { t } = useTranslation();
|
|
14
16
|
const [email, setEmail] = useState('');
|
|
15
17
|
const [password, setPassword] = useState('');
|
|
16
18
|
const [isLoading, setIsLoading] = useState(false);
|
|
@@ -21,38 +23,38 @@ export function LoginContent({ onLogin }: LoginContentProps) {
|
|
|
21
23
|
setError('');
|
|
22
24
|
setIsLoading(true);
|
|
23
25
|
await new Promise(resolve => setTimeout(resolve, 1000));
|
|
24
|
-
if (!onLogin(email, password)) setError('
|
|
26
|
+
if (!onLogin(email, password)) setError(t('login.errorFillFields'));
|
|
25
27
|
setIsLoading(false);
|
|
26
28
|
};
|
|
27
29
|
|
|
28
30
|
return (
|
|
29
31
|
<AuthPageShell
|
|
30
32
|
imageSrc="https://images.unsplash.com/photo-1551434678-e076c223a692?w=1200&h=800&fit=crop&auto=format"
|
|
31
|
-
imageAlt=
|
|
33
|
+
imageAlt={t('login.heroImageAlt')}
|
|
32
34
|
>
|
|
33
35
|
<div className="text-center">
|
|
34
36
|
<div className="flex items-center justify-center mb-4">
|
|
35
37
|
<XerticaLogo className="h-12 w-auto text-primary dark:text-foreground" variant="theme" />
|
|
36
38
|
</div>
|
|
37
|
-
<h2 className="text-sm text-muted-foreground">
|
|
39
|
+
<h2 className="text-sm text-muted-foreground">{t('login.heading')}</h2>
|
|
38
40
|
</div>
|
|
39
41
|
|
|
40
42
|
<form className="space-y-6" onSubmit={handleSubmit}>
|
|
41
43
|
<div className="space-y-2">
|
|
42
|
-
<Label htmlFor="email">
|
|
44
|
+
<Label htmlFor="email">{t('login.emailLabel')}</Label>
|
|
43
45
|
<Input
|
|
44
46
|
id="email"
|
|
45
47
|
name="email"
|
|
46
48
|
type="email"
|
|
47
49
|
required
|
|
48
50
|
className="w-full"
|
|
49
|
-
placeholder=
|
|
51
|
+
placeholder={t('login.emailPlaceholder')}
|
|
50
52
|
value={email}
|
|
51
53
|
onChange={e => setEmail(e.target.value)}
|
|
52
54
|
/>
|
|
53
55
|
</div>
|
|
54
56
|
<div className="space-y-2">
|
|
55
|
-
<Label htmlFor="password">
|
|
57
|
+
<Label htmlFor="password">{t('login.passwordLabel')}</Label>
|
|
56
58
|
<Input
|
|
57
59
|
id="password"
|
|
58
60
|
name="password"
|
|
@@ -66,7 +68,7 @@ export function LoginContent({ onLogin }: LoginContentProps) {
|
|
|
66
68
|
</div>
|
|
67
69
|
{error && <div className="text-destructive text-sm text-center">{error}</div>}
|
|
68
70
|
<Button type="submit" className="w-full" disabled={isLoading}>
|
|
69
|
-
{isLoading ? '
|
|
71
|
+
{isLoading ? t('login.submitting') : t('login.submit')}
|
|
70
72
|
</Button>
|
|
71
73
|
<div className="text-center">
|
|
72
74
|
<button
|
|
@@ -74,7 +76,7 @@ export function LoginContent({ onLogin }: LoginContentProps) {
|
|
|
74
76
|
onClick={() => navigate('/forgot-password')}
|
|
75
77
|
className="text-sm text-primary hover:opacity-80 transition-colors"
|
|
76
78
|
>
|
|
77
|
-
|
|
79
|
+
{t('login.forgotPassword')}
|
|
78
80
|
</button>
|
|
79
81
|
</div>
|
|
80
82
|
</form>
|