@lastbrain/module-auth 2.0.13 → 2.0.19
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/dist/auth.build.config.d.ts.map +1 -1
- package/dist/auth.build.config.js +33 -47
- package/dist/components/AccountButton.d.ts.map +1 -1
- package/dist/components/AccountButton.js +9 -5
- package/dist/web/admin/signup-stats.d.ts.map +1 -1
- package/dist/web/admin/signup-stats.js +4 -2
- package/dist/web/admin/user-detail.d.ts.map +1 -1
- package/dist/web/admin/user-detail.js +42 -17
- package/dist/web/admin/users-by-signup-source.d.ts.map +1 -1
- package/dist/web/admin/users-by-signup-source.js +18 -7
- package/dist/web/admin/users.d.ts.map +1 -1
- package/dist/web/admin/users.js +11 -6
- package/dist/web/auth/dashboard.d.ts.map +1 -1
- package/dist/web/auth/dashboard.js +7 -3
- package/dist/web/auth/folder.d.ts.map +1 -1
- package/dist/web/auth/folder.js +5 -3
- package/dist/web/auth/profile.d.ts.map +1 -1
- package/dist/web/auth/profile.js +13 -6
- package/dist/web/auth/reglage.d.ts.map +1 -1
- package/dist/web/auth/reglage.js +11 -6
- package/dist/web/public/SignInPage.d.ts.map +1 -1
- package/dist/web/public/SignInPage.js +14 -56
- package/dist/web/public/SignUpPage.d.ts.map +1 -1
- package/dist/web/public/SignUpPage.js +18 -11
- package/package.json +4 -3
- package/src/auth.build.config.ts +34 -48
- package/src/components/AccountButton.tsx +17 -10
- package/src/i18n/en.json +263 -0
- package/src/i18n/fr.json +261 -0
- package/src/web/admin/signup-stats.tsx +10 -3
- package/src/web/admin/user-detail.tsx +135 -56
- package/src/web/admin/users-by-signup-source.tsx +60 -21
- package/src/web/admin/users.tsx +41 -18
- package/src/web/auth/dashboard.tsx +25 -9
- package/src/web/auth/folder.tsx +11 -3
- package/src/web/auth/profile.tsx +63 -29
- package/src/web/auth/reglage.tsx +43 -19
- package/src/web/public/SignInPage.tsx +32 -70
- package/src/web/public/SignUpPage.tsx +48 -26
- package/supabase/migrations/20251112000000_user_init.sql +35 -19
- package/supabase/migrations/20251112000001_auto_profile_and_admin_view.sql +8 -3
- package/supabase/migrations/20251112000002_sync_avatars.sql +7 -1
- package/supabase/migrations/20251124000001_add_get_admin_user_details.sql +2 -1
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
|
|
3
3
|
import { useState, useEffect, useCallback } from "react";
|
|
4
|
+
import { useModuleTranslation, useLanguage } from "@lastbrain/core";
|
|
4
5
|
import {
|
|
5
6
|
Card,
|
|
6
7
|
CardHeader,
|
|
@@ -70,6 +71,8 @@ export function UserDetailPage({
|
|
|
70
71
|
userId,
|
|
71
72
|
moduleUserTabs = [],
|
|
72
73
|
}: UserDetailPageProps) {
|
|
74
|
+
const t = useModuleTranslation("auth");
|
|
75
|
+
const { t: tGlobal } = useLanguage(); // Pour les traductions des autres modules
|
|
73
76
|
const { user: _currentUser } = useAuth();
|
|
74
77
|
const [userProfile, setUserProfile] = useState<UserProfile | null>(null);
|
|
75
78
|
const [loading, setLoading] = useState(true);
|
|
@@ -100,7 +103,11 @@ export function UserDetailPage({
|
|
|
100
103
|
const userDetails = await response.json();
|
|
101
104
|
setUserProfile(userDetails);
|
|
102
105
|
} catch (error) {
|
|
103
|
-
console.error(
|
|
106
|
+
console.error(
|
|
107
|
+
t("user_detail.profile_loading_error") ||
|
|
108
|
+
"Erreur lors du chargement du profil:",
|
|
109
|
+
error
|
|
110
|
+
);
|
|
104
111
|
setUserProfile(null);
|
|
105
112
|
} finally {
|
|
106
113
|
setLoading(false);
|
|
@@ -116,7 +123,9 @@ export function UserDetailPage({
|
|
|
116
123
|
if (!notificationTitle.trim() || !notificationMessage.trim()) {
|
|
117
124
|
addToast({
|
|
118
125
|
color: "danger",
|
|
119
|
-
title:
|
|
126
|
+
title:
|
|
127
|
+
t("user_detail.fill_title_message") ||
|
|
128
|
+
"Veuillez remplir le titre et le message",
|
|
120
129
|
});
|
|
121
130
|
return;
|
|
122
131
|
}
|
|
@@ -149,12 +158,16 @@ export function UserDetailPage({
|
|
|
149
158
|
|
|
150
159
|
addToast({
|
|
151
160
|
color: "success",
|
|
152
|
-
title:
|
|
161
|
+
title:
|
|
162
|
+
t("user_detail.notification_sent_success") ||
|
|
163
|
+
"Notification envoyée avec succès",
|
|
153
164
|
});
|
|
154
165
|
} catch {
|
|
155
166
|
addToast({
|
|
156
167
|
color: "danger",
|
|
157
|
-
title:
|
|
168
|
+
title:
|
|
169
|
+
t("user_detail.notification_send_error") ||
|
|
170
|
+
"Erreur lors de l'envoi de la notification",
|
|
158
171
|
});
|
|
159
172
|
} finally {
|
|
160
173
|
setSendingNotification(false);
|
|
@@ -173,7 +186,9 @@ export function UserDetailPage({
|
|
|
173
186
|
return (
|
|
174
187
|
<Card>
|
|
175
188
|
<CardBody>
|
|
176
|
-
<p className="text-center text-gray-500">
|
|
189
|
+
<p className="text-center text-gray-500">
|
|
190
|
+
{t("user_detail.user_not_found") || "Utilisateur non trouvé"}
|
|
191
|
+
</p>
|
|
177
192
|
</CardBody>
|
|
178
193
|
</Card>
|
|
179
194
|
);
|
|
@@ -204,14 +219,16 @@ export function UserDetailPage({
|
|
|
204
219
|
{userProfile.full_name || userProfile.email}
|
|
205
220
|
</h1>
|
|
206
221
|
<p className="text-sm text-default-500 ">
|
|
207
|
-
<span className="">
|
|
222
|
+
<span className="">
|
|
223
|
+
{t("user_detail.last_login") || "Dernière connexion:"}
|
|
224
|
+
</span>{" "}
|
|
208
225
|
{userProfile.last_sign_in_at
|
|
209
226
|
? new Date(userProfile.last_sign_in_at).toLocaleDateString()
|
|
210
|
-
: "N/A"}{" "}
|
|
211
|
-
à{" "}
|
|
227
|
+
: t("user_detail.not_available") || "N/A"}{" "}
|
|
228
|
+
{t("user_detail.at") || "à"}{" "}
|
|
212
229
|
{userProfile.last_sign_in_at
|
|
213
230
|
? new Date(userProfile.last_sign_in_at).toLocaleTimeString()
|
|
214
|
-
: "N/A"}
|
|
231
|
+
: t("user_detail.not_available") || "N/A"}
|
|
215
232
|
</p>
|
|
216
233
|
</div>
|
|
217
234
|
<p className="text-gray-500">{userProfile.email}</p>
|
|
@@ -221,7 +238,9 @@ export function UserDetailPage({
|
|
|
221
238
|
color={isAdmin ? "danger" : "primary"}
|
|
222
239
|
size="sm"
|
|
223
240
|
>
|
|
224
|
-
{isAdmin
|
|
241
|
+
{isAdmin
|
|
242
|
+
? t("user_detail.administrator") || "Administrateur"
|
|
243
|
+
: t("user_detail.user") || "Utilisateur"}
|
|
225
244
|
</Chip>
|
|
226
245
|
{userProfile.profile?.signup_source && (
|
|
227
246
|
<Chip
|
|
@@ -234,8 +253,8 @@ export function UserDetailPage({
|
|
|
234
253
|
size="sm"
|
|
235
254
|
>
|
|
236
255
|
{userProfile.profile.signup_source.toLowerCase() === "recipe"
|
|
237
|
-
? "Recipe"
|
|
238
|
-
: "LastBrain"}
|
|
256
|
+
? t("user_detail.source_recipe") || "Recipe"
|
|
257
|
+
: t("user_detail.source_lastbrain") || "LastBrain"}
|
|
239
258
|
</Chip>
|
|
240
259
|
)}
|
|
241
260
|
<Snippet color="default" size="sm" symbol="#">
|
|
@@ -252,7 +271,7 @@ export function UserDetailPage({
|
|
|
252
271
|
{/* moduleUserTabs injectés (debug retiré) */}
|
|
253
272
|
|
|
254
273
|
<Tabs
|
|
255
|
-
aria-label="Options utilisateur"
|
|
274
|
+
aria-label={t("user_detail.user_options") || "Options utilisateur"}
|
|
256
275
|
color="primary"
|
|
257
276
|
variant="underlined"
|
|
258
277
|
>
|
|
@@ -262,7 +281,7 @@ export function UserDetailPage({
|
|
|
262
281
|
title={
|
|
263
282
|
<div className="flex items-center space-x-2">
|
|
264
283
|
<User size={16} />
|
|
265
|
-
<span>Profil</span>
|
|
284
|
+
<span>{t("user_detail.tab_profile") || "Profil"}</span>
|
|
266
285
|
</div>
|
|
267
286
|
}
|
|
268
287
|
>
|
|
@@ -272,18 +291,24 @@ export function UserDetailPage({
|
|
|
272
291
|
<CardBody>
|
|
273
292
|
<h3 className="font-semibold text-lg mb-4 flex items-center gap-2">
|
|
274
293
|
<User size={18} />
|
|
275
|
-
Identité
|
|
294
|
+
{t("user_detail.identity_section") || "Identité"}
|
|
276
295
|
</h3>
|
|
277
296
|
<div className="grid grid-cols-1 md:grid-cols-2 gap-4 text-sm">
|
|
278
297
|
<div>
|
|
279
|
-
<span className="text-default-500">
|
|
298
|
+
<span className="text-default-500">
|
|
299
|
+
{t("user_detail.full_name") || "Nom complet"}
|
|
300
|
+
</span>
|
|
280
301
|
<p className="font-medium mt-1">
|
|
281
|
-
{userProfile.full_name ||
|
|
302
|
+
{userProfile.full_name ||
|
|
303
|
+
t("user_detail.not_provided") ||
|
|
304
|
+
"Non renseigné"}
|
|
282
305
|
</p>
|
|
283
306
|
</div>
|
|
284
307
|
{userProfile.profile?.first_name && (
|
|
285
308
|
<div>
|
|
286
|
-
<span className="text-default-500">
|
|
309
|
+
<span className="text-default-500">
|
|
310
|
+
{t("user_detail.first_name") || "Prénom"}
|
|
311
|
+
</span>
|
|
287
312
|
<p className="font-medium mt-1">
|
|
288
313
|
{userProfile.profile.first_name}
|
|
289
314
|
</p>
|
|
@@ -291,19 +316,25 @@ export function UserDetailPage({
|
|
|
291
316
|
)}
|
|
292
317
|
{userProfile.profile?.last_name && (
|
|
293
318
|
<div>
|
|
294
|
-
<span className="text-default-500">
|
|
319
|
+
<span className="text-default-500">
|
|
320
|
+
{t("user_detail.last_name") || "Nom"}
|
|
321
|
+
</span>
|
|
295
322
|
<p className="font-medium mt-1">
|
|
296
323
|
{userProfile.profile.last_name}
|
|
297
324
|
</p>
|
|
298
325
|
</div>
|
|
299
326
|
)}
|
|
300
327
|
<div>
|
|
301
|
-
<span className="text-default-500">
|
|
328
|
+
<span className="text-default-500">
|
|
329
|
+
{t("user_detail.email") || "Email"}
|
|
330
|
+
</span>
|
|
302
331
|
<p className="font-medium mt-1">{userProfile.email}</p>
|
|
303
332
|
</div>
|
|
304
333
|
{userProfile.profile?.phone && (
|
|
305
334
|
<div>
|
|
306
|
-
<span className="text-default-500">
|
|
335
|
+
<span className="text-default-500">
|
|
336
|
+
{t("user_detail.phone") || "Téléphone"}
|
|
337
|
+
</span>
|
|
307
338
|
<p className="font-medium mt-1">
|
|
308
339
|
{userProfile.profile.phone}
|
|
309
340
|
</p>
|
|
@@ -312,7 +343,9 @@ export function UserDetailPage({
|
|
|
312
343
|
</div>
|
|
313
344
|
{userProfile.profile?.bio && (
|
|
314
345
|
<div className="mt-4">
|
|
315
|
-
<span className="text-default-500 text-sm">
|
|
346
|
+
<span className="text-default-500 text-sm">
|
|
347
|
+
{t("user_detail.bio") || "Bio"}
|
|
348
|
+
</span>
|
|
316
349
|
<p className="font-medium mt-1 text-sm">
|
|
317
350
|
{userProfile.profile.bio}
|
|
318
351
|
</p>
|
|
@@ -328,12 +361,15 @@ export function UserDetailPage({
|
|
|
328
361
|
<Card>
|
|
329
362
|
<CardBody>
|
|
330
363
|
<h3 className="font-semibold text-lg mb-4">
|
|
331
|
-
|
|
364
|
+
{t("user_detail.professional_info") ||
|
|
365
|
+
"Informations professionnelles"}
|
|
332
366
|
</h3>
|
|
333
367
|
<div className="grid grid-cols-1 md:grid-cols-2 gap-4 text-sm">
|
|
334
368
|
{userProfile.profile?.company && (
|
|
335
369
|
<div>
|
|
336
|
-
<span className="text-default-500">
|
|
370
|
+
<span className="text-default-500">
|
|
371
|
+
{t("user_detail.company") || "Entreprise"}
|
|
372
|
+
</span>
|
|
337
373
|
<p className="font-medium mt-1">
|
|
338
374
|
{userProfile.profile.company}
|
|
339
375
|
</p>
|
|
@@ -341,7 +377,9 @@ export function UserDetailPage({
|
|
|
341
377
|
)}
|
|
342
378
|
{userProfile.profile?.website && (
|
|
343
379
|
<div>
|
|
344
|
-
<span className="text-default-500">
|
|
380
|
+
<span className="text-default-500">
|
|
381
|
+
{t("user_detail.website") || "Site web"}
|
|
382
|
+
</span>
|
|
345
383
|
<p className="font-medium mt-1">
|
|
346
384
|
<a
|
|
347
385
|
href={userProfile.profile.website}
|
|
@@ -357,7 +395,7 @@ export function UserDetailPage({
|
|
|
357
395
|
{userProfile.profile?.location && (
|
|
358
396
|
<div>
|
|
359
397
|
<span className="text-default-500">
|
|
360
|
-
Localisation
|
|
398
|
+
{t("user_detail.location") || "Localisation"}
|
|
361
399
|
</span>
|
|
362
400
|
<p className="font-medium mt-1">
|
|
363
401
|
{userProfile.profile.location}
|
|
@@ -375,12 +413,14 @@ export function UserDetailPage({
|
|
|
375
413
|
<Card>
|
|
376
414
|
<CardBody>
|
|
377
415
|
<h3 className="font-semibold text-lg mb-4">
|
|
378
|
-
Préférences
|
|
416
|
+
{t("user_detail.preferences") || "Préférences"}
|
|
379
417
|
</h3>
|
|
380
418
|
<div className="grid grid-cols-1 md:grid-cols-2 gap-4 text-sm">
|
|
381
419
|
{userProfile.profile?.language && (
|
|
382
420
|
<div>
|
|
383
|
-
<span className="text-default-500">
|
|
421
|
+
<span className="text-default-500">
|
|
422
|
+
{t("user_detail.language") || "Langue"}
|
|
423
|
+
</span>
|
|
384
424
|
<p className="font-medium mt-1">
|
|
385
425
|
{userProfile.profile.language.toUpperCase()}
|
|
386
426
|
</p>
|
|
@@ -389,7 +429,7 @@ export function UserDetailPage({
|
|
|
389
429
|
{userProfile.profile?.timezone && (
|
|
390
430
|
<div>
|
|
391
431
|
<span className="text-default-500">
|
|
392
|
-
Fuseau horaire
|
|
432
|
+
{t("user_detail.timezone") || "Fuseau horaire"}
|
|
393
433
|
</span>
|
|
394
434
|
<p className="font-medium mt-1">
|
|
395
435
|
{userProfile.profile.timezone}
|
|
@@ -405,11 +445,14 @@ export function UserDetailPage({
|
|
|
405
445
|
<Card>
|
|
406
446
|
<CardBody>
|
|
407
447
|
<h3 className="font-semibold text-lg mb-4">
|
|
408
|
-
|
|
448
|
+
{t("user_detail.account_info") ||
|
|
449
|
+
"Informations du compte"}
|
|
409
450
|
</h3>
|
|
410
451
|
<div className="grid grid-cols-1 md:grid-cols-2 gap-4 text-sm">
|
|
411
452
|
<div>
|
|
412
|
-
<span className="text-default-500">
|
|
453
|
+
<span className="text-default-500">
|
|
454
|
+
{t("user_detail.role") || "Rôle"}
|
|
455
|
+
</span>
|
|
413
456
|
<div className="mt-2">
|
|
414
457
|
<Chip
|
|
415
458
|
variant="flat"
|
|
@@ -417,14 +460,16 @@ export function UserDetailPage({
|
|
|
417
460
|
size="sm"
|
|
418
461
|
>
|
|
419
462
|
{isAdmin
|
|
420
|
-
? "
|
|
421
|
-
|
|
463
|
+
? t("user_detail.administrator") ||
|
|
464
|
+
"Administrateur"
|
|
465
|
+
: t("user_detail.standard_user") ||
|
|
466
|
+
"Utilisateur standard"}
|
|
422
467
|
</Chip>
|
|
423
468
|
</div>
|
|
424
469
|
</div>
|
|
425
470
|
<div>
|
|
426
471
|
<span className="text-default-500">
|
|
427
|
-
Date de création
|
|
472
|
+
{t("user_detail.creation_date") || "Date de création"}
|
|
428
473
|
</span>
|
|
429
474
|
<p className="font-medium mt-1">
|
|
430
475
|
{userProfile.created_at
|
|
@@ -435,12 +480,12 @@ export function UserDetailPage({
|
|
|
435
480
|
month: "long",
|
|
436
481
|
day: "numeric",
|
|
437
482
|
})
|
|
438
|
-
: "N/A"}
|
|
483
|
+
: t("user_detail.not_available") || "N/A"}
|
|
439
484
|
</p>
|
|
440
485
|
</div>
|
|
441
486
|
<div>
|
|
442
487
|
<span className="text-default-500">
|
|
443
|
-
Dernière connexion
|
|
488
|
+
{t("user_detail.last_login") || "Dernière connexion"}
|
|
444
489
|
</span>
|
|
445
490
|
<p className="font-medium mt-1">
|
|
446
491
|
{userProfile.last_sign_in_at
|
|
@@ -453,7 +498,7 @@ export function UserDetailPage({
|
|
|
453
498
|
hour: "2-digit",
|
|
454
499
|
minute: "2-digit",
|
|
455
500
|
})
|
|
456
|
-
: "Jamais"}
|
|
501
|
+
: t("user_detail.never") || "Jamais"}
|
|
457
502
|
</p>
|
|
458
503
|
</div>
|
|
459
504
|
</div>
|
|
@@ -468,25 +513,39 @@ export function UserDetailPage({
|
|
|
468
513
|
title={
|
|
469
514
|
<div className="flex items-center space-x-2">
|
|
470
515
|
<Bell size={16} />
|
|
471
|
-
<span>
|
|
516
|
+
<span>
|
|
517
|
+
{t("user_detail.tab_notifications") || "Notifications"}
|
|
518
|
+
</span>
|
|
472
519
|
</div>
|
|
473
520
|
}
|
|
474
521
|
>
|
|
475
522
|
<div className="space-y-4 mt-4">
|
|
476
|
-
<h3 className="font-semibold">
|
|
523
|
+
<h3 className="font-semibold">
|
|
524
|
+
{t("user_detail.send_notification") ||
|
|
525
|
+
"Envoyer une notification"}
|
|
526
|
+
</h3>
|
|
477
527
|
|
|
478
528
|
<div className="space-y-4">
|
|
479
529
|
<Input
|
|
480
|
-
label=
|
|
481
|
-
|
|
530
|
+
label={
|
|
531
|
+
t("user_detail.notification_title") ||
|
|
532
|
+
"Titre de la notification"
|
|
533
|
+
}
|
|
534
|
+
placeholder={
|
|
535
|
+
t("user_detail.notification_title_placeholder") ||
|
|
536
|
+
"Ex: Nouveau message important"
|
|
537
|
+
}
|
|
482
538
|
value={notificationTitle}
|
|
483
539
|
onChange={(e) => setNotificationTitle(e.target.value)}
|
|
484
540
|
maxLength={100}
|
|
485
541
|
/>
|
|
486
542
|
|
|
487
543
|
<Textarea
|
|
488
|
-
label="Message"
|
|
489
|
-
placeholder=
|
|
544
|
+
label={t("user_detail.notification_message") || "Message"}
|
|
545
|
+
placeholder={
|
|
546
|
+
t("user_detail.notification_message_placeholder") ||
|
|
547
|
+
"Contenu de la notification..."
|
|
548
|
+
}
|
|
490
549
|
value={notificationMessage}
|
|
491
550
|
onChange={(e) => setNotificationMessage(e.target.value)}
|
|
492
551
|
maxLength={500}
|
|
@@ -494,16 +553,28 @@ export function UserDetailPage({
|
|
|
494
553
|
/>
|
|
495
554
|
|
|
496
555
|
<Select
|
|
497
|
-
label=
|
|
556
|
+
label={
|
|
557
|
+
t("user_detail.notification_type") ||
|
|
558
|
+
"Type de notification"
|
|
559
|
+
}
|
|
498
560
|
selectedKeys={[notificationType]}
|
|
499
561
|
onSelectionChange={(keys) =>
|
|
500
562
|
setNotificationType(Array.from(keys)[0] as string)
|
|
501
563
|
}
|
|
502
564
|
>
|
|
503
|
-
<SelectItem key="info">
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
<SelectItem key="
|
|
565
|
+
<SelectItem key="info">
|
|
566
|
+
{t("user_detail.notification_type_info") || "Information"}
|
|
567
|
+
</SelectItem>
|
|
568
|
+
<SelectItem key="warning">
|
|
569
|
+
{t("user_detail.notification_type_warning") ||
|
|
570
|
+
"Avertissement"}
|
|
571
|
+
</SelectItem>
|
|
572
|
+
<SelectItem key="danger">
|
|
573
|
+
{t("user_detail.notification_type_danger") || "Danger"}
|
|
574
|
+
</SelectItem>
|
|
575
|
+
<SelectItem key="success">
|
|
576
|
+
{t("user_detail.notification_type_success") || "Succès"}
|
|
577
|
+
</SelectItem>
|
|
507
578
|
</Select>
|
|
508
579
|
|
|
509
580
|
<Button
|
|
@@ -515,7 +586,7 @@ export function UserDetailPage({
|
|
|
515
586
|
!notificationTitle.trim() || !notificationMessage.trim()
|
|
516
587
|
}
|
|
517
588
|
>
|
|
518
|
-
Envoyer la notification
|
|
589
|
+
{t("user_detail.send_button") || "Envoyer la notification"}
|
|
519
590
|
</Button>
|
|
520
591
|
</div>
|
|
521
592
|
</div>
|
|
@@ -527,29 +598,37 @@ export function UserDetailPage({
|
|
|
527
598
|
title={
|
|
528
599
|
<div className="flex items-center space-x-2">
|
|
529
600
|
<Settings size={16} />
|
|
530
|
-
<span>Paramètres</span>
|
|
601
|
+
<span>{t("user_detail.tab_settings") || "Paramètres"}</span>
|
|
531
602
|
</div>
|
|
532
603
|
}
|
|
533
604
|
>
|
|
534
605
|
<div className="space-y-4 mt-4">
|
|
535
|
-
<h3 className="font-semibold">
|
|
606
|
+
<h3 className="font-semibold">
|
|
607
|
+
{t("user_detail.admin_actions") || "Actions administrateur"}
|
|
608
|
+
</h3>
|
|
536
609
|
|
|
537
610
|
<div className="space-y-3 space-x-5">
|
|
538
611
|
<Button color="warning" variant="bordered" size="sm">
|
|
539
|
-
|
|
612
|
+
{t("user_detail.reset_password_btn") ||
|
|
613
|
+
"Réinitialiser le mot de passe"}
|
|
540
614
|
</Button>
|
|
541
615
|
|
|
542
616
|
<Button color="danger" variant="bordered" size="sm">
|
|
543
|
-
|
|
617
|
+
{t("user_detail.suspend_account_btn") ||
|
|
618
|
+
"Suspendre le compte"}
|
|
544
619
|
</Button>
|
|
545
620
|
|
|
546
621
|
<Button color="secondary" variant="bordered" size="sm">
|
|
547
|
-
|
|
622
|
+
{t("user_detail.promote_admin_btn") ||
|
|
623
|
+
"Promouvoir en administrateur"}
|
|
548
624
|
</Button>
|
|
549
625
|
</div>
|
|
550
626
|
|
|
551
627
|
<div className="mt-6 p-4 bg-gray-50 dark:bg-gray-800 rounded-lg">
|
|
552
|
-
<h4 className="font-medium mb-2">
|
|
628
|
+
<h4 className="font-medium mb-2">
|
|
629
|
+
{t("user_detail.technical_metadata") ||
|
|
630
|
+
"Métadonnées techniques"}
|
|
631
|
+
</h4>
|
|
553
632
|
<pre className="text-xs text-gray-600 dark:text-gray-400 overflow-auto">
|
|
554
633
|
{JSON.stringify(
|
|
555
634
|
{
|
|
@@ -576,7 +655,7 @@ export function UserDetailPage({
|
|
|
576
655
|
title={
|
|
577
656
|
<div className="flex items-center space-x-2">
|
|
578
657
|
{IconComponent && <IconComponent size={16} />}
|
|
579
|
-
<span>{tab.title}</span>
|
|
658
|
+
<span>{tGlobal(tab.title)}</span>
|
|
580
659
|
</div>
|
|
581
660
|
}
|
|
582
661
|
>
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
|
|
3
3
|
import { useEffect, useState } from "react";
|
|
4
|
+
import { useModuleTranslation } from "@lastbrain/core";
|
|
4
5
|
import {
|
|
5
6
|
Card,
|
|
6
7
|
CardBody,
|
|
@@ -37,6 +38,7 @@ interface PaginationData {
|
|
|
37
38
|
}
|
|
38
39
|
|
|
39
40
|
export function UsersBySignupSourcePage() {
|
|
41
|
+
const t = useModuleTranslation("auth");
|
|
40
42
|
const [users, setUsers] = useState<User[]>([]);
|
|
41
43
|
const [loading, setLoading] = useState(true);
|
|
42
44
|
const [error, setError] = useState<string | null>(null);
|
|
@@ -69,7 +71,10 @@ export function UsersBySignupSourcePage() {
|
|
|
69
71
|
const response = await fetch(url);
|
|
70
72
|
|
|
71
73
|
if (!response.ok) {
|
|
72
|
-
throw new Error(
|
|
74
|
+
throw new Error(
|
|
75
|
+
t("users_by_source.loading_error") ||
|
|
76
|
+
"Erreur lors du chargement des utilisateurs"
|
|
77
|
+
);
|
|
73
78
|
}
|
|
74
79
|
|
|
75
80
|
const result = await response.json();
|
|
@@ -88,7 +93,10 @@ export function UsersBySignupSourcePage() {
|
|
|
88
93
|
setPagination(result.pagination);
|
|
89
94
|
} catch (err) {
|
|
90
95
|
setError(
|
|
91
|
-
err instanceof Error
|
|
96
|
+
err instanceof Error
|
|
97
|
+
? err.message
|
|
98
|
+
: t("users_by_source.loading_error_generic") ||
|
|
99
|
+
"Erreur lors du chargement"
|
|
92
100
|
);
|
|
93
101
|
} finally {
|
|
94
102
|
setLoading(false);
|
|
@@ -132,14 +140,18 @@ export function UsersBySignupSourcePage() {
|
|
|
132
140
|
<div className="flex items-center gap-2 mb-8">
|
|
133
141
|
<Users size={28} className="text-primary-600" />
|
|
134
142
|
<h1 className="text-3xl font-bold">
|
|
135
|
-
|
|
143
|
+
{t("users_by_source.title") ||
|
|
144
|
+
"Utilisateurs par source d'inscription"}
|
|
136
145
|
</h1>
|
|
137
146
|
</div>
|
|
138
147
|
|
|
139
148
|
{/* Filters */}
|
|
140
149
|
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
|
|
141
150
|
<Input
|
|
142
|
-
placeholder=
|
|
151
|
+
placeholder={
|
|
152
|
+
t("users_by_source.search_placeholder") ||
|
|
153
|
+
"Rechercher par email ou nom..."
|
|
154
|
+
}
|
|
143
155
|
value={searchQuery}
|
|
144
156
|
onChange={handleSearch}
|
|
145
157
|
startContent={<Search size={16} />}
|
|
@@ -151,45 +163,71 @@ export function UsersBySignupSourcePage() {
|
|
|
151
163
|
/>
|
|
152
164
|
|
|
153
165
|
<Select
|
|
154
|
-
label="Filtrer par source"
|
|
166
|
+
label={t("users_by_source.filter_label") || "Filtrer par source"}
|
|
155
167
|
selectedKeys={[source]}
|
|
156
168
|
onChange={(e) => handleSourceChange(e.target.value)}
|
|
157
169
|
>
|
|
158
|
-
<SelectItem key="">
|
|
159
|
-
|
|
160
|
-
|
|
170
|
+
<SelectItem key="">
|
|
171
|
+
{t("users_by_source.all_sources") || "Toutes les sources"}
|
|
172
|
+
</SelectItem>
|
|
173
|
+
<SelectItem key="lastbrain">
|
|
174
|
+
{t("users_by_source.source_lastbrain") || "LastBrain"}
|
|
175
|
+
</SelectItem>
|
|
176
|
+
<SelectItem key="recipe">
|
|
177
|
+
{t("users_by_source.source_recipe") || "Recipe"}
|
|
178
|
+
</SelectItem>
|
|
161
179
|
</Select>
|
|
162
180
|
</div>
|
|
163
181
|
|
|
164
182
|
{/* Results info */}
|
|
165
183
|
<div className="text-sm text-default-600">
|
|
166
|
-
Affichage de{" "}
|
|
184
|
+
{t("users_by_source.showing") || "Affichage de"}{" "}
|
|
167
185
|
<span className="font-semibold">
|
|
168
186
|
{(pagination.page - 1) * pagination.limit + 1}-
|
|
169
187
|
{Math.min(pagination.page * pagination.limit, pagination.total)}
|
|
170
188
|
</span>{" "}
|
|
171
|
-
|
|
172
|
-
|
|
189
|
+
{t("users_by_source.of") || "sur"}{" "}
|
|
190
|
+
<span className="font-semibold">{pagination.total}</span>{" "}
|
|
191
|
+
{t("users_by_source.users") || "utilisateurs"}
|
|
173
192
|
</div>
|
|
174
193
|
|
|
175
194
|
{/* Users Table */}
|
|
176
195
|
<Card>
|
|
177
196
|
<CardHeader>
|
|
178
|
-
<h3 className="text-lg font-semibold">
|
|
197
|
+
<h3 className="text-lg font-semibold">
|
|
198
|
+
{t("users_by_source.list_title") || "Liste des utilisateurs"}
|
|
199
|
+
</h3>
|
|
179
200
|
</CardHeader>
|
|
180
201
|
<CardBody>
|
|
181
202
|
{loading ? (
|
|
182
203
|
<div className="flex justify-center py-8">
|
|
183
|
-
<Spinner
|
|
204
|
+
<Spinner
|
|
205
|
+
size="lg"
|
|
206
|
+
label={t("users_by_source.loading") || "Chargement..."}
|
|
207
|
+
/>
|
|
184
208
|
</div>
|
|
185
209
|
) : (
|
|
186
210
|
<>
|
|
187
|
-
<Table
|
|
211
|
+
<Table
|
|
212
|
+
aria-label={
|
|
213
|
+
t("users_by_source.table_aria_label") ||
|
|
214
|
+
"Tableau des utilisateurs"
|
|
215
|
+
}
|
|
216
|
+
>
|
|
188
217
|
<TableHeader>
|
|
189
|
-
<TableColumn>
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
<TableColumn>
|
|
218
|
+
<TableColumn>
|
|
219
|
+
{t("users_by_source.column_name") || "Nom"}
|
|
220
|
+
</TableColumn>
|
|
221
|
+
<TableColumn>
|
|
222
|
+
{t("users_by_source.column_email") || "Email"}
|
|
223
|
+
</TableColumn>
|
|
224
|
+
<TableColumn>
|
|
225
|
+
{t("users_by_source.column_source") || "Source"}
|
|
226
|
+
</TableColumn>
|
|
227
|
+
<TableColumn>
|
|
228
|
+
{t("users_by_source.column_signup_date") ||
|
|
229
|
+
"Date d'inscription"}
|
|
230
|
+
</TableColumn>
|
|
193
231
|
</TableHeader>
|
|
194
232
|
<TableBody>
|
|
195
233
|
{users.length > 0 ? (
|
|
@@ -210,8 +248,8 @@ export function UsersBySignupSourcePage() {
|
|
|
210
248
|
variant="flat"
|
|
211
249
|
>
|
|
212
250
|
{user.signup_source.toLowerCase() === "recipe"
|
|
213
|
-
? "
|
|
214
|
-
: "
|
|
251
|
+
? `🍳 ${t("users_by_source.recipe") || "Recipe"}`
|
|
252
|
+
: `🧠 ${t("users_by_source.lastbrain") || "LastBrain"}`}
|
|
215
253
|
</Chip>
|
|
216
254
|
</TableCell>
|
|
217
255
|
<TableCell>
|
|
@@ -234,7 +272,8 @@ export function UsersBySignupSourcePage() {
|
|
|
234
272
|
<TableRow>
|
|
235
273
|
<TableCell colSpan={4} className="text-center py-8">
|
|
236
274
|
<p className="text-default-500">
|
|
237
|
-
|
|
275
|
+
{t("users_by_source.no_users") ||
|
|
276
|
+
"Aucun utilisateur trouvé"}
|
|
238
277
|
</p>
|
|
239
278
|
</TableCell>
|
|
240
279
|
</TableRow>
|