@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
@@ -1,6 +1,6 @@
1
1
  "use client";
2
2
 
3
- import { AppLink, Button } from "@lastbrain/ui";
3
+ import { AppLink, Button, ThemeSwitcher } from "@lastbrain/ui";
4
4
  import { Avatar } from "@lastbrain/ui";
5
5
  import {
6
6
  Dropdown,
@@ -10,8 +10,9 @@ import {
10
10
  } from "@lastbrain/ui";
11
11
  import * as LucideIcons from "lucide-react";
12
12
  import type { User } from "@supabase/supabase-js";
13
- import { useModuleTranslation, langHref } from "@lastbrain/app";
13
+ import { useModuleTranslation } from "@lastbrain/app";
14
14
  import { useLanguage } from "@lastbrain/app";
15
+ import { useLocalizedRouter, supabaseBrowserClient } from "@lastbrain/core";
15
16
 
16
17
  interface AccountButtonProps {
17
18
  item: {
@@ -42,49 +43,46 @@ export const AccountButton = ({
42
43
  }: AccountButtonProps) => {
43
44
  const { lang } = useLanguage();
44
45
  const t = useModuleTranslation("auth");
46
+ const router = useLocalizedRouter();
45
47
 
46
48
  if (!user)
47
49
  return (
48
50
  <>
49
- <div className="block md:hidden">
51
+ <div className="block sm:hidden flex flex-inline gap-2 item-center">
50
52
  <Button
51
53
  as={AppLink}
52
54
  href="/signin"
53
55
  radius="full"
54
56
  isIconOnly
55
- variant="light"
56
- color="primary"
57
- >
58
- <LucideIcons.LogIn size={16} />
59
- </Button>
60
- <Button
61
- as={AppLink}
62
- href="/signup"
63
- radius="full"
64
- isIconOnly
65
57
  variant="flat"
66
- className="ml-2"
67
- color="secondary"
58
+ color="primary"
68
59
  >
69
60
  <LucideIcons.UserPlus2 size={16} />
70
61
  </Button>
62
+ <div className="ms-2">
63
+ <ThemeSwitcher />
64
+ </div>
71
65
  </div>
72
- <div className="hidden md:block">
66
+ <div className="hidden sm:block flex flex-inline gap-2 item-center">
73
67
  <Button
74
68
  as={AppLink}
75
69
  href="/signin"
76
70
  startContent={<LucideIcons.LogIn size={16} />}
77
71
  variant="light"
78
72
  color="primary"
73
+ radius="full"
74
+ size="sm"
79
75
  >
80
76
  {t("signin")}
81
77
  </Button>
82
78
  <Button
83
79
  as={AppLink}
84
80
  href="/signup"
85
- variant="flat"
81
+ variant="bordered"
86
82
  className="ml-2"
87
83
  color="secondary"
84
+ radius="full"
85
+ size="sm"
88
86
  startContent={<LucideIcons.UserPlus2 size={16} />}
89
87
  >
90
88
  {t("signup")}
@@ -94,80 +92,106 @@ export const AccountButton = ({
94
92
  );
95
93
 
96
94
  return (
97
- <Dropdown>
98
- <DropdownTrigger>
99
- {user?.user_metadata.avatar ? (
100
- <Avatar
101
- size="sm"
102
- src={
103
- user?.user_metadata.avatar
104
- ? `/api/storage/${user?.user_metadata.avatar}`
105
- : undefined
106
- }
107
- title={user.email}
108
- fallback={<LucideIcons.User2 size={18} />}
109
- classNames={{
110
- base: "cursor-pointer bg-white/0",
111
- icon: "text-default-700",
112
- }}
113
- />
114
- ) : (
115
- <Button size="sm" variant="flat" radius="full" isIconOnly>
116
- <LucideIcons.User2 size={16} />
117
- </Button>
118
- )}
119
- </DropdownTrigger>
95
+ <div className="flex flex-inline gap-2">
96
+ <Dropdown>
97
+ <DropdownTrigger>
98
+ {user?.user_metadata.avatar ? (
99
+ <Avatar
100
+ size="sm"
101
+ src={
102
+ user?.user_metadata.avatar
103
+ ? `/api/storage/${user?.user_metadata.avatar}`
104
+ : undefined
105
+ }
106
+ title={user.email}
107
+ fallback={<LucideIcons.User2 size={18} />}
108
+ classNames={{
109
+ base: "cursor-pointer bg-white/0",
110
+ icon: "text-default-700",
111
+ }}
112
+ />
113
+ ) : (
114
+ <Button size="sm" variant="flat" radius="full" isIconOnly>
115
+ <LucideIcons.User2 size={16} />
116
+ </Button>
117
+ )}
118
+ </DropdownTrigger>
119
+
120
+ <DropdownMenu
121
+ className="relative"
122
+ items={[
123
+ {
124
+ key: "hello",
125
+ label: `${t("hello")} ${
126
+ user?.user_metadata?.full_name || user.email
127
+ }`,
128
+ isReadOnly: true,
129
+ },
130
+ ...accountMenu.map((item) => ({
131
+ key: item.path,
132
+ label: item.title,
133
+ description: item.description,
134
+ icon: item.icon,
135
+ isLogout:
136
+ item.path.includes("signout") || item.path.includes("logout"),
137
+ href:
138
+ item.path.includes("signout") || item.path.includes("logout")
139
+ ? undefined
140
+ : item.path,
141
+ })),
142
+ ]}
143
+ >
144
+ {(item: {
145
+ key: string;
146
+ label: string;
147
+ description?: string;
148
+ icon?: string;
149
+ isLogout?: boolean;
150
+ href?: string;
151
+ isReadOnly?: boolean;
152
+ }) => {
153
+ const Icon = item.icon ? getIcon(item.icon) : null;
120
154
 
121
- <DropdownMenu
122
- items={[
123
- {
124
- key: "hello",
125
- label: `${t("hello")} ${
126
- user?.user_metadata?.full_name || user.email
127
- }`,
128
- isReadOnly: true,
129
- },
130
- ...accountMenu.map((item) => ({
131
- key: item.path,
132
- label: item.title,
133
- description: item.description,
134
- icon: item.icon,
135
- isLogout:
136
- item.path.includes("signout") || item.path.includes("logout"),
137
- href:
138
- item.path.includes("signout") || item.path.includes("logout")
139
- ? undefined
140
- : langHref(item.path, lang),
141
- })),
142
- ]}
143
- >
144
- {(item: {
145
- key: string;
146
- label: string;
147
- description?: string;
148
- icon?: string;
149
- isLogout?: boolean;
150
- href?: string;
151
- isReadOnly?: boolean;
152
- }) => {
153
- const Icon = item.icon ? getIcon(item.icon) : null;
155
+ const handleLogout = async () => {
156
+ try {
157
+ if (onLogout) {
158
+ await onLogout();
159
+ } else {
160
+ // default fallback: sign out via supabase client
161
+ try {
162
+ await supabaseBrowserClient.auth.signOut();
163
+ } catch (_e) {
164
+ // ignore
165
+ }
166
+ }
167
+ } finally {
168
+ try {
169
+ router.push("/");
170
+ } catch (_e) {
171
+ // ignore routing errors
172
+ }
173
+ }
174
+ };
154
175
 
155
- return (
156
- <DropdownItem
157
- key={item.key}
158
- href={item.href}
159
- onPress={item.isLogout ? () => onLogout?.() : undefined}
160
- color={item.isLogout ? "danger" : "default"}
161
- description={!item.isLogout && item.description}
162
- startContent={Icon && <Icon size={16} />}
163
- isDisabled={item.isReadOnly}
164
- isReadOnly={item.isReadOnly}
165
- >
166
- {item.label}
167
- </DropdownItem>
168
- );
169
- }}
170
- </DropdownMenu>
171
- </Dropdown>
176
+ return (
177
+ <DropdownItem
178
+ as={AppLink}
179
+ key={item.key}
180
+ href={item.href}
181
+ onPress={item.isLogout ? handleLogout : undefined}
182
+ color={item.isLogout ? "danger" : "default"}
183
+ description={!item.isLogout && item.description}
184
+ startContent={Icon && <Icon size={16} />}
185
+ isDisabled={item.isReadOnly}
186
+ isReadOnly={item.isReadOnly}
187
+ >
188
+ {item.label}
189
+ </DropdownItem>
190
+ );
191
+ }}
192
+ </DropdownMenu>
193
+ </Dropdown>
194
+ <ThemeSwitcher />
195
+ </div>
172
196
  );
173
197
  };
@@ -93,6 +93,24 @@ export function Doc() {
93
93
  - ResetPassword
94
94
  </span>
95
95
  </div>
96
+ <div className="flex items-start gap-2">
97
+ <Chip size="sm" color="success" variant="flat">
98
+ GET
99
+ </Chip>
100
+ <code className="text-sm">/confirm</code>
101
+ <span className="text-sm text-slate-600 dark:text-slate-400">
102
+ - ConfirmPage
103
+ </span>
104
+ </div>
105
+ <div className="flex items-start gap-2">
106
+ <Chip size="sm" color="success" variant="flat">
107
+ GET
108
+ </Chip>
109
+ <code className="text-sm">/auth-code-error</code>
110
+ <span className="text-sm text-slate-600 dark:text-slate-400">
111
+ - AuthCodeErrorPage
112
+ </span>
113
+ </div>
96
114
  </div>
97
115
  </div>
98
116
  <div>
@@ -100,15 +118,6 @@ export function Doc() {
100
118
  Pages Protégées (Auth)
101
119
  </h3>
102
120
  <div className="space-y-2">
103
- <div className="flex items-start gap-2">
104
- <Chip size="sm" color="primary" variant="flat">
105
- GET
106
- </Chip>
107
- <code className="text-sm">/dashboard</code>
108
- <span className="text-sm text-slate-600 dark:text-slate-400">
109
- - DashboardPage
110
- </span>
111
- </div>
112
121
  <div className="flex items-start gap-2">
113
122
  <Chip size="sm" color="primary" variant="flat">
114
123
  GET
@@ -201,6 +210,25 @@ export function Doc() {
201
210
  </Chip>
202
211
  </div>
203
212
  </div>
213
+ <div>
214
+ <h3 className="text-lg font-semibold mb-2">
215
+ <code>/api/public/callback</code>
216
+ </h3>
217
+ <div className="flex gap-2">
218
+ <Chip size="sm" color="success" variant="flat">
219
+ GET
220
+ </Chip>
221
+ <Chip size="sm" color="primary" variant="flat">
222
+ POST
223
+ </Chip>
224
+ <Chip size="sm" color="warning" variant="flat">
225
+ PUT
226
+ </Chip>
227
+ <Chip size="sm" color="danger" variant="flat">
228
+ DELETE
229
+ </Chip>
230
+ </div>
231
+ </div>
204
232
  <div>
205
233
  <h3 className="text-lg font-semibold mb-2">
206
234
  <code>/api/auth/profile</code>
@@ -284,6 +312,16 @@ export function Doc() {
284
312
  title="user_notifications"
285
313
  description="Table user_notifications du module auth"
286
314
  />
315
+ <TableStructure
316
+ tableName="global_addons"
317
+ title="global_addons"
318
+ description="Table global_addons du module auth"
319
+ />
320
+ <TableStructure
321
+ tableName="user_global_addons"
322
+ title="user_global_addons"
323
+ description="Table user_global_addons du module auth"
324
+ />
287
325
  </CardBody>
288
326
  </Card>
289
327
 
@@ -0,0 +1,63 @@
1
+ import { logger, useModuleTranslation } from "@lastbrain/core";
2
+ import { Alert, AppLink, Skeleton } from "@lastbrain/ui";
3
+ import { useEffect, useState } from "react";
4
+
5
+ export const HasProfil = ({
6
+ hasUpdate,
7
+ }: {
8
+ hasUpdate?: (value: boolean | null) => void;
9
+ }) => {
10
+ const [profil, setProfil] = useState<any>(null);
11
+ const t = useModuleTranslation("auth");
12
+ const [loading, setLoading] = useState(true);
13
+
14
+ useEffect(() => {
15
+ const fetchStats = async () => {
16
+ try {
17
+ const responseProfil = await fetch("/api/auth/profile");
18
+ if (!responseProfil.ok) {
19
+ throw new Error("Failed to load stats");
20
+ }
21
+ const jsonProfil = await responseProfil.json();
22
+
23
+ setProfil(jsonProfil.data);
24
+ if (jsonProfil.data && jsonProfil.data.first_name) {
25
+ hasUpdate?.(true);
26
+ } else {
27
+ hasUpdate?.(false);
28
+ }
29
+ } catch (error) {
30
+ logger.error("[HasProfil] stats error", error);
31
+ } finally {
32
+ setLoading(false);
33
+ }
34
+ };
35
+
36
+ fetchStats();
37
+ }, []);
38
+
39
+ if (loading) {
40
+ return (
41
+ <div className="mb-6 mt-6">
42
+ <Skeleton className="w-full h-12 w-full rounded-md" />
43
+ </div>
44
+ );
45
+ }
46
+ return (
47
+ <div className="mb-6 mt-6">
48
+ {!profil ||
49
+ (!profil.first_name ? (
50
+ <Alert
51
+ color="danger"
52
+ endContent={
53
+ <AppLink className="underline" href="/auth/profile">
54
+ Go
55
+ </AppLink>
56
+ }
57
+ >
58
+ {t("please_complete_your_profile")}
59
+ </Alert>
60
+ ) : null)}
61
+ </div>
62
+ );
63
+ };
@@ -9,10 +9,13 @@ import {
9
9
  Chip,
10
10
  Divider,
11
11
  Avatar,
12
+ Skeleton,
12
13
  } from "@lastbrain/ui";
13
14
 
14
- import { User, Mail, Calendar, Shield } from "lucide-react";
15
+ import { User, Mail, Calendar, Shield, LayoutDashboard } from "lucide-react";
15
16
  import { useModuleTranslation } from "@lastbrain/core";
17
+ import { useRouter } from "next/navigation";
18
+ import { HasProfil } from "../HasProfil";
16
19
 
17
20
  interface UserData {
18
21
  id: string;
@@ -33,6 +36,8 @@ export function DashboardPage() {
33
36
  const [userData, setUserData] = useState<UserData | null>(null);
34
37
  const [isLoading, setIsLoading] = useState(true);
35
38
  const [error, setError] = useState<string | null>(null);
39
+ const [hasUpdate, setHasUpdate] = useState<boolean | null>(null);
40
+ const router = useRouter();
36
41
 
37
42
  useEffect(() => {
38
43
  fetchUserData();
@@ -41,13 +46,36 @@ export function DashboardPage() {
41
46
  const fetchUserData = async () => {
42
47
  try {
43
48
  setIsLoading(true);
44
- const response = await fetch("/api/auth/me");
49
+ const response = await fetch("/api/auth/me", { credentials: "include" });
45
50
 
46
51
  if (!response.ok) {
47
52
  throw new Error("Failed to fetch user data");
48
53
  }
49
54
 
50
55
  const result = await response.json();
56
+
57
+ // Si le serveur a synchronisé la locale côté cookie, rediriger
58
+ // le client vers le même chemin préfixé par la langue du profil.
59
+ if (result?.localeUpdated && result?.profileLang) {
60
+ try {
61
+ const profileLang = result.profileLang as string;
62
+ const pathname = window.location.pathname;
63
+ const search = window.location.search || "";
64
+ // Retirer un préfixe langue existant
65
+ const pathWithoutLang =
66
+ pathname.replace(/^\/[a-z]{2}(?=\/|$)/, "") || "/";
67
+ // Éviter de rediriger vers des routes API
68
+ const safePath = pathWithoutLang.startsWith("/api")
69
+ ? "/"
70
+ : pathWithoutLang;
71
+ const target = `${profileLang.startsWith("/") ? profileLang : "/" + profileLang}${safePath}${search}`;
72
+ router.replace(target);
73
+ return;
74
+ } catch (e) {
75
+ console.debug("dashboard: locale redirect failed", e);
76
+ }
77
+ }
78
+
51
79
  setUserData(result.data);
52
80
  } catch (err) {
53
81
  setError(err instanceof Error ? err.message : "An error occurred");
@@ -84,21 +112,33 @@ export function DashboardPage() {
84
112
  }
85
113
 
86
114
  if (!userData) {
87
- return null;
115
+ return (
116
+ <>
117
+ <HasProfil />
118
+ </>
119
+ );
88
120
  }
89
121
 
90
122
  const fullName =
91
- userData.profile?.first_name && userData.profile?.last_name
123
+ userData?.profile?.first_name && userData.profile?.last_name
92
124
  ? `${userData.profile.first_name} ${userData.profile.last_name}`
93
125
  : "User";
94
126
 
95
127
  return (
96
- <div className="pt-12 pb-12 max-w-6xl mx-auto px-4">
97
- <h1 className="text-3xl font-bold mb-8">
98
- {t("dashboard.title") || "Dashboard"}
99
- </h1>
128
+ <div className="max-w-6xl mx-auto">
129
+ <HasProfil
130
+ hasUpdate={(v) => {
131
+ setHasUpdate(v);
132
+ }}
133
+ />
134
+ <div className="flex flex-inline items-center gap-2 mb-4">
135
+ <LayoutDashboard size={24} />
136
+ <h1 className="text-3xl font-bold ">
137
+ {t("dashboard.title") || "Dashboard"}
138
+ </h1>
139
+ </div>
100
140
 
101
- <div className="grid gap-6 md:grid-cols-2">
141
+ <div className="mx-0 px-0 grid gap-6 md:grid-cols-2">
102
142
  {/* Profile Summary Card */}
103
143
  <Card className="col-span-full md:col-span-1">
104
144
  <CardHeader className="flex gap-3">
@@ -159,15 +199,19 @@ export function DashboardPage() {
159
199
  <span className="text-small">
160
200
  {t("dashboard.profile") || "Profile"}
161
201
  </span>
162
- <Chip
163
- color={userData.profile ? "success" : "warning"}
164
- size="sm"
165
- variant="flat"
166
- >
167
- {userData.profile
168
- ? t("dashboard.complete") || "Complete"
169
- : t("dashboard.incomplete") || "Incomplete"}
170
- </Chip>
202
+ {hasUpdate === null ? (
203
+ <Skeleton className="w-16 h-6 rounded-full" />
204
+ ) : (
205
+ <Chip
206
+ color={hasUpdate ? "success" : "warning"}
207
+ size="sm"
208
+ variant="flat"
209
+ >
210
+ {hasUpdate
211
+ ? t("dashboard.complete") || "Complete"
212
+ : t("dashboard.incomplete") || "Incomplete"}
213
+ </Chip>
214
+ )}
171
215
  </div>
172
216
  </div>
173
217
  </CardBody>
@@ -188,7 +232,7 @@ export function DashboardPage() {
188
232
  </Card>
189
233
  )}
190
234
 
191
- {/* Quick Stats */}
235
+ {/* Quick Stats
192
236
  <Card className="col-span-full">
193
237
  <CardHeader>
194
238
  <h3 className="text-lg font-semibold">Quick Stats</h3>
@@ -219,7 +263,7 @@ export function DashboardPage() {
219
263
  </div>
220
264
  </div>
221
265
  </CardBody>
222
- </Card>
266
+ </Card> */}
223
267
  </div>
224
268
  </div>
225
269
  );