@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
|
@@ -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
|
|
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
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
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
|
-
|
|
45
|
-
|
|
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.
|
|
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.
|
|
41
|
-
"react-dom": "^19.
|
|
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.
|
|
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
|
+
}
|
package/src/api/admin/users.ts
CHANGED
|
@@ -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
|
}
|
package/src/api/auth/me.ts
CHANGED
package/src/api/auth/profile.ts
CHANGED
|
@@ -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
|
+
}
|
package/src/api/storage.ts
CHANGED
|
@@ -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
|
package/src/auth.build.config.ts
CHANGED
|
@@ -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