@lastbrain/module-auth 0.1.21 → 0.1.23
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/api/admin/signup-stats.d.ts +21 -0
- package/dist/api/admin/signup-stats.d.ts.map +1 -0
- package/dist/api/admin/signup-stats.js +75 -0
- package/dist/api/admin/users-by-source.d.ts +22 -0
- package/dist/api/admin/users-by-source.d.ts.map +1 -0
- package/dist/api/admin/users-by-source.js +56 -0
- package/dist/api/public/signup.d.ts +10 -0
- package/dist/api/public/signup.d.ts.map +1 -0
- package/dist/api/public/signup.js +71 -0
- package/dist/auth.build.config.d.ts.map +1 -1
- package/dist/auth.build.config.js +26 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -0
- package/dist/server.d.ts +1 -0
- package/dist/server.d.ts.map +1 -1
- package/dist/server.js +1 -0
- package/dist/web/admin/signup-stats.d.ts +2 -0
- package/dist/web/admin/signup-stats.d.ts.map +1 -0
- package/dist/web/admin/signup-stats.js +50 -0
- package/dist/web/admin/user-detail.d.ts.map +1 -1
- package/dist/web/admin/user-detail.js +5 -1
- package/dist/web/admin/users-by-signup-source.d.ts +2 -0
- package/dist/web/admin/users-by-signup-source.d.ts.map +1 -0
- package/dist/web/admin/users-by-signup-source.js +79 -0
- package/dist/web/auth/folder.d.ts.map +1 -1
- package/dist/web/auth/folder.js +3 -5
- package/dist/web/public/SignUpPage.d.ts.map +1 -1
- package/dist/web/public/SignUpPage.js +15 -23
- package/package.json +6 -6
- package/src/api/admin/signup-stats.ts +109 -0
- package/src/api/admin/users/[id]/notifications.ts +5 -5
- package/src/api/admin/users/[id].ts +5 -5
- package/src/api/admin/users-by-source.ts +87 -0
- package/src/api/admin/users.ts +4 -4
- package/src/api/auth/me.ts +1 -1
- package/src/api/auth/profile.ts +4 -4
- package/src/api/public/signup.ts +106 -0
- package/src/api/storage.ts +3 -3
- package/src/auth.build.config.ts +27 -0
- package/src/index.ts +1 -0
- package/src/server.ts +1 -0
- package/src/web/admin/signup-stats.tsx +304 -0
- package/src/web/admin/user-detail.tsx +19 -3
- package/src/web/admin/users-by-signup-source.tsx +262 -0
- package/src/web/admin/users.tsx +1 -1
- package/src/web/auth/dashboard.tsx +1 -1
- package/src/web/auth/folder.tsx +4 -14
- package/src/web/auth/profile.tsx +3 -3
- package/src/web/public/SignUpPage.tsx +17 -26
- package/supabase/migrations/20251112000000_user_init.sql +18 -1
- package/supabase/migrations/20251112000001_auto_profile_and_admin_view.sql +10 -2
- package/supabase/migrations/20251124000001_add_get_admin_user_details.sql +2 -1
- package/supabase/migrations-down/20251204000000_add_signup_source.sql +12 -0
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { NextRequest, NextResponse } from "next/server";
|
|
2
|
+
interface SignupStats {
|
|
3
|
+
total: number;
|
|
4
|
+
bySource: {
|
|
5
|
+
lastbrain: number;
|
|
6
|
+
recipe: number;
|
|
7
|
+
};
|
|
8
|
+
byDate: Array<{
|
|
9
|
+
date: string;
|
|
10
|
+
lastbrain: number;
|
|
11
|
+
recipe: number;
|
|
12
|
+
total: number;
|
|
13
|
+
}>;
|
|
14
|
+
}
|
|
15
|
+
export declare function GET(request: NextRequest): Promise<NextResponse<{
|
|
16
|
+
error: string;
|
|
17
|
+
}> | NextResponse<{
|
|
18
|
+
data: SignupStats;
|
|
19
|
+
}>>;
|
|
20
|
+
export {};
|
|
21
|
+
//# sourceMappingURL=signup-stats.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"signup-stats.d.ts","sourceRoot":"","sources":["../../../src/api/admin/signup-stats.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,WAAW,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAExD,UAAU,WAAW;IACnB,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,EAAE;QACR,SAAS,EAAE,MAAM,CAAC;QAClB,MAAM,EAAE,MAAM,CAAC;KAChB,CAAC;IACF,MAAM,EAAE,KAAK,CAAC;QACZ,IAAI,EAAE,MAAM,CAAC;QACb,SAAS,EAAE,MAAM,CAAC;QAClB,MAAM,EAAE,MAAM,CAAC;QACf,KAAK,EAAE,MAAM,CAAC;KACf,CAAC,CAAC;CACJ;AAED,wBAAsB,GAAG,CAAC,OAAO,EAAE,WAAW;;;;IAuC7C"}
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import { getSupabaseServiceClient } from "@lastbrain/core/server";
|
|
2
|
+
import { NextResponse } from "next/server";
|
|
3
|
+
export async function GET(request) {
|
|
4
|
+
try {
|
|
5
|
+
const supabase = await getSupabaseServiceClient();
|
|
6
|
+
// Get total signups by source
|
|
7
|
+
const { data: signupData, error: signupError } = await supabase
|
|
8
|
+
.from("user_profil")
|
|
9
|
+
.select("signup_source", { count: "exact" })
|
|
10
|
+
.order("created_at", { ascending: false });
|
|
11
|
+
if (signupError) {
|
|
12
|
+
return NextResponse.json({ error: signupError.message }, { status: 400 });
|
|
13
|
+
}
|
|
14
|
+
// Get signups by date and source (last 30 days)
|
|
15
|
+
const thirtyDaysAgo = new Date();
|
|
16
|
+
thirtyDaysAgo.setDate(thirtyDaysAgo.getDate() - 30);
|
|
17
|
+
const { data: dateData, error: dateError } = await supabase
|
|
18
|
+
.from("user_profil")
|
|
19
|
+
.select("created_at, signup_source")
|
|
20
|
+
.gte("created_at", thirtyDaysAgo.toISOString())
|
|
21
|
+
.order("created_at", { ascending: false });
|
|
22
|
+
if (dateError) {
|
|
23
|
+
return NextResponse.json({ error: dateError.message }, { status: 400 });
|
|
24
|
+
}
|
|
25
|
+
// Process stats
|
|
26
|
+
const stats = processSignupStats(signupData, dateData);
|
|
27
|
+
return NextResponse.json({ data: stats }, { status: 200 });
|
|
28
|
+
}
|
|
29
|
+
catch (error) {
|
|
30
|
+
console.error("Error fetching signup stats:", error);
|
|
31
|
+
return NextResponse.json({ error: "Erreur interne du serveur" }, { status: 500 });
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
function processSignupStats(allData, dateData) {
|
|
35
|
+
// Count by source
|
|
36
|
+
const bySource = {
|
|
37
|
+
lastbrain: 0,
|
|
38
|
+
recipe: 0,
|
|
39
|
+
};
|
|
40
|
+
for (const record of allData) {
|
|
41
|
+
const source = (record.signup_source || "lastbrain").toLowerCase();
|
|
42
|
+
if (source === "lastbrain")
|
|
43
|
+
bySource.lastbrain++;
|
|
44
|
+
else if (source === "recipe")
|
|
45
|
+
bySource.recipe++;
|
|
46
|
+
}
|
|
47
|
+
// Count by date
|
|
48
|
+
const byDate = {};
|
|
49
|
+
for (const record of dateData) {
|
|
50
|
+
const date = new Date(record.created_at).toISOString().split("T")[0];
|
|
51
|
+
if (!byDate[date]) {
|
|
52
|
+
byDate[date] = { lastbrain: 0, recipe: 0, total: 0 };
|
|
53
|
+
}
|
|
54
|
+
const source = (record.signup_source || "lastbrain").toLowerCase();
|
|
55
|
+
if (source === "lastbrain") {
|
|
56
|
+
byDate[date].lastbrain++;
|
|
57
|
+
}
|
|
58
|
+
else if (source === "recipe") {
|
|
59
|
+
byDate[date].recipe++;
|
|
60
|
+
}
|
|
61
|
+
byDate[date].total++;
|
|
62
|
+
}
|
|
63
|
+
// Sort dates
|
|
64
|
+
const sortedByDate = Object.entries(byDate)
|
|
65
|
+
.sort(([dateA], [dateB]) => dateB.localeCompare(dateA))
|
|
66
|
+
.map(([date, stats]) => ({
|
|
67
|
+
date,
|
|
68
|
+
...stats,
|
|
69
|
+
}));
|
|
70
|
+
return {
|
|
71
|
+
total: allData.length,
|
|
72
|
+
bySource,
|
|
73
|
+
byDate: sortedByDate,
|
|
74
|
+
};
|
|
75
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { NextRequest, NextResponse } from "next/server";
|
|
2
|
+
interface UserSignupData {
|
|
3
|
+
id: string;
|
|
4
|
+
email: string;
|
|
5
|
+
created_at: string;
|
|
6
|
+
signup_source: string | null;
|
|
7
|
+
first_name: string | null;
|
|
8
|
+
last_name: string | null;
|
|
9
|
+
}
|
|
10
|
+
export declare function GET(request: NextRequest): Promise<NextResponse<{
|
|
11
|
+
error: string;
|
|
12
|
+
}> | NextResponse<{
|
|
13
|
+
data: UserSignupData[];
|
|
14
|
+
pagination: {
|
|
15
|
+
page: number;
|
|
16
|
+
limit: number;
|
|
17
|
+
total: number;
|
|
18
|
+
totalPages: number;
|
|
19
|
+
};
|
|
20
|
+
}>>;
|
|
21
|
+
export {};
|
|
22
|
+
//# sourceMappingURL=users-by-source.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"users-by-source.d.ts","sourceRoot":"","sources":["../../../src/api/admin/users-by-source.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,WAAW,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAExD,UAAU,cAAc;IACtB,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,EAAE,MAAM,CAAC;IACd,UAAU,EAAE,MAAM,CAAC;IACnB,aAAa,EAAE,MAAM,GAAG,IAAI,CAAC;IAC7B,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1B,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;CAC1B;AAED,wBAAsB,GAAG,CAAC,OAAO,EAAE,WAAW;;;;;;;;;;IA0E7C"}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { getSupabaseServiceClient } from "@lastbrain/core/server";
|
|
2
|
+
import { NextResponse } from "next/server";
|
|
3
|
+
export async function GET(request) {
|
|
4
|
+
try {
|
|
5
|
+
const supabase = await getSupabaseServiceClient();
|
|
6
|
+
// Get query params for filtering
|
|
7
|
+
const url = new URL(request.url);
|
|
8
|
+
const source = url.searchParams.get("source"); // 'lastbrain' or 'recipe'
|
|
9
|
+
const page = parseInt(url.searchParams.get("page") || "1");
|
|
10
|
+
const limit = parseInt(url.searchParams.get("limit") || "50");
|
|
11
|
+
const offset = (page - 1) * limit;
|
|
12
|
+
let query = supabase.from("user_profil").select(`
|
|
13
|
+
id,
|
|
14
|
+
owner_id,
|
|
15
|
+
first_name,
|
|
16
|
+
last_name,
|
|
17
|
+
signup_source,
|
|
18
|
+
created_at
|
|
19
|
+
`, { count: "exact" });
|
|
20
|
+
// Filter by source if specified
|
|
21
|
+
if (source) {
|
|
22
|
+
query = query.eq("signup_source", source);
|
|
23
|
+
}
|
|
24
|
+
const { data, count, error } = await query
|
|
25
|
+
.order("created_at", { ascending: false })
|
|
26
|
+
.range(offset, offset + limit - 1);
|
|
27
|
+
if (error) {
|
|
28
|
+
return NextResponse.json({ error: error.message }, { status: 400 });
|
|
29
|
+
}
|
|
30
|
+
// Fetch email from auth.users for each profile
|
|
31
|
+
const usersWithEmails = await Promise.all((data || []).map(async (profile) => {
|
|
32
|
+
const { data: authUser } = await supabase.auth.admin.getUserById(profile.owner_id);
|
|
33
|
+
return {
|
|
34
|
+
id: profile.id,
|
|
35
|
+
email: authUser?.user?.email || "Unknown",
|
|
36
|
+
created_at: profile.created_at,
|
|
37
|
+
signup_source: profile.signup_source || "lastbrain",
|
|
38
|
+
first_name: profile.first_name,
|
|
39
|
+
last_name: profile.last_name,
|
|
40
|
+
};
|
|
41
|
+
}));
|
|
42
|
+
return NextResponse.json({
|
|
43
|
+
data: usersWithEmails,
|
|
44
|
+
pagination: {
|
|
45
|
+
page,
|
|
46
|
+
limit,
|
|
47
|
+
total: count || 0,
|
|
48
|
+
totalPages: Math.ceil((count || 0) / limit),
|
|
49
|
+
},
|
|
50
|
+
}, { status: 200 });
|
|
51
|
+
}
|
|
52
|
+
catch (error) {
|
|
53
|
+
console.error("Error fetching users by signup source:", error);
|
|
54
|
+
return NextResponse.json({ error: "Erreur interne du serveur" }, { status: 500 });
|
|
55
|
+
}
|
|
56
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { NextRequest, NextResponse } from "next/server";
|
|
2
|
+
export declare function POST(request: NextRequest): Promise<NextResponse<{
|
|
3
|
+
error: string;
|
|
4
|
+
}> | NextResponse<{
|
|
5
|
+
data: {
|
|
6
|
+
user: import("@supabase/supabase-js").AuthUser;
|
|
7
|
+
message: string;
|
|
8
|
+
};
|
|
9
|
+
}>>;
|
|
10
|
+
//# sourceMappingURL=signup.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"signup.d.ts","sourceRoot":"","sources":["../../../src/api/public/signup.ts"],"names":[],"mappings":"AAIA,OAAO,EAAE,WAAW,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AASxD,wBAAsB,IAAI,CAAC,OAAO,EAAE,WAAW;;;;;;;IA4F9C"}
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import { getSupabaseServerClient, getSupabaseServiceClient, } from "@lastbrain/core/server";
|
|
2
|
+
import { NextResponse } from "next/server";
|
|
3
|
+
export async function POST(request) {
|
|
4
|
+
try {
|
|
5
|
+
const body = await request.json();
|
|
6
|
+
const defaultSource = process.env.APP_NAME || "undefined";
|
|
7
|
+
console.log("🚀 ~ POST ~ defaultSource:", defaultSource);
|
|
8
|
+
const { email, password, fullName, signupSource = defaultSource } = body;
|
|
9
|
+
// Validate required fields
|
|
10
|
+
if (!email || !password) {
|
|
11
|
+
return NextResponse.json({ error: "Email et mot de passe requis." }, { status: 400 });
|
|
12
|
+
}
|
|
13
|
+
// Get Supabase client for authentication
|
|
14
|
+
const supabase = await getSupabaseServerClient();
|
|
15
|
+
// Sign up the user
|
|
16
|
+
const { data: authData, error: signUpError } = await supabase.auth.signUp({
|
|
17
|
+
email,
|
|
18
|
+
password,
|
|
19
|
+
options: {
|
|
20
|
+
emailRedirectTo: `${request.nextUrl.origin}/api/auth/callback`,
|
|
21
|
+
data: {
|
|
22
|
+
full_name: fullName,
|
|
23
|
+
signup_source: signupSource,
|
|
24
|
+
},
|
|
25
|
+
},
|
|
26
|
+
});
|
|
27
|
+
if (signUpError) {
|
|
28
|
+
return NextResponse.json({ error: signUpError.message }, { status: 400 });
|
|
29
|
+
}
|
|
30
|
+
if (!authData.user) {
|
|
31
|
+
return NextResponse.json({ error: "Erreur lors de la création du compte" }, { status: 500 });
|
|
32
|
+
}
|
|
33
|
+
// Create user profile with signup_source
|
|
34
|
+
const serviceClient = await getSupabaseServiceClient();
|
|
35
|
+
// Check if profile already exists
|
|
36
|
+
const { data: existingProfile } = await serviceClient
|
|
37
|
+
.from("user_profil")
|
|
38
|
+
.select("owner_id")
|
|
39
|
+
.eq("owner_id", authData.user.id)
|
|
40
|
+
.single();
|
|
41
|
+
// Only create profile if it doesn't exist
|
|
42
|
+
if (!existingProfile) {
|
|
43
|
+
const { error: profileError } = await serviceClient
|
|
44
|
+
.from("user_profil")
|
|
45
|
+
.insert({
|
|
46
|
+
owner_id: authData.user.id,
|
|
47
|
+
first_name: fullName?.split(" ")[0] || "",
|
|
48
|
+
last_name: fullName?.split(" ").slice(1).join(" ") || "",
|
|
49
|
+
signup_source: signupSource,
|
|
50
|
+
preferences: {},
|
|
51
|
+
});
|
|
52
|
+
if (profileError) {
|
|
53
|
+
console.error("Error creating user profile:", profileError);
|
|
54
|
+
return NextResponse.json({
|
|
55
|
+
error: "Compte créé mais profil non configuré",
|
|
56
|
+
message: profileError.message,
|
|
57
|
+
}, { status: 500 });
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
return NextResponse.json({
|
|
61
|
+
data: {
|
|
62
|
+
user: authData.user,
|
|
63
|
+
message: "Compte créé avec succès",
|
|
64
|
+
},
|
|
65
|
+
}, { status: 201 });
|
|
66
|
+
}
|
|
67
|
+
catch (error) {
|
|
68
|
+
console.error("Signup error:", error);
|
|
69
|
+
return NextResponse.json({ error: "Erreur interne du serveur" }, { status: 500 });
|
|
70
|
+
}
|
|
71
|
+
}
|
|
@@ -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,iBAgWtB,CAAC;AAEF,eAAe,eAAe,CAAC"}
|
|
@@ -46,6 +46,11 @@ const authBuildConfig = {
|
|
|
46
46
|
path: "/users/[id]",
|
|
47
47
|
componentExport: "UserPage",
|
|
48
48
|
},
|
|
49
|
+
{
|
|
50
|
+
section: "admin",
|
|
51
|
+
path: "/signup-stats",
|
|
52
|
+
componentExport: "SignupStatsPage",
|
|
53
|
+
},
|
|
49
54
|
],
|
|
50
55
|
apis: [
|
|
51
56
|
{
|
|
@@ -55,6 +60,13 @@ const authBuildConfig = {
|
|
|
55
60
|
entryPoint: "api/public/signin",
|
|
56
61
|
authRequired: false,
|
|
57
62
|
},
|
|
63
|
+
{
|
|
64
|
+
method: "POST",
|
|
65
|
+
path: "/api/auth/signup",
|
|
66
|
+
handlerExport: "POST",
|
|
67
|
+
entryPoint: "api/public/signup",
|
|
68
|
+
authRequired: false,
|
|
69
|
+
},
|
|
58
70
|
{
|
|
59
71
|
method: "GET",
|
|
60
72
|
path: "/api/auth/profile",
|
|
@@ -104,6 +116,13 @@ const authBuildConfig = {
|
|
|
104
116
|
entryPoint: "api/admin/users/[id]/notifications",
|
|
105
117
|
authRequired: true,
|
|
106
118
|
},
|
|
119
|
+
{
|
|
120
|
+
method: "GET",
|
|
121
|
+
path: "/api/admin/signup-stats",
|
|
122
|
+
handlerExport: "GET",
|
|
123
|
+
entryPoint: "api/admin/signup-stats",
|
|
124
|
+
authRequired: true,
|
|
125
|
+
},
|
|
107
126
|
],
|
|
108
127
|
migrations: {
|
|
109
128
|
enabled: true,
|
|
@@ -160,6 +179,13 @@ const authBuildConfig = {
|
|
|
160
179
|
shortcut: "cmd+shift+u",
|
|
161
180
|
shortcutDisplay: "⌘⇧U",
|
|
162
181
|
},
|
|
182
|
+
{
|
|
183
|
+
title: "Statistiques d'inscriptions",
|
|
184
|
+
description: "Suivez les inscriptions par source",
|
|
185
|
+
icon: "BarChart3",
|
|
186
|
+
path: "/admin/auth/signup-stats",
|
|
187
|
+
order: 2,
|
|
188
|
+
},
|
|
163
189
|
{
|
|
164
190
|
title: "Notifications",
|
|
165
191
|
description: "Vos notifications",
|
package/dist/index.d.ts
CHANGED
|
@@ -8,6 +8,7 @@ export { ReglagePage } from "./web/auth/reglage.js";
|
|
|
8
8
|
export { AdminUsersPage } from "./web/admin/users.js";
|
|
9
9
|
export { default as UserPage } from "./web/admin/users/[id].js";
|
|
10
10
|
export { UserDetailPage } from "./web/admin/user-detail.js";
|
|
11
|
+
export { SignupStatsPage } from "./web/admin/signup-stats.js";
|
|
11
12
|
export { AccountButton } from "./components/AccountButton.js";
|
|
12
13
|
export { NotificationButton } from "./components/NotificationButton.js";
|
|
13
14
|
export { ThemeSwitcherButton } from "./components/ThemeSwitcherButton.js";
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,UAAU,EAAE,MAAM,4BAA4B,CAAC;AACxD,OAAO,EAAE,UAAU,EAAE,MAAM,4BAA4B,CAAC;AACxD,OAAO,EAAE,aAAa,EAAE,MAAM,+BAA+B,CAAC;AAC9D,OAAO,EAAE,aAAa,EAAE,MAAM,yBAAyB,CAAC;AACxD,OAAO,EAAE,UAAU,EAAE,MAAM,sBAAsB,CAAC;AAClD,OAAO,EAAE,WAAW,EAAE,MAAM,uBAAuB,CAAC;AACpD,OAAO,EAAE,WAAW,EAAE,MAAM,uBAAuB,CAAC;AACpD,OAAO,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAC;AACtD,OAAO,EAAE,OAAO,IAAI,QAAQ,EAAE,MAAM,2BAA2B,CAAC;AAChE,OAAO,EAAE,cAAc,EAAE,MAAM,4BAA4B,CAAC;
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,UAAU,EAAE,MAAM,4BAA4B,CAAC;AACxD,OAAO,EAAE,UAAU,EAAE,MAAM,4BAA4B,CAAC;AACxD,OAAO,EAAE,aAAa,EAAE,MAAM,+BAA+B,CAAC;AAC9D,OAAO,EAAE,aAAa,EAAE,MAAM,yBAAyB,CAAC;AACxD,OAAO,EAAE,UAAU,EAAE,MAAM,sBAAsB,CAAC;AAClD,OAAO,EAAE,WAAW,EAAE,MAAM,uBAAuB,CAAC;AACpD,OAAO,EAAE,WAAW,EAAE,MAAM,uBAAuB,CAAC;AACpD,OAAO,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAC;AACtD,OAAO,EAAE,OAAO,IAAI,QAAQ,EAAE,MAAM,2BAA2B,CAAC;AAChE,OAAO,EAAE,cAAc,EAAE,MAAM,4BAA4B,CAAC;AAC5D,OAAO,EAAE,eAAe,EAAE,MAAM,6BAA6B,CAAC;AAG9D,OAAO,EAAE,aAAa,EAAE,MAAM,+BAA+B,CAAC;AAC9D,OAAO,EAAE,kBAAkB,EAAE,MAAM,oCAAoC,CAAC;AACxE,OAAO,EAAE,mBAAmB,EAAE,MAAM,qCAAqC,CAAC;AAG1E,OAAO,EAAE,GAAG,EAAE,MAAM,qBAAqB,CAAC;AAC1C,OAAO,EAAE,GAAG,IAAI,aAAa,EAAE,MAAM,qBAAqB,CAAC;AAE3D,OAAO,EAAE,OAAO,IAAI,eAAe,EAAE,MAAM,wBAAwB,CAAC"}
|
package/dist/index.js
CHANGED
|
@@ -9,6 +9,7 @@ export { ReglagePage } from "./web/auth/reglage.js";
|
|
|
9
9
|
export { AdminUsersPage } from "./web/admin/users.js";
|
|
10
10
|
export { default as UserPage } from "./web/admin/users/[id].js";
|
|
11
11
|
export { UserDetailPage } from "./web/admin/user-detail.js";
|
|
12
|
+
export { SignupStatsPage } from "./web/admin/signup-stats.js";
|
|
12
13
|
// Header Components
|
|
13
14
|
export { AccountButton } from "./components/AccountButton.js";
|
|
14
15
|
export { NotificationButton } from "./components/NotificationButton.js";
|
package/dist/server.d.ts
CHANGED
package/dist/server.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,IAAI,IAAI,SAAS,EAAE,MAAM,wBAAwB,CAAC"}
|
|
1
|
+
{"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,IAAI,IAAI,SAAS,EAAE,MAAM,wBAAwB,CAAC;AAC3D,OAAO,EAAE,IAAI,IAAI,SAAS,EAAE,MAAM,wBAAwB,CAAC"}
|
package/dist/server.js
CHANGED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"signup-stats.d.ts","sourceRoot":"","sources":["../../../src/web/admin/signup-stats.tsx"],"names":[],"mappings":"AAkCA,wBAAgB,eAAe,4CA6Q9B"}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
+
import { useEffect, useState } from "react";
|
|
4
|
+
import { Card, CardBody, CardHeader, Chip, Spinner, Tab, Table, TableBody, TableCell, TableColumn, TableHeader, TableRow, Tabs, } from "@lastbrain/ui";
|
|
5
|
+
import { BarChart3, TrendingUp } from "lucide-react";
|
|
6
|
+
export function SignupStatsPage() {
|
|
7
|
+
const [stats, setStats] = useState(null);
|
|
8
|
+
const [loading, setLoading] = useState(true);
|
|
9
|
+
const [error, setError] = useState(null);
|
|
10
|
+
useEffect(() => {
|
|
11
|
+
fetchStats();
|
|
12
|
+
}, []);
|
|
13
|
+
const fetchStats = async () => {
|
|
14
|
+
try {
|
|
15
|
+
setLoading(true);
|
|
16
|
+
const response = await fetch("/api/admin/signup-stats");
|
|
17
|
+
if (!response.ok) {
|
|
18
|
+
throw new Error("Erreur lors du chargement des statistiques");
|
|
19
|
+
}
|
|
20
|
+
const result = await response.json();
|
|
21
|
+
setStats(result.data);
|
|
22
|
+
}
|
|
23
|
+
catch (err) {
|
|
24
|
+
setError(err instanceof Error ? err.message : "Erreur lors du chargement");
|
|
25
|
+
}
|
|
26
|
+
finally {
|
|
27
|
+
setLoading(false);
|
|
28
|
+
}
|
|
29
|
+
};
|
|
30
|
+
if (loading) {
|
|
31
|
+
return (_jsx("div", { className: "flex justify-center items-center min-h-screen", children: _jsx(Spinner, { size: "lg", label: "Chargement des statistiques..." }) }));
|
|
32
|
+
}
|
|
33
|
+
if (error || !stats) {
|
|
34
|
+
return (_jsx("div", { className: "p-6", children: _jsx(Card, { className: "border border-danger-200 bg-danger-50/50", children: _jsx(CardBody, { children: _jsx("p", { className: "text-danger-600", children: error || "Erreur de chargement" }) }) }) }));
|
|
35
|
+
}
|
|
36
|
+
const lastbrainPercentage = ((stats.bySource.lastbrain / stats.total) *
|
|
37
|
+
100).toFixed(1);
|
|
38
|
+
const recipePercentage = ((stats.bySource.recipe / stats.total) *
|
|
39
|
+
100).toFixed(1);
|
|
40
|
+
return (_jsxs("div", { className: "space-y-6 px-2 md:p-6", children: [_jsxs("div", { className: "flex items-center gap-2 mb-8", children: [_jsx(BarChart3, { size: 28, className: "text-primary-600" }), _jsx("h1", { className: "text-3xl font-bold", children: "Statistiques d'inscriptions" })] }), _jsxs("div", { className: "grid grid-cols-1 md:grid-cols-3 gap-12", children: [_jsx(Card, { className: "bg-content2 border border-primary-200 dark:border-primary-800", children: _jsxs(CardBody, { className: "gap-4", children: [_jsxs("div", { className: "flex items-center justify-between", children: [_jsx("span", { className: "text-sm font-medium text-primary-600 dark:text-primary-400", children: "Total d'inscriptions" }), _jsx(TrendingUp, { size: 20, className: "text-primary-600" })] }), _jsx("p", { className: "text-4xl font-bold text-primary-700 dark:text-primary-300", children: stats.total })] }) }), _jsx(Card, { className: "bg-content2 border border-secondary-200 dark:border-secondary-800", children: _jsxs(CardBody, { className: "gap-4", children: [_jsx("div", { className: "flex items-center justify-between", children: _jsx("span", { className: "text-sm font-medium text-secondary-600 dark:text-secondary-400", children: "Inscriptions LastBrain" }) }), _jsxs("div", { className: "flex items-end gap-2", children: [_jsx("p", { className: "text-4xl font-bold text-secondary-700 dark:text-secondary-300", children: stats.bySource.lastbrain }), _jsxs(Chip, { size: "sm", color: "secondary", variant: "flat", children: [lastbrainPercentage, "%"] })] })] }) }), _jsx(Card, { className: "bg-content2 border border-success-200 dark:border-success-800", children: _jsxs(CardBody, { className: "gap-4", children: [_jsx("div", { className: "flex items-center justify-between", children: _jsx("span", { className: "text-sm font-medium text-success-600 dark:text-success-400", children: "Inscriptions Recipe" }) }), _jsxs("div", { className: "flex items-end gap-2", children: [_jsx("p", { className: "text-4xl font-bold text-success-700 dark:text-success-300", children: stats.bySource.recipe }), _jsxs(Chip, { size: "sm", color: "success", variant: "flat", children: [recipePercentage, "%"] })] })] }) })] }), _jsxs(Tabs, { "aria-label": "Vues des statistiques", color: "primary", variant: "bordered", children: [_jsx(Tab, { title: "Par Source", children: _jsxs(Card, { className: "mt-6", children: [_jsx(CardHeader, { children: _jsx("h3", { className: "text-lg font-semibold", children: "R\u00E9sum\u00E9 par source" }) }), _jsx(CardBody, { children: _jsxs("div", { className: "space-y-4", children: [_jsxs("div", { className: "p-4 rounded-lg border border-secondary-800 dark:border-secondary-800", children: [_jsxs("div", { className: "flex items-center justify-between mb-2", children: [_jsx("span", { className: "font-medium text-secondary-700 dark:text-secondary-300", children: "LastBrain" }), _jsxs(Chip, { size: "sm", color: "secondary", variant: "flat", children: [stats.bySource.lastbrain, " inscriptions"] })] }), _jsx("div", { className: "w-full bg-default-200 rounded-full h-2 dark:bg-default-700", children: _jsx("div", { className: "bg-secondary-500 h-2 rounded-full", style: {
|
|
41
|
+
width: `${(stats.bySource.lastbrain / stats.total) * 100}%`,
|
|
42
|
+
} }) }), _jsxs("p", { className: "text-xs text-default-500 mt-2", children: [lastbrainPercentage, "% du total"] })] }), _jsxs("div", { className: "p-4 rounded-lg border border-success-200 dark:border-success-800", children: [_jsxs("div", { className: "flex items-center justify-between mb-2", children: [_jsx("span", { className: "font-medium text-success-700 dark:text-success-300", children: "Recipe" }), _jsxs(Chip, { size: "sm", color: "success", variant: "flat", children: [stats.bySource.recipe, " inscriptions"] })] }), _jsx("div", { className: "w-full bg-default-200 rounded-full h-2 dark:bg-default-700", children: _jsx("div", { className: "bg-success-500 h-2 rounded-full", style: {
|
|
43
|
+
width: `${(stats.bySource.recipe / stats.total) * 100}%`,
|
|
44
|
+
} }) }), _jsxs("p", { className: "text-xs text-default-500 mt-2", children: [recipePercentage, "% du total"] })] })] }) })] }) }, "by-source"), _jsx(Tab, { title: "Par Date (30 derniers jours)", children: _jsxs(Card, { className: "mt-6", children: [_jsxs(CardHeader, { children: [_jsx("h3", { className: "text-lg font-semibold", children: "Inscriptions par date" }), _jsx("p", { className: "text-sm text-default-500", children: "Derniers 30 jours avec r\u00E9partition par source" })] }), _jsxs(CardBody, { children: [_jsxs(Table, { "aria-label": "Tableau des inscriptions par date", className: "rounded-lg", children: [_jsxs(TableHeader, { children: [_jsx(TableColumn, { children: "Date" }), _jsx(TableColumn, { className: "text-right", children: "LastBrain" }), _jsx(TableColumn, { className: "text-right", children: "Recipe" }), _jsx(TableColumn, { className: "text-right", children: "Total" })] }), _jsx(TableBody, { children: stats.byDate.map((row) => (_jsxs(TableRow, { children: [_jsx(TableCell, { children: _jsx("span", { className: "font-medium", children: new Date(row.date).toLocaleDateString("fr-FR", {
|
|
45
|
+
weekday: "short",
|
|
46
|
+
year: "2-digit",
|
|
47
|
+
month: "2-digit",
|
|
48
|
+
day: "2-digit",
|
|
49
|
+
}) }) }), _jsx(TableCell, { className: "text-right", children: _jsx(Chip, { size: "sm", color: row.lastbrain > 0 ? "secondary" : "default", variant: "flat", children: row.lastbrain }) }), _jsx(TableCell, { className: "text-right", children: _jsx(Chip, { size: "sm", color: row.recipe > 0 ? "success" : "default", variant: "flat", children: row.recipe }) }), _jsx(TableCell, { className: "text-right", children: _jsx("span", { className: "font-semibold text-primary-600 dark:text-primary-400", children: row.total }) })] }, row.date))) })] }), stats.byDate.length === 0 && (_jsx("div", { className: "text-center py-8 text-default-500", children: _jsx("p", { children: "Aucune donn\u00E9e disponible pour les 30 derniers jours" }) }))] })] }) }, "by-date")] }), _jsx(Card, { className: "border-l-4 border-l-primary-500 bg-primary-50/30 dark:bg-primary-950/20", children: _jsx(CardBody, { className: "py-3", children: _jsxs("p", { className: "text-sm text-primary-700 dark:text-primary-300", children: [_jsx("strong", { children: "\uD83D\uDCA1 Info:" }), " Ces statistiques vous permettent de suivre les inscriptions en fonction de la source (LastBrain ou Recipe) pour mieux comprendre d'o\u00F9 proviennent vos utilisateurs."] }) }) })] }));
|
|
50
|
+
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"user-detail.d.ts","sourceRoot":"","sources":["../../../src/web/admin/user-detail.tsx"],"names":[],"mappings":"AAyBA,UAAU,aAAa;IACrB,GAAG,EAAE,MAAM,CAAC;IACZ,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,SAAS,EAAE,KAAK,CAAC,aAAa,CAAC;QAAE,MAAM,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;CACpD;
|
|
1
|
+
{"version":3,"file":"user-detail.d.ts","sourceRoot":"","sources":["../../../src/web/admin/user-detail.tsx"],"names":[],"mappings":"AAyBA,UAAU,aAAa;IACrB,GAAG,EAAE,MAAM,CAAC;IACZ,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,SAAS,EAAE,KAAK,CAAC,aAAa,CAAC;QAAE,MAAM,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;CACpD;AAkCD,UAAU,mBAAmB;IAC3B,MAAM,EAAE,MAAM,CAAC;IACf,cAAc,CAAC,EAAE,aAAa,EAAE,CAAC;CAClC;AAED,wBAAgB,cAAc,CAAC,EAC7B,MAAM,EACN,cAAmB,GACpB,EAAE,mBAAmB,2CAygBrB"}
|
|
@@ -103,7 +103,11 @@ export function UserDetailPage({ userId, moduleUserTabs = [], }) {
|
|
|
103
103
|
? new Date(userProfile.last_sign_in_at).toLocaleDateString()
|
|
104
104
|
: "N/A", " ", "\u00E0", " ", userProfile.last_sign_in_at
|
|
105
105
|
? new Date(userProfile.last_sign_in_at).toLocaleTimeString()
|
|
106
|
-
: "N/A"] })] }), _jsx("p", { className: "text-gray-500", children: userProfile.email }), _jsxs("div", { className: "flex flex-col md:flex-row md:items-center gap-2", children: [_jsx(Chip, { variant: "flat", color: isAdmin ? "danger" : "primary", size: "sm", children: isAdmin ? "Administrateur" : "Utilisateur" }),
|
|
106
|
+
: "N/A"] })] }), _jsx("p", { className: "text-gray-500", children: userProfile.email }), _jsxs("div", { className: "flex flex-col md:flex-row md:items-center gap-2", children: [_jsx(Chip, { variant: "flat", color: isAdmin ? "danger" : "primary", size: "sm", children: isAdmin ? "Administrateur" : "Utilisateur" }), userProfile.profile?.signup_source && (_jsx(Chip, { variant: "flat", color: userProfile.profile.signup_source.toLowerCase() === "recipe"
|
|
107
|
+
? "success"
|
|
108
|
+
: "secondary", size: "sm", children: userProfile.profile.signup_source.toLowerCase() === "recipe"
|
|
109
|
+
? "Recipe"
|
|
110
|
+
: "LastBrain" })), _jsx(Snippet, { color: "default", size: "sm", symbol: "#", children: userId })] })] })] }) }), _jsx(Card, { children: _jsx(CardBody, { children: _jsxs(Tabs, { "aria-label": "Options utilisateur", color: "primary", variant: "underlined", children: [_jsx(Tab, { title: _jsxs("div", { className: "flex items-center space-x-2", children: [_jsx(User, { size: 16 }), _jsx("span", { children: "Profil" })] }), children: _jsxs("div", { className: "space-y-6 mt-4", children: [_jsx(Card, { children: _jsxs(CardBody, { children: [_jsxs("h3", { className: "font-semibold text-lg mb-4 flex items-center gap-2", children: [_jsx(User, { size: 18 }), "Identit\u00E9"] }), _jsxs("div", { className: "grid grid-cols-1 md:grid-cols-2 gap-4 text-sm", children: [_jsxs("div", { children: [_jsx("span", { className: "text-default-500", children: "Nom complet" }), _jsx("p", { className: "font-medium mt-1", children: userProfile.full_name || "Non renseigné" })] }), userProfile.profile?.first_name && (_jsxs("div", { children: [_jsx("span", { className: "text-default-500", children: "Pr\u00E9nom" }), _jsx("p", { className: "font-medium mt-1", children: userProfile.profile.first_name })] })), userProfile.profile?.last_name && (_jsxs("div", { children: [_jsx("span", { className: "text-default-500", children: "Nom" }), _jsx("p", { className: "font-medium mt-1", children: userProfile.profile.last_name })] })), _jsxs("div", { children: [_jsx("span", { className: "text-default-500", children: "Email" }), _jsx("p", { className: "font-medium mt-1", children: userProfile.email })] }), userProfile.profile?.phone && (_jsxs("div", { children: [_jsx("span", { className: "text-default-500", children: "T\u00E9l\u00E9phone" }), _jsx("p", { className: "font-medium mt-1", children: userProfile.profile.phone })] }))] }), userProfile.profile?.bio && (_jsxs("div", { className: "mt-4", children: [_jsx("span", { className: "text-default-500 text-sm", children: "Bio" }), _jsx("p", { className: "font-medium mt-1 text-sm", children: userProfile.profile.bio })] }))] }) }), (userProfile.profile?.company ||
|
|
107
111
|
userProfile.profile?.website ||
|
|
108
112
|
userProfile.profile?.location) && (_jsx(Card, { children: _jsxs(CardBody, { children: [_jsx("h3", { className: "font-semibold text-lg mb-4", children: "Informations professionnelles" }), _jsxs("div", { className: "grid grid-cols-1 md:grid-cols-2 gap-4 text-sm", children: [userProfile.profile?.company && (_jsxs("div", { children: [_jsx("span", { className: "text-default-500", children: "Entreprise" }), _jsx("p", { className: "font-medium mt-1", children: userProfile.profile.company })] })), userProfile.profile?.website && (_jsxs("div", { children: [_jsx("span", { className: "text-default-500", children: "Site web" }), _jsx("p", { className: "font-medium mt-1", children: _jsx("a", { href: userProfile.profile.website, target: "_blank", rel: "noopener noreferrer", className: "text-primary hover:underline", children: userProfile.profile.website }) })] })), userProfile.profile?.location && (_jsxs("div", { children: [_jsx("span", { className: "text-default-500", children: "Localisation" }), _jsx("p", { className: "font-medium mt-1", children: userProfile.profile.location })] }))] })] }) })), (userProfile.profile?.language ||
|
|
109
113
|
userProfile.profile?.timezone) && (_jsx(Card, { children: _jsxs(CardBody, { children: [_jsx("h3", { className: "font-semibold text-lg mb-4", children: "Pr\u00E9f\u00E9rences" }), _jsxs("div", { className: "grid grid-cols-1 md:grid-cols-2 gap-4 text-sm", children: [userProfile.profile?.language && (_jsxs("div", { children: [_jsx("span", { className: "text-default-500", children: "Langue" }), _jsx("p", { className: "font-medium mt-1", children: userProfile.profile.language.toUpperCase() })] })), userProfile.profile?.timezone && (_jsxs("div", { children: [_jsx("span", { className: "text-default-500", children: "Fuseau horaire" }), _jsx("p", { className: "font-medium mt-1", children: userProfile.profile.timezone })] }))] })] }) })), _jsx(Card, { children: _jsxs(CardBody, { children: [_jsx("h3", { className: "font-semibold text-lg mb-4", children: "Informations du compte" }), _jsxs("div", { className: "grid grid-cols-1 md:grid-cols-2 gap-4 text-sm", children: [_jsxs("div", { children: [_jsx("span", { className: "text-default-500", children: "R\u00F4le" }), _jsx("div", { className: "mt-2", children: _jsx(Chip, { variant: "flat", color: isAdmin ? "danger" : "default", size: "sm", children: isAdmin
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"users-by-signup-source.d.ts","sourceRoot":"","sources":["../../../src/web/admin/users-by-signup-source.tsx"],"names":[],"mappings":"AAsCA,wBAAgB,uBAAuB,4CA+NtC"}
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
3
|
+
import { useEffect, useState } from "react";
|
|
4
|
+
import { Card, CardBody, CardHeader, Chip, Input, Pagination, Select, SelectItem, Spinner, Table, TableBody, TableCell, TableColumn, TableHeader, TableRow, } from "@lastbrain/ui";
|
|
5
|
+
import { Search, Users } from "lucide-react";
|
|
6
|
+
export function UsersBySignupSourcePage() {
|
|
7
|
+
const [users, setUsers] = useState([]);
|
|
8
|
+
const [loading, setLoading] = useState(true);
|
|
9
|
+
const [error, setError] = useState(null);
|
|
10
|
+
const [pagination, setPagination] = useState({
|
|
11
|
+
page: 1,
|
|
12
|
+
limit: 25,
|
|
13
|
+
total: 0,
|
|
14
|
+
totalPages: 0,
|
|
15
|
+
});
|
|
16
|
+
const [source, setSource] = useState(""); // '' for all, 'lastbrain', 'recipe'
|
|
17
|
+
const [searchQuery, setSearchQuery] = useState("");
|
|
18
|
+
useEffect(() => {
|
|
19
|
+
fetchUsers(pagination.page, source, searchQuery);
|
|
20
|
+
}, []);
|
|
21
|
+
const fetchUsers = async (page, selectedSource, query) => {
|
|
22
|
+
try {
|
|
23
|
+
setLoading(true);
|
|
24
|
+
let url = `/api/admin/users-by-source?page=${page}&limit=25`;
|
|
25
|
+
if (selectedSource) {
|
|
26
|
+
url += `&source=${selectedSource}`;
|
|
27
|
+
}
|
|
28
|
+
const response = await fetch(url);
|
|
29
|
+
if (!response.ok) {
|
|
30
|
+
throw new Error("Erreur lors du chargement des utilisateurs");
|
|
31
|
+
}
|
|
32
|
+
const result = await response.json();
|
|
33
|
+
// Filter by search query if provided
|
|
34
|
+
let filteredUsers = result.data;
|
|
35
|
+
if (query) {
|
|
36
|
+
filteredUsers = filteredUsers.filter((user) => user.email.toLowerCase().includes(query.toLowerCase()) ||
|
|
37
|
+
user.name.toLowerCase().includes(query.toLowerCase()));
|
|
38
|
+
}
|
|
39
|
+
setUsers(filteredUsers);
|
|
40
|
+
setPagination(result.pagination);
|
|
41
|
+
}
|
|
42
|
+
catch (err) {
|
|
43
|
+
setError(err instanceof Error ? err.message : "Erreur lors du chargement");
|
|
44
|
+
}
|
|
45
|
+
finally {
|
|
46
|
+
setLoading(false);
|
|
47
|
+
}
|
|
48
|
+
};
|
|
49
|
+
const handleSourceChange = (value) => {
|
|
50
|
+
setSource(value);
|
|
51
|
+
fetchUsers(1, value, searchQuery);
|
|
52
|
+
};
|
|
53
|
+
const handleSearch = (e) => {
|
|
54
|
+
const query = e.target.value;
|
|
55
|
+
setSearchQuery(query);
|
|
56
|
+
fetchUsers(1, source, query);
|
|
57
|
+
};
|
|
58
|
+
const handlePageChange = (page) => {
|
|
59
|
+
fetchUsers(page, source, searchQuery);
|
|
60
|
+
};
|
|
61
|
+
const getSourceColor = (src) => {
|
|
62
|
+
return src.toLowerCase() === "recipe" ? "success" : "secondary";
|
|
63
|
+
};
|
|
64
|
+
if (error) {
|
|
65
|
+
return (_jsx("div", { className: "p-6", children: _jsx(Card, { className: "border border-danger-200 bg-danger-50/50", children: _jsx(CardBody, { children: _jsx("p", { className: "text-danger-600", children: error }) }) }) }));
|
|
66
|
+
}
|
|
67
|
+
return (_jsxs("div", { className: "space-y-6 p-6", children: [_jsxs("div", { className: "flex items-center gap-2 mb-8", children: [_jsx(Users, { size: 28, className: "text-primary-600" }), _jsx("h1", { className: "text-3xl font-bold", children: "Utilisateurs par source d'inscription" })] }), _jsxs("div", { className: "grid grid-cols-1 md:grid-cols-2 gap-4", children: [_jsx(Input, { placeholder: "Rechercher par email ou nom...", value: searchQuery, onChange: handleSearch, startContent: _jsx(Search, { size: 16 }), isClearable: true, onClear: () => {
|
|
68
|
+
setSearchQuery("");
|
|
69
|
+
fetchUsers(1, source, "");
|
|
70
|
+
} }), _jsxs(Select, { label: "Filtrer par source", selectedKeys: [source], onChange: (e) => handleSourceChange(e.target.value), children: [_jsx(SelectItem, { children: "Toutes les sources" }, ""), _jsx(SelectItem, { children: "LastBrain" }, "lastbrain"), _jsx(SelectItem, { children: "Recipe" }, "recipe")] })] }), _jsxs("div", { className: "text-sm text-default-600", children: ["Affichage de", " ", _jsxs("span", { className: "font-semibold", children: [(pagination.page - 1) * pagination.limit + 1, "-", Math.min(pagination.page * pagination.limit, pagination.total)] }), " ", "sur ", _jsx("span", { className: "font-semibold", children: pagination.total }), " ", "utilisateurs"] }), _jsxs(Card, { children: [_jsx(CardHeader, { children: _jsx("h3", { className: "text-lg font-semibold", children: "Liste des utilisateurs" }) }), _jsx(CardBody, { children: loading ? (_jsx("div", { className: "flex justify-center py-8", children: _jsx(Spinner, { size: "lg", label: "Chargement..." }) })) : (_jsxs(_Fragment, { children: [_jsxs(Table, { "aria-label": "Tableau des utilisateurs", children: [_jsxs(TableHeader, { children: [_jsx(TableColumn, { children: "Nom" }), _jsx(TableColumn, { children: "Email" }), _jsx(TableColumn, { children: "Source" }), _jsx(TableColumn, { children: "Date d'inscription" })] }), _jsx(TableBody, { children: users.length > 0 ? (users.map((user) => (_jsxs(TableRow, { children: [_jsx(TableCell, { children: _jsx("span", { className: "font-medium", children: user.name }) }), _jsx(TableCell, { children: _jsx("span", { className: "text-sm text-default-600", children: user.email }) }), _jsx(TableCell, { children: _jsx(Chip, { size: "sm", color: getSourceColor(user.signup_source), variant: "flat", children: user.signup_source.toLowerCase() === "recipe"
|
|
71
|
+
? "🍳 Recipe"
|
|
72
|
+
: "🧠 LastBrain" }) }), _jsx(TableCell, { children: _jsx("span", { className: "text-sm text-default-500", children: new Date(user.created_at).toLocaleDateString("fr-FR", {
|
|
73
|
+
year: "2-digit",
|
|
74
|
+
month: "2-digit",
|
|
75
|
+
day: "2-digit",
|
|
76
|
+
hour: "2-digit",
|
|
77
|
+
minute: "2-digit",
|
|
78
|
+
}) }) })] }, user.id)))) : (_jsx(TableRow, { children: _jsx(TableCell, { colSpan: 4, className: "text-center py-8", children: _jsx("p", { className: "text-default-500", children: "Aucun utilisateur trouv\u00E9" }) }) })) })] }), pagination.totalPages > 1 && (_jsx("div", { className: "flex justify-center mt-6", children: _jsx(Pagination, { total: pagination.totalPages, page: pagination.page, onChange: handlePageChange, showControls: true }) }))] })) })] })] }));
|
|
79
|
+
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"folder.d.ts","sourceRoot":"","sources":["../../../src/web/auth/folder.tsx"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"folder.d.ts","sourceRoot":"","sources":["../../../src/web/auth/folder.tsx"],"names":[],"mappings":"AAqBA,wBAAgB,UAAU,mDAqEzB"}
|
package/dist/web/auth/folder.js
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
3
|
import { useEffect, useState } from "react";
|
|
4
|
-
import { Card, CardBody, Spinner
|
|
4
|
+
import { Card, CardBody, Spinner } from "@lastbrain/ui";
|
|
5
5
|
import { FileManager } from "@lastbrain/ui";
|
|
6
|
+
import { ImageGenerative } from "@lastbrain/module-ai";
|
|
6
7
|
export function FolderPage() {
|
|
7
8
|
const [userData, setUserData] = useState(null);
|
|
8
9
|
const [isLoading, setIsLoading] = useState(true);
|
|
@@ -36,8 +37,5 @@ export function FolderPage() {
|
|
|
36
37
|
if (!userData) {
|
|
37
38
|
return null;
|
|
38
39
|
}
|
|
39
|
-
|
|
40
|
-
? `${userData.profile.first_name} ${userData.profile.last_name}`
|
|
41
|
-
: "User";
|
|
42
|
-
return (_jsxs("div", { className: "pt-4 pb-12 max-w-8xl mx-auto px-4", children: [_jsx("h1", { className: "text-3xl font-bold mb-8", children: "Dossier" }), _jsx(FileManager, { bucket: "app", basePath: userData.id, allowUpload: true, allowCreateFolder: true, className: "min-h-[80vh]" })] }));
|
|
40
|
+
return (_jsxs("div", { className: "pt-4 pb-12 max-w-8xl mx-auto px-4", children: [_jsx("h1", { className: "text-3xl font-bold mb-8", children: "Dossier" }), _jsx(FileManager, { bucket: "app", basePath: userData.id, allowUpload: true, allowCreateFolder: true, allowAIImageGeneration: true, renderImageGenerator: (props) => _jsx(ImageGenerative, { ...props }), className: "min-h-[80vh]" })] }));
|
|
43
41
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"SignUpPage.d.ts","sourceRoot":"","sources":["../../../src/web/public/SignUpPage.tsx"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"SignUpPage.d.ts","sourceRoot":"","sources":["../../../src/web/public/SignUpPage.tsx"],"names":[],"mappings":"AAqRA,wBAAgB,UAAU,4CAMzB"}
|