@lastbrain/module-auth 2.0.13 → 2.0.19
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/dist/auth.build.config.d.ts.map +1 -1
- package/dist/auth.build.config.js +33 -47
- package/dist/components/AccountButton.d.ts.map +1 -1
- package/dist/components/AccountButton.js +9 -5
- package/dist/web/admin/signup-stats.d.ts.map +1 -1
- package/dist/web/admin/signup-stats.js +4 -2
- package/dist/web/admin/user-detail.d.ts.map +1 -1
- package/dist/web/admin/user-detail.js +42 -17
- package/dist/web/admin/users-by-signup-source.d.ts.map +1 -1
- package/dist/web/admin/users-by-signup-source.js +18 -7
- package/dist/web/admin/users.d.ts.map +1 -1
- package/dist/web/admin/users.js +11 -6
- package/dist/web/auth/dashboard.d.ts.map +1 -1
- package/dist/web/auth/dashboard.js +7 -3
- package/dist/web/auth/folder.d.ts.map +1 -1
- package/dist/web/auth/folder.js +5 -3
- package/dist/web/auth/profile.d.ts.map +1 -1
- package/dist/web/auth/profile.js +13 -6
- package/dist/web/auth/reglage.d.ts.map +1 -1
- package/dist/web/auth/reglage.js +11 -6
- package/dist/web/public/SignInPage.d.ts.map +1 -1
- package/dist/web/public/SignInPage.js +14 -56
- package/dist/web/public/SignUpPage.d.ts.map +1 -1
- package/dist/web/public/SignUpPage.js +18 -11
- package/package.json +4 -3
- package/src/auth.build.config.ts +34 -48
- package/src/components/AccountButton.tsx +17 -10
- package/src/i18n/en.json +263 -0
- package/src/i18n/fr.json +261 -0
- package/src/web/admin/signup-stats.tsx +10 -3
- package/src/web/admin/user-detail.tsx +135 -56
- package/src/web/admin/users-by-signup-source.tsx +60 -21
- package/src/web/admin/users.tsx +41 -18
- package/src/web/auth/dashboard.tsx +25 -9
- package/src/web/auth/folder.tsx +11 -3
- package/src/web/auth/profile.tsx +63 -29
- package/src/web/auth/reglage.tsx +43 -19
- package/src/web/public/SignInPage.tsx +32 -70
- package/src/web/public/SignUpPage.tsx +48 -26
- package/supabase/migrations/20251112000000_user_init.sql +35 -19
- package/supabase/migrations/20251112000001_auto_profile_and_admin_view.sql +8 -3
- package/supabase/migrations/20251112000002_sync_avatars.sql +7 -1
- package/supabase/migrations/20251124000001_add_get_admin_user_details.sql +2 -1
package/src/web/admin/users.tsx
CHANGED
|
@@ -19,7 +19,8 @@ import {
|
|
|
19
19
|
Avatar,
|
|
20
20
|
} from "@lastbrain/ui";
|
|
21
21
|
import { Search, RefreshCw, Eye, Users2 } from "lucide-react";
|
|
22
|
-
import {
|
|
22
|
+
import { useModuleTranslation } from "@lastbrain/core";
|
|
23
|
+
import { useLocalizedRouter } from "@lastbrain/core";
|
|
23
24
|
|
|
24
25
|
interface User {
|
|
25
26
|
id: string;
|
|
@@ -44,7 +45,8 @@ interface PaginationData {
|
|
|
44
45
|
}
|
|
45
46
|
|
|
46
47
|
export function AdminUsersPage() {
|
|
47
|
-
const router =
|
|
48
|
+
const router = useLocalizedRouter();
|
|
49
|
+
const t = useModuleTranslation("auth");
|
|
48
50
|
const searchInputId = useId();
|
|
49
51
|
|
|
50
52
|
const [users, setUsers] = useState<User[]>([]);
|
|
@@ -144,7 +146,9 @@ export function AdminUsersPage() {
|
|
|
144
146
|
<div className="pt-12 pb-12 max-w-7xl mx-auto px-4">
|
|
145
147
|
<div className="flex items-center gap-2 mb-8">
|
|
146
148
|
<Users2 className="w-8 h-8" />
|
|
147
|
-
<h1 className="text-3xl font-bold">
|
|
149
|
+
<h1 className="text-3xl font-bold">
|
|
150
|
+
{t("users.title") || "User Management"}
|
|
151
|
+
</h1>
|
|
148
152
|
</div>
|
|
149
153
|
|
|
150
154
|
<Card>
|
|
@@ -153,7 +157,9 @@ export function AdminUsersPage() {
|
|
|
153
157
|
<div className="flex gap-2 flex-1">
|
|
154
158
|
<Input
|
|
155
159
|
id={searchInputId}
|
|
156
|
-
placeholder=
|
|
160
|
+
placeholder={
|
|
161
|
+
t("users.search_placeholder") || "Search by email or name..."
|
|
162
|
+
}
|
|
157
163
|
value={searchQuery}
|
|
158
164
|
onChange={(e) => setSearchQuery(e.target.value)}
|
|
159
165
|
onKeyPress={(e) => {
|
|
@@ -169,7 +175,7 @@ export function AdminUsersPage() {
|
|
|
169
175
|
onPress={handleSearch}
|
|
170
176
|
isDisabled={isLoading}
|
|
171
177
|
>
|
|
172
|
-
Search
|
|
178
|
+
{t("users.search_button") || "Search"}
|
|
173
179
|
</Button>
|
|
174
180
|
</div>
|
|
175
181
|
<Button
|
|
@@ -178,29 +184,43 @@ export function AdminUsersPage() {
|
|
|
178
184
|
isDisabled={isLoading}
|
|
179
185
|
startContent={<RefreshCw className="w-4 h-4" />}
|
|
180
186
|
>
|
|
181
|
-
Refresh
|
|
187
|
+
{t("users.refresh_button") || "Refresh"}
|
|
182
188
|
</Button>
|
|
183
189
|
</div>
|
|
184
190
|
</CardHeader>
|
|
185
191
|
<CardBody>
|
|
186
192
|
{isLoading ? (
|
|
187
193
|
<div className="flex justify-center items-center py-12">
|
|
188
|
-
<Spinner
|
|
194
|
+
<Spinner
|
|
195
|
+
size="lg"
|
|
196
|
+
label={t("users.loading_users") || "Loading users..."}
|
|
197
|
+
/>
|
|
189
198
|
</div>
|
|
190
199
|
) : users.length === 0 ? (
|
|
191
200
|
<div className="text-center py-12 text-default-500">
|
|
192
|
-
No users found
|
|
201
|
+
{t("users.no_users_found") || "No users found"}
|
|
193
202
|
</div>
|
|
194
203
|
) : (
|
|
195
204
|
<>
|
|
196
|
-
<Table
|
|
205
|
+
<Table
|
|
206
|
+
isStriped
|
|
207
|
+
aria-label={t("users.table_aria_label") || "Users table"}
|
|
208
|
+
>
|
|
197
209
|
<TableHeader>
|
|
198
|
-
<TableColumn>USER</TableColumn>
|
|
199
|
-
<TableColumn>
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
<TableColumn>
|
|
203
|
-
<TableColumn>
|
|
210
|
+
<TableColumn>{t("users.column_user") || "USER"}</TableColumn>
|
|
211
|
+
<TableColumn>
|
|
212
|
+
{t("users.column_email") || "EMAIL"}
|
|
213
|
+
</TableColumn>
|
|
214
|
+
<TableColumn>{t("users.column_role") || "ROLE"}</TableColumn>
|
|
215
|
+
<TableColumn>
|
|
216
|
+
{t("users.column_last_sign_in") || "LAST SIGN IN"}
|
|
217
|
+
</TableColumn>
|
|
218
|
+
<TableColumn>
|
|
219
|
+
{t("users.column_created") || "CREATED"}
|
|
220
|
+
</TableColumn>
|
|
221
|
+
<TableColumn>
|
|
222
|
+
{t("users.column_actions") || "ACTIONS"}
|
|
223
|
+
</TableColumn>
|
|
204
224
|
</TableHeader>
|
|
205
225
|
<TableBody>
|
|
206
226
|
{users.map((user) => {
|
|
@@ -241,7 +261,7 @@ export function AdminUsersPage() {
|
|
|
241
261
|
<span className="text-small">
|
|
242
262
|
{user.last_sign_in_at
|
|
243
263
|
? formatDate(user.last_sign_in_at)
|
|
244
|
-
: "Jamais"}
|
|
264
|
+
: t("users.never") || "Jamais"}
|
|
245
265
|
</span>
|
|
246
266
|
</TableCell>
|
|
247
267
|
<TableCell>
|
|
@@ -257,7 +277,7 @@ export function AdminUsersPage() {
|
|
|
257
277
|
onPress={() => handleViewUser(user.id)}
|
|
258
278
|
startContent={<Eye size={14} />}
|
|
259
279
|
>
|
|
260
|
-
Voir
|
|
280
|
+
{t("users.view_button") || "Voir"}
|
|
261
281
|
</Button>
|
|
262
282
|
</TableCell>
|
|
263
283
|
</TableRow>
|
|
@@ -278,7 +298,10 @@ export function AdminUsersPage() {
|
|
|
278
298
|
)}
|
|
279
299
|
|
|
280
300
|
<div className="mt-4 text-small text-default-500 text-center">
|
|
281
|
-
|
|
301
|
+
{t("users.showing_results")
|
|
302
|
+
?.replace("{{count}}", users.length.toString())
|
|
303
|
+
.replace("{{total}}", pagination.total.toString()) ||
|
|
304
|
+
`Showing ${users.length} of ${pagination.total} users`}
|
|
282
305
|
</div>
|
|
283
306
|
</>
|
|
284
307
|
)}
|
|
@@ -12,6 +12,7 @@ import {
|
|
|
12
12
|
} from "@lastbrain/ui";
|
|
13
13
|
|
|
14
14
|
import { User, Mail, Calendar, Shield } from "lucide-react";
|
|
15
|
+
import { useModuleTranslation } from "@lastbrain/core";
|
|
15
16
|
|
|
16
17
|
interface UserData {
|
|
17
18
|
id: string;
|
|
@@ -28,6 +29,7 @@ interface UserData {
|
|
|
28
29
|
}
|
|
29
30
|
|
|
30
31
|
export function DashboardPage() {
|
|
32
|
+
const t = useModuleTranslation("auth");
|
|
31
33
|
const [userData, setUserData] = useState<UserData | null>(null);
|
|
32
34
|
const [isLoading, setIsLoading] = useState(true);
|
|
33
35
|
const [error, setError] = useState<string | null>(null);
|
|
@@ -59,7 +61,9 @@ export function DashboardPage() {
|
|
|
59
61
|
<div className="flex justify-center items-center min-h-[400px]">
|
|
60
62
|
<Spinner color="primary" size="lg">
|
|
61
63
|
{" "}
|
|
62
|
-
<span className="text-xs text-default-700">
|
|
64
|
+
<span className="text-xs text-default-700">
|
|
65
|
+
{t("dashboard.loading") || "Loading dashboard..."}
|
|
66
|
+
</span>
|
|
63
67
|
</Spinner>
|
|
64
68
|
</div>
|
|
65
69
|
);
|
|
@@ -70,7 +74,9 @@ export function DashboardPage() {
|
|
|
70
74
|
<div className="pt-12">
|
|
71
75
|
<Card className="max-w-2xl mx-auto">
|
|
72
76
|
<CardBody>
|
|
73
|
-
<p className="text-danger">
|
|
77
|
+
<p className="text-danger">
|
|
78
|
+
{t("dashboard.error") || "Error"}: {error}
|
|
79
|
+
</p>
|
|
74
80
|
</CardBody>
|
|
75
81
|
</Card>
|
|
76
82
|
</div>
|
|
@@ -88,7 +94,9 @@ export function DashboardPage() {
|
|
|
88
94
|
|
|
89
95
|
return (
|
|
90
96
|
<div className="pt-12 pb-12 max-w-6xl mx-auto px-4">
|
|
91
|
-
<h1 className="text-3xl font-bold mb-8">
|
|
97
|
+
<h1 className="text-3xl font-bold mb-8">
|
|
98
|
+
{t("dashboard.title") || "Dashboard"}
|
|
99
|
+
</h1>
|
|
92
100
|
|
|
93
101
|
<div className="grid gap-6 md:grid-cols-2">
|
|
94
102
|
{/* Profile Summary Card */}
|
|
@@ -115,7 +123,7 @@ export function DashboardPage() {
|
|
|
115
123
|
<div className="flex items-center gap-2">
|
|
116
124
|
<Calendar className="w-4 h-4 text-default-400" />
|
|
117
125
|
<span className="text-small">
|
|
118
|
-
Member since{" "}
|
|
126
|
+
{t("dashboard.member_since") || "Member since"}{" "}
|
|
119
127
|
{new Date(userData.created_at).toLocaleDateString()}
|
|
120
128
|
</span>
|
|
121
129
|
</div>
|
|
@@ -132,25 +140,33 @@ export function DashboardPage() {
|
|
|
132
140
|
{/* Account Status Card */}
|
|
133
141
|
<Card>
|
|
134
142
|
<CardHeader>
|
|
135
|
-
<h3 className="text-lg font-semibold">
|
|
143
|
+
<h3 className="text-lg font-semibold">
|
|
144
|
+
{t("dashboard.account_status") || "Account Status"}
|
|
145
|
+
</h3>
|
|
136
146
|
</CardHeader>
|
|
137
147
|
<Divider />
|
|
138
148
|
<CardBody>
|
|
139
149
|
<div className="space-y-4">
|
|
140
150
|
<div className="flex justify-between items-center">
|
|
141
|
-
<span className="text-small">
|
|
151
|
+
<span className="text-small">
|
|
152
|
+
{t("dashboard.status") || "Status"}
|
|
153
|
+
</span>
|
|
142
154
|
<Chip color="success" size="sm" variant="flat">
|
|
143
|
-
Active
|
|
155
|
+
{t("dashboard.active") || "Active"}
|
|
144
156
|
</Chip>
|
|
145
157
|
</div>
|
|
146
158
|
<div className="flex justify-between items-center">
|
|
147
|
-
<span className="text-small">
|
|
159
|
+
<span className="text-small">
|
|
160
|
+
{t("dashboard.profile") || "Profile"}
|
|
161
|
+
</span>
|
|
148
162
|
<Chip
|
|
149
163
|
color={userData.profile ? "success" : "warning"}
|
|
150
164
|
size="sm"
|
|
151
165
|
variant="flat"
|
|
152
166
|
>
|
|
153
|
-
{userData.profile
|
|
167
|
+
{userData.profile
|
|
168
|
+
? t("dashboard.complete") || "Complete"
|
|
169
|
+
: t("dashboard.incomplete") || "Incomplete"}
|
|
154
170
|
</Chip>
|
|
155
171
|
</div>
|
|
156
172
|
</div>
|
package/src/web/auth/folder.tsx
CHANGED
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
import { useEffect, useState } from "react";
|
|
4
4
|
import { Card, CardBody, Spinner } from "@lastbrain/ui";
|
|
5
5
|
import { FileManager } from "@lastbrain/ui";
|
|
6
|
+
import { useModuleTranslation } from "@lastbrain/core";
|
|
6
7
|
|
|
7
8
|
interface UserData {
|
|
8
9
|
id: string;
|
|
@@ -19,6 +20,7 @@ interface UserData {
|
|
|
19
20
|
}
|
|
20
21
|
|
|
21
22
|
export function FolderPage() {
|
|
23
|
+
const t = useModuleTranslation("auth");
|
|
22
24
|
const [userData, setUserData] = useState<UserData | null>(null);
|
|
23
25
|
const [isLoading, setIsLoading] = useState(true);
|
|
24
26
|
const [error, setError] = useState<string | null>(null);
|
|
@@ -50,7 +52,9 @@ export function FolderPage() {
|
|
|
50
52
|
<div className="flex justify-center items-center min-h-[400px]">
|
|
51
53
|
<Spinner color="primary" size="lg">
|
|
52
54
|
{" "}
|
|
53
|
-
<span className="text-xs text-default-700">
|
|
55
|
+
<span className="text-xs text-default-700">
|
|
56
|
+
{t("folder.loading") || "Loading folder..."}
|
|
57
|
+
</span>
|
|
54
58
|
</Spinner>
|
|
55
59
|
</div>
|
|
56
60
|
);
|
|
@@ -61,7 +65,9 @@ export function FolderPage() {
|
|
|
61
65
|
<div className="pt-12">
|
|
62
66
|
<Card className="max-w-2xl mx-auto">
|
|
63
67
|
<CardBody>
|
|
64
|
-
<p className="text-danger">
|
|
68
|
+
<p className="text-danger">
|
|
69
|
+
{t("folder.error") || "Error"}: {error}
|
|
70
|
+
</p>
|
|
65
71
|
</CardBody>
|
|
66
72
|
</Card>
|
|
67
73
|
</div>
|
|
@@ -74,7 +80,9 @@ export function FolderPage() {
|
|
|
74
80
|
|
|
75
81
|
return (
|
|
76
82
|
<div className="pt-4 pb-12 max-w-8xl mx-auto px-4">
|
|
77
|
-
<h1 className="text-3xl font-bold mb-8">
|
|
83
|
+
<h1 className="text-3xl font-bold mb-8">
|
|
84
|
+
{t("folder.title") || "Dossier"}
|
|
85
|
+
</h1>
|
|
78
86
|
|
|
79
87
|
<FileManager
|
|
80
88
|
bucket="app"
|
package/src/web/auth/profile.tsx
CHANGED
|
@@ -14,6 +14,7 @@ import {
|
|
|
14
14
|
AvatarUploader,
|
|
15
15
|
} from "@lastbrain/ui";
|
|
16
16
|
import { Save, User } from "lucide-react";
|
|
17
|
+
import { useModuleTranslation } from "@lastbrain/core";
|
|
17
18
|
import { uploadFile, deleteFilesWithPrefix } from "../../api/storage";
|
|
18
19
|
import { supabaseBrowserClient } from "@lastbrain/core";
|
|
19
20
|
|
|
@@ -40,6 +41,7 @@ interface UserMetadata {
|
|
|
40
41
|
}
|
|
41
42
|
|
|
42
43
|
export function ProfilePage() {
|
|
44
|
+
const t = useModuleTranslation("auth");
|
|
43
45
|
const [profile, setProfile] = useState<ProfileData>({});
|
|
44
46
|
const [isLoading, setIsLoading] = useState(true);
|
|
45
47
|
const [isSaving, setIsSaving] = useState(false);
|
|
@@ -106,16 +108,16 @@ export function ProfilePage() {
|
|
|
106
108
|
}
|
|
107
109
|
|
|
108
110
|
addToast({
|
|
109
|
-
title: "Success",
|
|
110
|
-
description: "Profile updated successfully",
|
|
111
|
+
title: t("profile.success") || "Success",
|
|
112
|
+
description: t("profile.updated") || "Profile updated successfully",
|
|
111
113
|
color: "success",
|
|
112
114
|
});
|
|
113
115
|
} catch (err) {
|
|
114
116
|
console.error("Error updating profile:", err);
|
|
115
117
|
setError(err instanceof Error ? err.message : "An error occurred");
|
|
116
118
|
addToast({
|
|
117
|
-
title: "Error",
|
|
118
|
-
description: "Failed to update profile",
|
|
119
|
+
title: t("profile.error") || "Error",
|
|
120
|
+
description: t("profile.update_failed") || "Failed to update profile",
|
|
119
121
|
color: "danger",
|
|
120
122
|
});
|
|
121
123
|
} finally {
|
|
@@ -245,7 +247,10 @@ export function ProfilePage() {
|
|
|
245
247
|
if (isLoading) {
|
|
246
248
|
return (
|
|
247
249
|
<div className="flex justify-center items-center min-h-[400px]">
|
|
248
|
-
<Spinner
|
|
250
|
+
<Spinner
|
|
251
|
+
size="lg"
|
|
252
|
+
label={t("profile.loading") || "Loading profile..."}
|
|
253
|
+
/>
|
|
249
254
|
</div>
|
|
250
255
|
);
|
|
251
256
|
}
|
|
@@ -293,7 +298,9 @@ export function ProfilePage() {
|
|
|
293
298
|
</div>
|
|
294
299
|
<div className="flex items-center gap-2 mb-4">
|
|
295
300
|
<User className="w-8 h-8" />
|
|
296
|
-
<h1 className="text-3xl font-bold">
|
|
301
|
+
<h1 className="text-3xl font-bold">
|
|
302
|
+
{t("profile.edit") || "Edit Profile"}
|
|
303
|
+
</h1>
|
|
297
304
|
</div>
|
|
298
305
|
|
|
299
306
|
{/* </CardBody>
|
|
@@ -302,34 +309,45 @@ export function ProfilePage() {
|
|
|
302
309
|
{/* Personal Information */}
|
|
303
310
|
<Card>
|
|
304
311
|
<CardHeader>
|
|
305
|
-
<h3 className="text-lg font-semibold">
|
|
312
|
+
<h3 className="text-lg font-semibold">
|
|
313
|
+
{t("profile.personal_info") || "Personal Information"}
|
|
314
|
+
</h3>
|
|
306
315
|
</CardHeader>
|
|
307
316
|
<Divider />
|
|
308
317
|
<CardBody>
|
|
309
318
|
<div className="grid gap-4 md:grid-cols-2">
|
|
310
319
|
<Input
|
|
311
|
-
label="First Name"
|
|
312
|
-
placeholder=
|
|
320
|
+
label={t("profile.first_name") || "First Name"}
|
|
321
|
+
placeholder={
|
|
322
|
+
t("profile.first_name_placeholder") ||
|
|
323
|
+
"Enter your first name"
|
|
324
|
+
}
|
|
313
325
|
value={profile.first_name || ""}
|
|
314
326
|
onChange={(e) => handleChange("first_name", e.target.value)}
|
|
315
327
|
/>
|
|
316
328
|
<Input
|
|
317
|
-
label="Last Name"
|
|
318
|
-
placeholder=
|
|
329
|
+
label={t("profile.last_name") || "Last Name"}
|
|
330
|
+
placeholder={
|
|
331
|
+
t("profile.last_name_placeholder") || "Enter your last name"
|
|
332
|
+
}
|
|
319
333
|
value={profile.last_name || ""}
|
|
320
334
|
onChange={(e) => handleChange("last_name", e.target.value)}
|
|
321
335
|
/>
|
|
322
336
|
<Input
|
|
323
|
-
label="Phone"
|
|
324
|
-
placeholder=
|
|
337
|
+
label={t("profile.phone") || "Phone"}
|
|
338
|
+
placeholder={
|
|
339
|
+
t("profile.phone_placeholder") || "Enter your phone number"
|
|
340
|
+
}
|
|
325
341
|
type="tel"
|
|
326
342
|
value={profile.phone || ""}
|
|
327
343
|
onChange={(e) => handleChange("phone", e.target.value)}
|
|
328
344
|
className="md:col-span-2"
|
|
329
345
|
/>
|
|
330
346
|
<Textarea
|
|
331
|
-
label="Bio"
|
|
332
|
-
placeholder=
|
|
347
|
+
label={t("profile.bio") || "Bio"}
|
|
348
|
+
placeholder={
|
|
349
|
+
t("profile.bio_placeholder") || "Tell us about yourself"
|
|
350
|
+
}
|
|
333
351
|
value={profile.bio || ""}
|
|
334
352
|
onChange={(e) => handleChange("bio", e.target.value)}
|
|
335
353
|
minRows={3}
|
|
@@ -343,28 +361,35 @@ export function ProfilePage() {
|
|
|
343
361
|
<Card>
|
|
344
362
|
<CardHeader>
|
|
345
363
|
<h3 className="text-lg font-semibold">
|
|
346
|
-
Professional Information
|
|
364
|
+
{t("profile.professional_info") || "Professional Information"}
|
|
347
365
|
</h3>
|
|
348
366
|
</CardHeader>
|
|
349
367
|
<Divider />
|
|
350
368
|
<CardBody>
|
|
351
369
|
<div className="grid gap-4 md:grid-cols-2">
|
|
352
370
|
<Input
|
|
353
|
-
label="Company"
|
|
354
|
-
placeholder=
|
|
371
|
+
label={t("profile.company") || "Company"}
|
|
372
|
+
placeholder={
|
|
373
|
+
t("profile.company_placeholder") ||
|
|
374
|
+
"Enter your company name"
|
|
375
|
+
}
|
|
355
376
|
value={profile.company || ""}
|
|
356
377
|
onChange={(e) => handleChange("company", e.target.value)}
|
|
357
378
|
/>
|
|
358
379
|
<Input
|
|
359
|
-
label="Website"
|
|
360
|
-
placeholder=
|
|
380
|
+
label={t("profile.website") || "Website"}
|
|
381
|
+
placeholder={
|
|
382
|
+
t("profile.website_placeholder") || "https://example.com"
|
|
383
|
+
}
|
|
361
384
|
type="url"
|
|
362
385
|
value={profile.website || ""}
|
|
363
386
|
onChange={(e) => handleChange("website", e.target.value)}
|
|
364
387
|
/>
|
|
365
388
|
<Input
|
|
366
|
-
label="Location"
|
|
367
|
-
placeholder=
|
|
389
|
+
label={t("profile.location") || "Location"}
|
|
390
|
+
placeholder={
|
|
391
|
+
t("profile.location_placeholder") || "City, Country"
|
|
392
|
+
}
|
|
368
393
|
value={profile.location || ""}
|
|
369
394
|
onChange={(e) => handleChange("location", e.target.value)}
|
|
370
395
|
className="md:col-span-2"
|
|
@@ -376,20 +401,27 @@ export function ProfilePage() {
|
|
|
376
401
|
{/* Preferences */}
|
|
377
402
|
<Card>
|
|
378
403
|
<CardHeader>
|
|
379
|
-
<h3 className="text-lg font-semibold">
|
|
404
|
+
<h3 className="text-lg font-semibold">
|
|
405
|
+
{t("profile.preferences") || "Preferences"}
|
|
406
|
+
</h3>
|
|
380
407
|
</CardHeader>
|
|
381
408
|
<Divider />
|
|
382
409
|
<CardBody>
|
|
383
410
|
<div className="grid gap-4 md:grid-cols-2">
|
|
384
411
|
<Input
|
|
385
|
-
label="Language"
|
|
386
|
-
placeholder=
|
|
412
|
+
label={t("profile.language") || "Language"}
|
|
413
|
+
placeholder={
|
|
414
|
+
t("profile.language_placeholder") || "en, fr, es..."
|
|
415
|
+
}
|
|
387
416
|
value={profile.language || ""}
|
|
388
417
|
onChange={(e) => handleChange("language", e.target.value)}
|
|
389
418
|
/>
|
|
390
419
|
<Input
|
|
391
|
-
label="Timezone"
|
|
392
|
-
placeholder=
|
|
420
|
+
label={t("profile.timezone") || "Timezone"}
|
|
421
|
+
placeholder={
|
|
422
|
+
t("profile.timezone_placeholder") ||
|
|
423
|
+
"Europe/Paris, America/New_York..."
|
|
424
|
+
}
|
|
393
425
|
value={profile.timezone || ""}
|
|
394
426
|
onChange={(e) => handleChange("timezone", e.target.value)}
|
|
395
427
|
/>
|
|
@@ -405,7 +437,7 @@ export function ProfilePage() {
|
|
|
405
437
|
onPress={() => fetchProfile()}
|
|
406
438
|
isDisabled={isSaving}
|
|
407
439
|
>
|
|
408
|
-
Cancel
|
|
440
|
+
{t("profile.cancel_button") || "Cancel"}
|
|
409
441
|
</Button>
|
|
410
442
|
<Button
|
|
411
443
|
type="submit"
|
|
@@ -413,7 +445,9 @@ export function ProfilePage() {
|
|
|
413
445
|
isLoading={isSaving}
|
|
414
446
|
startContent={!isSaving && <Save className="w-4 h-4" />}
|
|
415
447
|
>
|
|
416
|
-
{isSaving
|
|
448
|
+
{isSaving
|
|
449
|
+
? t("profile.saving") || "Saving..."
|
|
450
|
+
: t("profile.save_button") || "Save Changes"}
|
|
417
451
|
</Button>
|
|
418
452
|
</div>
|
|
419
453
|
</div>
|
package/src/web/auth/reglage.tsx
CHANGED
|
@@ -14,6 +14,7 @@ import {
|
|
|
14
14
|
addToast,
|
|
15
15
|
} from "@lastbrain/ui";
|
|
16
16
|
import { Settings, Save } from "lucide-react";
|
|
17
|
+
import { useModuleTranslation } from "@lastbrain/core";
|
|
17
18
|
|
|
18
19
|
interface UserPreferences {
|
|
19
20
|
email_notifications?: boolean;
|
|
@@ -31,6 +32,7 @@ interface ProfileData {
|
|
|
31
32
|
}
|
|
32
33
|
|
|
33
34
|
export function ReglagePage() {
|
|
35
|
+
const t = useModuleTranslation("auth");
|
|
34
36
|
const [preferences, setPreferences] = useState<UserPreferences>({
|
|
35
37
|
email_notifications: true,
|
|
36
38
|
push_notifications: false,
|
|
@@ -103,15 +105,15 @@ export function ReglagePage() {
|
|
|
103
105
|
}
|
|
104
106
|
|
|
105
107
|
addToast({
|
|
106
|
-
title: "Success",
|
|
107
|
-
description: "Settings updated successfully",
|
|
108
|
+
title: t("settings.success") || "Success",
|
|
109
|
+
description: t("settings.updated") || "Settings updated successfully",
|
|
108
110
|
color: "success",
|
|
109
111
|
});
|
|
110
112
|
} catch (err) {
|
|
111
113
|
console.error("Error updating settings:", err);
|
|
112
114
|
addToast({
|
|
113
|
-
title: "Error",
|
|
114
|
-
description: "Failed to update settings",
|
|
115
|
+
title: t("settings.error") || "Error",
|
|
116
|
+
description: t("settings.update_failed") || "Failed to update settings",
|
|
115
117
|
color: "danger",
|
|
116
118
|
});
|
|
117
119
|
} finally {
|
|
@@ -130,7 +132,10 @@ export function ReglagePage() {
|
|
|
130
132
|
if (isLoading) {
|
|
131
133
|
return (
|
|
132
134
|
<div className="flex justify-center items-center min-h-[400px]">
|
|
133
|
-
<Spinner
|
|
135
|
+
<Spinner
|
|
136
|
+
size="lg"
|
|
137
|
+
label={t("settings.loading") || "Loading settings..."}
|
|
138
|
+
/>
|
|
134
139
|
</div>
|
|
135
140
|
);
|
|
136
141
|
}
|
|
@@ -139,23 +144,30 @@ export function ReglagePage() {
|
|
|
139
144
|
<div className="pt-12 pb-12 max-w-4xl mx-auto px-4">
|
|
140
145
|
<div className="flex items-center gap-2 mb-8">
|
|
141
146
|
<Settings className="w-8 h-8" />
|
|
142
|
-
<h1 className="text-3xl font-bold">
|
|
147
|
+
<h1 className="text-3xl font-bold">
|
|
148
|
+
{t("settings.account") || "Account Settings"}
|
|
149
|
+
</h1>
|
|
143
150
|
</div>
|
|
144
151
|
|
|
145
152
|
<div className="space-y-6">
|
|
146
153
|
{/* Notifications */}
|
|
147
154
|
<Card>
|
|
148
155
|
<CardHeader>
|
|
149
|
-
<h3 className="text-lg font-semibold">
|
|
156
|
+
<h3 className="text-lg font-semibold">
|
|
157
|
+
{t("settings.notifications") || "Notifications"}
|
|
158
|
+
</h3>
|
|
150
159
|
</CardHeader>
|
|
151
160
|
<Divider />
|
|
152
161
|
<CardBody>
|
|
153
162
|
<div className="space-y-4">
|
|
154
163
|
<div className="flex justify-between items-center">
|
|
155
164
|
<div>
|
|
156
|
-
<p className="font-medium">
|
|
165
|
+
<p className="font-medium">
|
|
166
|
+
{t("settings.email_notifications") || "Email Notifications"}
|
|
167
|
+
</p>
|
|
157
168
|
<p className="text-small text-default-500">
|
|
158
|
-
|
|
169
|
+
{t("settings.email_notifications_desc") ||
|
|
170
|
+
"Receive email notifications for important updates"}
|
|
159
171
|
</p>
|
|
160
172
|
</div>
|
|
161
173
|
<Switch
|
|
@@ -168,9 +180,12 @@ export function ReglagePage() {
|
|
|
168
180
|
<Divider />
|
|
169
181
|
<div className="flex justify-between items-center">
|
|
170
182
|
<div>
|
|
171
|
-
<p className="font-medium">
|
|
183
|
+
<p className="font-medium">
|
|
184
|
+
{t("settings.push_notifications") || "Push Notifications"}
|
|
185
|
+
</p>
|
|
172
186
|
<p className="text-small text-default-500">
|
|
173
|
-
|
|
187
|
+
{t("settings.push_notifications_desc") ||
|
|
188
|
+
"Receive push notifications in your browser"}
|
|
174
189
|
</p>
|
|
175
190
|
</div>
|
|
176
191
|
<Switch
|
|
@@ -183,9 +198,12 @@ export function ReglagePage() {
|
|
|
183
198
|
<Divider />
|
|
184
199
|
<div className="flex justify-between items-center">
|
|
185
200
|
<div>
|
|
186
|
-
<p className="font-medium">
|
|
201
|
+
<p className="font-medium">
|
|
202
|
+
{t("settings.marketing_emails") || "Marketing Emails"}
|
|
203
|
+
</p>
|
|
187
204
|
<p className="text-small text-default-500">
|
|
188
|
-
|
|
205
|
+
{t("settings.marketing_emails_desc") ||
|
|
206
|
+
"Receive emails about new features and updates"}
|
|
189
207
|
</p>
|
|
190
208
|
</div>
|
|
191
209
|
<Switch
|
|
@@ -202,19 +220,25 @@ export function ReglagePage() {
|
|
|
202
220
|
{/* Appearance */}
|
|
203
221
|
<Card>
|
|
204
222
|
<CardHeader>
|
|
205
|
-
<h3 className="text-lg font-semibold">
|
|
223
|
+
<h3 className="text-lg font-semibold">
|
|
224
|
+
{t("settings.appearance") || "Appearance"}
|
|
225
|
+
</h3>
|
|
206
226
|
</CardHeader>
|
|
207
227
|
<Divider />
|
|
208
228
|
<CardBody>
|
|
209
229
|
<Select
|
|
210
|
-
label="Theme"
|
|
211
|
-
placeholder="Select a theme"
|
|
230
|
+
label={t("settings.theme") || "Theme"}
|
|
231
|
+
placeholder={t("settings.select_theme") || "Select a theme"}
|
|
212
232
|
selectedKeys={preferences.theme ? [preferences.theme] : []}
|
|
213
233
|
onChange={(e) => handleSelect("theme", e.target.value)}
|
|
214
234
|
>
|
|
215
|
-
<SelectItem key="light">
|
|
216
|
-
|
|
217
|
-
|
|
235
|
+
<SelectItem key="light">
|
|
236
|
+
{t("settings.light") || "Light"}
|
|
237
|
+
</SelectItem>
|
|
238
|
+
<SelectItem key="dark">{t("settings.dark") || "Dark"}</SelectItem>
|
|
239
|
+
<SelectItem key="system">
|
|
240
|
+
{t("settings.system") || "System"}
|
|
241
|
+
</SelectItem>
|
|
218
242
|
</Select>
|
|
219
243
|
</CardBody>
|
|
220
244
|
</Card>
|