@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.
Files changed (178) hide show
  1. package/README.md +55 -7
  2. package/dist/api/admin/signup-stats.d.ts.map +1 -1
  3. package/dist/api/admin/signup-stats.js +2 -1
  4. package/dist/api/admin/storage/usage.d.ts +18 -0
  5. package/dist/api/admin/storage/usage.d.ts.map +1 -0
  6. package/dist/api/admin/storage/usage.js +100 -0
  7. package/dist/api/admin/users/[id]/notifications.d.ts.map +1 -1
  8. package/dist/api/admin/users/[id]/notifications.js +3 -2
  9. package/dist/api/admin/users/[id].d.ts.map +1 -1
  10. package/dist/api/admin/users/[id].js +3 -2
  11. package/dist/api/admin/users/reactivate/[id].d.ts +16 -0
  12. package/dist/api/admin/users/reactivate/[id].d.ts.map +1 -0
  13. package/dist/api/admin/users/reactivate/[id].js +59 -0
  14. package/dist/api/admin/users/suspend/[id].d.ts +16 -0
  15. package/dist/api/admin/users/suspend/[id].d.ts.map +1 -0
  16. package/dist/api/admin/users/suspend/[id].js +59 -0
  17. package/dist/api/admin/users-by-source.d.ts.map +1 -1
  18. package/dist/api/admin/users-by-source.js +2 -1
  19. package/dist/api/admin/users.d.ts.map +1 -1
  20. package/dist/api/admin/users.js +53 -2
  21. package/dist/api/auth/account/email-change.d.ts +7 -0
  22. package/dist/api/auth/account/email-change.d.ts.map +1 -0
  23. package/dist/api/auth/account/email-change.js +39 -0
  24. package/dist/api/auth/account/reset-password.d.ts +7 -0
  25. package/dist/api/auth/account/reset-password.d.ts.map +1 -0
  26. package/dist/api/auth/account/reset-password.js +36 -0
  27. package/dist/api/auth/check-username.d.ts +9 -0
  28. package/dist/api/auth/check-username.d.ts.map +1 -0
  29. package/dist/api/auth/check-username.js +35 -0
  30. package/dist/api/auth/establish-session.d.ts +2 -0
  31. package/dist/api/auth/establish-session.d.ts.map +1 -0
  32. package/dist/api/auth/establish-session.js +23 -0
  33. package/dist/api/auth/me.d.ts +4 -4
  34. package/dist/api/auth/me.d.ts.map +1 -1
  35. package/dist/api/auth/me.js +28 -6
  36. package/dist/api/auth/profile.d.ts.map +1 -1
  37. package/dist/api/auth/profile.js +6 -3
  38. package/dist/api/auth/storage/recalculate.d.ts +15 -0
  39. package/dist/api/auth/storage/recalculate.d.ts.map +1 -0
  40. package/dist/api/auth/storage/recalculate.js +68 -0
  41. package/dist/api/auth/storage/usage.d.ts +10 -0
  42. package/dist/api/auth/storage/usage.d.ts.map +1 -0
  43. package/dist/api/auth/storage/usage.js +86 -0
  44. package/dist/api/public/add-welcome-bonus.d.ts +16 -0
  45. package/dist/api/public/add-welcome-bonus.d.ts.map +1 -0
  46. package/dist/api/public/add-welcome-bonus.js +177 -0
  47. package/dist/api/public/callback.d.ts +3 -0
  48. package/dist/api/public/callback.d.ts.map +1 -0
  49. package/dist/api/public/callback.js +197 -0
  50. package/dist/api/public/reset-password.d.ts +3 -0
  51. package/dist/api/public/reset-password.d.ts.map +1 -0
  52. package/dist/api/public/reset-password.js +43 -0
  53. package/dist/api/public/set-session.d.ts +7 -0
  54. package/dist/api/public/set-session.d.ts.map +1 -0
  55. package/dist/api/public/set-session.js +55 -0
  56. package/dist/api/public/signin.d.ts.map +1 -1
  57. package/dist/api/public/signin.js +31 -0
  58. package/dist/api/public/signup.d.ts.map +1 -1
  59. package/dist/api/public/signup.js +38 -27
  60. package/dist/api/public/webhook/storage-addon.d.ts +9 -0
  61. package/dist/api/public/webhook/storage-addon.d.ts.map +1 -0
  62. package/dist/api/public/webhook/storage-addon.js +155 -0
  63. package/dist/api/storage.js +2 -2
  64. package/dist/auth.build.config.d.ts.map +1 -1
  65. package/dist/auth.build.config.js +134 -14
  66. package/dist/components/AccountButton.d.ts.map +1 -1
  67. package/dist/components/AccountButton.js +54 -28
  68. package/dist/components/Doc.d.ts.map +1 -1
  69. package/dist/components/Doc.js +1 -1
  70. package/dist/components/HasProfil.d.ts +4 -0
  71. package/dist/components/HasProfil.d.ts.map +1 -0
  72. package/dist/components/HasProfil.js +39 -0
  73. package/dist/{web → components}/auth/dashboard.d.ts +1 -1
  74. package/dist/components/auth/dashboard.d.ts.map +1 -0
  75. package/dist/components/auth/dashboard.js +74 -0
  76. package/dist/index.d.ts +3 -1
  77. package/dist/index.d.ts.map +1 -1
  78. package/dist/index.js +3 -1
  79. package/dist/lib/app-branding-data.d.ts +22 -0
  80. package/dist/lib/app-branding-data.d.ts.map +1 -0
  81. package/dist/lib/app-branding-data.js +49 -0
  82. package/dist/lib/auth-email-service.d.ts +57 -0
  83. package/dist/lib/auth-email-service.d.ts.map +1 -0
  84. package/dist/lib/auth-email-service.js +382 -0
  85. package/dist/lib/auth-email-templates.d.ts +2 -0
  86. package/dist/lib/auth-email-templates.d.ts.map +1 -0
  87. package/dist/lib/auth-email-templates.js +1 -0
  88. package/dist/lib/site-url.d.ts +3 -0
  89. package/dist/lib/site-url.d.ts.map +1 -0
  90. package/dist/lib/site-url.js +11 -0
  91. package/dist/sitemap/manifest.d.ts +9 -0
  92. package/dist/sitemap/manifest.d.ts.map +1 -0
  93. package/dist/sitemap/manifest.js +14 -0
  94. package/dist/web/admin/signup-stats.js +3 -3
  95. package/dist/web/admin/user-detail.d.ts.map +1 -1
  96. package/dist/web/admin/user-detail.js +135 -14
  97. package/dist/web/admin/users-by-signup-source.js +2 -2
  98. package/dist/web/admin/users.d.ts.map +1 -1
  99. package/dist/web/admin/users.js +26 -7
  100. package/dist/web/auth/folder.d.ts.map +1 -1
  101. package/dist/web/auth/folder.js +4 -3
  102. package/dist/web/auth/profile.d.ts.map +1 -1
  103. package/dist/web/auth/profile.js +132 -13
  104. package/dist/web/auth/reglage.d.ts.map +1 -1
  105. package/dist/web/auth/reglage.js +15 -8
  106. package/dist/web/public/ResetPassword.d.ts.map +1 -1
  107. package/dist/web/public/ResetPassword.js +172 -2
  108. package/dist/web/public/SignInPage.d.ts.map +1 -1
  109. package/dist/web/public/SignInPage.js +39 -3
  110. package/dist/web/public/SignUpPage.d.ts.map +1 -1
  111. package/dist/web/public/SignUpPage.js +7 -2
  112. package/dist/web/public/auth-code-error.d.ts +2 -0
  113. package/dist/web/public/auth-code-error.d.ts.map +1 -0
  114. package/dist/web/public/auth-code-error.js +14 -0
  115. package/dist/web/public/confirm.d.ts +2 -0
  116. package/dist/web/public/confirm.d.ts.map +1 -0
  117. package/dist/web/public/confirm.js +157 -0
  118. package/package.json +10 -5
  119. package/src/api/admin/signup-stats.ts +2 -1
  120. package/src/api/admin/storage/usage.ts +141 -0
  121. package/src/api/admin/users/[id]/notifications.ts +3 -2
  122. package/src/api/admin/users/[id].ts +3 -2
  123. package/src/api/admin/users/reactivate/[id].ts +88 -0
  124. package/src/api/admin/users/suspend/[id].ts +85 -0
  125. package/src/api/admin/users-by-source.ts +2 -1
  126. package/src/api/admin/users.ts +59 -2
  127. package/src/api/auth/account/email-change.ts +54 -0
  128. package/src/api/auth/account/reset-password.ts +47 -0
  129. package/src/api/auth/check-username.ts +52 -0
  130. package/src/api/auth/establish-session.ts +32 -0
  131. package/src/api/auth/me.ts +29 -7
  132. package/src/api/auth/profile.ts +6 -2
  133. package/src/api/auth/storage/recalculate.ts +108 -0
  134. package/src/api/auth/storage/usage.ts +113 -0
  135. package/src/api/public/add-welcome-bonus.ts +229 -0
  136. package/src/api/public/callback.ts +307 -0
  137. package/src/api/public/reset-password.ts +52 -0
  138. package/src/api/public/set-session.ts +73 -0
  139. package/src/api/public/signin.ts +36 -0
  140. package/src/api/public/signup.ts +44 -37
  141. package/src/api/public/webhook/storage-addon.ts +267 -0
  142. package/src/api/storage.ts +2 -2
  143. package/src/auth.build.config.ts +134 -14
  144. package/src/components/AccountButton.tsx +114 -90
  145. package/src/components/Doc.tsx +47 -9
  146. package/src/components/HasProfil.tsx +63 -0
  147. package/src/{web → components}/auth/dashboard.tsx +64 -20
  148. package/src/i18n/en.json +78 -8
  149. package/src/i18n/es.json +330 -0
  150. package/src/i18n/fr.json +75 -8
  151. package/src/index.ts +3 -1
  152. package/src/lib/app-branding-data.ts +90 -0
  153. package/src/lib/auth-email-service.ts +508 -0
  154. package/src/lib/auth-email-templates.ts +5 -0
  155. package/src/lib/site-url.ts +17 -0
  156. package/src/sitemap/manifest.ts +26 -0
  157. package/src/web/admin/signup-stats.tsx +3 -3
  158. package/src/web/admin/user-detail.tsx +314 -15
  159. package/src/web/admin/users-by-signup-source.tsx +2 -2
  160. package/src/web/admin/users.tsx +50 -14
  161. package/src/web/auth/folder.tsx +23 -5
  162. package/src/web/auth/profile.tsx +227 -13
  163. package/src/web/auth/reglage.tsx +55 -24
  164. package/src/web/public/ResetPassword.tsx +301 -1
  165. package/src/web/public/SignInPage.tsx +43 -3
  166. package/src/web/public/SignUpPage.tsx +14 -5
  167. package/src/web/public/auth-code-error.tsx +49 -0
  168. package/src/web/public/confirm.tsx +195 -0
  169. package/supabase/migrations/20251112000001_auto_profile_and_admin_view.sql +3 -1
  170. package/supabase/migrations/20251127100000_rename_body_to_message.sql +8 -3
  171. package/supabase/migrations/20260120150001_add_user_storage.sql +105 -0
  172. package/supabase/migrations/20260122131200_add_global_addons_system.sql +305 -0
  173. package/supabase/migrations/20260123100000_enable_vector_extension.sql +9 -0
  174. package/supabase/migrations/20260123140000_module_auth_fix_get_user_limits_null.sql +93 -0
  175. package/supabase/migrations/20260123145000_module_auth_fix_circular_storage_base.sql +89 -0
  176. package/supabase/migrations/20260129400000_add_username_to_user_profil.sql +33 -0
  177. package/dist/web/auth/dashboard.d.ts.map +0 -1
  178. package/dist/web/auth/dashboard.js +0 -48
