@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,7 +1,7 @@
1
1
  "use client";
2
2
 
3
3
  import { useState, useEffect, useCallback } from "react";
4
- import { useModuleTranslation, useLanguage } from "@lastbrain/core";
4
+ import { useModuleTranslation, useLanguage, logger } from "@lastbrain/core";
5
5
  import {
6
6
  Card,
7
7
  CardHeader,
@@ -18,8 +18,15 @@ import {
18
18
  Spinner,
19
19
  addToast,
20
20
  Snippet,
21
+ StorageUsageCard,
22
+ Table,
23
+ TableHeader,
24
+ TableColumn,
25
+ TableBody,
26
+ TableRow,
27
+ TableCell,
21
28
  } from "@lastbrain/ui";
22
- import { User, Bell, Settings } from "lucide-react";
29
+ import { User, Bell, Settings, HardDrive, Package } from "lucide-react";
23
30
  import { useAuth } from "@lastbrain/core";
24
31
  import * as LucideIcons from "lucide-react";
25
32
 
@@ -81,6 +88,29 @@ export function UserDetailPage({
81
88
  const [notificationMessage, setNotificationMessage] = useState("");
82
89
  const [notificationType, setNotificationType] = useState("info");
83
90
  const [sendingNotification, setSendingNotification] = useState(false);
91
+ const [storageAddons, setStorageAddons] = useState<any[]>([]);
92
+ const [loadingAddons, setLoadingAddons] = useState(false);
93
+
94
+ // Fonction pour charger les addons de stockage de l'utilisateur
95
+ const fetchStorageAddons = useCallback(async () => {
96
+ try {
97
+ setLoadingAddons(true);
98
+ const response = await fetch(
99
+ `/api/admin/billing/user-addons?userId=${userId}`
100
+ );
101
+ if (response.ok) {
102
+ const data = await response.json();
103
+ const storageAddonsOnly = (data.addons || []).filter(
104
+ (addon: any) => addon.addon_type === "storage"
105
+ );
106
+ setStorageAddons(storageAddonsOnly);
107
+ }
108
+ } catch (err) {
109
+ logger.error("Error fetching storage addons:", err);
110
+ } finally {
111
+ setLoadingAddons(false);
112
+ }
113
+ }, [userId]);
84
114
 
85
115
  // Fonction pour charger les données de l'utilisateur
86
116
  const fetchUserProfile = useCallback(async () => {
@@ -92,7 +122,7 @@ export function UserDetailPage({
92
122
 
93
123
  if (!response.ok) {
94
124
  if (response.status === 404) {
95
- console.error("Utilisateur non trouvé");
125
+ logger.error("Utilisateur non trouvé");
96
126
  setUserProfile(null);
97
127
  return;
98
128
  }
@@ -103,7 +133,7 @@ export function UserDetailPage({
103
133
  const userDetails = await response.json();
104
134
  setUserProfile(userDetails);
105
135
  } catch (error) {
106
- console.error(
136
+ logger.error(
107
137
  t("user_detail.profile_loading_error") ||
108
138
  "Erreur lors du chargement du profil:",
109
139
  error
@@ -117,7 +147,8 @@ export function UserDetailPage({
117
147
  // Charger les données de l'utilisateur
118
148
  useEffect(() => {
119
149
  fetchUserProfile();
120
- }, [fetchUserProfile]);
150
+ fetchStorageAddons();
151
+ }, [fetchUserProfile, fetchStorageAddons]);
121
152
 
122
153
  const handleSendNotification = async () => {
123
154
  if (!notificationTitle.trim() || !notificationMessage.trim()) {
@@ -174,9 +205,109 @@ export function UserDetailPage({
174
205
  }
175
206
  };
176
207
 
208
+ const handleResetPassword = async () => {
209
+ try {
210
+ if (!userProfile?.email) {
211
+ addToast({
212
+ color: "danger",
213
+ title: t("user_detail.no_email") || "Email introuvable",
214
+ });
215
+ return;
216
+ }
217
+
218
+ const resp = await fetch(`/api/auth/account/reset-password`, {
219
+ method: "POST",
220
+ headers: { "Content-Type": "application/json" },
221
+ body: JSON.stringify({ email: userProfile.email }),
222
+ });
223
+
224
+ if (!resp.ok) {
225
+ const err = await resp.json().catch(() => ({}));
226
+ throw new Error(
227
+ err?.error || "Erreur lors de l'envoi du lien de réinitialisation"
228
+ );
229
+ }
230
+
231
+ addToast({
232
+ color: "success",
233
+ title:
234
+ t("user_detail.reset_password_sent") ||
235
+ "Lien de réinitialisation envoyé",
236
+ });
237
+ } catch (err: any) {
238
+ addToast({
239
+ color: "danger",
240
+ title:
241
+ t("user_detail.reset_password_error") || (err?.message ?? "Erreur"),
242
+ });
243
+ }
244
+ };
245
+
246
+ const handleSuspendAccount = async () => {
247
+ if (
248
+ !confirm(
249
+ t("user_detail.suspend_confirm") ||
250
+ "Confirmer la suspension du compte ?"
251
+ )
252
+ )
253
+ return;
254
+ try {
255
+ const resp = await fetch(`/api/admin/users/suspend/${userId}`, {
256
+ method: "POST",
257
+ });
258
+ if (!resp.ok) {
259
+ const err = await resp.json().catch(() => ({}));
260
+ throw new Error(err?.error || "Failed to suspend user");
261
+ }
262
+
263
+ addToast({
264
+ color: "success",
265
+ title: t("user_detail.suspend_success") || "Compte suspendu",
266
+ });
267
+ // refresh profile
268
+ fetchUserProfile();
269
+ } catch (err: any) {
270
+ addToast({
271
+ color: "danger",
272
+ title: t("user_detail.suspend_error") || (err?.message ?? "Erreur"),
273
+ });
274
+ }
275
+ };
276
+
277
+ const handleReactivateAccount = async () => {
278
+ if (
279
+ !confirm(
280
+ t("user_detail.reactivate_confirm") ||
281
+ "Confirmer la réactivation du compte ?"
282
+ )
283
+ )
284
+ return;
285
+ try {
286
+ const resp = await fetch(`/api/admin/users/reactivate/${userId}`, {
287
+ method: "POST",
288
+ });
289
+ if (!resp.ok) {
290
+ const err = await resp.json().catch(() => ({}));
291
+ throw new Error(err?.error || "Failed to reactivate user");
292
+ }
293
+
294
+ addToast({
295
+ color: "success",
296
+ title: t("user_detail.reactivate_success") || "Compte réactivé",
297
+ });
298
+ // refresh profile
299
+ fetchUserProfile();
300
+ } catch (err: any) {
301
+ addToast({
302
+ color: "danger",
303
+ title: t("user_detail.reactivate_error") || (err?.message ?? "Erreur"),
304
+ });
305
+ }
306
+ };
307
+
177
308
  if (loading) {
178
309
  return (
179
- <div className="flex justify-center items-center min-h-64">
310
+ <div className="mt-16 flex justify-center items-center min-h-64">
180
311
  <Spinner size="lg" />
181
312
  </div>
182
313
  );
@@ -198,8 +329,14 @@ export function UserDetailPage({
198
329
  Array.isArray(userProfile.raw_app_meta_data?.roles) &&
199
330
  userProfile.raw_app_meta_data.roles.includes("admin");
200
331
 
332
+ const isSuspended = Boolean(
333
+ (userProfile as any).raw_app_meta_data?.suspended ||
334
+ (userProfile as any).raw_app_meta_data?.app_metadata?.suspended ||
335
+ (userProfile as any).app_metadata?.suspended
336
+ );
337
+
201
338
  return (
202
- <div className="max-w-[calc(100vw-8rem)] mx-auto mt-4 space-y-6">
339
+ <div className="mt-16 max-w-7xl mx-auto mt-4 space-y-6 pt-12 ">
203
340
  {/* Header utilisateur */}
204
341
  <Card>
205
342
  <CardHeader className="flex flex-col md:flex-row gap-4">
@@ -242,6 +379,15 @@ export function UserDetailPage({
242
379
  ? t("user_detail.administrator") || "Administrateur"
243
380
  : t("user_detail.user") || "Utilisateur"}
244
381
  </Chip>
382
+ {isSuspended ? (
383
+ <Chip variant="flat" color="danger" size="sm">
384
+ {t("user_detail.suspended") || "Suspendu"}
385
+ </Chip>
386
+ ) : (
387
+ <Chip variant="flat" color="success" size="sm">
388
+ {t("user_detail.active") || "Actif"}
389
+ </Chip>
390
+ )}
245
391
  {userProfile.profile?.signup_source && (
246
392
  <Chip
247
393
  variant="flat"
@@ -608,20 +754,42 @@ export function UserDetailPage({
608
754
  </h3>
609
755
 
610
756
  <div className="space-y-3 space-x-5">
611
- <Button color="warning" variant="bordered" size="sm">
757
+ <Button
758
+ color="warning"
759
+ variant="bordered"
760
+ size="sm"
761
+ onPress={handleResetPassword}
762
+ >
612
763
  {t("user_detail.reset_password_btn") ||
613
764
  "Réinitialiser le mot de passe"}
614
765
  </Button>
615
766
 
616
- <Button color="danger" variant="bordered" size="sm">
617
- {t("user_detail.suspend_account_btn") ||
618
- "Suspendre le compte"}
619
- </Button>
620
-
621
- <Button color="secondary" variant="bordered" size="sm">
767
+ {isSuspended ? (
768
+ <Button
769
+ color="success"
770
+ variant="bordered"
771
+ size="sm"
772
+ onPress={handleReactivateAccount}
773
+ >
774
+ {t("user_detail.reactivate_account_btn") ||
775
+ "Réactiver le compte"}
776
+ </Button>
777
+ ) : (
778
+ <Button
779
+ color="danger"
780
+ variant="bordered"
781
+ size="sm"
782
+ onPress={handleSuspendAccount}
783
+ >
784
+ {t("user_detail.suspend_account_btn") ||
785
+ "Suspendre le compte"}
786
+ </Button>
787
+ )}
788
+
789
+ {/* <Button color="secondary" variant="bordered" size="sm">
622
790
  {t("user_detail.promote_admin_btn") ||
623
791
  "Promouvoir en administrateur"}
624
- </Button>
792
+ </Button> */}
625
793
  </div>
626
794
 
627
795
  <div className="mt-6 p-4 bg-gray-50 dark:bg-gray-800 rounded-lg">
@@ -642,6 +810,137 @@ export function UserDetailPage({
642
810
  </div>
643
811
  </div>
644
812
  </Tab>
813
+
814
+ {/* Tab Stockage */}
815
+ <Tab
816
+ key="storage"
817
+ title={
818
+ <div className="flex items-center space-x-2">
819
+ <HardDrive size={16} />
820
+ <span>{t("user_detail.tab_storage") || "Stockage"}</span>
821
+ </div>
822
+ }
823
+ >
824
+ <div className="space-y-6 mt-4">
825
+ {/* Carte d'usage du stockage */}
826
+ <Card>
827
+ <CardBody>
828
+ <h3 className="font-semibold text-lg mb-4 flex items-center gap-2">
829
+ <HardDrive size={18} />
830
+ {t("user_detail.storage_usage") || "Usage du stockage"}
831
+ </h3>
832
+ <StorageUsageCard
833
+ ownerId={userId}
834
+ title=""
835
+ showDetails={true}
836
+ apiEndpoint="/api/admin/storage/usage"
837
+ />
838
+ </CardBody>
839
+ </Card>
840
+
841
+ {/* Addons de stockage */}
842
+ <Card>
843
+ <CardBody>
844
+ <h3 className="font-semibold text-lg mb-4 flex items-center gap-2">
845
+ <Package size={18} />
846
+ {t("user_detail.storage_addons") ||
847
+ "Addons de stockage actifs"}
848
+ </h3>
849
+
850
+ {loadingAddons ? (
851
+ <div className="flex justify-center py-8">
852
+ <Spinner size="md" />
853
+ </div>
854
+ ) : storageAddons.length === 0 ? (
855
+ <div className="text-center py-8 text-default-500">
856
+ {t("user_detail.no_storage_addons") ||
857
+ "Aucun addon de stockage actif"}
858
+ </div>
859
+ ) : (
860
+ <Table aria-label="Storage addons">
861
+ <TableHeader>
862
+ <TableColumn>NOM</TableColumn>
863
+ <TableColumn>QUOTA</TableColumn>
864
+ <TableColumn>QUANTITÉ</TableColumn>
865
+ <TableColumn>STATUT</TableColumn>
866
+ <TableColumn>DATE D'AJOUT</TableColumn>
867
+ </TableHeader>
868
+ <TableBody>
869
+ {storageAddons.map((addon) => (
870
+ <TableRow key={addon.id}>
871
+ <TableCell>
872
+ <div>
873
+ <div className="font-medium">
874
+ {addon.name}
875
+ </div>
876
+ {addon.description && (
877
+ <div className="text-xs text-default-500">
878
+ {addon.description}
879
+ </div>
880
+ )}
881
+ </div>
882
+ </TableCell>
883
+ <TableCell>
884
+ {addon.quota_value} {addon.quota_unit}
885
+ </TableCell>
886
+ <TableCell>
887
+ <Chip size="sm" variant="flat">
888
+ × {addon.quantity}
889
+ </Chip>
890
+ </TableCell>
891
+ <TableCell>
892
+ <Chip
893
+ size="sm"
894
+ color={
895
+ addon.status === "active"
896
+ ? "success"
897
+ : addon.status === "cancelled"
898
+ ? "warning"
899
+ : "default"
900
+ }
901
+ variant="flat"
902
+ >
903
+ {addon.status === "active"
904
+ ? "Actif"
905
+ : addon.status === "cancelled"
906
+ ? "Résilié"
907
+ : addon.status}
908
+ </Chip>
909
+ </TableCell>
910
+ <TableCell>
911
+ {new Date(addon.created_at).toLocaleDateString(
912
+ "fr-FR",
913
+ {
914
+ year: "numeric",
915
+ month: "short",
916
+ day: "numeric",
917
+ }
918
+ )}
919
+ </TableCell>
920
+ </TableRow>
921
+ ))}
922
+ </TableBody>
923
+ </Table>
924
+ )}
925
+
926
+ {storageAddons.length > 0 && (
927
+ <div className="mt-4 p-3 bg-primary-50 dark:bg-primary-900/20 rounded-lg">
928
+ <p className="text-sm text-primary-700 dark:text-primary-300">
929
+ <strong>Total addons :</strong>{" "}
930
+ {storageAddons.reduce(
931
+ (sum, addon) =>
932
+ sum + addon.quota_value * addon.quantity,
933
+ 0
934
+ )}{" "}
935
+ GB additionnels
936
+ </p>
937
+ </div>
938
+ )}
939
+ </CardBody>
940
+ </Card>
941
+ </div>
942
+ </Tab>
943
+
645
944
  {/* Tabs dynamiques depuis les modules */}
646
945
  {moduleUserTabs.map((tab) => {
647
946
  const TabComponent = tab.component;
@@ -124,7 +124,7 @@ export function UsersBySignupSourcePage() {
124
124
 
125
125
  if (error) {
126
126
  return (
127
- <div className="p-6">
127
+ <div className="mt-16 p-6">
128
128
  <Card className="border border-danger-200 bg-danger-50/50">
129
129
  <CardBody>
130
130
  <p className="text-danger-600">{error}</p>
@@ -135,7 +135,7 @@ export function UsersBySignupSourcePage() {
135
135
  }
136
136
 
137
137
  return (
138
- <div className="space-y-6 p-6">
138
+ <div className="mt-16 space-y-6 p-6">
139
139
  {/* Header */}
140
140
  <div className="flex items-center gap-2 mb-8">
141
141
  <Users size={28} className="text-primary-600" />
@@ -19,7 +19,7 @@ import {
19
19
  Avatar,
20
20
  } from "@lastbrain/ui";
21
21
  import { Search, RefreshCw, Eye, Users2 } from "lucide-react";
22
- import { useModuleTranslation } from "@lastbrain/core";
22
+ import { logger, useModuleTranslation } from "@lastbrain/core";
23
23
  import { useLocalizedRouter } from "@lastbrain/core";
24
24
 
25
25
  interface User {
@@ -35,6 +35,10 @@ interface User {
35
35
  large?: string;
36
36
  };
37
37
  role?: string;
38
+ app_metadata?: {
39
+ suspended?: boolean;
40
+ };
41
+ raw_app_meta_data?: any;
38
42
  }
39
43
 
40
44
  interface PaginationData {
@@ -97,7 +101,7 @@ export function AdminUsersPage() {
97
101
  setError(null);
98
102
  } catch (err) {
99
103
  setError(err instanceof Error ? err.message : "An error occurred");
100
- console.error("Erreur lors du chargement des utilisateurs:", err);
104
+ logger.error("Erreur lors du chargement des utilisateurs:", err);
101
105
  } finally {
102
106
  setIsLoading(false);
103
107
  }
@@ -132,7 +136,7 @@ export function AdminUsersPage() {
132
136
 
133
137
  if (error && users.length === 0) {
134
138
  return (
135
- <div className="pt-12 pb-12 max-w-7xl mx-auto px-4">
139
+ <div className="mt-16 pt-12 pb-12 max-w-7xl mx-auto px-4">
136
140
  <Card>
137
141
  <CardBody>
138
142
  <p className="text-danger">{error}</p>
@@ -143,7 +147,7 @@ export function AdminUsersPage() {
143
147
  }
144
148
 
145
149
  return (
146
- <div className="pt-12 pb-12 max-w-7xl mx-auto px-4">
150
+ <div className="mt-16 pt-12 pb-12 max-w-7xl mx-auto px-4">
147
151
  <div className="flex items-center gap-2 mb-8">
148
152
  <Users2 className="w-8 h-8" />
149
153
  <h1 className="text-3xl font-bold">
@@ -212,13 +216,14 @@ export function AdminUsersPage() {
212
216
  {t("users.column_email") || "EMAIL"}
213
217
  </TableColumn>
214
218
  <TableColumn>{t("users.column_role") || "ROLE"}</TableColumn>
219
+ <TableColumn>{"STATUT"}</TableColumn>
215
220
  <TableColumn>
216
221
  {t("users.column_last_sign_in") || "LAST SIGN IN"}
217
222
  </TableColumn>
218
223
  <TableColumn>
219
224
  {t("users.column_created") || "CREATED"}
220
225
  </TableColumn>
221
- <TableColumn>
226
+ <TableColumn align="end">
222
227
  {t("users.column_actions") || "ACTIONS"}
223
228
  </TableColumn>
224
229
  </TableHeader>
@@ -226,6 +231,24 @@ export function AdminUsersPage() {
226
231
  {users.map((user) => {
227
232
  const displayName = user.full_name || user.email;
228
233
 
234
+ const isSuspended = (() => {
235
+ const u = user as any;
236
+ // check multiple possible locations returned by different RPCs
237
+ if (u.app_metadata?.suspended) return true;
238
+ if (u.raw_app_meta_data?.suspended) return true;
239
+ if (u.raw_app_meta_data?.app_metadata?.suspended)
240
+ return true;
241
+ // some RPCs may wrap app metadata twice
242
+ if (u.raw_app_meta_data?.raw_app_meta_data?.suspended)
243
+ return true;
244
+ if (
245
+ u.raw_app_meta_data?.raw_app_meta_data?.app_metadata
246
+ ?.suspended
247
+ )
248
+ return true;
249
+ return false;
250
+ })();
251
+
229
252
  return (
230
253
  <TableRow key={user.id}>
231
254
  <TableCell>
@@ -257,6 +280,17 @@ export function AdminUsersPage() {
257
280
  {user.role || "user"}
258
281
  </Chip>
259
282
  </TableCell>
283
+ <TableCell>
284
+ <Chip
285
+ size="sm"
286
+ variant="flat"
287
+ color={isSuspended ? "danger" : "success"}
288
+ >
289
+ {isSuspended
290
+ ? t("user_detail.suspended") || "Suspendu"
291
+ : t("user_detail.active") || "Actif"}
292
+ </Chip>
293
+ </TableCell>
260
294
  <TableCell>
261
295
  <span className="text-small">
262
296
  {user.last_sign_in_at
@@ -270,15 +304,17 @@ export function AdminUsersPage() {
270
304
  </span>
271
305
  </TableCell>
272
306
  <TableCell>
273
- <Button
274
- size="sm"
275
- variant="flat"
276
- color="primary"
277
- onPress={() => handleViewUser(user.id)}
278
- startContent={<Eye size={14} />}
279
- >
280
- {t("users.view_button") || "Voir"}
281
- </Button>
307
+ <div className="flex justify-end">
308
+ <Button
309
+ size="sm"
310
+ variant="flat"
311
+ color="primary"
312
+ onPress={() => handleViewUser(user.id)}
313
+ startContent={<Eye size={14} />}
314
+ >
315
+ {t("users.view_button") || "Voir"}
316
+ </Button>
317
+ </div>
282
318
  </TableCell>
283
319
  </TableRow>
284
320
  );
@@ -1,7 +1,13 @@
1
1
  "use client";
2
2
 
3
3
  import { useEffect, useState } from "react";
4
- import { Card, CardBody, Spinner } from "@lastbrain/ui";
4
+ import {
5
+ Alert,
6
+ Card,
7
+ CardBody,
8
+ Spinner,
9
+ StorageUsageCard,
10
+ } from "@lastbrain/ui";
5
11
  import { FileManager } from "@lastbrain/ui";
6
12
  import { useModuleTranslation } from "@lastbrain/core";
7
13
 
@@ -32,7 +38,7 @@ export function FolderPage() {
32
38
  const fetchUserData = async () => {
33
39
  try {
34
40
  setIsLoading(true);
35
- const response = await fetch("/api/auth/me");
41
+ const response = await fetch("/api/auth/me", { credentials: "include" });
36
42
 
37
43
  if (!response.ok) {
38
44
  throw new Error("Failed to fetch user data");
@@ -80,9 +86,16 @@ export function FolderPage() {
80
86
 
81
87
  return (
82
88
  <div className="pt-4 pb-12 max-w-8xl mx-auto px-4">
83
- <h1 className="text-3xl font-bold mb-8">
84
- {t("folder.title") || "Dossier"}
85
- </h1>
89
+ <div className="flex justify-between items-center mb-4 gap-4">
90
+ <h1 className="text-3xl font-bold">{t("folder.title") || "Dossier"}</h1>
91
+
92
+ <StorageUsageCard
93
+ ownerId={userData.id}
94
+ title={t("folder.storage_usage") || "Espace de stockage"}
95
+ showDetails={false}
96
+ size="sm"
97
+ />
98
+ </div>
86
99
 
87
100
  <FileManager
88
101
  bucket="app"
@@ -92,6 +105,11 @@ export function FolderPage() {
92
105
  allowAIImageGeneration={false}
93
106
  className="min-h-[80vh]"
94
107
  />
108
+
109
+ <Alert color="primary" className="mt-6">
110
+ {t("folder.note") ||
111
+ "Note : Cet espace regroupe les fichiers générés par vos différentes applications et modules. Tous les fichiers restent centralisés et accessibles depuis votre espace personnel."}
112
+ </Alert>
95
113
  </div>
96
114
  );
97
115
  }