@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.
- package/README.md +55 -7
- package/dist/api/admin/signup-stats.d.ts.map +1 -1
- package/dist/api/admin/signup-stats.js +2 -1
- package/dist/api/admin/storage/usage.d.ts +18 -0
- package/dist/api/admin/storage/usage.d.ts.map +1 -0
- package/dist/api/admin/storage/usage.js +100 -0
- package/dist/api/admin/users/[id]/notifications.d.ts.map +1 -1
- package/dist/api/admin/users/[id]/notifications.js +3 -2
- package/dist/api/admin/users/[id].d.ts.map +1 -1
- package/dist/api/admin/users/[id].js +3 -2
- package/dist/api/admin/users/reactivate/[id].d.ts +16 -0
- package/dist/api/admin/users/reactivate/[id].d.ts.map +1 -0
- package/dist/api/admin/users/reactivate/[id].js +59 -0
- package/dist/api/admin/users/suspend/[id].d.ts +16 -0
- package/dist/api/admin/users/suspend/[id].d.ts.map +1 -0
- package/dist/api/admin/users/suspend/[id].js +59 -0
- package/dist/api/admin/users-by-source.d.ts.map +1 -1
- package/dist/api/admin/users-by-source.js +2 -1
- package/dist/api/admin/users.d.ts.map +1 -1
- package/dist/api/admin/users.js +53 -2
- package/dist/api/auth/account/email-change.d.ts +7 -0
- package/dist/api/auth/account/email-change.d.ts.map +1 -0
- package/dist/api/auth/account/email-change.js +39 -0
- package/dist/api/auth/account/reset-password.d.ts +7 -0
- package/dist/api/auth/account/reset-password.d.ts.map +1 -0
- package/dist/api/auth/account/reset-password.js +36 -0
- package/dist/api/auth/check-username.d.ts +9 -0
- package/dist/api/auth/check-username.d.ts.map +1 -0
- package/dist/api/auth/check-username.js +35 -0
- package/dist/api/auth/establish-session.d.ts +2 -0
- package/dist/api/auth/establish-session.d.ts.map +1 -0
- package/dist/api/auth/establish-session.js +23 -0
- package/dist/api/auth/me.d.ts +4 -4
- package/dist/api/auth/me.d.ts.map +1 -1
- package/dist/api/auth/me.js +28 -6
- package/dist/api/auth/profile.d.ts.map +1 -1
- package/dist/api/auth/profile.js +6 -3
- package/dist/api/auth/storage/recalculate.d.ts +15 -0
- package/dist/api/auth/storage/recalculate.d.ts.map +1 -0
- package/dist/api/auth/storage/recalculate.js +68 -0
- package/dist/api/auth/storage/usage.d.ts +10 -0
- package/dist/api/auth/storage/usage.d.ts.map +1 -0
- package/dist/api/auth/storage/usage.js +86 -0
- package/dist/api/public/add-welcome-bonus.d.ts +16 -0
- package/dist/api/public/add-welcome-bonus.d.ts.map +1 -0
- package/dist/api/public/add-welcome-bonus.js +177 -0
- package/dist/api/public/callback.d.ts +3 -0
- package/dist/api/public/callback.d.ts.map +1 -0
- package/dist/api/public/callback.js +197 -0
- package/dist/api/public/reset-password.d.ts +3 -0
- package/dist/api/public/reset-password.d.ts.map +1 -0
- package/dist/api/public/reset-password.js +43 -0
- package/dist/api/public/set-session.d.ts +7 -0
- package/dist/api/public/set-session.d.ts.map +1 -0
- package/dist/api/public/set-session.js +55 -0
- package/dist/api/public/signin.d.ts.map +1 -1
- package/dist/api/public/signin.js +31 -0
- package/dist/api/public/signup.d.ts.map +1 -1
- package/dist/api/public/signup.js +38 -27
- package/dist/api/public/webhook/storage-addon.d.ts +9 -0
- package/dist/api/public/webhook/storage-addon.d.ts.map +1 -0
- package/dist/api/public/webhook/storage-addon.js +155 -0
- package/dist/api/storage.js +2 -2
- package/dist/auth.build.config.d.ts.map +1 -1
- package/dist/auth.build.config.js +126 -11
- package/dist/components/AccountButton.d.ts.map +1 -1
- package/dist/components/AccountButton.js +54 -28
- package/dist/components/Doc.d.ts.map +1 -1
- package/dist/components/Doc.js +1 -1
- package/dist/components/HasProfil.d.ts +4 -0
- package/dist/components/HasProfil.d.ts.map +1 -0
- package/dist/components/HasProfil.js +39 -0
- package/dist/components/auth/dashboard.d.ts +1 -1
- package/dist/components/auth/dashboard.d.ts.map +1 -1
- package/dist/components/auth/dashboard.js +34 -7
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -0
- package/dist/lib/app-branding-data.d.ts +22 -0
- package/dist/lib/app-branding-data.d.ts.map +1 -0
- package/dist/lib/app-branding-data.js +49 -0
- package/dist/lib/auth-email-service.d.ts +57 -0
- package/dist/lib/auth-email-service.d.ts.map +1 -0
- package/dist/lib/auth-email-service.js +382 -0
- package/dist/lib/auth-email-templates.d.ts +2 -0
- package/dist/lib/auth-email-templates.d.ts.map +1 -0
- package/dist/lib/auth-email-templates.js +1 -0
- package/dist/lib/site-url.d.ts +3 -0
- package/dist/lib/site-url.d.ts.map +1 -0
- package/dist/lib/site-url.js +11 -0
- package/dist/sitemap/manifest.d.ts +9 -0
- package/dist/sitemap/manifest.d.ts.map +1 -0
- package/dist/sitemap/manifest.js +14 -0
- package/dist/web/admin/signup-stats.js +3 -3
- package/dist/web/admin/user-detail.d.ts.map +1 -1
- package/dist/web/admin/user-detail.js +135 -14
- package/dist/web/admin/users-by-signup-source.js +2 -2
- package/dist/web/admin/users.d.ts.map +1 -1
- package/dist/web/admin/users.js +26 -7
- package/dist/web/auth/folder.d.ts.map +1 -1
- package/dist/web/auth/folder.js +4 -3
- package/dist/web/auth/profile.d.ts.map +1 -1
- package/dist/web/auth/profile.js +132 -13
- package/dist/web/auth/reglage.d.ts.map +1 -1
- package/dist/web/auth/reglage.js +15 -8
- package/dist/web/public/ResetPassword.d.ts.map +1 -1
- package/dist/web/public/ResetPassword.js +172 -2
- package/dist/web/public/SignInPage.d.ts.map +1 -1
- package/dist/web/public/SignInPage.js +39 -3
- package/dist/web/public/SignUpPage.d.ts.map +1 -1
- package/dist/web/public/SignUpPage.js +7 -2
- package/dist/web/public/auth-code-error.d.ts +2 -0
- package/dist/web/public/auth-code-error.d.ts.map +1 -0
- package/dist/web/public/auth-code-error.js +14 -0
- package/dist/web/public/confirm.d.ts +2 -0
- package/dist/web/public/confirm.d.ts.map +1 -0
- package/dist/web/public/confirm.js +157 -0
- package/package.json +10 -5
- package/src/api/admin/signup-stats.ts +2 -1
- package/src/api/admin/storage/usage.ts +141 -0
- package/src/api/admin/users/[id]/notifications.ts +3 -2
- package/src/api/admin/users/[id].ts +3 -2
- package/src/api/admin/users/reactivate/[id].ts +88 -0
- package/src/api/admin/users/suspend/[id].ts +85 -0
- package/src/api/admin/users-by-source.ts +2 -1
- package/src/api/admin/users.ts +59 -2
- package/src/api/auth/account/email-change.ts +54 -0
- package/src/api/auth/account/reset-password.ts +47 -0
- package/src/api/auth/check-username.ts +52 -0
- package/src/api/auth/establish-session.ts +32 -0
- package/src/api/auth/me.ts +29 -7
- package/src/api/auth/profile.ts +6 -2
- package/src/api/auth/storage/recalculate.ts +108 -0
- package/src/api/auth/storage/usage.ts +113 -0
- package/src/api/public/add-welcome-bonus.ts +229 -0
- package/src/api/public/callback.ts +307 -0
- package/src/api/public/reset-password.ts +52 -0
- package/src/api/public/set-session.ts +73 -0
- package/src/api/public/signin.ts +36 -0
- package/src/api/public/signup.ts +44 -37
- package/src/api/public/webhook/storage-addon.ts +267 -0
- package/src/api/storage.ts +2 -2
- package/src/auth.build.config.ts +126 -11
- package/src/components/AccountButton.tsx +114 -90
- package/src/components/Doc.tsx +47 -9
- package/src/components/HasProfil.tsx +63 -0
- package/src/components/auth/dashboard.tsx +54 -13
- package/src/i18n/en.json +76 -8
- package/src/i18n/es.json +330 -0
- package/src/i18n/fr.json +74 -8
- package/src/index.ts +2 -0
- package/src/lib/app-branding-data.ts +90 -0
- package/src/lib/auth-email-service.ts +508 -0
- package/src/lib/auth-email-templates.ts +5 -0
- package/src/lib/site-url.ts +17 -0
- package/src/sitemap/manifest.ts +26 -0
- package/src/web/admin/signup-stats.tsx +3 -3
- package/src/web/admin/user-detail.tsx +314 -15
- package/src/web/admin/users-by-signup-source.tsx +2 -2
- package/src/web/admin/users.tsx +50 -14
- package/src/web/auth/folder.tsx +23 -5
- package/src/web/auth/profile.tsx +227 -13
- package/src/web/auth/reglage.tsx +55 -24
- package/src/web/public/ResetPassword.tsx +301 -1
- package/src/web/public/SignInPage.tsx +43 -3
- package/src/web/public/SignUpPage.tsx +14 -5
- package/src/web/public/auth-code-error.tsx +49 -0
- package/src/web/public/confirm.tsx +195 -0
- package/supabase/migrations/20251112000001_auto_profile_and_admin_view.sql +3 -1
- package/supabase/migrations/20251127100000_rename_body_to_message.sql +8 -3
- package/supabase/migrations/20260120150001_add_user_storage.sql +105 -0
- package/supabase/migrations/20260122131200_add_global_addons_system.sql +305 -0
- package/supabase/migrations/20260123100000_enable_vector_extension.sql +9 -0
- package/supabase/migrations/20260123140000_module_auth_fix_get_user_limits_null.sql +93 -0
- package/supabase/migrations/20260123145000_module_auth_fix_circular_storage_base.sql +89 -0
- package/supabase/migrations/20260129400000_add_username_to_user_profil.sql +33 -0
- package/dist/web/auth/dashboard.d.ts +0 -2
- package/dist/web/auth/dashboard.d.ts.map +0 -1
- 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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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-
|
|
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
|
|
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
|
-
|
|
617
|
-
|
|
618
|
-
"
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
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" />
|
package/src/web/admin/users.tsx
CHANGED
|
@@ -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
|
-
|
|
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
|
-
<
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
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
|
);
|
package/src/web/auth/folder.tsx
CHANGED
|
@@ -1,7 +1,13 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
|
|
3
3
|
import { useEffect, useState } from "react";
|
|
4
|
-
import {
|
|
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
|
-
<
|
|
84
|
-
{t("folder.title") || "Dossier"}
|
|
85
|
-
|
|
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
|
}
|