@@ -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
- console.error("Error fetching current user:", err);
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
- console.error("Error updating profile:", err);
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
- console.error("Failed to update avatar_url in profile");
311
+ logger.error("Failed to update avatar_url in profile");
192
312
  }
193
313
  } catch (error) {
194
- console.error("Error updating profile avatar_url:", error);
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
- console.error("Failed to update avatar_url in profile");
358
+ logger.error("Failed to update avatar_url in profile");
238
359
  }
239
360
  } catch (error) {
240
- console.error("Error updating profile avatar_url:", error);
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-4xl mx-auto px-4">
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
- {/* Preferences */}
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-end gap-3">
647
+ <div className="flex justify-center gap-3">
434
648
  <Button
435
649
  type="button"
436
650
  variant="flat"
@@ -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
- console.error("Error loading settings:", err);
73
+ logger.error("Error loading settings:", err);
72
74
  addToast({
73
- title: "Error",
74
- description: "Failed to load settings",
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
- console.error("Error updating settings:", err);
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-4xl mx-auto px-4">
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">Language & Region</h3>
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="Select a language"
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">English</SelectItem>
263
- <SelectItem key="fr">Français</SelectItem>
264
- <SelectItem key="es">Español</SelectItem>
265
- <SelectItem key="de">Deutsch</SelectItem>
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="Select a timezone"
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">UTC</SelectItem>
276
- <SelectItem key="Europe/Paris">Europe/Paris</SelectItem>
277
- <SelectItem key="America/New_York">America/New_York</SelectItem>
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-end gap-3">
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 ? "Saving..." : "Save Settings"}
333
+ {isSaving
334
+ ? t("settings.saving") || "Saving..."
335
+ : t("settings.save_button") || "Save Settings"}
305
336
  </Button>
306
337
  </div>
307
338
  </div>