@lastbrain/module-auth 0.1.3 → 0.1.4
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 +38 -9
- package/dist/api/admin/users.d.ts +1 -28
- package/dist/api/admin/users.d.ts.map +1 -1
- package/dist/api/admin/users.js +12 -64
- package/dist/api/auth/me.d.ts +3 -3
- package/dist/api/auth/me.d.ts.map +1 -1
- package/dist/api/auth/me.js +3 -5
- package/dist/api/auth/profile.d.ts.map +1 -1
- package/dist/api/auth/profile.js +4 -8
- package/dist/api/public/signin.js +3 -3
- package/dist/api/storage.d.ts.map +1 -1
- package/dist/api/storage.js +1 -1
- package/dist/auth.build.config.d.ts.map +1 -1
- package/dist/auth.build.config.js +21 -2
- package/dist/web/admin/users.d.ts.map +1 -1
- package/dist/web/admin/users.js +16 -9
- package/dist/web/auth/dashboard.d.ts.map +1 -1
- package/dist/web/auth/dashboard.js +2 -2
- package/dist/web/auth/profile.d.ts.map +1 -1
- package/dist/web/auth/profile.js +43 -4
- package/dist/web/auth/reglage.d.ts.map +1 -1
- package/dist/web/public/SignInPage.d.ts.map +1 -1
- package/dist/web/public/SignInPage.js +1 -1
- package/dist/web/public/SignUpPage.js +1 -1
- package/package.json +3 -2
- package/src/api/admin/users.ts +17 -90
- package/src/api/auth/me.ts +8 -17
- package/src/api/auth/profile.ts +10 -24
- package/src/api/public/signin.ts +3 -3
- package/src/api/storage.ts +8 -5
- package/src/auth.build.config.ts +21 -2
- package/src/web/admin/users.tsx +37 -11
- package/src/web/auth/dashboard.tsx +14 -7
- package/src/web/auth/profile.tsx +45 -4
- package/src/web/auth/reglage.tsx +17 -35
- package/src/web/public/SignInPage.tsx +1 -2
- package/src/web/public/SignUpPage.tsx +2 -2
- package/supabase/.temp/cli-latest +1 -0
- package/supabase/migrations/20251112000000_user_init.sql +1 -1
- package/supabase/migrations/20251112000001_auto_profile_and_admin_view.sql +206 -0
- package/supabase/migrations/20251112000002_sync_avatars.sql +54 -0
- package/supabase/migrations-down/20251112000000_user_init.down.sql +2 -0
- package/supabase/migrations-down/20251112000001_auto_profile_and_admin_view.down.sql +23 -0
- package/supabase/migrations-down/20251112000002_sync_avatars.down.sql +9 -0
package/README.md
CHANGED
|
@@ -25,6 +25,7 @@ pnpm add @lastbrain/module-auth
|
|
|
25
25
|
### Prérequis
|
|
26
26
|
|
|
27
27
|
1. **Supabase configuré** : Vous devez avoir un projet Supabase avec les variables d'environnement suivantes :
|
|
28
|
+
|
|
28
29
|
```env
|
|
29
30
|
NEXT_PUBLIC_SUPABASE_URL=https://your-project.supabase.co
|
|
30
31
|
NEXT_PUBLIC_SUPABASE_ANON_KEY=your-anon-key
|
|
@@ -65,15 +66,18 @@ Le module fournit plusieurs composants React prêts à l'emploi utilisant HeroUI
|
|
|
65
66
|
### Pages Utilisateur Authentifié
|
|
66
67
|
|
|
67
68
|
#### 1. Dashboard (`/auth/dashboard`)
|
|
69
|
+
|
|
68
70
|
**Component**: `DashboardPage`
|
|
69
71
|
|
|
70
72
|
Tableau de bord utilisateur affichant :
|
|
73
|
+
|
|
71
74
|
- Résumé du profil avec avatar
|
|
72
75
|
- Email et date de création du compte
|
|
73
76
|
- Statut du compte
|
|
74
77
|
- Statistiques rapides (projets, tâches, etc.)
|
|
75
78
|
|
|
76
79
|
**Usage** :
|
|
80
|
+
|
|
77
81
|
```tsx
|
|
78
82
|
import { DashboardPage } from "@lastbrain/module-auth";
|
|
79
83
|
|
|
@@ -83,14 +87,17 @@ export default function Dashboard() {
|
|
|
83
87
|
```
|
|
84
88
|
|
|
85
89
|
#### 2. Profil (`/auth/profile`)
|
|
90
|
+
|
|
86
91
|
**Component**: `ProfilePage`
|
|
87
92
|
|
|
88
93
|
Page de modification du profil utilisateur permettant de mettre à jour :
|
|
94
|
+
|
|
89
95
|
- Informations personnelles (nom, prénom, téléphone, bio)
|
|
90
96
|
- Informations professionnelles (entreprise, site web, localisation)
|
|
91
97
|
- Préférences (langue, timezone, avatar)
|
|
92
98
|
|
|
93
99
|
**Usage** :
|
|
100
|
+
|
|
94
101
|
```tsx
|
|
95
102
|
import { ProfilePage } from "@lastbrain/module-auth";
|
|
96
103
|
|
|
@@ -100,14 +107,17 @@ export default function Profile() {
|
|
|
100
107
|
```
|
|
101
108
|
|
|
102
109
|
#### 3. Paramètres (`/auth/settings`)
|
|
110
|
+
|
|
103
111
|
**Component**: `ReglagePage`
|
|
104
112
|
|
|
105
113
|
Page de configuration du compte avec :
|
|
114
|
+
|
|
106
115
|
- Notifications (email, push, marketing)
|
|
107
116
|
- Apparence (thème)
|
|
108
117
|
- Langue et région (langue, timezone)
|
|
109
118
|
|
|
110
119
|
**Usage** :
|
|
120
|
+
|
|
111
121
|
```tsx
|
|
112
122
|
import { ReglagePage } from "@lastbrain/module-auth";
|
|
113
123
|
|
|
@@ -119,11 +129,13 @@ export default function Settings() {
|
|
|
119
129
|
### Pages Publiques
|
|
120
130
|
|
|
121
131
|
#### 4. Connexion (`/signin`)
|
|
132
|
+
|
|
122
133
|
**Component**: `SignInPage`
|
|
123
134
|
|
|
124
135
|
Page de connexion avec gestion des erreurs et redirection.
|
|
125
136
|
|
|
126
137
|
**Usage** :
|
|
138
|
+
|
|
127
139
|
```tsx
|
|
128
140
|
import { SignInPage } from "@lastbrain/module-auth";
|
|
129
141
|
|
|
@@ -133,11 +145,13 @@ export default function SignIn() {
|
|
|
133
145
|
```
|
|
134
146
|
|
|
135
147
|
#### 5. Inscription (`/signup`)
|
|
148
|
+
|
|
136
149
|
**Component**: `SignUpPage`
|
|
137
150
|
|
|
138
151
|
Page d'inscription avec validation des champs.
|
|
139
152
|
|
|
140
153
|
**Usage** :
|
|
154
|
+
|
|
141
155
|
```tsx
|
|
142
156
|
import { SignUpPage } from "@lastbrain/module-auth";
|
|
143
157
|
|
|
@@ -149,18 +163,21 @@ export default function SignUp() {
|
|
|
149
163
|
### Pages Administration
|
|
150
164
|
|
|
151
165
|
#### 6. Gestion des Utilisateurs (`/admin/users`)
|
|
166
|
+
|
|
152
167
|
**Component**: `AdminUsersPage`
|
|
153
168
|
|
|
154
169
|
Page d'administration pour lister et gérer tous les utilisateurs.
|
|
155
170
|
**Accès réservé aux super admins uniquement.**
|
|
156
171
|
|
|
157
172
|
Fonctionnalités :
|
|
173
|
+
|
|
158
174
|
- Liste de tous les utilisateurs
|
|
159
175
|
- Recherche par email ou nom
|
|
160
176
|
- Pagination
|
|
161
177
|
- Affichage des informations (email, rôle, date de création)
|
|
162
178
|
|
|
163
179
|
**Usage** :
|
|
180
|
+
|
|
164
181
|
```tsx
|
|
165
182
|
import { AdminUsersPage } from "@lastbrain/module-auth";
|
|
166
183
|
|
|
@@ -176,9 +193,11 @@ Le module expose plusieurs routes API pour la gestion des utilisateurs et profil
|
|
|
176
193
|
### API Authentification (`/api/auth/*`)
|
|
177
194
|
|
|
178
195
|
#### GET `/api/auth/me`
|
|
196
|
+
|
|
179
197
|
Récupère les informations de l'utilisateur connecté et son profil.
|
|
180
198
|
|
|
181
199
|
**Réponse** :
|
|
200
|
+
|
|
182
201
|
```json
|
|
183
202
|
{
|
|
184
203
|
"data": {
|
|
@@ -203,12 +222,15 @@ Récupère les informations de l'utilisateur connecté et son profil.
|
|
|
203
222
|
```
|
|
204
223
|
|
|
205
224
|
**Erreurs** :
|
|
225
|
+
|
|
206
226
|
- `401` : Utilisateur non authentifié
|
|
207
227
|
|
|
208
228
|
#### GET `/api/auth/profile`
|
|
229
|
+
|
|
209
230
|
Récupère le profil de l'utilisateur connecté.
|
|
210
231
|
|
|
211
232
|
**Réponse** :
|
|
233
|
+
|
|
212
234
|
```json
|
|
213
235
|
{
|
|
214
236
|
"data": {
|
|
@@ -222,9 +244,11 @@ Récupère le profil de l'utilisateur connecté.
|
|
|
222
244
|
```
|
|
223
245
|
|
|
224
246
|
#### PUT/PATCH `/api/auth/profile`
|
|
247
|
+
|
|
225
248
|
Met à jour le profil de l'utilisateur. Crée le profil s'il n'existe pas.
|
|
226
249
|
|
|
227
250
|
**Body** :
|
|
251
|
+
|
|
228
252
|
```json
|
|
229
253
|
{
|
|
230
254
|
"first_name": "John",
|
|
@@ -245,6 +269,7 @@ Met à jour le profil de l'utilisateur. Crée le profil s'il n'existe pas.
|
|
|
245
269
|
```
|
|
246
270
|
|
|
247
271
|
**Réponse** :
|
|
272
|
+
|
|
248
273
|
```json
|
|
249
274
|
{
|
|
250
275
|
"data": {
|
|
@@ -261,14 +286,17 @@ Met à jour le profil de l'utilisateur. Crée le profil s'il n'existe pas.
|
|
|
261
286
|
⚠️ **Toutes les routes admin nécessitent un accès super admin.**
|
|
262
287
|
|
|
263
288
|
#### GET `/api/admin/users`
|
|
289
|
+
|
|
264
290
|
Liste tous les utilisateurs (paginé).
|
|
265
291
|
|
|
266
292
|
**Query Parameters** :
|
|
293
|
+
|
|
267
294
|
- `page` (optionnel, défaut: 1) : Numéro de page
|
|
268
295
|
- `per_page` (optionnel, défaut: 20) : Nombre d'éléments par page
|
|
269
296
|
- `search` (optionnel) : Recherche par email ou nom
|
|
270
297
|
|
|
271
298
|
**Réponse** :
|
|
299
|
+
|
|
272
300
|
```json
|
|
273
301
|
{
|
|
274
302
|
"data": [
|
|
@@ -293,6 +321,7 @@ Liste tous les utilisateurs (paginé).
|
|
|
293
321
|
```
|
|
294
322
|
|
|
295
323
|
**Erreurs** :
|
|
324
|
+
|
|
296
325
|
- `401` : Utilisateur non authentifié
|
|
297
326
|
- `403` : Accès refusé (non super admin)
|
|
298
327
|
|
|
@@ -338,9 +367,11 @@ CREATE TABLE public.user_profil (
|
|
|
338
367
|
```
|
|
339
368
|
|
|
340
369
|
**Index** :
|
|
370
|
+
|
|
341
371
|
- `idx_user_profil_owner_id` sur `owner_id`
|
|
342
372
|
|
|
343
373
|
**RLS (Row Level Security)** :
|
|
374
|
+
|
|
344
375
|
- Les utilisateurs peuvent lire, créer, modifier et supprimer leur propre profil
|
|
345
376
|
- Les super admins ont accès à tous les profils
|
|
346
377
|
|
|
@@ -446,6 +477,7 @@ NEXT_PUBLIC_SUPABASE_ANON_KEY=your-anon-key
|
|
|
446
477
|
### Row Level Security (RLS)
|
|
447
478
|
|
|
448
479
|
Toutes les tables sont protégées par RLS :
|
|
480
|
+
|
|
449
481
|
- Les utilisateurs ne peuvent accéder qu'à leurs propres données
|
|
450
482
|
- Les super admins ont accès à toutes les données
|
|
451
483
|
|
|
@@ -459,10 +491,7 @@ const { data: isSuperAdmin } = await supabase.rpc("is_superadmin", {
|
|
|
459
491
|
});
|
|
460
492
|
|
|
461
493
|
if (!isSuperAdmin) {
|
|
462
|
-
return NextResponse.json(
|
|
463
|
-
{ error: "Forbidden" },
|
|
464
|
-
{ status: 403 }
|
|
465
|
-
);
|
|
494
|
+
return NextResponse.json({ error: "Forbidden" }, { status: 403 });
|
|
466
495
|
}
|
|
467
496
|
```
|
|
468
497
|
|
|
@@ -471,13 +500,13 @@ if (!isSuperAdmin) {
|
|
|
471
500
|
Les routes API vérifient l'authentification :
|
|
472
501
|
|
|
473
502
|
```typescript
|
|
474
|
-
const {
|
|
503
|
+
const {
|
|
504
|
+
data: { user },
|
|
505
|
+
error,
|
|
506
|
+
} = await supabase.auth.getUser();
|
|
475
507
|
|
|
476
508
|
if (error || !user) {
|
|
477
|
-
return NextResponse.json(
|
|
478
|
-
{ error: "Unauthorized" },
|
|
479
|
-
{ status: 401 }
|
|
480
|
-
);
|
|
509
|
+
return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
|
|
481
510
|
}
|
|
482
511
|
```
|
|
483
512
|
|
|
@@ -5,32 +5,5 @@ import { NextRequest, NextResponse } from "next/server";
|
|
|
5
5
|
* Supports pagination via query params: page, per_page
|
|
6
6
|
* Supports search via query param: search (email)
|
|
7
7
|
*/
|
|
8
|
-
export declare function GET(request: NextRequest): Promise<NextResponse<
|
|
9
|
-
error: string;
|
|
10
|
-
message: string;
|
|
11
|
-
}> | NextResponse<{
|
|
12
|
-
data: {
|
|
13
|
-
id: any;
|
|
14
|
-
email: string;
|
|
15
|
-
created_at: any;
|
|
16
|
-
profile: {
|
|
17
|
-
first_name: any;
|
|
18
|
-
last_name: any;
|
|
19
|
-
avatar_url: any;
|
|
20
|
-
bio: any;
|
|
21
|
-
phone: any;
|
|
22
|
-
company: any;
|
|
23
|
-
website: any;
|
|
24
|
-
location: any;
|
|
25
|
-
language: any;
|
|
26
|
-
timezone: any;
|
|
27
|
-
};
|
|
28
|
-
}[];
|
|
29
|
-
pagination: {
|
|
30
|
-
page: number;
|
|
31
|
-
per_page: number;
|
|
32
|
-
total: number;
|
|
33
|
-
total_pages: number;
|
|
34
|
-
};
|
|
35
|
-
}>>;
|
|
8
|
+
export declare function GET(request: NextRequest): Promise<NextResponse<any>>;
|
|
36
9
|
//# sourceMappingURL=users.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"users.d.ts","sourceRoot":"","sources":["../../../src/api/admin/users.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAGxD;;;;;GAKG;AACH,wBAAsB,GAAG,CAAC,OAAO,EAAE,WAAW
|
|
1
|
+
{"version":3,"file":"users.d.ts","sourceRoot":"","sources":["../../../src/api/admin/users.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAGxD;;;;;GAKG;AACH,wBAAsB,GAAG,CAAC,OAAO,EAAE,WAAW,8BAyC7C"}
|
package/dist/api/admin/users.js
CHANGED
|
@@ -9,76 +9,24 @@ import { getSupabaseServerClient } from "@lastbrain/core/server";
|
|
|
9
9
|
export async function GET(request) {
|
|
10
10
|
try {
|
|
11
11
|
const supabase = await getSupabaseServerClient();
|
|
12
|
-
//
|
|
13
|
-
const { data: { user }, error: authError, } = await supabase.auth.getUser();
|
|
14
|
-
if (authError || !user) {
|
|
15
|
-
return NextResponse.json({ error: "Unauthorized", message: "User not authenticated" }, { status: 401 });
|
|
16
|
-
}
|
|
17
|
-
// Check if user is superadmin
|
|
18
|
-
const { data: isSuperAdmin } = await supabase.rpc("is_superadmin", {
|
|
19
|
-
user_id: user.id,
|
|
20
|
-
});
|
|
21
|
-
if (!isSuperAdmin) {
|
|
22
|
-
return NextResponse.json({ error: "Forbidden", message: "Superadmin access required" }, { status: 403 });
|
|
23
|
-
}
|
|
12
|
+
// L'authentification et les droits superadmin sont déjà vérifiés par le middleware
|
|
24
13
|
// Get query parameters
|
|
25
14
|
const searchParams = request.nextUrl.searchParams;
|
|
26
15
|
const page = parseInt(searchParams.get("page") || "1");
|
|
27
16
|
const perPage = parseInt(searchParams.get("per_page") || "20");
|
|
28
17
|
const search = searchParams.get("search") || "";
|
|
29
|
-
//
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
.order("created_at", { ascending: false });
|
|
35
|
-
// Add search filter if provided - using Supabase's built-in parameterized query
|
|
36
|
-
if (search) {
|
|
37
|
-
// Using .ilike with % wildcards - Supabase handles escaping
|
|
38
|
-
query = query.or(`first_name.ilike.%${search}%,last_name.ilike.%${search}%`);
|
|
39
|
-
}
|
|
40
|
-
// Apply pagination
|
|
41
|
-
const start = (page - 1) * perPage;
|
|
42
|
-
query = query.range(start, start + perPage - 1);
|
|
43
|
-
const { data: profiles, error: profileError, count } = await query;
|
|
44
|
-
if (profileError) {
|
|
45
|
-
console.error("Error fetching users:", profileError);
|
|
46
|
-
return NextResponse.json({ error: "Database Error", message: profileError.message }, { status: 500 });
|
|
47
|
-
}
|
|
48
|
-
// Get auth users data in batch - more efficient than individual calls
|
|
49
|
-
// Note: auth.admin methods require service role, so results may be limited
|
|
50
|
-
// In production, consider creating a database view or RPC function to join
|
|
51
|
-
// user_profil with auth.users for better performance
|
|
52
|
-
const users = await Promise.all((profiles || []).map(async (profile) => {
|
|
53
|
-
// Get basic auth info - this is limited to what's available
|
|
54
|
-
const { data: authData } = await supabase.auth.admin.getUserById(profile.owner_id);
|
|
55
|
-
return {
|
|
56
|
-
id: profile.owner_id,
|
|
57
|
-
email: authData?.user?.email || "N/A",
|
|
58
|
-
created_at: authData?.user?.created_at || profile.created_at,
|
|
59
|
-
profile: {
|
|
60
|
-
first_name: profile.first_name,
|
|
61
|
-
last_name: profile.last_name,
|
|
62
|
-
avatar_url: profile.avatar_url,
|
|
63
|
-
bio: profile.bio,
|
|
64
|
-
phone: profile.phone,
|
|
65
|
-
company: profile.company,
|
|
66
|
-
website: profile.website,
|
|
67
|
-
location: profile.location,
|
|
68
|
-
language: profile.language,
|
|
69
|
-
timezone: profile.timezone,
|
|
70
|
-
},
|
|
71
|
-
};
|
|
72
|
-
}));
|
|
73
|
-
return NextResponse.json({
|
|
74
|
-
data: users,
|
|
75
|
-
pagination: {
|
|
76
|
-
page,
|
|
77
|
-
per_page: perPage,
|
|
78
|
-
total: count || 0,
|
|
79
|
-
total_pages: Math.ceil((count || 0) / perPage),
|
|
80
|
-
},
|
|
18
|
+
// Use RPC function to get users with emails
|
|
19
|
+
const { data: result, error: usersError } = await supabase.rpc("get_admin_users", {
|
|
20
|
+
page_number: page,
|
|
21
|
+
page_size: perPage,
|
|
22
|
+
search_term: search,
|
|
81
23
|
});
|
|
24
|
+
if (usersError) {
|
|
25
|
+
console.error("Error fetching users:", usersError);
|
|
26
|
+
return NextResponse.json({ error: "Database Error", message: usersError.message }, { status: 500 });
|
|
27
|
+
}
|
|
28
|
+
// The RPC function returns the complete response with data and pagination
|
|
29
|
+
return NextResponse.json(result || { data: [], pagination: { page, per_page: perPage, total: 0, total_pages: 0 } });
|
|
82
30
|
}
|
|
83
31
|
catch (error) {
|
|
84
32
|
console.error("Error in admin users endpoint:", error);
|
package/dist/api/auth/me.d.ts
CHANGED
|
@@ -4,14 +4,14 @@ import { NextResponse } from "next/server";
|
|
|
4
4
|
* Returns the current authenticated user and their profile
|
|
5
5
|
*/
|
|
6
6
|
export declare function GET(): Promise<NextResponse<{
|
|
7
|
-
error: string;
|
|
8
|
-
message: string;
|
|
9
|
-
}> | NextResponse<{
|
|
10
7
|
data: {
|
|
11
8
|
id: string;
|
|
12
9
|
email: string | undefined;
|
|
13
10
|
created_at: string;
|
|
14
11
|
profile: any;
|
|
15
12
|
};
|
|
13
|
+
}> | NextResponse<{
|
|
14
|
+
error: string;
|
|
15
|
+
message: string;
|
|
16
16
|
}>>;
|
|
17
17
|
//# sourceMappingURL=me.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"me.d.ts","sourceRoot":"","sources":["../../../src/api/auth/me.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAG3C;;;GAGG;AACH,wBAAsB,GAAG;;;;;;;;;;
|
|
1
|
+
{"version":3,"file":"me.d.ts","sourceRoot":"","sources":["../../../src/api/auth/me.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAG3C;;;GAGG;AACH,wBAAsB,GAAG;;;;;;;;;;IA+BxB"}
|
package/dist/api/auth/me.js
CHANGED
|
@@ -8,12 +8,10 @@ export async function GET() {
|
|
|
8
8
|
try {
|
|
9
9
|
const supabase = await getSupabaseServerClient();
|
|
10
10
|
// Get the authenticated user
|
|
11
|
-
const { data: { user }
|
|
12
|
-
|
|
13
|
-
return NextResponse.json({ error: "Unauthorized", message: "User not authenticated" }, { status: 401 });
|
|
14
|
-
}
|
|
11
|
+
const { data: { user } } = await supabase.auth.getUser();
|
|
12
|
+
// L'utilisateur est déjà authentifié grâce au middleware
|
|
15
13
|
// Get user profile
|
|
16
|
-
const { data: profile
|
|
14
|
+
const { data: profile } = await supabase
|
|
17
15
|
.from("user_profil")
|
|
18
16
|
.select("*")
|
|
19
17
|
.eq("owner_id", user.id)
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"profile.d.ts","sourceRoot":"","sources":["../../../src/api/auth/profile.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAGxD;;;GAGG;AACH,wBAAsB,GAAG;;;;;
|
|
1
|
+
{"version":3,"file":"profile.d.ts","sourceRoot":"","sources":["../../../src/api/auth/profile.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAGxD;;;GAGG;AACH,wBAAsB,GAAG;;;;;IA+BxB;AAED;;;GAGG;AACH,wBAAsB,GAAG,CAAC,OAAO,EAAE,WAAW;;;;;IAyF7C;AAED;;;GAGG;AACH,wBAAsB,KAAK,CAAC,OAAO,EAAE,WAAW;;;;;IAE/C"}
|
package/dist/api/auth/profile.js
CHANGED
|
@@ -7,10 +7,8 @@ import { getSupabaseServerClient } from "@lastbrain/core/server";
|
|
|
7
7
|
export async function GET() {
|
|
8
8
|
try {
|
|
9
9
|
const supabase = await getSupabaseServerClient();
|
|
10
|
-
const { data: { user },
|
|
11
|
-
|
|
12
|
-
return NextResponse.json({ error: "Unauthorized", message: "User not authenticated" }, { status: 401 });
|
|
13
|
-
}
|
|
10
|
+
const { data: { user }, } = await supabase.auth.getUser();
|
|
11
|
+
// L'utilisateur est déjà authentifié grâce au middleware
|
|
14
12
|
const { data: profile, error: profileError } = await supabase
|
|
15
13
|
.from("user_profil")
|
|
16
14
|
.select("*")
|
|
@@ -34,10 +32,8 @@ export async function GET() {
|
|
|
34
32
|
export async function PUT(request) {
|
|
35
33
|
try {
|
|
36
34
|
const supabase = await getSupabaseServerClient();
|
|
37
|
-
const { data: { user },
|
|
38
|
-
|
|
39
|
-
return NextResponse.json({ error: "Unauthorized", message: "User not authenticated" }, { status: 401 });
|
|
40
|
-
}
|
|
35
|
+
const { data: { user }, } = await supabase.auth.getUser();
|
|
36
|
+
// L'utilisateur est déjà authentifié grâce au middleware
|
|
41
37
|
const body = await request.json();
|
|
42
38
|
const { first_name, last_name, avatar_url, bio, phone, company, website, location, language, timezone, preferences, } = body;
|
|
43
39
|
// Check if profile exists
|
|
@@ -2,9 +2,9 @@ import { getSupabaseServerClient } from "@lastbrain/core/server";
|
|
|
2
2
|
const jsonResponse = (payload, status = 200) => {
|
|
3
3
|
return new Response(JSON.stringify(payload), {
|
|
4
4
|
headers: {
|
|
5
|
-
"content-type": "application/json"
|
|
5
|
+
"content-type": "application/json",
|
|
6
6
|
},
|
|
7
|
-
status
|
|
7
|
+
status,
|
|
8
8
|
});
|
|
9
9
|
};
|
|
10
10
|
export async function POST(request) {
|
|
@@ -16,7 +16,7 @@ export async function POST(request) {
|
|
|
16
16
|
}
|
|
17
17
|
const { error, data } = await supabase.auth.signInWithPassword({
|
|
18
18
|
email,
|
|
19
|
-
password
|
|
19
|
+
password,
|
|
20
20
|
});
|
|
21
21
|
if (error) {
|
|
22
22
|
return jsonResponse({ error: error.message }, 400);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"storage.d.ts","sourceRoot":"","sources":["../../src/api/storage.ts"],"names":[],"mappings":"AAEA;;GAEG;AACH,wBAAsB,UAAU,CAC9B,MAAM,EAAE,MAAM,EACd,IAAI,EAAE,MAAM,EACZ,IAAI,EAAE,IAAI,EACV,WAAW,EAAE,MAAM,GAClB,OAAO,CAAC,MAAM,CAAC,CAcjB;AAED;;GAEG;AACH,wBAAsB,WAAW,
|
|
1
|
+
{"version":3,"file":"storage.d.ts","sourceRoot":"","sources":["../../src/api/storage.ts"],"names":[],"mappings":"AAEA;;GAEG;AACH,wBAAsB,UAAU,CAC9B,MAAM,EAAE,MAAM,EACd,IAAI,EAAE,MAAM,EACZ,IAAI,EAAE,IAAI,EACV,WAAW,EAAE,MAAM,GAClB,OAAO,CAAC,MAAM,CAAC,CAcjB;AAED;;GAEG;AACH,wBAAsB,WAAW,CAC/B,MAAM,EAAE,MAAM,EACd,KAAK,EAAE,MAAM,EAAE,GACd,OAAO,CAAC,IAAI,CAAC,CAQf;AAED;;GAEG;AACH,wBAAsB,qBAAqB,CACzC,MAAM,EAAE,MAAM,EACd,MAAM,EAAE,MAAM,GACb,OAAO,CAAC,IAAI,CAAC,CAiBf"}
|
package/dist/api/storage.js
CHANGED
|
@@ -41,7 +41,7 @@ export async function deleteFilesWithPrefix(bucket, prefix) {
|
|
|
41
41
|
return;
|
|
42
42
|
}
|
|
43
43
|
if (files && files.length > 0) {
|
|
44
|
-
const filePaths = files.map(file => file.name);
|
|
44
|
+
const filePaths = files.map((file) => file.name);
|
|
45
45
|
await deleteFiles(bucket, filePaths);
|
|
46
46
|
}
|
|
47
47
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"auth.build.config.d.ts","sourceRoot":"","sources":["../src/auth.build.config.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,iBAAiB,CAAC;AAEzD,QAAA,MAAM,eAAe,EAAE,
|
|
1
|
+
{"version":3,"file":"auth.build.config.d.ts","sourceRoot":"","sources":["../src/auth.build.config.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,iBAAiB,CAAC;AAEzD,QAAA,MAAM,eAAe,EAAE,iBAuJtB,CAAC;AAEF,eAAe,eAAe,CAAC"}
|
|
@@ -66,12 +66,31 @@ const authBuildConfig = {
|
|
|
66
66
|
entryPoint: "api/auth/profile",
|
|
67
67
|
authRequired: true,
|
|
68
68
|
},
|
|
69
|
+
{
|
|
70
|
+
method: "GET",
|
|
71
|
+
path: "/api/auth/me",
|
|
72
|
+
handlerExport: "GET",
|
|
73
|
+
entryPoint: "api/auth/me",
|
|
74
|
+
authRequired: true,
|
|
75
|
+
},
|
|
76
|
+
{
|
|
77
|
+
method: "GET",
|
|
78
|
+
path: "/api/admin/users",
|
|
79
|
+
handlerExport: "GET",
|
|
80
|
+
entryPoint: "api/admin/users",
|
|
81
|
+
authRequired: true,
|
|
82
|
+
},
|
|
69
83
|
],
|
|
70
84
|
migrations: {
|
|
71
85
|
enabled: true,
|
|
72
86
|
priority: 20,
|
|
73
87
|
path: "supabase/migrations",
|
|
74
|
-
files: [
|
|
88
|
+
files: [
|
|
89
|
+
"20251112000000_user_init.sql",
|
|
90
|
+
"20251112000001_auto_profile_and_admin_view.sql",
|
|
91
|
+
"20251112000002_sync_avatars.sql"
|
|
92
|
+
],
|
|
93
|
+
migrationsDownPath: "supabase/migrations-down",
|
|
75
94
|
},
|
|
76
95
|
menu: {
|
|
77
96
|
public: [
|
|
@@ -95,7 +114,7 @@ const authBuildConfig = {
|
|
|
95
114
|
title: "Gestion des utilisateurs",
|
|
96
115
|
description: "Gérez les utilisateurs de la plateforme",
|
|
97
116
|
icon: "Users2",
|
|
98
|
-
path: "/admin/users",
|
|
117
|
+
path: "/admin/auth/users",
|
|
99
118
|
order: 1,
|
|
100
119
|
},
|
|
101
120
|
],
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"users.d.ts","sourceRoot":"","sources":["../../../src/web/admin/users.tsx"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"users.d.ts","sourceRoot":"","sources":["../../../src/web/admin/users.tsx"],"names":[],"mappings":"AAsDA,wBAAgB,cAAc,4CA6O7B"}
|
package/dist/web/admin/users.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
3
|
-
import { useEffect, useState } from "react";
|
|
3
|
+
import { useCallback, useEffect, useState } from "react";
|
|
4
4
|
import { Card, CardBody, CardHeader, Table, TableHeader, TableColumn, TableBody, TableRow, TableCell, Spinner, Chip, Input, Button, Pagination, Avatar, addToast, } from "@lastbrain/ui";
|
|
5
5
|
import { Users, Search, RefreshCw } from "lucide-react";
|
|
6
6
|
export function AdminUsersPage() {
|
|
@@ -14,10 +14,7 @@ export function AdminUsersPage() {
|
|
|
14
14
|
total: 0,
|
|
15
15
|
total_pages: 0,
|
|
16
16
|
});
|
|
17
|
-
|
|
18
|
-
fetchUsers();
|
|
19
|
-
}, [pagination.page]);
|
|
20
|
-
const fetchUsers = async () => {
|
|
17
|
+
const fetchUsers = useCallback(async () => {
|
|
21
18
|
try {
|
|
22
19
|
setIsLoading(true);
|
|
23
20
|
const params = new URLSearchParams({
|
|
@@ -58,7 +55,10 @@ export function AdminUsersPage() {
|
|
|
58
55
|
finally {
|
|
59
56
|
setIsLoading(false);
|
|
60
57
|
}
|
|
61
|
-
};
|
|
58
|
+
}, [pagination.page, pagination.per_page, searchQuery]);
|
|
59
|
+
useEffect(() => {
|
|
60
|
+
fetchUsers();
|
|
61
|
+
}, [fetchUsers]);
|
|
62
62
|
const handleSearch = () => {
|
|
63
63
|
setPagination((prev) => ({ ...prev, page: 1 }));
|
|
64
64
|
fetchUsers();
|
|
@@ -80,10 +80,17 @@ export function AdminUsersPage() {
|
|
|
80
80
|
if (e.key === "Enter") {
|
|
81
81
|
handleSearch();
|
|
82
82
|
}
|
|
83
|
-
}, startContent: _jsx(Search, { className: "w-4 h-4 text-default-400" }), className: "flex-1" }), _jsx(Button, { color: "primary", onPress: handleSearch, isDisabled: isLoading, children: "Search" })] }), _jsx(Button, { variant: "flat", onPress: fetchUsers, isDisabled: isLoading, startContent: _jsx(RefreshCw, { className: "w-4 h-4" }), children: "Refresh" })] }) }), _jsx(CardBody, { children: isLoading ? (_jsx("div", { className: "flex justify-center items-center py-12", children: _jsx(Spinner, { size: "lg", label: "Loading users..." }) })) : users.length === 0 ? (_jsx("div", { className: "text-center py-12 text-default-500", children: "No users found" })) : (_jsxs(_Fragment, { children: [_jsxs(Table, { "aria-label": "Users table", children: [_jsxs(TableHeader, { children: [_jsx(TableColumn, { children: "USER" }), _jsx(TableColumn, { children: "EMAIL" }), _jsx(TableColumn, { children: "COMPANY" }), _jsx(TableColumn, { children: "
|
|
83
|
+
}, startContent: _jsx(Search, { className: "w-4 h-4 text-default-400" }), className: "flex-1" }), _jsx(Button, { color: "primary", onPress: handleSearch, isDisabled: isLoading, children: "Search" })] }), _jsx(Button, { variant: "flat", onPress: fetchUsers, isDisabled: isLoading, startContent: _jsx(RefreshCw, { className: "w-4 h-4" }), children: "Refresh" })] }) }), _jsx(CardBody, { children: isLoading ? (_jsx("div", { className: "flex justify-center items-center py-12", children: _jsx(Spinner, { size: "lg", label: "Loading users..." }) })) : users.length === 0 ? (_jsx("div", { className: "text-center py-12 text-default-500", children: "No users found" })) : (_jsxs(_Fragment, { children: [_jsxs(Table, { "aria-label": "Users table", children: [_jsxs(TableHeader, { children: [_jsx(TableColumn, { children: "USER" }), _jsx(TableColumn, { children: "EMAIL" }), _jsx(TableColumn, { children: "ROLE" }), _jsx(TableColumn, { children: "COMPANY" }), _jsx(TableColumn, { children: "LAST SIGN IN" }), _jsx(TableColumn, { children: "CREATED" }), _jsx(TableColumn, { children: "STATUS" })] }), _jsx(TableBody, { children: users.map((user) => {
|
|
84
84
|
const fullName = user.profile?.first_name && user.profile?.last_name
|
|
85
85
|
? `${user.profile.first_name} ${user.profile.last_name}`
|
|
86
|
-
: "N/A";
|
|
87
|
-
|
|
86
|
+
: user.full_name || "N/A";
|
|
87
|
+
const roleColor = user.role === "admin" || user.role === "superadmin"
|
|
88
|
+
? "danger"
|
|
89
|
+
: user.role === "moderator"
|
|
90
|
+
? "secondary"
|
|
91
|
+
: "default";
|
|
92
|
+
return (_jsxs(TableRow, { children: [_jsx(TableCell, { children: _jsxs("div", { className: "flex items-center gap-2", children: [_jsx(Avatar, { src: `/api/storage/${user.profile?.avatar_url}`, name: fullName, size: "sm" }), _jsx("span", { className: "text-small font-medium", children: fullName })] }) }), _jsx(TableCell, { children: _jsx("span", { className: "text-small", children: user.email }) }), _jsx(TableCell, { children: _jsx(Chip, { color: roleColor, size: "sm", variant: "flat", children: user.role || "user" }) }), _jsx(TableCell, { children: _jsx("span", { className: "text-small", children: user.profile?.company || "-" }) }), _jsx(TableCell, { children: _jsx("span", { className: "text-small", children: user.last_sign_in_at
|
|
93
|
+
? formatDate(user.last_sign_in_at)
|
|
94
|
+
: "Never" }) }), _jsx(TableCell, { children: _jsx("span", { className: "text-small", children: formatDate(user.created_at) }) }), _jsx(TableCell, { children: _jsx(Chip, { color: "success", size: "sm", variant: "flat", children: "Active" }) })] }, user.id));
|
|
88
95
|
}) })] }), pagination.total_pages > 1 && (_jsx("div", { className: "flex justify-center mt-4", children: _jsx(Pagination, { total: pagination.total_pages, page: pagination.page, onChange: handlePageChange, showControls: true }) })), _jsxs("div", { className: "mt-4 text-small text-default-500 text-center", children: ["Showing ", users.length, " of ", pagination.total, " users"] })] })) })] })] }));
|
|
89
96
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"dashboard.d.ts","sourceRoot":"","sources":["../../../src/web/auth/dashboard.tsx"],"names":[],"mappings":"AA4BA,wBAAgB,aAAa,
|
|
1
|
+
{"version":3,"file":"dashboard.d.ts","sourceRoot":"","sources":["../../../src/web/auth/dashboard.tsx"],"names":[],"mappings":"AA4BA,wBAAgB,aAAa,mDAoL5B"}
|
|
@@ -28,7 +28,7 @@ export function DashboardPage() {
|
|
|
28
28
|
}
|
|
29
29
|
};
|
|
30
30
|
if (isLoading) {
|
|
31
|
-
return (_jsx("div", { className: "flex justify-center items-center min-h-[400px]", children:
|
|
31
|
+
return (_jsx("div", { className: "flex justify-center items-center min-h-[400px]", children: _jsxs(Spinner, { color: "primary", size: "lg", children: [" ", _jsx("span", { className: "text-xs text-default-700", children: "Loading dashboard..." })] }) }));
|
|
32
32
|
}
|
|
33
33
|
if (error) {
|
|
34
34
|
return (_jsx("div", { className: "pt-12", children: _jsx(Card, { className: "max-w-2xl mx-auto", children: _jsx(CardBody, { children: _jsxs("p", { className: "text-danger", children: ["Error: ", error] }) }) }) }));
|
|
@@ -39,6 +39,6 @@ export function DashboardPage() {
|
|
|
39
39
|
const fullName = userData.profile?.first_name && userData.profile?.last_name
|
|
40
40
|
? `${userData.profile.first_name} ${userData.profile.last_name}`
|
|
41
41
|
: "User";
|
|
42
|
-
return (_jsxs("div", { className: "pt-12 pb-12 max-w-6xl mx-auto px-4", children: [_jsx("h1", { className: "text-3xl font-bold mb-8", children: "Dashboard" }), _jsxs("div", { className: "grid gap-6 md:grid-cols-2", children: [_jsxs(Card, { className: "col-span-full md:col-span-1", children: [_jsxs(CardHeader, { className: "flex gap-3", children: [_jsx(Avatar, { src: userData.profile?.avatar_url, icon: _jsx(User, {}), size: "lg", className: "flex-shrink-0" }), _jsxs("div", { className: "flex flex-col", children: [_jsx("p", { className: "text-xl font-semibold", children: fullName }), _jsx("p", { className: "text-small text-default-500", children: userData.email })] })] }), _jsx(Divider, {}), _jsx(CardBody, { children: _jsxs("div", { className: "space-y-3", children: [_jsxs("div", { className: "flex items-center gap-2", children: [_jsx(Mail, { className: "w-4 h-4 text-default-400" }), _jsx("span", { className: "text-small", children: userData.email })] }), _jsxs("div", { className: "flex items-center gap-2", children: [_jsx(Calendar, { className: "w-4 h-4 text-default-400" }), _jsxs("span", { className: "text-small", children: ["Member since ", new Date(userData.created_at).toLocaleDateString()] })] }), userData.profile?.company && (_jsxs("div", { className: "flex items-center gap-2", children: [_jsx(Shield, { className: "w-4 h-4 text-default-400" }), _jsx("span", { className: "text-small", children: userData.profile.company })] }))] }) })] }), _jsxs(Card, { children: [_jsx(CardHeader, { children: _jsx("h3", { className: "text-lg font-semibold", children: "Account Status" }) }), _jsx(Divider, {}), _jsx(CardBody, { children: _jsxs("div", { className: "space-y-4", children: [_jsxs("div", { className: "flex justify-between items-center", children: [_jsx("span", { className: "text-small", children: "Status" }), _jsx(Chip, { color: "success", size: "sm", variant: "flat", children: "Active" })] }), _jsxs("div", { className: "flex justify-between items-center", children: [_jsx("span", { className: "text-small", children: "Profile" }), _jsx(Chip, { color: userData.profile ? "success" : "warning", size: "sm", variant: "flat", children: userData.profile ? "Complete" : "Incomplete" })] })] }) })] }), userData.profile?.bio && (_jsxs(Card, { className: "col-span-full", children: [_jsx(CardHeader, { children: _jsx("h3", { className: "text-lg font-semibold", children: "Bio" }) }), _jsx(Divider, {}), _jsx(CardBody, { children: _jsx("p", { className: "text-small text-default-600", children: userData.profile.bio }) })] })), _jsxs(Card, { className: "col-span-full", children: [_jsx(CardHeader, { children: _jsx("h3", { className: "text-lg font-semibold", children: "Quick Stats" }) }), _jsx(Divider, {}), _jsx(CardBody, { children: _jsxs("div", { className: "grid grid-cols-2 md:grid-cols-4 gap-4", children: [_jsxs("div", { className: "text-center", children: [_jsx("p", { className: "text-2xl font-bold text-primary", children: "0" }), _jsx("p", { className: "text-small text-default-500", children: "Projects" })] }), _jsxs("div", { className: "text-center", children: [_jsx("p", { className: "text-2xl font-bold text-success", children: "0" }), _jsx("p", { className: "text-small text-default-500", children: "Tasks" })] }), _jsxs("div", { className: "text-center", children: [_jsx("p", { className: "text-2xl font-bold text-warning", children: "0" }), _jsx("p", { className: "text-small text-default-500", children: "Notifications" })] }), _jsxs("div", { className: "text-center", children: [_jsx("p", { className: "text-2xl font-bold text-secondary", children: Math.floor((Date.now() - new Date(userData.created_at).getTime()) /
|
|
42
|
+
return (_jsxs("div", { className: "pt-12 pb-12 max-w-6xl mx-auto px-4", children: [_jsx("h1", { className: "text-3xl font-bold mb-8", children: "Dashboard" }), _jsxs("div", { className: "grid gap-6 md:grid-cols-2", children: [_jsxs(Card, { className: "col-span-full md:col-span-1", children: [_jsxs(CardHeader, { className: "flex gap-3", children: [_jsx(Avatar, { src: userData.profile?.avatar_url, icon: _jsx(User, {}), size: "lg", className: "flex-shrink-0" }), _jsxs("div", { className: "flex flex-col", children: [_jsx("p", { className: "text-xl font-semibold", children: fullName }), _jsx("p", { className: "text-small text-default-500", children: userData.email })] })] }), _jsx(Divider, {}), _jsx(CardBody, { children: _jsxs("div", { className: "space-y-3", children: [_jsxs("div", { className: "flex items-center gap-2", children: [_jsx(Mail, { className: "w-4 h-4 text-default-400" }), _jsx("span", { className: "text-small", children: userData.email })] }), _jsxs("div", { className: "flex items-center gap-2", children: [_jsx(Calendar, { className: "w-4 h-4 text-default-400" }), _jsxs("span", { className: "text-small", children: ["Member since", " ", new Date(userData.created_at).toLocaleDateString()] })] }), userData.profile?.company && (_jsxs("div", { className: "flex items-center gap-2", children: [_jsx(Shield, { className: "w-4 h-4 text-default-400" }), _jsx("span", { className: "text-small", children: userData.profile.company })] }))] }) })] }), _jsxs(Card, { children: [_jsx(CardHeader, { children: _jsx("h3", { className: "text-lg font-semibold", children: "Account Status" }) }), _jsx(Divider, {}), _jsx(CardBody, { children: _jsxs("div", { className: "space-y-4", children: [_jsxs("div", { className: "flex justify-between items-center", children: [_jsx("span", { className: "text-small", children: "Status" }), _jsx(Chip, { color: "success", size: "sm", variant: "flat", children: "Active" })] }), _jsxs("div", { className: "flex justify-between items-center", children: [_jsx("span", { className: "text-small", children: "Profile" }), _jsx(Chip, { color: userData.profile ? "success" : "warning", size: "sm", variant: "flat", children: userData.profile ? "Complete" : "Incomplete" })] })] }) })] }), userData.profile?.bio && (_jsxs(Card, { className: "col-span-full", children: [_jsx(CardHeader, { children: _jsx("h3", { className: "text-lg font-semibold", children: "Bio" }) }), _jsx(Divider, {}), _jsx(CardBody, { children: _jsx("p", { className: "text-small text-default-600", children: userData.profile.bio }) })] })), _jsxs(Card, { className: "col-span-full", children: [_jsx(CardHeader, { children: _jsx("h3", { className: "text-lg font-semibold", children: "Quick Stats" }) }), _jsx(Divider, {}), _jsx(CardBody, { children: _jsxs("div", { className: "grid grid-cols-2 md:grid-cols-4 gap-4", children: [_jsxs("div", { className: "text-center", children: [_jsx("p", { className: "text-2xl font-bold text-primary", children: "0" }), _jsx("p", { className: "text-small text-default-500", children: "Projects" })] }), _jsxs("div", { className: "text-center", children: [_jsx("p", { className: "text-2xl font-bold text-success", children: "0" }), _jsx("p", { className: "text-small text-default-500", children: "Tasks" })] }), _jsxs("div", { className: "text-center", children: [_jsx("p", { className: "text-2xl font-bold text-warning", children: "0" }), _jsx("p", { className: "text-small text-default-500", children: "Notifications" })] }), _jsxs("div", { className: "text-center", children: [_jsx("p", { className: "text-2xl font-bold text-secondary", children: Math.floor((Date.now() - new Date(userData.created_at).getTime()) /
|
|
43
43
|
(1000 * 60 * 60 * 24)) }), _jsx("p", { className: "text-small text-default-500", children: "Days active" })] })] }) })] })] })] }));
|
|
44
44
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"profile.d.ts","sourceRoot":"","sources":["../../../src/web/auth/profile.tsx"],"names":[],"mappings":"AAyCA,wBAAgB,WAAW,
|
|
1
|
+
{"version":3,"file":"profile.d.ts","sourceRoot":"","sources":["../../../src/web/auth/profile.tsx"],"names":[],"mappings":"AAyCA,wBAAgB,WAAW,4CA4X1B"}
|