@lastbrain/module-auth 0.1.22 → 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 (42) 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/public/SignUpPage.d.ts.map +1 -1
  27. package/dist/web/public/SignUpPage.js +15 -23
  28. package/package.json +4 -4
  29. package/src/api/admin/signup-stats.ts +109 -0
  30. package/src/api/admin/users-by-source.ts +87 -0
  31. package/src/api/public/signup.ts +106 -0
  32. package/src/auth.build.config.ts +27 -0
  33. package/src/index.ts +1 -0
  34. package/src/server.ts +1 -0
  35. package/src/web/admin/signup-stats.tsx +304 -0
  36. package/src/web/admin/user-detail.tsx +16 -0
  37. package/src/web/admin/users-by-signup-source.tsx +262 -0
  38. package/src/web/public/SignUpPage.tsx +16 -25
  39. package/supabase/migrations/20251112000000_user_init.sql +18 -1
  40. package/supabase/migrations/20251112000001_auto_profile_and_admin_view.sql +10 -2
  41. package/supabase/migrations/20251124000001_add_get_admin_user_details.sql +2 -1
  42. 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.22",
3
+ "version": "0.1.23",
4
4
  "description": "Module d'authentification complet pour LastBrain avec Supabase",
5
5
  "private": false,
6
6
  "type": "module",
@@ -37,14 +37,14 @@
37
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
+ }
@@ -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
+ }
@@ -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
+ }
@@ -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";