@lastbrain/module-auth 2.0.19 → 2.0.31
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +55 -7
- package/dist/api/admin/signup-stats.d.ts.map +1 -1
- package/dist/api/admin/signup-stats.js +2 -1
- package/dist/api/admin/storage/usage.d.ts +18 -0
- package/dist/api/admin/storage/usage.d.ts.map +1 -0
- package/dist/api/admin/storage/usage.js +100 -0
- package/dist/api/admin/users/[id]/notifications.d.ts.map +1 -1
- package/dist/api/admin/users/[id]/notifications.js +3 -2
- package/dist/api/admin/users/[id].d.ts.map +1 -1
- package/dist/api/admin/users/[id].js +3 -2
- package/dist/api/admin/users/reactivate/[id].d.ts +16 -0
- package/dist/api/admin/users/reactivate/[id].d.ts.map +1 -0
- package/dist/api/admin/users/reactivate/[id].js +59 -0
- package/dist/api/admin/users/suspend/[id].d.ts +16 -0
- package/dist/api/admin/users/suspend/[id].d.ts.map +1 -0
- package/dist/api/admin/users/suspend/[id].js +59 -0
- package/dist/api/admin/users-by-source.d.ts.map +1 -1
- package/dist/api/admin/users-by-source.js +2 -1
- package/dist/api/admin/users.d.ts.map +1 -1
- package/dist/api/admin/users.js +53 -2
- package/dist/api/auth/account/email-change.d.ts +7 -0
- package/dist/api/auth/account/email-change.d.ts.map +1 -0
- package/dist/api/auth/account/email-change.js +39 -0
- package/dist/api/auth/account/reset-password.d.ts +7 -0
- package/dist/api/auth/account/reset-password.d.ts.map +1 -0
- package/dist/api/auth/account/reset-password.js +36 -0
- package/dist/api/auth/check-username.d.ts +9 -0
- package/dist/api/auth/check-username.d.ts.map +1 -0
- package/dist/api/auth/check-username.js +35 -0
- package/dist/api/auth/establish-session.d.ts +2 -0
- package/dist/api/auth/establish-session.d.ts.map +1 -0
- package/dist/api/auth/establish-session.js +23 -0
- package/dist/api/auth/me.d.ts +4 -4
- package/dist/api/auth/me.d.ts.map +1 -1
- package/dist/api/auth/me.js +28 -6
- package/dist/api/auth/profile.d.ts.map +1 -1
- package/dist/api/auth/profile.js +6 -3
- package/dist/api/auth/storage/recalculate.d.ts +15 -0
- package/dist/api/auth/storage/recalculate.d.ts.map +1 -0
- package/dist/api/auth/storage/recalculate.js +68 -0
- package/dist/api/auth/storage/usage.d.ts +10 -0
- package/dist/api/auth/storage/usage.d.ts.map +1 -0
- package/dist/api/auth/storage/usage.js +86 -0
- package/dist/api/public/add-welcome-bonus.d.ts +16 -0
- package/dist/api/public/add-welcome-bonus.d.ts.map +1 -0
- package/dist/api/public/add-welcome-bonus.js +177 -0
- package/dist/api/public/callback.d.ts +3 -0
- package/dist/api/public/callback.d.ts.map +1 -0
- package/dist/api/public/callback.js +197 -0
- package/dist/api/public/reset-password.d.ts +3 -0
- package/dist/api/public/reset-password.d.ts.map +1 -0
- package/dist/api/public/reset-password.js +43 -0
- package/dist/api/public/set-session.d.ts +7 -0
- package/dist/api/public/set-session.d.ts.map +1 -0
- package/dist/api/public/set-session.js +55 -0
- package/dist/api/public/signin.d.ts.map +1 -1
- package/dist/api/public/signin.js +31 -0
- package/dist/api/public/signup.d.ts.map +1 -1
- package/dist/api/public/signup.js +38 -27
- package/dist/api/public/webhook/storage-addon.d.ts +9 -0
- package/dist/api/public/webhook/storage-addon.d.ts.map +1 -0
- package/dist/api/public/webhook/storage-addon.js +155 -0
- package/dist/api/storage.js +2 -2
- package/dist/auth.build.config.d.ts.map +1 -1
- package/dist/auth.build.config.js +134 -14
- package/dist/components/AccountButton.d.ts.map +1 -1
- package/dist/components/AccountButton.js +54 -28
- package/dist/components/Doc.d.ts.map +1 -1
- package/dist/components/Doc.js +1 -1
- package/dist/components/HasProfil.d.ts +4 -0
- package/dist/components/HasProfil.d.ts.map +1 -0
- package/dist/components/HasProfil.js +39 -0
- package/dist/{web → components}/auth/dashboard.d.ts +1 -1
- package/dist/components/auth/dashboard.d.ts.map +1 -0
- package/dist/components/auth/dashboard.js +74 -0
- package/dist/index.d.ts +3 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -1
- package/dist/lib/app-branding-data.d.ts +22 -0
- package/dist/lib/app-branding-data.d.ts.map +1 -0
- package/dist/lib/app-branding-data.js +49 -0
- package/dist/lib/auth-email-service.d.ts +57 -0
- package/dist/lib/auth-email-service.d.ts.map +1 -0
- package/dist/lib/auth-email-service.js +382 -0
- package/dist/lib/auth-email-templates.d.ts +2 -0
- package/dist/lib/auth-email-templates.d.ts.map +1 -0
- package/dist/lib/auth-email-templates.js +1 -0
- package/dist/lib/site-url.d.ts +3 -0
- package/dist/lib/site-url.d.ts.map +1 -0
- package/dist/lib/site-url.js +11 -0
- package/dist/sitemap/manifest.d.ts +9 -0
- package/dist/sitemap/manifest.d.ts.map +1 -0
- package/dist/sitemap/manifest.js +14 -0
- package/dist/web/admin/signup-stats.js +3 -3
- package/dist/web/admin/user-detail.d.ts.map +1 -1
- package/dist/web/admin/user-detail.js +135 -14
- package/dist/web/admin/users-by-signup-source.js +2 -2
- package/dist/web/admin/users.d.ts.map +1 -1
- package/dist/web/admin/users.js +26 -7
- package/dist/web/auth/folder.d.ts.map +1 -1
- package/dist/web/auth/folder.js +4 -3
- package/dist/web/auth/profile.d.ts.map +1 -1
- package/dist/web/auth/profile.js +132 -13
- package/dist/web/auth/reglage.d.ts.map +1 -1
- package/dist/web/auth/reglage.js +15 -8
- package/dist/web/public/ResetPassword.d.ts.map +1 -1
- package/dist/web/public/ResetPassword.js +172 -2
- package/dist/web/public/SignInPage.d.ts.map +1 -1
- package/dist/web/public/SignInPage.js +39 -3
- package/dist/web/public/SignUpPage.d.ts.map +1 -1
- package/dist/web/public/SignUpPage.js +7 -2
- package/dist/web/public/auth-code-error.d.ts +2 -0
- package/dist/web/public/auth-code-error.d.ts.map +1 -0
- package/dist/web/public/auth-code-error.js +14 -0
- package/dist/web/public/confirm.d.ts +2 -0
- package/dist/web/public/confirm.d.ts.map +1 -0
- package/dist/web/public/confirm.js +157 -0
- package/package.json +10 -5
- package/src/api/admin/signup-stats.ts +2 -1
- package/src/api/admin/storage/usage.ts +141 -0
- package/src/api/admin/users/[id]/notifications.ts +3 -2
- package/src/api/admin/users/[id].ts +3 -2
- package/src/api/admin/users/reactivate/[id].ts +88 -0
- package/src/api/admin/users/suspend/[id].ts +85 -0
- package/src/api/admin/users-by-source.ts +2 -1
- package/src/api/admin/users.ts +59 -2
- package/src/api/auth/account/email-change.ts +54 -0
- package/src/api/auth/account/reset-password.ts +47 -0
- package/src/api/auth/check-username.ts +52 -0
- package/src/api/auth/establish-session.ts +32 -0
- package/src/api/auth/me.ts +29 -7
- package/src/api/auth/profile.ts +6 -2
- package/src/api/auth/storage/recalculate.ts +108 -0
- package/src/api/auth/storage/usage.ts +113 -0
- package/src/api/public/add-welcome-bonus.ts +229 -0
- package/src/api/public/callback.ts +307 -0
- package/src/api/public/reset-password.ts +52 -0
- package/src/api/public/set-session.ts +73 -0
- package/src/api/public/signin.ts +36 -0
- package/src/api/public/signup.ts +44 -37
- package/src/api/public/webhook/storage-addon.ts +267 -0
- package/src/api/storage.ts +2 -2
- package/src/auth.build.config.ts +134 -14
- package/src/components/AccountButton.tsx +114 -90
- package/src/components/Doc.tsx +47 -9
- package/src/components/HasProfil.tsx +63 -0
- package/src/{web → components}/auth/dashboard.tsx +64 -20
- package/src/i18n/en.json +78 -8
- package/src/i18n/es.json +330 -0
- package/src/i18n/fr.json +75 -8
- package/src/index.ts +3 -1
- package/src/lib/app-branding-data.ts +90 -0
- package/src/lib/auth-email-service.ts +508 -0
- package/src/lib/auth-email-templates.ts +5 -0
- package/src/lib/site-url.ts +17 -0
- package/src/sitemap/manifest.ts +26 -0
- package/src/web/admin/signup-stats.tsx +3 -3
- package/src/web/admin/user-detail.tsx +314 -15
- package/src/web/admin/users-by-signup-source.tsx +2 -2
- package/src/web/admin/users.tsx +50 -14
- package/src/web/auth/folder.tsx +23 -5
- package/src/web/auth/profile.tsx +227 -13
- package/src/web/auth/reglage.tsx +55 -24
- package/src/web/public/ResetPassword.tsx +301 -1
- package/src/web/public/SignInPage.tsx +43 -3
- package/src/web/public/SignUpPage.tsx +14 -5
- package/src/web/public/auth-code-error.tsx +49 -0
- package/src/web/public/confirm.tsx +195 -0
- package/supabase/migrations/20251112000001_auto_profile_and_admin_view.sql +3 -1
- package/supabase/migrations/20251127100000_rename_body_to_message.sql +8 -3
- package/supabase/migrations/20260120150001_add_user_storage.sql +105 -0
- package/supabase/migrations/20260122131200_add_global_addons_system.sql +305 -0
- package/supabase/migrations/20260123100000_enable_vector_extension.sql +9 -0
- package/supabase/migrations/20260123140000_module_auth_fix_get_user_limits_null.sql +93 -0
- package/supabase/migrations/20260123145000_module_auth_fix_circular_storage_base.sql +89 -0
- package/supabase/migrations/20260129400000_add_username_to_user_profil.sql +33 -0
- package/dist/web/auth/dashboard.d.ts.map +0 -1
- package/dist/web/auth/dashboard.js +0 -48
package/src/web/auth/profile.tsx
CHANGED
|
@@ -13,14 +13,15 @@ import {
|
|
|
13
13
|
addToast,
|
|
14
14
|
AvatarUploader,
|
|
15
15
|
} from "@lastbrain/ui";
|
|
16
|
-
import { Save, User } from "lucide-react";
|
|
17
|
-
import { useModuleTranslation } from "@lastbrain/core";
|
|
16
|
+
import { Check, Save, User, XCircle } from "lucide-react";
|
|
17
|
+
import { logger, useModuleTranslation } from "@lastbrain/core";
|
|
18
18
|
import { uploadFile, deleteFilesWithPrefix } from "../../api/storage";
|
|
19
19
|
import { supabaseBrowserClient } from "@lastbrain/core";
|
|
20
20
|
|
|
21
21
|
interface ProfileData {
|
|
22
22
|
first_name?: string;
|
|
23
23
|
last_name?: string;
|
|
24
|
+
username?: string;
|
|
24
25
|
avatar_url?: string;
|
|
25
26
|
bio?: string;
|
|
26
27
|
phone?: string;
|
|
@@ -46,6 +47,12 @@ export function ProfilePage() {
|
|
|
46
47
|
const [isLoading, setIsLoading] = useState(true);
|
|
47
48
|
const [isSaving, setIsSaving] = useState(false);
|
|
48
49
|
const [_error, setError] = useState<string | null>(null);
|
|
50
|
+
const [newEmail, setNewEmail] = useState("");
|
|
51
|
+
const [securityLoading, setSecurityLoading] = useState(false);
|
|
52
|
+
const [usernameAvailable, setUsernameAvailable] = useState<boolean | null>(
|
|
53
|
+
null
|
|
54
|
+
);
|
|
55
|
+
const [checkingUsername, setCheckingUsername] = useState(false);
|
|
49
56
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
50
57
|
const [currentUser, setCurrentUser] = useState<any>(null);
|
|
51
58
|
|
|
@@ -61,14 +68,16 @@ export function ProfilePage() {
|
|
|
61
68
|
} = await supabaseBrowserClient.auth.getUser();
|
|
62
69
|
setCurrentUser(user);
|
|
63
70
|
} catch (err) {
|
|
64
|
-
|
|
71
|
+
logger.error("Error fetching current user:", err);
|
|
65
72
|
}
|
|
66
73
|
};
|
|
67
74
|
|
|
68
75
|
const fetchProfile = async () => {
|
|
69
76
|
try {
|
|
70
77
|
setIsLoading(true);
|
|
71
|
-
const response = await fetch("/api/auth/profile"
|
|
78
|
+
const response = await fetch("/api/auth/profile", {
|
|
79
|
+
credentials: "include",
|
|
80
|
+
});
|
|
72
81
|
|
|
73
82
|
if (!response.ok) {
|
|
74
83
|
throw new Error("Failed to fetch profile");
|
|
@@ -100,6 +109,7 @@ export function ProfilePage() {
|
|
|
100
109
|
headers: {
|
|
101
110
|
"Content-Type": "application/json",
|
|
102
111
|
},
|
|
112
|
+
credentials: "include",
|
|
103
113
|
body: JSON.stringify(profile),
|
|
104
114
|
});
|
|
105
115
|
|
|
@@ -113,7 +123,7 @@ export function ProfilePage() {
|
|
|
113
123
|
color: "success",
|
|
114
124
|
});
|
|
115
125
|
} catch (err) {
|
|
116
|
-
|
|
126
|
+
logger.error("Error updating profile:", err);
|
|
117
127
|
setError(err instanceof Error ? err.message : "An error occurred");
|
|
118
128
|
addToast({
|
|
119
129
|
title: t("profile.error") || "Error",
|
|
@@ -129,6 +139,115 @@ export function ProfilePage() {
|
|
|
129
139
|
setProfile((prev) => ({ ...prev, [field]: value }));
|
|
130
140
|
};
|
|
131
141
|
|
|
142
|
+
// Check username availability with debounce
|
|
143
|
+
useEffect(() => {
|
|
144
|
+
if (!profile.username?.trim()) {
|
|
145
|
+
setUsernameAvailable(null);
|
|
146
|
+
return;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
const timeoutId = setTimeout(async () => {
|
|
150
|
+
setCheckingUsername(true);
|
|
151
|
+
if (!profile.username) {
|
|
152
|
+
setUsernameAvailable(null);
|
|
153
|
+
setCheckingUsername(false);
|
|
154
|
+
return;
|
|
155
|
+
}
|
|
156
|
+
try {
|
|
157
|
+
const usernameLower = profile.username.toLowerCase();
|
|
158
|
+
const response = await fetch(
|
|
159
|
+
`/api/auth/check-username?username=${encodeURIComponent(usernameLower)}&owner_id=${currentUser?.id || ""}`
|
|
160
|
+
);
|
|
161
|
+
|
|
162
|
+
if (response.ok) {
|
|
163
|
+
const { available } = await response.json();
|
|
164
|
+
setUsernameAvailable(available);
|
|
165
|
+
}
|
|
166
|
+
} catch (error) {
|
|
167
|
+
logger.error("Error checking username:", error);
|
|
168
|
+
} finally {
|
|
169
|
+
setCheckingUsername(false);
|
|
170
|
+
}
|
|
171
|
+
}, 500);
|
|
172
|
+
|
|
173
|
+
return () => clearTimeout(timeoutId);
|
|
174
|
+
}, [profile.username, currentUser?.id]);
|
|
175
|
+
|
|
176
|
+
const handleSendResetLink = async () => {
|
|
177
|
+
try {
|
|
178
|
+
setSecurityLoading(true);
|
|
179
|
+
const response = await fetch("/api/auth/account/reset-password", {
|
|
180
|
+
method: "POST",
|
|
181
|
+
});
|
|
182
|
+
|
|
183
|
+
const data = await response.json();
|
|
184
|
+
if (!response.ok) {
|
|
185
|
+
throw new Error(data.error || "Erreur lors de l'envoi du lien");
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
addToast({
|
|
189
|
+
title: t("profile.security_reset_sent") || "Lien envoyé",
|
|
190
|
+
description:
|
|
191
|
+
t("profile.security_reset_desc") ||
|
|
192
|
+
"Vérifiez votre email pour réinitialiser votre mot de passe",
|
|
193
|
+
color: "success",
|
|
194
|
+
});
|
|
195
|
+
} catch (error: any) {
|
|
196
|
+
addToast({
|
|
197
|
+
title: t("profile.security_error") || "Erreur",
|
|
198
|
+
description: error?.message || "Impossible d'envoyer le lien",
|
|
199
|
+
color: "danger",
|
|
200
|
+
});
|
|
201
|
+
} finally {
|
|
202
|
+
setSecurityLoading(false);
|
|
203
|
+
}
|
|
204
|
+
};
|
|
205
|
+
|
|
206
|
+
const handleEmailChangeRequest = async () => {
|
|
207
|
+
if (!newEmail) {
|
|
208
|
+
addToast({
|
|
209
|
+
title: t("profile.security_error") || "Erreur",
|
|
210
|
+
description:
|
|
211
|
+
t("profile.security_email_required") || "Nouvel email requis",
|
|
212
|
+
color: "warning",
|
|
213
|
+
});
|
|
214
|
+
return;
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
try {
|
|
218
|
+
setSecurityLoading(true);
|
|
219
|
+
const response = await fetch("/api/auth/account/email-change", {
|
|
220
|
+
method: "POST",
|
|
221
|
+
headers: {
|
|
222
|
+
"Content-Type": "application/json",
|
|
223
|
+
},
|
|
224
|
+
body: JSON.stringify({ newEmail }),
|
|
225
|
+
});
|
|
226
|
+
|
|
227
|
+
const data = await response.json();
|
|
228
|
+
if (!response.ok) {
|
|
229
|
+
throw new Error(data.error || "Erreur lors de l'envoi du lien");
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
addToast({
|
|
233
|
+
title:
|
|
234
|
+
t("profile.security_email_change_sent") || "Confirmation envoyée",
|
|
235
|
+
description:
|
|
236
|
+
t("profile.security_email_change_desc") ||
|
|
237
|
+
"Vérifiez le nouvel email pour confirmer le changement",
|
|
238
|
+
color: "success",
|
|
239
|
+
});
|
|
240
|
+
} catch (error: any) {
|
|
241
|
+
addToast({
|
|
242
|
+
title: t("profile.security_error") || "Erreur",
|
|
243
|
+
description: error?.message || "Impossible d'envoyer la confirmation",
|
|
244
|
+
color: "danger",
|
|
245
|
+
});
|
|
246
|
+
} finally {
|
|
247
|
+
setSecurityLoading(false);
|
|
248
|
+
}
|
|
249
|
+
};
|
|
250
|
+
|
|
132
251
|
const handleAvatarUpload = async (files: {
|
|
133
252
|
small: Blob;
|
|
134
253
|
medium: Blob;
|
|
@@ -182,16 +301,17 @@ export function ProfilePage() {
|
|
|
182
301
|
headers: {
|
|
183
302
|
"Content-Type": "application/json",
|
|
184
303
|
},
|
|
304
|
+
credentials: "include",
|
|
185
305
|
body: JSON.stringify({
|
|
186
306
|
avatar_url: `/avatar/${currentUser.id}_128_${version}.webp`,
|
|
187
307
|
}),
|
|
188
308
|
});
|
|
189
309
|
|
|
190
310
|
if (!response.ok) {
|
|
191
|
-
|
|
311
|
+
logger.error("Failed to update avatar_url in profile");
|
|
192
312
|
}
|
|
193
313
|
} catch (error) {
|
|
194
|
-
|
|
314
|
+
logger.error("Error updating profile avatar_url:", error);
|
|
195
315
|
}
|
|
196
316
|
|
|
197
317
|
// Update profile avatar_url locally
|
|
@@ -228,16 +348,17 @@ export function ProfilePage() {
|
|
|
228
348
|
headers: {
|
|
229
349
|
"Content-Type": "application/json",
|
|
230
350
|
},
|
|
351
|
+
credentials: "include",
|
|
231
352
|
body: JSON.stringify({
|
|
232
353
|
avatar_url: null,
|
|
233
354
|
}),
|
|
234
355
|
});
|
|
235
356
|
|
|
236
357
|
if (!response.ok) {
|
|
237
|
-
|
|
358
|
+
logger.error("Failed to update avatar_url in profile");
|
|
238
359
|
}
|
|
239
360
|
} catch (error) {
|
|
240
|
-
|
|
361
|
+
logger.error("Error updating profile avatar_url:", error);
|
|
241
362
|
}
|
|
242
363
|
|
|
243
364
|
// Update profile locally
|
|
@@ -256,7 +377,7 @@ export function ProfilePage() {
|
|
|
256
377
|
}
|
|
257
378
|
|
|
258
379
|
return (
|
|
259
|
-
<div className="pt-12 pb-12 max-w-
|
|
380
|
+
<div className="md:pt-12 pb-12 max-w-2xl mx-auto md:px-4">
|
|
260
381
|
<form onSubmit={handleSubmit}>
|
|
261
382
|
<div className="space-y-6">
|
|
262
383
|
{/* Avatar Section */}
|
|
@@ -343,6 +464,41 @@ export function ProfilePage() {
|
|
|
343
464
|
onChange={(e) => handleChange("phone", e.target.value)}
|
|
344
465
|
className="md:col-span-2"
|
|
345
466
|
/>
|
|
467
|
+
<Input
|
|
468
|
+
label="Username"
|
|
469
|
+
placeholder="Enter your username"
|
|
470
|
+
value={profile.username || ""}
|
|
471
|
+
onChange={(e) => handleChange("username", e.target.value)}
|
|
472
|
+
description="3-30 characters, alphanumeric, underscore or dash only"
|
|
473
|
+
errorMessage={
|
|
474
|
+
checkingUsername
|
|
475
|
+
? "Checking..."
|
|
476
|
+
: usernameAvailable === false
|
|
477
|
+
? "Username already taken"
|
|
478
|
+
: undefined
|
|
479
|
+
}
|
|
480
|
+
color={
|
|
481
|
+
checkingUsername
|
|
482
|
+
? "default"
|
|
483
|
+
: usernameAvailable === true
|
|
484
|
+
? "success"
|
|
485
|
+
: usernameAvailable === false
|
|
486
|
+
? "danger"
|
|
487
|
+
: "default"
|
|
488
|
+
}
|
|
489
|
+
className="md:col-span-2"
|
|
490
|
+
endContent={
|
|
491
|
+
checkingUsername ? (
|
|
492
|
+
<Spinner size="sm" />
|
|
493
|
+
) : profile.username && profile.username.length > 0 ? (
|
|
494
|
+
usernameAvailable === false ? (
|
|
495
|
+
<XCircle className="w-4 h-4 text-danger" />
|
|
496
|
+
) : (
|
|
497
|
+
<Check className="w-4 h-4 text-success" />
|
|
498
|
+
)
|
|
499
|
+
) : null
|
|
500
|
+
}
|
|
501
|
+
/>
|
|
346
502
|
<Textarea
|
|
347
503
|
label={t("profile.bio") || "Bio"}
|
|
348
504
|
placeholder={
|
|
@@ -358,7 +514,7 @@ export function ProfilePage() {
|
|
|
358
514
|
</Card>
|
|
359
515
|
|
|
360
516
|
{/* Professional Information */}
|
|
361
|
-
<Card>
|
|
517
|
+
{/* <Card>
|
|
362
518
|
<CardHeader>
|
|
363
519
|
<h3 className="text-lg font-semibold">
|
|
364
520
|
{t("profile.professional_info") || "Professional Information"}
|
|
@@ -398,7 +554,7 @@ export function ProfilePage() {
|
|
|
398
554
|
</CardBody>
|
|
399
555
|
</Card>
|
|
400
556
|
|
|
401
|
-
|
|
557
|
+
|
|
402
558
|
<Card>
|
|
403
559
|
<CardHeader>
|
|
404
560
|
<h3 className="text-lg font-semibold">
|
|
@@ -427,10 +583,68 @@ export function ProfilePage() {
|
|
|
427
583
|
/>
|
|
428
584
|
</div>
|
|
429
585
|
</CardBody>
|
|
586
|
+
</Card> */}
|
|
587
|
+
|
|
588
|
+
{/* Security */}
|
|
589
|
+
<Card>
|
|
590
|
+
<CardHeader>
|
|
591
|
+
<h3 className="text-lg font-semibold">
|
|
592
|
+
{t("profile.security") || "Sécurité"}
|
|
593
|
+
</h3>
|
|
594
|
+
</CardHeader>
|
|
595
|
+
<Divider />
|
|
596
|
+
<CardBody className="space-y-4">
|
|
597
|
+
<div className="grid gap-4 md:grid-cols-2 items-center justify-center">
|
|
598
|
+
<Input
|
|
599
|
+
label={t("profile.security_new_email") || "Nouvel email"}
|
|
600
|
+
placeholder={
|
|
601
|
+
t("profile.security_new_email_placeholder") ||
|
|
602
|
+
"nouveau@email.com"
|
|
603
|
+
}
|
|
604
|
+
value={newEmail}
|
|
605
|
+
onChange={(e) => setNewEmail(e.target.value)}
|
|
606
|
+
/>
|
|
607
|
+
<Button
|
|
608
|
+
onPress={handleEmailChangeRequest}
|
|
609
|
+
color="secondary"
|
|
610
|
+
size="lg"
|
|
611
|
+
variant="flat"
|
|
612
|
+
isLoading={securityLoading}
|
|
613
|
+
className="w-full md:w-auto"
|
|
614
|
+
>
|
|
615
|
+
{t("profile.security_email_change_btn") ||
|
|
616
|
+
"Envoyer la confirmation"}
|
|
617
|
+
</Button>
|
|
618
|
+
</div>
|
|
619
|
+
|
|
620
|
+
<Divider className="my-2" />
|
|
621
|
+
|
|
622
|
+
<div className="flex flex-col md:flex-row md:items-center md:justify-between gap-3">
|
|
623
|
+
<div>
|
|
624
|
+
<h4 className="font-medium">
|
|
625
|
+
{t("profile.security_reset_title") ||
|
|
626
|
+
"Réinitialiser le mot de passe"}
|
|
627
|
+
</h4>
|
|
628
|
+
<p className="text-sm text-default-500">
|
|
629
|
+
{t("profile.security_reset_help") ||
|
|
630
|
+
"Nous enverrons un lien de réinitialisation à votre email"}
|
|
631
|
+
</p>
|
|
632
|
+
</div>
|
|
633
|
+
<Button
|
|
634
|
+
variant="flat"
|
|
635
|
+
color="secondary"
|
|
636
|
+
onPress={handleSendResetLink}
|
|
637
|
+
isLoading={securityLoading}
|
|
638
|
+
className="w-full md:w-auto"
|
|
639
|
+
>
|
|
640
|
+
{t("profile.security_reset_btn") || "Envoyer le lien"}
|
|
641
|
+
</Button>
|
|
642
|
+
</div>
|
|
643
|
+
</CardBody>
|
|
430
644
|
</Card>
|
|
431
645
|
|
|
432
646
|
{/* Actions */}
|
|
433
|
-
<div className="flex justify-
|
|
647
|
+
<div className="flex justify-center gap-3">
|
|
434
648
|
<Button
|
|
435
649
|
type="button"
|
|
436
650
|
variant="flat"
|
package/src/web/auth/reglage.tsx
CHANGED
|
@@ -14,7 +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
|
+
import { logger, useModuleTranslation } from "@lastbrain/core";
|
|
18
18
|
|
|
19
19
|
interface UserPreferences {
|
|
20
20
|
email_notifications?: boolean;
|
|
@@ -51,7 +51,9 @@ export function ReglagePage() {
|
|
|
51
51
|
const fetchSettings = async () => {
|
|
52
52
|
try {
|
|
53
53
|
setIsLoading(true);
|
|
54
|
-
const response = await fetch("/api/auth/profile"
|
|
54
|
+
const response = await fetch("/api/auth/profile", {
|
|
55
|
+
credentials: "include",
|
|
56
|
+
});
|
|
55
57
|
|
|
56
58
|
if (!response.ok) {
|
|
57
59
|
throw new Error("Failed to fetch settings");
|
|
@@ -68,10 +70,11 @@ export function ReglagePage() {
|
|
|
68
70
|
}));
|
|
69
71
|
}
|
|
70
72
|
} catch (err) {
|
|
71
|
-
|
|
73
|
+
logger.error("Error loading settings:", err);
|
|
72
74
|
addToast({
|
|
73
|
-
title: "Error",
|
|
74
|
-
description:
|
|
75
|
+
title: t("settings.load_error_title") || "Error",
|
|
76
|
+
description:
|
|
77
|
+
t("settings.load_error_description") || "Failed to load settings",
|
|
75
78
|
color: "danger",
|
|
76
79
|
});
|
|
77
80
|
} finally {
|
|
@@ -88,6 +91,7 @@ export function ReglagePage() {
|
|
|
88
91
|
headers: {
|
|
89
92
|
"Content-Type": "application/json",
|
|
90
93
|
},
|
|
94
|
+
credentials: "include",
|
|
91
95
|
body: JSON.stringify({
|
|
92
96
|
language: preferences.language,
|
|
93
97
|
timezone: preferences.timezone,
|
|
@@ -109,8 +113,11 @@ export function ReglagePage() {
|
|
|
109
113
|
description: t("settings.updated") || "Settings updated successfully",
|
|
110
114
|
color: "success",
|
|
111
115
|
});
|
|
116
|
+
|
|
117
|
+
// Si la langue a été modifiée, attendre la confirmation du serveur
|
|
118
|
+
// (localeUpdated) avant de forcer le reload complet.
|
|
112
119
|
} catch (err) {
|
|
113
|
-
|
|
120
|
+
logger.error("Error updating settings:", err);
|
|
114
121
|
addToast({
|
|
115
122
|
title: t("settings.error") || "Error",
|
|
116
123
|
description: t("settings.update_failed") || "Failed to update settings",
|
|
@@ -141,7 +148,7 @@ export function ReglagePage() {
|
|
|
141
148
|
}
|
|
142
149
|
|
|
143
150
|
return (
|
|
144
|
-
<div className="pt-12 pb-12 max-w-
|
|
151
|
+
<div className="md:pt-12 pb-12 max-w-2xl mx-auto md:px-4">
|
|
145
152
|
<div className="flex items-center gap-2 mb-8">
|
|
146
153
|
<Settings className="w-8 h-8" />
|
|
147
154
|
<h1 className="text-3xl font-bold">
|
|
@@ -246,53 +253,75 @@ export function ReglagePage() {
|
|
|
246
253
|
{/* Language & Region */}
|
|
247
254
|
<Card>
|
|
248
255
|
<CardHeader>
|
|
249
|
-
<h3 className="text-lg font-semibold">
|
|
256
|
+
<h3 className="text-lg font-semibold">
|
|
257
|
+
{t("settings.language_region") || "Language & Region"}
|
|
258
|
+
</h3>
|
|
250
259
|
</CardHeader>
|
|
251
260
|
<Divider />
|
|
252
261
|
<CardBody>
|
|
253
262
|
<div className="grid gap-4 md:grid-cols-2">
|
|
254
263
|
<Select
|
|
255
|
-
label="Language"
|
|
256
|
-
placeholder=
|
|
264
|
+
label={t("settings.language") || "Language"}
|
|
265
|
+
placeholder={
|
|
266
|
+
t("settings.language_placeholder") || "Select a language"
|
|
267
|
+
}
|
|
257
268
|
selectedKeys={
|
|
258
269
|
preferences.language ? [preferences.language] : []
|
|
259
270
|
}
|
|
260
271
|
onChange={(e) => handleSelect("language", e.target.value)}
|
|
261
272
|
>
|
|
262
|
-
<SelectItem key="en">
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
<SelectItem key="
|
|
273
|
+
<SelectItem key="en">
|
|
274
|
+
{t("settings.lang_en") || "English"}
|
|
275
|
+
</SelectItem>
|
|
276
|
+
<SelectItem key="fr">
|
|
277
|
+
{t("settings.lang_fr") || "Français"}
|
|
278
|
+
</SelectItem>
|
|
279
|
+
<SelectItem key="es">
|
|
280
|
+
{t("settings.lang_es") || "Español"}
|
|
281
|
+
</SelectItem>
|
|
282
|
+
<SelectItem key="de">
|
|
283
|
+
{t("settings.lang_de") || "Deutsch"}
|
|
284
|
+
</SelectItem>
|
|
266
285
|
</Select>
|
|
267
286
|
<Select
|
|
268
|
-
label="Timezone"
|
|
269
|
-
placeholder=
|
|
287
|
+
label={t("settings.timezone") || "Timezone"}
|
|
288
|
+
placeholder={
|
|
289
|
+
t("settings.timezone_placeholder") || "Select a timezone"
|
|
290
|
+
}
|
|
270
291
|
selectedKeys={
|
|
271
292
|
preferences.timezone ? [preferences.timezone] : []
|
|
272
293
|
}
|
|
273
294
|
onChange={(e) => handleSelect("timezone", e.target.value)}
|
|
274
295
|
>
|
|
275
|
-
<SelectItem key="UTC">
|
|
276
|
-
|
|
277
|
-
|
|
296
|
+
<SelectItem key="UTC">
|
|
297
|
+
{t("settings.tz_utc") || "UTC"}
|
|
298
|
+
</SelectItem>
|
|
299
|
+
<SelectItem key="Europe/Paris">
|
|
300
|
+
{t("settings.tz_paris") || "Europe/Paris"}
|
|
301
|
+
</SelectItem>
|
|
302
|
+
<SelectItem key="America/New_York">
|
|
303
|
+
{t("settings.tz_ny") || "America/New_York"}
|
|
304
|
+
</SelectItem>
|
|
278
305
|
<SelectItem key="America/Los_Angeles">
|
|
279
|
-
America/Los_Angeles
|
|
306
|
+
{t("settings.tz_la") || "America/Los_Angeles"}
|
|
307
|
+
</SelectItem>
|
|
308
|
+
<SelectItem key="Asia/Tokyo">
|
|
309
|
+
{t("settings.tz_tokyo") || "Asia/Tokyo"}
|
|
280
310
|
</SelectItem>
|
|
281
|
-
<SelectItem key="Asia/Tokyo">Asia/Tokyo</SelectItem>
|
|
282
311
|
</Select>
|
|
283
312
|
</div>
|
|
284
313
|
</CardBody>
|
|
285
314
|
</Card>
|
|
286
315
|
|
|
287
316
|
{/* Actions */}
|
|
288
|
-
<div className="flex justify-
|
|
317
|
+
<div className="flex justify-center gap-3">
|
|
289
318
|
<Button
|
|
290
319
|
type="button"
|
|
291
320
|
variant="flat"
|
|
292
321
|
onPress={() => fetchSettings()}
|
|
293
322
|
isDisabled={isSaving}
|
|
294
323
|
>
|
|
295
|
-
Reset
|
|
324
|
+
{t("settings.reset") || "Reset"}
|
|
296
325
|
</Button>
|
|
297
326
|
<Button
|
|
298
327
|
type="button"
|
|
@@ -301,7 +330,9 @@ export function ReglagePage() {
|
|
|
301
330
|
onPress={handleSave}
|
|
302
331
|
startContent={!isSaving && <Save className="w-4 h-4" />}
|
|
303
332
|
>
|
|
304
|
-
{isSaving
|
|
333
|
+
{isSaving
|
|
334
|
+
? t("settings.saving") || "Saving..."
|
|
335
|
+
: t("settings.save_button") || "Save Settings"}
|
|
305
336
|
</Button>
|
|
306
337
|
</div>
|
|
307
338
|
</div>
|