@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.
Files changed (54) hide show
  1. package/dist/api/admin/signup-stats.d.ts +21 -0
  2. package/dist/api/admin/signup-stats.d.ts.map +1 -0
  3. package/dist/api/admin/signup-stats.js +75 -0
  4. package/dist/api/admin/users-by-source.d.ts +22 -0
  5. package/dist/api/admin/users-by-source.d.ts.map +1 -0
  6. package/dist/api/admin/users-by-source.js +56 -0
  7. package/dist/api/public/signup.d.ts +10 -0
  8. package/dist/api/public/signup.d.ts.map +1 -0
  9. package/dist/api/public/signup.js +71 -0
  10. package/dist/auth.build.config.d.ts.map +1 -1
  11. package/dist/auth.build.config.js +26 -0
  12. package/dist/index.d.ts +1 -0
  13. package/dist/index.d.ts.map +1 -1
  14. package/dist/index.js +1 -0
  15. package/dist/server.d.ts +1 -0
  16. package/dist/server.d.ts.map +1 -1
  17. package/dist/server.js +1 -0
  18. package/dist/web/admin/signup-stats.d.ts +2 -0
  19. package/dist/web/admin/signup-stats.d.ts.map +1 -0
  20. package/dist/web/admin/signup-stats.js +50 -0
  21. package/dist/web/admin/user-detail.d.ts.map +1 -1
  22. package/dist/web/admin/user-detail.js +5 -1
  23. package/dist/web/admin/users-by-signup-source.d.ts +2 -0
  24. package/dist/web/admin/users-by-signup-source.d.ts.map +1 -0
  25. package/dist/web/admin/users-by-signup-source.js +79 -0
  26. package/dist/web/auth/folder.d.ts.map +1 -1
  27. package/dist/web/auth/folder.js +3 -5
  28. package/dist/web/public/SignUpPage.d.ts.map +1 -1
  29. package/dist/web/public/SignUpPage.js +15 -23
  30. package/package.json +6 -6
  31. package/src/api/admin/signup-stats.ts +109 -0
  32. package/src/api/admin/users/[id]/notifications.ts +5 -5
  33. package/src/api/admin/users/[id].ts +5 -5
  34. package/src/api/admin/users-by-source.ts +87 -0
  35. package/src/api/admin/users.ts +4 -4
  36. package/src/api/auth/me.ts +1 -1
  37. package/src/api/auth/profile.ts +4 -4
  38. package/src/api/public/signup.ts +106 -0
  39. package/src/api/storage.ts +3 -3
  40. package/src/auth.build.config.ts +27 -0
  41. package/src/index.ts +1 -0
  42. package/src/server.ts +1 -0
  43. package/src/web/admin/signup-stats.tsx +304 -0
  44. package/src/web/admin/user-detail.tsx +19 -3
  45. package/src/web/admin/users-by-signup-source.tsx +262 -0
  46. package/src/web/admin/users.tsx +1 -1
  47. package/src/web/auth/dashboard.tsx +1 -1
  48. package/src/web/auth/folder.tsx +4 -14
  49. package/src/web/auth/profile.tsx +3 -3
  50. package/src/web/public/SignUpPage.tsx +17 -26
  51. package/supabase/migrations/20251112000000_user_init.sql +18 -1
  52. package/supabase/migrations/20251112000001_auto_profile_and_admin_view.sql +10 -2
  53. package/supabase/migrations/20251124000001_add_get_admin_user_details.sql +2 -1
  54. package/supabase/migrations-down/20251204000000_add_signup_source.sql +12 -0
@@ -4,7 +4,6 @@ import { Button, Card, CardBody, Input, Link, Chip, addToast, } from "@lastbrain
4
4
  import { useRouter, useSearchParams } from "next/navigation";
5
5
  import { Suspense, useState } from "react";
6
6
  import { Mail, Lock, User, ArrowRight, Sparkles, CheckCircle2, } from "lucide-react";
