@lastbrain/module-auth 2.0.27 → 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 (179) 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 +126 -11
  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/components/auth/dashboard.d.ts +1 -1
  74. package/dist/components/auth/dashboard.d.ts.map +1 -1
  75. package/dist/components/auth/dashboard.js +34 -7
  76. package/dist/index.d.ts +2 -0
  77. package/dist/index.d.ts.map +1 -1
  78. package/dist/index.js +2 -0
  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 +126 -11
  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/components/auth/dashboard.tsx +54 -13
  148. package/src/i18n/en.json +76 -8
  149. package/src/i18n/es.json +330 -0
  150. package/src/i18n/fr.json +74 -8
  151. package/src/index.ts +2 -0
  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 +0 -2
  178. package/dist/web/auth/dashboard.d.ts.map +0 -1
  179. 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
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,16 +112,25 @@ 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="md:pt-8 max-w-6xl mx-auto">
128
+ <div className="max-w-6xl mx-auto">
129
+ <HasProfil
130
+ hasUpdate={(v) => {
131
+ setHasUpdate(v);
132
+ }}
133
+ />
97
134
  <div className="flex flex-inline items-center gap-2 mb-4">
98
135
  <LayoutDashboard size={24} />
99
136
  <h1 className="text-3xl font-bold ">
@@ -162,15 +199,19 @@ export function DashboardPage() {
162
199
  <span className="text-small">
163
200
  {t("dashboard.profile") || "Profile"}
164
201
  </span>
165
- <Chip
166
- color={userData.profile ? "success" : "warning"}
167
- size="sm"
168
- variant="flat"
169
- >
170
- {userData.profile
171
- ? t("dashboard.complete") || "Complete"
172
- : t("dashboard.incomplete") || "Incomplete"}
173
- </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
+ )}
174
215
  </div>
175
216
  </div>
176
217
  </CardBody>
package/src/i18n/en.json CHANGED
@@ -17,9 +17,11 @@
17
17
  "hello": "Hello",
18
18
  "menu.notifications": "Notifications",
19
19
  "menu.notifications_desc": "Your notifications",
20
+ "module-auth.folder.storage_usage": "Storage Usage",
20
21
  "menu.account": "Account",
21
22
  "menu.account_desc": "Manage your account",
22
23
  "menu.theme": "Theme",
24
+ "module-auth.or": "OR",
23
25
  "menu.theme_desc": "Change the theme",
24
26
  "menu.dashboard": "Dashboard",
25
27
  "menu.dashboard_desc": "Access your dashboard",
@@ -119,6 +121,19 @@
119
121
  "profile.avatar": "Profile picture",
120
122
  "profile.save": "Save",
121
123
  "profile.saving": "Saving...",
124
+ "profile.security": "Security",
125
+ "profile.security_new_email": "New email",
126
+ "profile.security_new_email_placeholder": "new@email.com",
127
+ "profile.security_email_change_btn": "Send confirmation",
128
+ "profile.security_email_change_sent": "Confirmation sent",
129
+ "profile.security_email_change_desc": "Check the new email to confirm the change",
130
+ "profile.security_email_required": "New email is required",
131
+ "profile.security_error": "Error",
132
+ "profile.security_reset_sent": "Link sent",
133
+ "profile.security_reset_desc": "Check your email to reset your password",
134
+ "profile.security_reset_title": "Reset password",
135
+ "profile.security_reset_help": "We will send a reset link to your email",
136
+ "profile.security_reset_btn": "Send link",
122
137
  "profile.success": "Success",
123
138
  "profile.updated": "Profile updated successfully",
124
139
  "profile.success_title": "Success",
@@ -164,16 +179,20 @@
164
179
  "signup_stats.total": "Total signups",
165
180
  "signup_stats.lastbrain": "LastBrain signups",
166
181
  "signup_stats.recipe": "Recipe signups",
167
- "reset_password.title": "Reset password",
168
- "reset_password.subtitle": "Enter your email to receive a reset link",
169
- "reset_password.email_placeholder": "your@email.com",
170
- "reset_password.submit": "Send link",
171
- "reset_password.success": "Check your email for the reset link",
172
- "reset_password.error": "Error sending link",
173
- "reset_password.back_to_signin": "Back to sign in",
182
+ "module-auth.reset_password.title": "Reset password",
183
+ "module-auth.reset_password.subtitle": "Enter your email to receive a reset link",
184
+ "module-auth.reset_password.email_placeholder": "your@email.com",
185
+ "module-auth.reset_password.submit": "Send link",
186
+ "module-auth.reset_password.success": "Check your email for the reset link",
187
+ "module-auth.reset_password.error": "Error sending link",
188
+ "module-auth.reset_password.link_expired": "Invalid or expired link, please request a new one.",
189
+ "module-auth.reset_password.back_to_signin": "Back to sign in",
174
190
  "folder.loading": "Loading folder...",