7
- import { supabaseBrowserClient } from "@lastbrain/core";
8
7
  function SignUpForm() {
9
8
  const router = useRouter();
10
9
  const searchParams = useSearchParams();
@@ -16,7 +15,7 @@ function SignUpForm() {
16
15
  const [error, setError] = useState(null);
17
16
  const [success, setSuccess] = useState(null);
18
17
  // Récupérer le paramètre redirect
19
- const redirectUrl = searchParams.get("redirect");
18
+ const redirectUrl = searchParams?.get("redirect") || "";
20
19
  const handleSubmit = async (event) => {
21
20
  event.preventDefault();
22
21
  setError(null);
@@ -31,35 +30,28 @@ function SignUpForm() {
31
30
  }
32
31
  setLoading(true);
33
32
  try {
34
- const { data, error: signUpError } = await supabaseBrowserClient.auth.signUp({
35
- email,
36
- password,
37
- options: {
38
- emailRedirectTo: `${window.location.origin}/api/auth/callback${redirectUrl ? `?next=${encodeURIComponent(redirectUrl)}` : ""}`,
39
- data: {
40
- full_name: fullName,
41
- },
33
+ // Appeler la nouvelle route API signup (signupSource sera déterminé côté serveur via VERCEL_URL)
34
+ const response = await fetch("/api/auth/signup", {
35
+ method: "POST",
36
+ headers: {
37
+ "Content-Type": "application/json",
42
38
  },
39
+ body: JSON.stringify({
40
+ email,
41
+ password,
42
+ fullName,
43
+ }),
43
44
  });
44
- if (signUpError) {
45
- setError(signUpError.message);
45
+ const result = await response.json();
46
+ if (!response.ok) {
47
+ setError(result.error || "Erreur lors de l'inscription");
46
48
  return;
47
49
  }
48
50
  // Si la confirmation par email est requise
49
- if (data.user && !data.session) {
51
+ if (result.data.user && !result.data.session) {
50
52
  setSuccess("Compte créé avec succès ! Veuillez vérifier votre email pour confirmer votre compte.");
51
53
  return;
52
54
  }
53
- // Si l'utilisateur est directement connecté (confirmation email désactivée)
54
- if (data.session) {
55
- if (redirectUrl) {
56
- window.location.href = redirectUrl;
57
- }
58
- else {
59
- window.location.href = "/auth/dashboard";
60
- }
61
- return;
62
- }
63
55
  setSuccess("Compte créé. Vous pouvez désormais vous connecter.");
64
56
  setTimeout(() => {
65
57
  if (redirectUrl) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lastbrain/module-auth",
3
- "version": "0.1.21",
3
+ "version": "0.1.23",
4
4
  "description": "Module d'authentification complet pour LastBrain avec Supabase",
5
5
  "private": false,
6
6
  "type": "module",
@@ -24,8 +24,7 @@
24
24
  "directory": "packages/module-auth"
25
25
  },
26
26
  "publishConfig": {
27
- "access": "public",
28
- "registry": "https://registry.npmjs.org"
27
+ "access": "public"
29
28
  },
30
29
  "files": [
31
30
  "dist",
@@ -35,16 +34,17 @@
35
34
  "dependencies": {
36
35
  "@lastbrain/core": "^0.1.0",
37
36
  "@lastbrain/ui": "^0.1.0",
37
+ "@lastbrain/module-ai": "^0.1.0",
38
38
  "@supabase/supabase-js": "^2.86.0",
39
39
  "lucide-react": "^0.554.0",
40
- "react": "^19.0.0",
41
- "react-dom": "^19.0.0"
40
+ "react": "^19.2.1",
41
+ "react-dom": "^19.2.1"
42
42
  },
43
43
  "peerDependencies": {
44
44
  "next": ">=15.0.0"
45
45
  },
46
46
  "devDependencies": {
47
- "next": "^16.0.4",
47
+ "next": "^16.0.7",
48
48
  "typescript": "^5.4.0"
49
49
  },
50
50
  "exports": {
@@ -0,0 +1,109 @@
1
+ import { getSupabaseServiceClient } from "@lastbrain/core/server";
2
+ import { NextRequest, NextResponse } from "next/server";
3
+
4
+ interface SignupStats {
5
+ total: number;
6
+ bySource: {
7
+ lastbrain: number;
8
+ recipe: number;
9
+ };
10
+ byDate: Array<{
11
+ date: string;
12
+ lastbrain: number;
13
+ recipe: number;
14
+ total: number;
15
+ }>;
16
+ }
17
+
18
+ export async function GET(request: NextRequest) {
19
+ try {
20
+ const supabase = await getSupabaseServiceClient();
21
+
22
+ // Get total signups by source
23
+ const { data: signupData, error: signupError } = await supabase
24
+ .from("user_profil")
25
+ .select("signup_source", { count: "exact" })
26
+ .order("created_at", { ascending: false });
27
+
28
+ if (signupError) {
29
+ return NextResponse.json({ error: signupError.message }, { status: 400 });
30
+ }
31
+
32
+ // Get signups by date and source (last 30 days)
33
+ const thirtyDaysAgo = new Date();
34
+ thirtyDaysAgo.setDate(thirtyDaysAgo.getDate() - 30);
35
+
36
+ const { data: dateData, error: dateError } = await supabase
37
+ .from("user_profil")
38
+ .select("created_at, signup_source")
39
+ .gte("created_at", thirtyDaysAgo.toISOString())
40
+ .order("created_at", { ascending: false });
41
+
42
+ if (dateError) {
43
+ return NextResponse.json({ error: dateError.message }, { status: 400 });
44
+ }
45
+
46
+ // Process stats
47
+ const stats = processSignupStats(signupData, dateData);
48
+
49
+ return NextResponse.json({ data: stats }, { status: 200 });
50
+ } catch (error) {
51
+ console.error("Error fetching signup stats:", error);
52
+ return NextResponse.json(
53
+ { error: "Erreur interne du serveur" },
54
+ { status: 500 }
55
+ );
56
+ }
57
+ }
58
+
59
+ function processSignupStats(
60
+ allData: Array<{ signup_source: string | null }>,
61
+ dateData: Array<{ created_at: string; signup_source: string | null }>
62
+ ): SignupStats {
63
+ // Count by source
64
+ const bySource = {
65
+ lastbrain: 0,
66
+ recipe: 0,
67
+ };
68
+
69
+ for (const record of allData) {
70
+ const source = (record.signup_source || "lastbrain").toLowerCase();
71
+ if (source === "lastbrain") bySource.lastbrain++;
72
+ else if (source === "recipe") bySource.recipe++;
73
+ }
74
+
75
+ // Count by date
76
+ const byDate: Record<
77
+ string,
78
+ { lastbrain: number; recipe: number; total: number }
79
+ > = {};
80
+
81
+ for (const record of dateData) {
82
+ const date = new Date(record.created_at).toISOString().split("T")[0];
83
+ if (!byDate[date]) {
84
+ byDate[date] = { lastbrain: 0, recipe: 0, total: 0 };
85
+ }
86
+
87
+ const source = (record.signup_source || "lastbrain").toLowerCase();
88
+ if (source === "lastbrain") {
89
+ byDate[date].lastbrain++;
90
+ } else if (source === "recipe") {
91
+ byDate[date].recipe++;
92
+ }
93
+ byDate[date].total++;
94
+ }
95
+
96
+ // Sort dates
97
+ const sortedByDate = Object.entries(byDate)
98
+ .sort(([dateA], [dateB]) => dateB.localeCompare(dateA))
99
+ .map(([date, stats]) => ({
100
+ date,
101
+ ...stats,
102
+ }));
103
+
104
+ return {
105
+ total: allData.length,
106
+ bySource,
107
+ byDate: sortedByDate,
108
+ };
109
+ }
@@ -7,7 +7,7 @@ import { getSupabaseServerClient } from "@lastbrain/core/server";
7
7
  */
8
8
  export async function POST(
9
9
  request: NextRequest,
10
- context: { params: Promise<{ id: string }> },
10
+ context: { params: Promise<{ id: string }> }
11
11
  ) {
12
12
  try {
13
13
  const supabase = await getSupabaseServerClient();
@@ -16,7 +16,7 @@ export async function POST(
16
16
  if (!userId) {
17
17
  return NextResponse.json(
18
18
  { error: "User ID is required" },
19
- { status: 400 },
19
+ { status: 400 }
20
20
  );
21
21
  }
22
22
 
@@ -26,7 +26,7 @@ export async function POST(
26
26
  if (!title?.trim() || !message?.trim()) {
27
27
  return NextResponse.json(
28
28
  { error: "Title and message are required" },
29
- { status: 400 },
29
+ { status: 400 }
30
30
  );
31
31
  }
32
32
 
@@ -47,7 +47,7 @@ export async function POST(
47
47
  console.error("Error creating notification:", error);
48
48
  return NextResponse.json(
49
49
  { error: "Database Error", message: error.message },
50
- { status: 500 },
50
+ { status: 500 }
51
51
  );
52
52
  }
53
53
 
@@ -62,7 +62,7 @@ export async function POST(
62
62
  error: "Internal Server Error",
63
63
  message: "Failed to send notification",
64
64
  },
65
- { status: 500 },
65
+ { status: 500 }
66
66
  );
67
67
  }
68
68
  }
@@ -7,7 +7,7 @@ import { getSupabaseServerClient } from "@lastbrain/core/server";
7
7
  */
8
8
  export async function GET(
9
9
  request: NextRequest,
10
- context: { params: Promise<{ id: string }> },
10
+ context: { params: Promise<{ id: string }> }
11
11
  ) {
12
12
  try {
13
13
  const supabase = await getSupabaseServerClient();
@@ -16,21 +16,21 @@ export async function GET(
16
16
  if (!userId) {
17
17
  return NextResponse.json(
18
18
  { error: "User ID is required" },
19
- { status: 400 },
19
+ { status: 400 }
20
20
  );
21
21
  }
22
22
 
23
23
  // Utiliser la fonction RPC pour récupérer les détails de l'utilisateur
24
24
  const { data: userDetails, error: userError } = await supabase.rpc(
25
25
  "get_admin_user_details",
26
- { user_id: userId },
26
+ { user_id: userId }
27
27
  );
28
28
 
29
29
  if (userError) {
30
30
  console.error("Error fetching user details:", userError);
31
31
  return NextResponse.json(
32
32
  { error: "Database Error", message: userError.message },
33
- { status: 500 },
33
+ { status: 500 }
34
34
  );
35
35
  }
36
36
 
@@ -46,7 +46,7 @@ export async function GET(
46
46
  error: "Internal Server Error",
47
47
  message: "Failed to fetch user details",
48
48
  },
49
- { status: 500 },
49
+ { status: 500 }
50
50
  );
51
51
  }
52
52
  }
@@ -0,0 +1,87 @@
1
+ import { getSupabaseServiceClient } from "@lastbrain/core/server";
2
+ import { NextRequest, NextResponse } from "next/server";
3
+
4
+ interface UserSignupData {
5
+ id: string;
6
+ email: string;
7
+ created_at: string;
8
+ signup_source: string | null;
9
+ first_name: string | null;
10
+ last_name: string | null;
11
+ }
12
+
13
+ export async function GET(request: NextRequest) {
14
+ try {
15
+ const supabase = await getSupabaseServiceClient();
16
+
17
+ // Get query params for filtering
18
+ const url = new URL(request.url);
19
+ const source = url.searchParams.get("source"); // 'lastbrain' or 'recipe'
20
+ const page = parseInt(url.searchParams.get("page") || "1");
21
+ const limit = parseInt(url.searchParams.get("limit") || "50");
22
+
23
+ const offset = (page - 1) * limit;
24
+
25
+ let query = supabase.from("user_profil").select(
26
+ `
27
+ id,
28
+ owner_id,
29
+ first_name,
30
+ last_name,
31
+ signup_source,
32
+ created_at
33
+ `,
34
+ { count: "exact" }
35
+ );
36
+
37
+ // Filter by source if specified
38
+ if (source) {
39
+ query = query.eq("signup_source", source);
40
+ }
41
+
42
+ const { data, count, error } = await query
43
+ .order("created_at", { ascending: false })
44
+ .range(offset, offset + limit - 1);
45
+
46
+ if (error) {
47
+ return NextResponse.json({ error: error.message }, { status: 400 });
48
+ }
49
+
50
+ // Fetch email from auth.users for each profile
51
+ const usersWithEmails: UserSignupData[] = await Promise.all(
52
+ (data || []).map(async (profile: any) => {
53
+ const { data: authUser } = await supabase.auth.admin.getUserById(
54
+ profile.owner_id
55
+ );
56
+
57
+ return {
58
+ id: profile.id,
59
+ email: authUser?.user?.email || "Unknown",
60
+ created_at: profile.created_at,
61
+ signup_source: profile.signup_source || "lastbrain",
62
+ first_name: profile.first_name,
63
+ last_name: profile.last_name,
64
+ };
65
+ })
66
+ );
67
+
68
+ return NextResponse.json(
69
+ {
70
+ data: usersWithEmails,
71
+ pagination: {
72
+ page,
73
+ limit,
74
+ total: count || 0,
75
+ totalPages: Math.ceil((count || 0) / limit),
76
+ },
77
+ },
78
+ { status: 200 }
79
+ );
80
+ } catch (error) {
81
+ console.error("Error fetching users by signup source:", error);
82
+ return NextResponse.json(
83
+ { error: "Erreur interne du serveur" },
84
+ { status: 500 }
85
+ );
86
+ }
87
+ }
@@ -25,14 +25,14 @@ export async function GET(request: NextRequest) {
25
25
  page_number: page,
26
26
  page_size: perPage,
27
27
  search_term: search,
28
- },
28
+ }
29
29
  );
30
30
 
31
31
  if (usersError) {
32
32
  console.error("Error fetching users:", usersError);
33
33
  return NextResponse.json(
34
34
  { error: "Database Error", message: usersError.message },
35
- { status: 500 },
35
+ { status: 500 }
36
36
  );
37
37
  }
38
38
 
@@ -41,7 +41,7 @@ export async function GET(request: NextRequest) {
41
41
  result || {
42
42
  data: [],
43
43
  pagination: { page, per_page: perPage, total: 0, total_pages: 0 },
44
- },
44
+ }
45
45
  );
46
46
  } catch (error) {
47
47
  console.error("Error in admin users endpoint:", error);
@@ -50,7 +50,7 @@ export async function GET(request: NextRequest) {
50
50
  error: "Internal Server Error",
51
51
  message: "Failed to fetch users",
52
52
  },
53
- { status: 500 },
53
+ { status: 500 }
54
54
  );
55
55
  }
56
56
  }
@@ -35,7 +35,7 @@ export async function GET() {
35
35
  console.error("Error fetching user:", error);
36
36
  return NextResponse.json(
37
37
  { error: "Internal Server Error", message: "Failed to fetch user data" },
38
- { status: 500 },
38
+ { status: 500 }
39
39
  );
40
40
  }
41
41
  }
@@ -24,7 +24,7 @@ export async function GET() {
24
24
  // PGRST116 = no rows returned, which is OK
25
25
  return NextResponse.json(
26
26
  { error: "Database Error", message: profileError.message },
27
- { status: 500 },
27
+ { status: 500 }
28
28
  );
29
29
  }
30
30
 
@@ -33,7 +33,7 @@ export async function GET() {
33
33
  console.error("Error fetching profile:", error);
34
34
  return NextResponse.json(
35
35
  { error: "Internal Server Error", message: "Failed to fetch profile" },
36
- { status: 500 },
36
+ { status: 500 }
37
37
  );
38
38
  }
39
39
  }
@@ -119,7 +119,7 @@ export async function PUT(request: NextRequest) {
119
119
  if (result.error) {
120
120
  return NextResponse.json(
121
121
  { error: "Database Error", message: result.error.message },
122
- { status: 500 },
122
+ { status: 500 }
123
123
  );
124
124
  }
125
125
 
@@ -128,7 +128,7 @@ export async function PUT(request: NextRequest) {
128
128
  console.error("Error updating profile:", error);
129
129
  return NextResponse.json(
130
130
  { error: "Internal Server Error", message: "Failed to update profile" },
131
- { status: 500 },
131
+ { status: 500 }
132
132
  );
133
133
  }
134
134
  }
@@ -0,0 +1,106 @@
1
+ import {
2
+ getSupabaseServerClient,
3
+ getSupabaseServiceClient,
4
+ } from "@lastbrain/core/server";
5
+ import { NextRequest, NextResponse } from "next/server";
6
+
7
+ interface SignUpRequest {
8
+ email: string;
9
+ password: string;
10
+ fullName: string;
11
+ signupSource?: string; // 'lastbrain' or 'recipe'
12
+ }
13
+
14
+ export async function POST(request: NextRequest) {
15
+ try {
16
+ const body: SignUpRequest = await request.json();
17
+ const defaultSource = process.env.APP_NAME || "undefined";
18
+ console.log("🚀 ~ POST ~ defaultSource:", defaultSource);
19
+ const { email, password, fullName, signupSource = defaultSource } = body;
20
+
21
+ // Validate required fields
22
+ if (!email || !password) {
23
+ return NextResponse.json(
24
+ { error: "Email et mot de passe requis." },
25
+ { status: 400 }
26
+ );
27
+ }
28
+
29
+ // Get Supabase client for authentication
30
+ const supabase = await getSupabaseServerClient();
31
+
32
+ // Sign up the user
33
+ const { data: authData, error: signUpError } = await supabase.auth.signUp({
34
+ email,
35
+ password,
36
+ options: {
37
+ emailRedirectTo: `${request.nextUrl.origin}/api/auth/callback`,
38
+ data: {
39
+ full_name: fullName,
40
+ signup_source: signupSource,
41
+ },
42
+ },
43
+ });
44
+
45
+ if (signUpError) {
46
+ return NextResponse.json({ error: signUpError.message }, { status: 400 });
47
+ }
48
+
49
+ if (!authData.user) {
50
+ return NextResponse.json(
51
+ { error: "Erreur lors de la création du compte" },
52
+ { status: 500 }
53
+ );
54
+ }
55
+
56
+ // Create user profile with signup_source
57
+ const serviceClient = await getSupabaseServiceClient();
58
+
59
+ // Check if profile already exists
60
+ const { data: existingProfile } = await serviceClient
61
+ .from("user_profil")
62
+ .select("owner_id")
63
+ .eq("owner_id", authData.user.id)
64
+ .single();
65
+
66
+ // Only create profile if it doesn't exist
67
+ if (!existingProfile) {
68
+ const { error: profileError } = await serviceClient
69
+ .from("user_profil")
70
+ .insert({
71
+ owner_id: authData.user.id,
72
+ first_name: fullName?.split(" ")[0] || "",
73
+ last_name: fullName?.split(" ").slice(1).join(" ") || "",
74
+ signup_source: signupSource,
75
+ preferences: {},
76
+ });
77
+
78
+ if (profileError) {
79
+ console.error("Error creating user profile:", profileError);
80
+ return NextResponse.json(
81
+ {
82
+ error: "Compte créé mais profil non configuré",
83
+ message: profileError.message,
84
+ },
85
+ { status: 500 }
86
+ );
87
+ }
88
+ }
89
+
90
+ return NextResponse.json(
91
+ {
92
+ data: {
93
+ user: authData.user,
94
+ message: "Compte créé avec succès",
95
+ },
96
+ },
97
+ { status: 201 }
98
+ );
99
+ } catch (error) {
100
+ console.error("Signup error:", error);
101
+ return NextResponse.json(
102
+ { error: "Erreur interne du serveur" },
103
+ { status: 500 }
104
+ );
105
+ }
106
+ }
@@ -7,7 +7,7 @@ export async function uploadFile(
7
7
  bucket: string,
8
8
  path: string,
9
9
  file: Blob,
10
- contentType: string,
10
+ contentType: string
11
11
  ): Promise<string> {
12
12
  const { data, error } = await supabaseBrowserClient.storage
13
13
  .from(bucket)
@@ -29,7 +29,7 @@ export async function uploadFile(
29
29
  */
30
30
  export async function deleteFiles(
31
31
  bucket: string,
32
- paths: string[],
32
+ paths: string[]
33
33
  ): Promise<void> {
34
34
  const { error } = await supabaseBrowserClient.storage
35
35
  .from(bucket)
@@ -45,7 +45,7 @@ export async function deleteFiles(
45
45
  */
46
46
  export async function deleteFilesWithPrefix(
47
47
  bucket: string,
48
- prefix: string,
48
+ prefix: string
49
49
  ): Promise<void> {
50
50
  // List files with the prefix
51
51
  const { data: files, error: listError } = await supabaseBrowserClient.storage
@@ -48,6 +48,11 @@ const authBuildConfig: ModuleBuildConfig = {
48
48
  path: "/users/[id]",
49
49
  componentExport: "UserPage",
50
50
  },
51
+ {
52
+ section: "admin",
53
+ path: "/signup-stats",
54
+ componentExport: "SignupStatsPage",
55
+ },
51
56
  ],
52
57
  apis: [
53
58
  {
@@ -57,6 +62,13 @@ const authBuildConfig: ModuleBuildConfig = {
57
62
  entryPoint: "api/public/signin",
58
63
  authRequired: false,
59
64
  },
65
+ {
66
+ method: "POST",
67
+ path: "/api/auth/signup",
68
+ handlerExport: "POST",
69
+ entryPoint: "api/public/signup",
70
+ authRequired: false,
71
+ },
60
72
  {
61
73
  method: "GET",
62
74
  path: "/api/auth/profile",
@@ -106,6 +118,13 @@ const authBuildConfig: ModuleBuildConfig = {
106
118
  entryPoint: "api/admin/users/[id]/notifications",
107
119
  authRequired: true,
108
120
  },
121
+ {
122
+ method: "GET",
123
+ path: "/api/admin/signup-stats",
124
+ handlerExport: "GET",
125
+ entryPoint: "api/admin/signup-stats",
126
+ authRequired: true,
127
+ },
109
128
  ],
110
129
  migrations: {
111
130
  enabled: true,
@@ -162,6 +181,14 @@ const authBuildConfig: ModuleBuildConfig = {
162
181
  shortcut: "cmd+shift+u",
163
182
  shortcutDisplay: "⌘⇧U",
164
183
  },
184
+ {
185
+ title: "Statistiques d'inscriptions",
186
+ description: "Suivez les inscriptions par source",
187
+ icon: "BarChart3",
188
+ path: "/admin/auth/signup-stats",
189
+ order: 2,
190
+ },
191
+
165
192
  {
166
193
  title: "Notifications",
167
194
  description: "Vos notifications",
package/src/index.ts 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
 
13
14
  // Header Components
14
15
  export { AccountButton } from "./components/AccountButton.js";
package/src/server.ts CHANGED
@@ -1,2 +1,3 @@
1
1
  // Server-only exports (Route Handlers, Server Actions, etc.)
2
2
  export { POST as signInApi } from "./api/public/signin.js";
3
+ export { POST as signUpApi } from "./api/public/signup.js";