175
191
  "folder.error": "Error",
176
192
  "folder.title": "Folder",
193
+ "please_complete_your_profile": "Please complete your profile to continue.",
194
+ "module-auth.dashboard.incomplete": "Incomplete",
195
+
177
196
  "users_by_source.title": "Users by signup source",
178
197
  "users_by_source.search_placeholder": "Search by email or name...",
179
198
  "users_by_source.filter_label": "Filter by source",
@@ -242,6 +261,10 @@
242
261
  "user_detail.notification_type_danger": "Danger",
243
262
  "user_detail.notification_type_success": "Success",
244
263
  "user_detail.send_button": "Send notification",
264
+ "user_detail.tab_storage": "Storage",
265
+ "user_detail.storage_usage": "Storage usage",
266
+ "user_detail.storage_addons": "Active storage addons",
267
+ "user_detail.no_storage_addons": "No active storage addons",
245
268
  "user_detail.tab_settings": "Settings",
246
269
  "user_detail.admin_actions": "Admin actions",
247
270
  "user_detail.reset_password_btn": "Reset password",
@@ -261,5 +284,50 @@
261
284
  "users.column_actions": "ACTIONS",
262
285
  "users.never": "Never",
263
286
  "users.view_button": "View",
264
- "users.showing_results": "Showing {{count}} of {{total}} users"
287
+ "users.showing_results": "Showing {{count}} of {{total}} users",
288
+ "module-auth.settings.account": "Account Settings",
289
+ "module-auth.settings.appearance": "Appearance",
290
+ "module-auth.settings.dark": "Dark",
291
+ "module-auth.settings.email_notifications": "Email Notifications",
292
+ "module-auth.settings.email_notifications_desc": "Receive email notifications for important updates",
293
+ "module-auth.settings.error": "Error",
294
+ "module-auth.settings.language": "Language",
295
+ "module-auth.settings.language_placeholder": "Select a language",
296
+ "module-auth.settings.language_region": "Language & Region",
297
+ "module-auth.settings.lang_de": "Deutsch",
298
+ "module-auth.settings.lang_en": "English",
299
+ "module-auth.settings.lang_es": "Español",
300
+ "module-auth.settings.lang_fr": "Français",
301
+ "module-auth.settings.light": "Light",
302
+ "module-auth.settings.load_error_description": "Failed to load settings",
303
+ "module-auth.settings.load_error_title": "Error",
304
+ "module-auth.settings.loading": "Loading settings...",
305
+ "module-auth.settings.marketing_emails": "Marketing Emails",
306
+ "module-auth.settings.marketing_emails_desc": "Receive emails about new features and updates",
307
+ "module-auth.settings.notifications": "Notifications",
308
+ "module-auth.settings.push_notifications": "Push Notifications",
309
+ "module-auth.settings.push_notifications_desc": "Receive push notifications in your browser",
310
+ "module-auth.settings.reset": "Reset",
311
+ "module-auth.settings.save_button": "Save Settings",
312
+ "module-auth.settings.saving": "Saving...",
313
+ "module-auth.settings.select_theme": "Select a theme",
314
+ "module-auth.settings.success": "Success",
315
+ "module-auth.settings.system": "System",
316
+ "module-auth.settings.theme": "Theme",
317
+ "module-auth.settings.timezone": "Timezone",
318
+ "module-auth.settings.timezone_placeholder": "Select a timezone",
319
+ "module-auth.settings.tz_la": "America/Los_Angeles",
320
+ "module-auth.settings.tz_ny": "America/New_York",
321
+ "module-auth.settings.tz_paris": "Europe/Paris",
322
+ "module-auth.settings.tz_tokyo": "Asia/Tokyo",
323
+ "module-auth.settings.tz_utc": "UTC",
324
+ "module-auth.settings.update_failed": "Failed to update settings",
325
+ "module-auth.settings.updated": "Settings updated successfully",
326
+ "module-auth.user_detail.suspended": "Suspended",
327
+ "module-auth.user_detail.active": "Active",
328
+ "module-auth.user_detail.reactivate_account_btn": "Reactivate account",
329
+ "signin.suspended_title": "Account Suspended",
330
+ "module-auth.folder.note": "Note: This space groups the files generated by your different applications and modules. All files remain centralized and accessible from your personal space.",
331
+
332
+ "signin.suspended_message": "Your account has been suspended. Please contact support for more information."
265
333
  }