@lastbrain/module-auth 0.1.1 → 0.1.2
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/package.json +11 -10
- package/src/api/public/signin.ts +31 -0
- package/src/auth.build.config.ts +116 -0
- package/src/components/Doc.tsx +310 -0
- package/src/index.ts +12 -0
- package/src/server.ts +2 -0
- package/src/web/admin/users.tsx +3 -0
- package/src/web/auth/dashboard.tsx +3 -0
- package/src/web/auth/profile.tsx +3 -0
- package/src/web/auth/reglage.tsx +3 -0
- package/src/web/public/ResetPassword.tsx +3 -0
- package/src/web/public/SignInPage.tsx +255 -0
- package/src/web/public/SignUpPage.tsx +293 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lastbrain/module-auth",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.2",
|
|
4
4
|
"description": "Module d'authentification complet pour LastBrain avec Supabase",
|
|
5
5
|
"private": false,
|
|
6
6
|
"type": "module",
|
|
@@ -25,14 +25,19 @@
|
|
|
25
25
|
},
|
|
26
26
|
"files": [
|
|
27
27
|
"dist",
|
|
28
|
+
"src",
|
|
28
29
|
"supabase"
|
|
29
30
|
],
|
|
31
|
+
"scripts": {
|
|
32
|
+
"build": "tsc -p tsconfig.json",
|
|
33
|
+
"dev": "tsc -p tsconfig.json --watch"
|
|
34
|
+
},
|
|
30
35
|
"dependencies": {
|
|
36
|
+
"@lastbrain/core": "^0.1.0",
|
|
37
|
+
"@lastbrain/ui": "^0.1.4",
|
|
31
38
|
"lucide-react": "^0.554.0",
|
|
32
39
|
"react": "^19.0.0",
|
|
33
|
-
"react-dom": "^19.0.0"
|
|
34
|
-
"@lastbrain/core": "0.1.0",
|
|
35
|
-
"@lastbrain/ui": "0.1.3"
|
|
40
|
+
"react-dom": "^19.0.0"
|
|
36
41
|
},
|
|
37
42
|
"peerDependencies": {
|
|
38
43
|
"next": ">=15.0.0"
|
|
@@ -59,9 +64,5 @@
|
|
|
59
64
|
"default": "./dist/api/*.js"
|
|
60
65
|
}
|
|
61
66
|
},
|
|
62
|
-
"sideEffects": false
|
|
63
|
-
|
|
64
|
-
"build": "tsc -p tsconfig.json",
|
|
65
|
-
"dev": "tsc -p tsconfig.json --watch"
|
|
66
|
-
}
|
|
67
|
-
}
|
|
67
|
+
"sideEffects": false
|
|
68
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { getSupabaseServerClient } from "@lastbrain/core/server";
|
|
2
|
+
|
|
3
|
+
const jsonResponse = (payload: unknown, status = 200) => {
|
|
4
|
+
return new Response(JSON.stringify(payload), {
|
|
5
|
+
headers: {
|
|
6
|
+
"content-type": "application/json"
|
|
7
|
+
},
|
|
8
|
+
status
|
|
9
|
+
});
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
export async function POST(request: Request) {
|
|
13
|
+
const supabase = await getSupabaseServerClient();
|
|
14
|
+
const body = await request.json();
|
|
15
|
+
const { email, password } = body;
|
|
16
|
+
|
|
17
|
+
if (!email || !password) {
|
|
18
|
+
return jsonResponse({ error: "Email et mot de passe requis." }, 400);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
const { error, data } = await supabase.auth.signInWithPassword({
|
|
22
|
+
email,
|
|
23
|
+
password
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
if (error) {
|
|
27
|
+
return jsonResponse({ error: error.message }, 400);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
return jsonResponse({ data });
|
|
31
|
+
}
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
import type { ModuleBuildConfig } from "@lastbrain/core";
|
|
2
|
+
|
|
3
|
+
const authBuildConfig: ModuleBuildConfig = {
|
|
4
|
+
moduleName: "@lastbrain/module-auth",
|
|
5
|
+
pages: [
|
|
6
|
+
{
|
|
7
|
+
section: "public",
|
|
8
|
+
path: "/signin",
|
|
9
|
+
componentExport: "SignInPage",
|
|
10
|
+
},
|
|
11
|
+
{
|
|
12
|
+
section: "public",
|
|
13
|
+
path: "/signup",
|
|
14
|
+
componentExport: "SignUpPage",
|
|
15
|
+
},
|
|
16
|
+
{
|
|
17
|
+
section: "public",
|
|
18
|
+
path: "/reset-password",
|
|
19
|
+
componentExport: "ResetPassword",
|
|
20
|
+
},
|
|
21
|
+
{
|
|
22
|
+
section: "auth",
|
|
23
|
+
path: "/dashboard",
|
|
24
|
+
componentExport: "DashboardPage",
|
|
25
|
+
},
|
|
26
|
+
{
|
|
27
|
+
section: "auth",
|
|
28
|
+
path: "/reglage",
|
|
29
|
+
componentExport: "ReglagePage",
|
|
30
|
+
},
|
|
31
|
+
{
|
|
32
|
+
section: "auth",
|
|
33
|
+
path: "/profile",
|
|
34
|
+
componentExport: "ProfilePage",
|
|
35
|
+
},
|
|
36
|
+
{
|
|
37
|
+
section: "admin",
|
|
38
|
+
path: "/users",
|
|
39
|
+
componentExport: "AdminUsersPage",
|
|
40
|
+
},
|
|
41
|
+
],
|
|
42
|
+
apis: [
|
|
43
|
+
{
|
|
44
|
+
method: "POST",
|
|
45
|
+
path: "/api/auth/signin",
|
|
46
|
+
handlerExport: "POST",
|
|
47
|
+
entryPoint: "api/public/signin",
|
|
48
|
+
authRequired: false,
|
|
49
|
+
},
|
|
50
|
+
],
|
|
51
|
+
migrations: {
|
|
52
|
+
enabled: true,
|
|
53
|
+
priority: 20,
|
|
54
|
+
path: "supabase/migrations",
|
|
55
|
+
files: ["001_auth_base.sql"],
|
|
56
|
+
},
|
|
57
|
+
menu: {
|
|
58
|
+
public: [
|
|
59
|
+
{
|
|
60
|
+
title: "Connexion",
|
|
61
|
+
description: "Connectez-vous à votre compte",
|
|
62
|
+
icon: "LogIn",
|
|
63
|
+
path: "/signin",
|
|
64
|
+
order: 1,
|
|
65
|
+
},
|
|
66
|
+
{
|
|
67
|
+
title: "Inscription",
|
|
68
|
+
description: "Créez votre compte",
|
|
69
|
+
icon: "UserPlus2",
|
|
70
|
+
path: "/signup",
|
|
71
|
+
order: 2,
|
|
72
|
+
},
|
|
73
|
+
],
|
|
74
|
+
admin: [
|
|
75
|
+
{
|
|
76
|
+
title: "Gestion des utilisateurs",
|
|
77
|
+
description: "Gérez les utilisateurs de la plateforme",
|
|
78
|
+
icon: "Users2",
|
|
79
|
+
path: "/admin/users",
|
|
80
|
+
order: 1,
|
|
81
|
+
},
|
|
82
|
+
],
|
|
83
|
+
account: [
|
|
84
|
+
{
|
|
85
|
+
title: "Tableau de bord",
|
|
86
|
+
description: "Accédez à votre tableau de bord",
|
|
87
|
+
icon: "LayoutDashboard",
|
|
88
|
+
path: "/auth/dashboard",
|
|
89
|
+
order: 1,
|
|
90
|
+
},
|
|
91
|
+
{
|
|
92
|
+
title: "Mon profil",
|
|
93
|
+
description: "Gérez vos informations personnelles",
|
|
94
|
+
icon: "User2",
|
|
95
|
+
path: "/auth/profile",
|
|
96
|
+
order: 1,
|
|
97
|
+
},
|
|
98
|
+
{
|
|
99
|
+
title: "Paramètres",
|
|
100
|
+
description: "Configurez votre compte",
|
|
101
|
+
icon: "Settings",
|
|
102
|
+
path: "/auth/reglage",
|
|
103
|
+
order: 2,
|
|
104
|
+
},
|
|
105
|
+
{
|
|
106
|
+
title: "Déconnexion",
|
|
107
|
+
description: "Se déconnecter",
|
|
108
|
+
icon: "LogOut",
|
|
109
|
+
path: "/signout",
|
|
110
|
+
order: 99,
|
|
111
|
+
},
|
|
112
|
+
],
|
|
113
|
+
},
|
|
114
|
+
};
|
|
115
|
+
|
|
116
|
+
export default authBuildConfig;
|
|
@@ -0,0 +1,310 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
import {
|
|
3
|
+
Card,
|
|
4
|
+
CardBody,
|
|
5
|
+
CardHeader,
|
|
6
|
+
Snippet,
|
|
7
|
+
Chip,
|
|
8
|
+
TableStructure,
|
|
9
|
+
} from "@lastbrain/ui";
|
|
10
|
+
|
|
11
|
+
export function AuthModuleDoc() {
|
|
12
|
+
return (
|
|
13
|
+
<div className="space-y-6">
|
|
14
|
+
{/* Header */}
|
|
15
|
+
<div className="flex items-start justify-between">
|
|
16
|
+
<div>
|
|
17
|
+
<h2 className="text-3xl font-bold mb-2">Module Auth</h2>
|
|
18
|
+
<p className="text-slate-600 dark:text-slate-400">
|
|
19
|
+
Authentification complète avec gestion des profils utilisateur
|
|
20
|
+
</p>
|
|
21
|
+
</div>
|
|
22
|
+
<Chip color="primary" variant="flat">
|
|
23
|
+
v0.1.0
|
|
24
|
+
</Chip>
|
|
25
|
+
</div>
|
|
26
|
+
|
|
27
|
+
{/* Author */}
|
|
28
|
+
<Card>
|
|
29
|
+
<CardHeader>
|
|
30
|
+
<h3 className="text-xl font-semibold">📝 Informations</h3>
|
|
31
|
+
</CardHeader>
|
|
32
|
+
<CardBody className="space-y-2">
|
|
33
|
+
<div>
|
|
34
|
+
<span className="font-semibold">Auteur:</span> LastBrain Team
|
|
35
|
+
</div>
|
|
36
|
+
<div>
|
|
37
|
+
<span className="font-semibold">Package:</span>{" "}
|
|
38
|
+
@lastbrain/module-auth
|
|
39
|
+
</div>
|
|
40
|
+
<div>
|
|
41
|
+
<span className="font-semibold">Dernière mise à jour:</span> 21
|
|
42
|
+
novembre 2025
|
|
43
|
+
</div>
|
|
44
|
+
</CardBody>
|
|
45
|
+
</Card>
|
|
46
|
+
|
|
47
|
+
{/* Pages disponibles */}
|
|
48
|
+
<Card>
|
|
49
|
+
<CardHeader>
|
|
50
|
+
<h3 className="text-xl font-semibold">📄 Pages Disponibles</h3>
|
|
51
|
+
</CardHeader>
|
|
52
|
+
<CardBody className="space-y-3">
|
|
53
|
+
<div>
|
|
54
|
+
<h4 className="font-semibold mb-2">Pages Publiques</h4>
|
|
55
|
+
<div className="space-y-2">
|
|
56
|
+
<div className="flex items-center gap-2">
|
|
57
|
+
<Chip size="sm" color="success" variant="flat">
|
|
58
|
+
GET
|
|
59
|
+
</Chip>
|
|
60
|
+
<code className="text-sm">/signin</code>
|
|
61
|
+
<span className="text-slate-600 dark:text-slate-400">
|
|
62
|
+
- Connexion
|
|
63
|
+
</span>
|
|
64
|
+
</div>
|
|
65
|
+
<div className="flex items-center gap-2">
|
|
66
|
+
<Chip size="sm" color="success" variant="flat">
|
|
67
|
+
GET
|
|
68
|
+
</Chip>
|
|
69
|
+
<code className="text-sm">/signup</code>
|
|
70
|
+
<span className="text-slate-600 dark:text-slate-400">
|
|
71
|
+
- Inscription
|
|
72
|
+
</span>
|
|
73
|
+
</div>
|
|
74
|
+
<div className="flex items-center gap-2">
|
|
75
|
+
<Chip size="sm" color="success" variant="flat">
|
|
76
|
+
GET
|
|
77
|
+
</Chip>
|
|
78
|
+
<code className="text-sm">/reset-password</code>
|
|
79
|
+
<span className="text-slate-600 dark:text-slate-400">
|
|
80
|
+
- Réinitialisation mot de passe
|
|
81
|
+
</span>
|
|
82
|
+
</div>
|
|
83
|
+
</div>
|
|
84
|
+
</div>
|
|
85
|
+
<div>
|
|
86
|
+
<h4 className="font-semibold mb-2">Pages Protégées (Auth)</h4>
|
|
87
|
+
<div className="space-y-2">
|
|
88
|
+
<div className="flex items-center gap-2">
|
|
89
|
+
<Chip size="sm" color="primary" variant="flat">
|
|
90
|
+
GET
|
|
91
|
+
</Chip>
|
|
92
|
+
<code className="text-sm">/auth/dashboard</code>
|
|
93
|
+
<span className="text-slate-600 dark:text-slate-400">
|
|
94
|
+
- Tableau de bord
|
|
95
|
+
</span>
|
|
96
|
+
</div>
|
|
97
|
+
<div className="flex items-center gap-2">
|
|
98
|
+
<Chip size="sm" color="primary" variant="flat">
|
|
99
|
+
GET
|
|
100
|
+
</Chip>
|
|
101
|
+
<code className="text-sm">/auth/profile</code>
|
|
102
|
+
<span className="text-slate-600 dark:text-slate-400">
|
|
103
|
+
- Profil utilisateur
|
|
104
|
+
</span>
|
|
105
|
+
</div>
|
|
106
|
+
<div className="flex items-center gap-2">
|
|
107
|
+
<Chip size="sm" color="primary" variant="flat">
|
|
108
|
+
GET
|
|
109
|
+
</Chip>
|
|
110
|
+
<code className="text-sm">/auth/reglage</code>
|
|
111
|
+
<span className="text-slate-600 dark:text-slate-400">
|
|
112
|
+
- Paramètres
|
|
113
|
+
</span>
|
|
114
|
+
</div>
|
|
115
|
+
</div>
|
|
116
|
+
</div>
|
|
117
|
+
<div>
|
|
118
|
+
<h4 className="font-semibold mb-2">Pages Admin</h4>
|
|
119
|
+
<div className="space-y-2">
|
|
120
|
+
<div className="flex items-center gap-2">
|
|
121
|
+
<Chip size="sm" color="danger" variant="flat">
|
|
122
|
+
GET
|
|
123
|
+
</Chip>
|
|
124
|
+
<code className="text-sm">/admin/users</code>
|
|
125
|
+
<span className="text-slate-600 dark:text-slate-400">
|
|
126
|
+
- Gestion des utilisateurs
|
|
127
|
+
</span>
|
|
128
|
+
</div>
|
|
129
|
+
</div>
|
|
130
|
+
</div>
|
|
131
|
+
</CardBody>
|
|
132
|
+
</Card>
|
|
133
|
+
|
|
134
|
+
{/* Routes API */}
|
|
135
|
+
<Card>
|
|
136
|
+
<CardHeader>
|
|
137
|
+
<h3 className="text-xl font-semibold">🔌 Routes API</h3>
|
|
138
|
+
</CardHeader>
|
|
139
|
+
<CardBody className="space-y-2">
|
|
140
|
+
<div className="flex items-center gap-2">
|
|
141
|
+
<Chip size="sm" color="warning" variant="flat">
|
|
142
|
+
POST
|
|
143
|
+
</Chip>
|
|
144
|
+
<code className="text-sm">/api/auth/signin</code>
|
|
145
|
+
<span className="text-slate-600 dark:text-slate-400">
|
|
146
|
+
- Authentification
|
|
147
|
+
</span>
|
|
148
|
+
</div>
|
|
149
|
+
<div className="flex items-center gap-2">
|
|
150
|
+
<Chip size="sm" color="warning" variant="flat">
|
|
151
|
+
POST
|
|
152
|
+
</Chip>
|
|
153
|
+
<code className="text-sm">/api/auth/signup</code>
|
|
154
|
+
<span className="text-slate-600 dark:text-slate-400">
|
|
155
|
+
- Création de compte
|
|
156
|
+
</span>
|
|
157
|
+
</div>
|
|
158
|
+
<div className="flex items-center gap-2">
|
|
159
|
+
<Chip size="sm" color="warning" variant="flat">
|
|
160
|
+
POST
|
|
161
|
+
</Chip>
|
|
162
|
+
<code className="text-sm">/api/auth/signout</code>
|
|
163
|
+
<span className="text-slate-600 dark:text-slate-400">
|
|
164
|
+
- Déconnexion
|
|
165
|
+
</span>
|
|
166
|
+
</div>
|
|
167
|
+
<div className="flex items-center gap-2">
|
|
168
|
+
<Chip size="sm" color="warning" variant="flat">
|
|
169
|
+
POST
|
|
170
|
+
</Chip>
|
|
171
|
+
<code className="text-sm">/api/auth/reset-password</code>
|
|
172
|
+
<span className="text-slate-600 dark:text-slate-400">
|
|
173
|
+
- Réinitialisation
|
|
174
|
+
</span>
|
|
175
|
+
</div>
|
|
176
|
+
</CardBody>
|
|
177
|
+
</Card>
|
|
178
|
+
|
|
179
|
+
{/* Structure */}
|
|
180
|
+
<Card>
|
|
181
|
+
<CardHeader>
|
|
182
|
+
<h3 className="text-xl font-semibold">📁 Structure</h3>
|
|
183
|
+
</CardHeader>
|
|
184
|
+
<CardBody>
|
|
185
|
+
<Snippet
|
|
186
|
+
symbol=""
|
|
187
|
+
color="default"
|
|
188
|
+
size="sm"
|
|
189
|
+
hideSymbol
|
|
190
|
+
hideCopyButton
|
|
191
|
+
>
|
|
192
|
+
<div className="flex flex-col gap-1 text-xs font-mono">
|
|
193
|
+
<span>module-auth/</span>
|
|
194
|
+
<span>├── src/</span>
|
|
195
|
+
<span>│ ├── web/</span>
|
|
196
|
+
<span>│ │ ├── signin/</span>
|
|
197
|
+
<span>│ │ ├── signup/</span>
|
|
198
|
+
<span>│ │ ├── dashboard/</span>
|
|
199
|
+
<span>│ │ ├── profile/</span>
|
|
200
|
+
<span>│ │ └── reglage/</span>
|
|
201
|
+
<span>│ ├── api/</span>
|
|
202
|
+
<span>│ │ ├── signin.ts</span>
|
|
203
|
+
<span>│ │ ├── signup.ts</span>
|
|
204
|
+
<span>│ │ ├── signout.ts</span>
|
|
205
|
+
<span>│ │ └── reset-password.ts</span>
|
|
206
|
+
<span>│ ├── components/</span>
|
|
207
|
+
<span>│ │ └── Doc.tsx</span>
|
|
208
|
+
<span>│ └── auth.build.config.ts</span>
|
|
209
|
+
<span>└── supabase/</span>
|
|
210
|
+
<span> └── migrations/</span>
|
|
211
|
+
<span> └── 20251112000000_user_init.sql</span>
|
|
212
|
+
</div>
|
|
213
|
+
</Snippet>
|
|
214
|
+
</CardBody>
|
|
215
|
+
</Card>
|
|
216
|
+
|
|
217
|
+
{/* Tables de données */}
|
|
218
|
+
<Card>
|
|
219
|
+
<CardHeader>
|
|
220
|
+
<h3 className="text-xl font-semibold">🗄️ Tables de Données</h3>
|
|
221
|
+
</CardHeader>
|
|
222
|
+
<CardBody className="space-y-4">
|
|
223
|
+
<TableStructure
|
|
224
|
+
tableName="user_profil"
|
|
225
|
+
title="user_profil"
|
|
226
|
+
description="Profil étendu des utilisateurs (prénom, nom, téléphone, avatar, bio, etc.)"
|
|
227
|
+
/>
|
|
228
|
+
<TableStructure
|
|
229
|
+
tableName="user_address"
|
|
230
|
+
title="user_address"
|
|
231
|
+
description="Adresses des utilisateurs (livraison, facturation)"
|
|
232
|
+
/>
|
|
233
|
+
<TableStructure
|
|
234
|
+
tableName="user_notifications"
|
|
235
|
+
title="user_notifications"
|
|
236
|
+
description="Préférences de notifications (email, SMS, push)"
|
|
237
|
+
/>
|
|
238
|
+
</CardBody>
|
|
239
|
+
</Card>
|
|
240
|
+
|
|
241
|
+
{/* Installation */}
|
|
242
|
+
<Card>
|
|
243
|
+
<CardHeader>
|
|
244
|
+
<h3 className="text-xl font-semibold">📦 Installation</h3>
|
|
245
|
+
</CardHeader>
|
|
246
|
+
<CardBody className="space-y-4">
|
|
247
|
+
<div className="flex flex-col gap-2">
|
|
248
|
+
<Snippet symbol="" color="default" size="sm">
|
|
249
|
+
pnpm lastbrain add-module auth
|
|
250
|
+
</Snippet>
|
|
251
|
+
<Snippet symbol="" color="default" size="sm">
|
|
252
|
+
pnpm build:modules
|
|
253
|
+
</Snippet>
|
|
254
|
+
<Snippet symbol="" color="default" size="sm">
|
|
255
|
+
supabase migration up
|
|
256
|
+
</Snippet>
|
|
257
|
+
</div>
|
|
258
|
+
|
|
259
|
+
{/* Danger Zone */}
|
|
260
|
+
<div className="border-2 border-danger rounded-lg p-4 mt-6">
|
|
261
|
+
<h4 className="text-lg font-semibold text-danger mb-3">
|
|
262
|
+
⚠️ Danger Zone
|
|
263
|
+
</h4>
|
|
264
|
+
<p className="text-sm text-slate-600 dark:text-slate-400 mb-3">
|
|
265
|
+
La suppression du module supprimera toutes les pages, routes API
|
|
266
|
+
et migrations associées. Cette action est irréversible.
|
|
267
|
+
</p>
|
|
268
|
+
<div className="flex flex-col gap-2">
|
|
269
|
+
<Snippet symbol="" color="danger" size="sm">
|
|
270
|
+
pnpm lastbrain remove-module auth
|
|
271
|
+
</Snippet>
|
|
272
|
+
<Snippet symbol="" color="danger" size="sm">
|
|
273
|
+
pnpm build:modules
|
|
274
|
+
</Snippet>
|
|
275
|
+
</div>
|
|
276
|
+
</div>
|
|
277
|
+
</CardBody>
|
|
278
|
+
</Card>
|
|
279
|
+
|
|
280
|
+
{/* Utilisation */}
|
|
281
|
+
<Card>
|
|
282
|
+
<CardHeader>
|
|
283
|
+
<h3 className="text-xl font-semibold">💡 Utilisation</h3>
|
|
284
|
+
</CardHeader>
|
|
285
|
+
<CardBody className="space-y-3">
|
|
286
|
+
<div>
|
|
287
|
+
<h4 className="font-semibold mb-2">Hooks disponibles</h4>
|
|
288
|
+
<Snippet symbol="" color="default" size="sm">
|
|
289
|
+
import {"{ useAuthSession }"} from "@lastbrain/module-auth";
|
|
290
|
+
</Snippet>
|
|
291
|
+
</div>
|
|
292
|
+
<div>
|
|
293
|
+
<h4 className="font-semibold mb-2 mt-4">Exemple d'utilisation</h4>
|
|
294
|
+
<Snippet symbol="" color="default" size="sm" hideSymbol>
|
|
295
|
+
<div className="flex flex-col gap-1">
|
|
296
|
+
<span>const {"{ session, user }"} = useAuthSession();</span>
|
|
297
|
+
<span>
|
|
298
|
+
if (!session) return <p>Non connecté</p>;
|
|
299
|
+
</span>
|
|
300
|
+
<span>
|
|
301
|
+
return <div>Bonjour {"{user.email}"}</div>;
|
|
302
|
+
</span>
|
|
303
|
+
</div>
|
|
304
|
+
</Snippet>
|
|
305
|
+
</div>
|
|
306
|
+
</CardBody>
|
|
307
|
+
</Card>
|
|
308
|
+
</div>
|
|
309
|
+
);
|
|
310
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
// Client Components uniquement
|
|
2
|
+
export { SignInPage } from "./web/public/SignInPage.js";
|
|
3
|
+
export { SignUpPage } from "./web/public/SignUpPage.js";
|
|
4
|
+
export { ResetPassword } from "./web/public/ResetPassword.js";
|
|
5
|
+
export { DashboardPage } from "./web/auth/dashboard.js";
|
|
6
|
+
export { ProfilePage } from "./web/auth/profile.js";
|
|
7
|
+
export { ReglagePage } from "./web/auth/reglage.js";
|
|
8
|
+
export { AdminUsersPage } from "./web/admin/users.js";
|
|
9
|
+
// Documentation
|
|
10
|
+
export { AuthModuleDoc } from "./components/Doc.js";
|
|
11
|
+
// Configuration de build (utilisée par les scripts)
|
|
12
|
+
export { default as authBuildConfig } from "./auth.build.config.js";
|
package/src/server.ts
ADDED
|
@@ -0,0 +1,255 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import type { FormEvent } from "react";
|
|
4
|
+
import { Suspense, useState } from "react";
|
|
5
|
+
import { supabaseBrowserClient } from "@lastbrain/core";
|
|
6
|
+
import {
|
|
7
|
+
addToast,
|
|
8
|
+
Button,
|
|
9
|
+
Card,
|
|
10
|
+
CardBody,
|
|
11
|
+
CardHeader,
|
|
12
|
+
Chip,
|
|
13
|
+
Input,
|
|
14
|
+
Link,
|
|
15
|
+
} from "@lastbrain/ui";
|
|
16
|
+
import { ArrowRight, Lock, Mail, Sparkles } from "lucide-react";
|
|
17
|
+
import { useRouter, useSearchParams } from "next/navigation";
|
|
18
|
+
|
|
19
|
+
function SignInForm() {
|
|
20
|
+
const searchParams = useSearchParams();
|
|
21
|
+
|
|
22
|
+
const [email, setEmail] = useState("");
|
|
23
|
+
const [password, setPassword] = useState("");
|
|
24
|
+
const [isLoading, setIsLoading] = useState(false);
|
|
25
|
+
const [error, setError] = useState<string | null>(null);
|
|
26
|
+
const redirectUrl = searchParams.get("redirect");
|
|
27
|
+
const router = useRouter();
|
|
28
|
+
const handleSubmit = async (event: FormEvent<HTMLFormElement>) => {
|
|
29
|
+
event.preventDefault();
|
|
30
|
+
setIsLoading(true);
|
|
31
|
+
|
|
32
|
+
try {
|
|
33
|
+
const { data, error } =
|
|
34
|
+
await supabaseBrowserClient.auth.signInWithPassword({
|
|
35
|
+
email,
|
|
36
|
+
password,
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
if (error) {
|
|
40
|
+
addToast({
|
|
41
|
+
color: "danger",
|
|
42
|
+
title: "Erreur de connexion",
|
|
43
|
+
description: error.message,
|
|
44
|
+
});
|
|
45
|
+
setIsLoading(false);
|
|
46
|
+
return;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
setIsLoading(false);
|
|
50
|
+
console.log("Signed in user:", data.user);
|
|
51
|
+
addToast({
|
|
52
|
+
color: "success",
|
|
53
|
+
title: "Connecté avec succès !",
|
|
54
|
+
description: `Bienvenue ${data.user?.email}`,
|
|
55
|
+
});
|
|
56
|
+
router.push(redirectUrl || "/auth/dashboard");
|
|
57
|
+
} catch (err) {
|
|
58
|
+
setIsLoading(false);
|
|
59
|
+
addToast({
|
|
60
|
+
color: "danger",
|
|
61
|
+
title: "Erreur de connexion",
|
|
62
|
+
description: `${err instanceof Error ? err.message : String(err)}`,
|
|
63
|
+
});
|
|
64
|
+
}
|
|
65
|
+
};
|
|
66
|
+
|
|
67
|
+
// return (
|
|
68
|
+
// <div className=" min-h-screen h-full flex flex-col justify-center items-center p-4">
|
|
69
|
+
// <Card className="w-full max-w-md">
|
|
70
|
+
// <CardHeader className="flex flex-col">
|
|
71
|
+
// <h1 className="text-2xl font-semibold text-slate-900 dark:text-white">
|
|
72
|
+
// Connexion
|
|
73
|
+
// </h1>
|
|
74
|
+
// <p className="mt-1 text-sm text-slate-500">
|
|
75
|
+
// Utilisez votre adresse email pour vous connecter.
|
|
76
|
+
// </p>
|
|
77
|
+
// </CardHeader>
|
|
78
|
+
// <CardBody>
|
|
79
|
+
// <form onSubmit={handleSubmit}>
|
|
80
|
+
// <div className="space-y-6">
|
|
81
|
+
// <Input
|
|
82
|
+
// label="Email"
|
|
83
|
+
// type="email"
|
|
84
|
+
// className="mb-6"
|
|
85
|
+
// required
|
|
86
|
+
// value={email}
|
|
87
|
+
// onChange={(event) => setEmail(event.target.value)}
|
|
88
|
+
// />
|
|
89
|
+
|
|
90
|
+
// <Input
|
|
91
|
+
// label="Mot de passe"
|
|
92
|
+
// type="password"
|
|
93
|
+
// required
|
|
94
|
+
// placeholder="Password"
|
|
95
|
+
// className="mb-6"
|
|
96
|
+
// value={password}
|
|
97
|
+
// onChange={(event) => setPassword(event.target.value)}
|
|
98
|
+
// />
|
|
99
|
+
|
|
100
|
+
// <Button
|
|
101
|
+
// size="lg"
|
|
102
|
+
// type="submit"
|
|
103
|
+
// className="w-full"
|
|
104
|
+
// isLoading={isLoading}
|
|
105
|
+
// >
|
|
106
|
+
// Se connecter
|
|
107
|
+
// </Button>
|
|
108
|
+
// </div>
|
|
109
|
+
// </form>
|
|
110
|
+
// </CardBody>
|
|
111
|
+
// </Card>
|
|
112
|
+
// </div>
|
|
113
|
+
// );
|
|
114
|
+
|
|
115
|
+
return (
|
|
116
|
+
<div className="pt-12 relative min-h-screen w-full overflow-hidden bg-gradient-to-br from-primary-50/30 to-secondary-50/30 dark:from-primary-950/50 dark:to-secondary-950/50">
|
|
117
|
+
{/* Background decoration */}
|
|
118
|
+
<div className="absolute inset-0 bg-grid-slate-100 dark:bg-grid-slate-800/20 mask-[linear-gradient(0deg,white,rgba(255,255,255,0.6))] dark:mask-[linear-gradient(0deg,rgba(255,255,255,0.1),rgba(255,255,255,0.05))]" />
|
|
119
|
+
|
|
120
|
+
<div className="relative flex min-h-[60vh] items-center justify-center px-4 py-12">
|
|
121
|
+
<div className="w-full max-w-md">
|
|
122
|
+
{/* Header */}
|
|
123
|
+
<div className="mb-8 text-center">
|
|
124
|
+
<Chip
|
|
125
|
+
color="primary"
|
|
126
|
+
variant="flat"
|
|
127
|
+
startContent={<Sparkles className="w-4 h-4" />}
|
|
128
|
+
className="mb-4 p-2"
|
|
129
|
+
>
|
|
130
|
+
Espace membre
|
|
131
|
+
</Chip>
|
|
132
|
+
<h1 className="mb-3 text-4xl font-bold">
|
|
133
|
+
<span className="bg-gradient-to-r from-primary-600 to-secondary-600 bg-clip-text text-transparent">
|
|
134
|
+
Bon retour
|
|
135
|
+
</span>
|
|
136
|
+
</h1>
|
|
137
|
+
<p className="text-default-600 dark:text-default-400">
|
|
138
|
+
Connectez-vous pour accéder à votre espace
|
|
139
|
+
</p>
|
|
140
|
+
</div>
|
|
141
|
+
|
|
142
|
+
{/* Form Card */}
|
|
143
|
+
<Card className="border border-default-200/60 bg-background/60 backdrop-blur-md backdrop-saturate-150 shadow-xl">
|
|
144
|
+
<CardBody className="gap-6 p-8">
|
|
145
|
+
<form onSubmit={handleSubmit} className="flex flex-col gap-6">
|
|
146
|
+
<Input
|
|
147
|
+
label="Email"
|
|
148
|
+
type="email"
|
|
149
|
+
placeholder="votre@email.com"
|
|
150
|
+
value={email}
|
|
151
|
+
onChange={(e) => setEmail(e.target.value)}
|
|
152
|
+
required
|
|
153
|
+
startContent={<Mail className="h-4 w-4 text-default-400" />}
|
|
154
|
+
classNames={{
|
|
155
|
+
input: "text-base",
|
|
156
|
+
inputWrapper:
|
|
157
|
+
"border border-default-200/60 hover:border-primary-400 focus-within:border-primary-500",
|
|
158
|
+
}}
|
|
159
|
+
/>
|
|
160
|
+
|
|
161
|
+
<div className="flex flex-col gap-2">
|
|
162
|
+
<Input
|
|
163
|
+
label="Mot de passe"
|
|
164
|
+
type="password"
|
|
165
|
+
placeholder="••••••••"
|
|
166
|
+
value={password}
|
|
167
|
+
onChange={(e) => setPassword(e.target.value)}
|
|
168
|
+
required
|
|
169
|
+
minLength={6}
|
|
170
|
+
startContent={<Lock className="h-4 w-4 text-default-400" />}
|
|
171
|
+
classNames={{
|
|
172
|
+
input: "text-base",
|
|
173
|
+
inputWrapper:
|
|
174
|
+
"border border-default-200/60 hover:border-primary-400 focus-within:border-primary-500",
|
|
175
|
+
}}
|
|
176
|
+
/>
|
|
177
|
+
<Link
|
|
178
|
+
href="/forgot-password"
|
|
179
|
+
className="text-xs text-default-500 hover:text-primary-500 self-end"
|
|
180
|
+
>
|
|
181
|
+
Mot de passe oublié ?
|
|
182
|
+
</Link>
|
|
183
|
+
</div>
|
|
184
|
+
|
|
185
|
+
{error && (
|
|
186
|
+
<div className="rounded-lg border border-danger-200 bg-danger-50/50 px-4 py-3 dark:border-danger-800 dark:bg-danger-950/50">
|
|
187
|
+
<p className="text-sm text-danger-600 dark:text-danger-400">
|
|
188
|
+
{error}
|
|
189
|
+
</p>
|
|
190
|
+
</div>
|
|
191
|
+
)}
|
|
192
|
+
|
|
193
|
+
<Button
|
|
194
|
+
type="submit"
|
|
195
|
+
color="primary"
|
|
196
|
+
size="lg"
|
|
197
|
+
className="w-full font-semibold"
|
|
198
|
+
endContent={
|
|
199
|
+
isLoading ? null : <ArrowRight className="h-5 w-5" />
|
|
200
|
+
}
|
|
201
|
+
isLoading={isLoading}
|
|
202
|
+
>
|
|
203
|
+
Se connecter
|
|
204
|
+
</Button>
|
|
205
|
+
</form>
|
|
206
|
+
|
|
207
|
+
{/* Divider */}
|
|
208
|
+
<div className="relative flex items-center gap-4 py-2">
|
|
209
|
+
<div className="h-px flex-1 bg-default-200" />
|
|
210
|
+
<span className="text-xs text-default-400">OU</span>
|
|
211
|
+
<div className="h-px flex-1 bg-default-200" />
|
|
212
|
+
</div>
|
|
213
|
+
|
|
214
|
+
{/* Sign up link */}
|
|
215
|
+
<div className="text-center">
|
|
216
|
+
<p className="text-sm text-default-600">
|
|
217
|
+
Pas encore de compte ?{" "}
|
|
218
|
+
<Link
|
|
219
|
+
href={
|
|
220
|
+
redirectUrl
|
|
221
|
+
? `/signup?redirect=${encodeURIComponent(redirectUrl)}`
|
|
222
|
+
: "/signup"
|
|
223
|
+
}
|
|
224
|
+
className="font-semibold text-primary-600 hover:text-primary-700"
|
|
225
|
+
>
|
|
226
|
+
Créer un compte
|
|
227
|
+
</Link>
|
|
228
|
+
</p>
|
|
229
|
+
</div>
|
|
230
|
+
</CardBody>
|
|
231
|
+
</Card>
|
|
232
|
+
|
|
233
|
+
{/* Footer */}
|
|
234
|
+
<p className="mt-8 text-center text-xs text-default-400">
|
|
235
|
+
En vous connectant, vous acceptez nos{" "}
|
|
236
|
+
<Link
|
|
237
|
+
href="/legal/terms"
|
|
238
|
+
className="underline hover:text-primary-500"
|
|
239
|
+
>
|
|
240
|
+
conditions d'utilisation
|
|
241
|
+
</Link>
|
|
242
|
+
</p>
|
|
243
|
+
</div>
|
|
244
|
+
</div>
|
|
245
|
+
</div>
|
|
246
|
+
);
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
export function SignInPage() {
|
|
250
|
+
return (
|
|
251
|
+
<Suspense fallback={<div>Chargement...</div>}>
|
|
252
|
+
<SignInForm />
|
|
253
|
+
</Suspense>
|
|
254
|
+
);
|
|
255
|
+
}
|
|
@@ -0,0 +1,293 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import {
|
|
4
|
+
Button,
|
|
5
|
+
Card,
|
|
6
|
+
CardBody,
|
|
7
|
+
Input,
|
|
8
|
+
Link,
|
|
9
|
+
Chip,
|
|
10
|
+
addToast,
|
|
11
|
+
} from "@lastbrain/ui";
|
|
12
|
+
import { useRouter, useSearchParams } from "next/navigation";
|
|
13
|
+
import { Suspense, useState } from "react";
|
|
14
|
+
import {
|
|
15
|
+
Mail,
|
|
16
|
+
Lock,
|
|
17
|
+
User,
|
|
18
|
+
ArrowRight,
|
|
19
|
+
Sparkles,
|
|
20
|
+
CheckCircle2,
|
|
21
|
+
} from "lucide-react";
|
|
22
|
+
import { supabaseBrowserClient } from "@lastbrain/core";
|
|
23
|
+
|
|
24
|
+
function SignUpForm() {
|
|
25
|
+
const router = useRouter();
|
|
26
|
+
const searchParams = useSearchParams();
|
|
27
|
+
const [fullName, setFullName] = useState("");
|
|
28
|
+
const [email, setEmail] = useState("");
|
|
29
|
+
const [password, setPassword] = useState("");
|
|
30
|
+
const [confirmPassword, setConfirmPassword] = useState("");
|
|
31
|
+
const [loading, setLoading] = useState(false);
|
|
32
|
+
const [error, setError] = useState<string | null>(null);
|
|
33
|
+
const [success, setSuccess] = useState<string | null>(null);
|
|
34
|
+
|
|
35
|
+
// Récupérer le paramètre redirect
|
|
36
|
+
const redirectUrl = searchParams.get("redirect");
|
|
37
|
+
|
|
38
|
+
const handleSubmit = async (event: React.FormEvent<HTMLFormElement>) => {
|
|
39
|
+
event.preventDefault();
|
|
40
|
+
setError(null);
|
|
41
|
+
setSuccess(null);
|
|
42
|
+
|
|
43
|
+
if (password !== confirmPassword) {
|
|
44
|
+
setError("Les mots de passe ne correspondent pas.");
|
|
45
|
+
return;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
if (password.length < 6) {
|
|
49
|
+
setError("Le mot de passe doit contenir au moins 6 caractères.");
|
|
50
|
+
return;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
setLoading(true);
|
|
54
|
+
|
|
55
|
+
try {
|
|
56
|
+
const { data, error: signUpError } =
|
|
57
|
+
await supabaseBrowserClient.auth.signUp({
|
|
58
|
+
email,
|
|
59
|
+
password,
|
|
60
|
+
options: {
|
|
61
|
+
emailRedirectTo: `${window.location.origin}/api/auth/callback${
|
|
62
|
+
redirectUrl ? `?next=${encodeURIComponent(redirectUrl)}` : ""
|
|
63
|
+
}`,
|
|
64
|
+
data: {
|
|
65
|
+
full_name: fullName,
|
|
66
|
+
},
|
|
67
|
+
},
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
if (signUpError) {
|
|
71
|
+
setError(signUpError.message);
|
|
72
|
+
return;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
// Si la confirmation par email est requise
|
|
76
|
+
if (data.user && !data.session) {
|
|
77
|
+
setSuccess(
|
|
78
|
+
"Compte créé avec succès ! Veuillez vérifier votre email pour confirmer votre compte."
|
|
79
|
+
);
|
|
80
|
+
return;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
// Si l'utilisateur est directement connecté (confirmation email désactivée)
|
|
84
|
+
if (data.session) {
|
|
85
|
+
if (redirectUrl) {
|
|
86
|
+
window.location.href = redirectUrl;
|
|
87
|
+
} else {
|
|
88
|
+
window.location.href = "/auth/dashboard";
|
|
89
|
+
}
|
|
90
|
+
return;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
setSuccess("Compte créé. Vous pouvez désormais vous connecter.");
|
|
94
|
+
setTimeout(() => {
|
|
95
|
+
if (redirectUrl) {
|
|
96
|
+
router.push(`/signin?redirect=${encodeURIComponent(redirectUrl)}`);
|
|
97
|
+
} else {
|
|
98
|
+
router.push("/signin");
|
|
99
|
+
}
|
|
100
|
+
}, 2000);
|
|
101
|
+
} catch (err) {
|
|
102
|
+
addToast({
|
|
103
|
+
title: "Erreur",
|
|
104
|
+
description: "Une erreur inattendue est survenue.",
|
|
105
|
+
color: "danger",
|
|
106
|
+
});
|
|
107
|
+
} finally {
|
|
108
|
+
setLoading(false);
|
|
109
|
+
}
|
|
110
|
+
};
|
|
111
|
+
|
|
112
|
+
return (
|
|
113
|
+
<div className="pt-12 relative min-h-screen w-full overflow-hidden bg-gradient-to-br from-secondary-50/30 to-primary-50/30 dark:from-secondary-950/50 dark:to-primary-950/50">
|
|
114
|
+
{/* Background decoration */}
|
|
115
|
+
<div className="absolute inset-0 bg-grid-slate-100 dark:bg-grid-slate-800/20 mask-[linear-gradient(0deg,white,rgba(255,255,255,0.6))] dark:mask-[linear-gradient(0deg,rgba(255,255,255,0.1),rgba(255,255,255,0.05))]" />
|
|
116
|
+
|
|
117
|
+
<div className="relative flex min-h-screen items-center justify-center px-4 py-12">
|
|
118
|
+
<div className="w-full max-w-md">
|
|
119
|
+
{/* Header */}
|
|
120
|
+
<div className="mb-8 text-center">
|
|
121
|
+
<Chip
|
|
122
|
+
color="secondary"
|
|
123
|
+
variant="flat"
|
|
124
|
+
startContent={<Sparkles className="w-4 h-4" />}
|
|
125
|
+
className="mb-4 p-2"
|
|
126
|
+
>
|
|
127
|
+
Rejoignez-nous
|
|
128
|
+
</Chip>
|
|
129
|
+
<h1 className="mb-3 text-4xl font-bold">
|
|
130
|
+
<span className="bg-gradient-to-r from-secondary-600 to-primary-600 bg-clip-text text-transparent">
|
|
131
|
+
Commencer gratuitement
|
|
132
|
+
</span>
|
|
133
|
+
</h1>
|
|
134
|
+
<p className="text-default-600 dark:text-default-400">
|
|
135
|
+
Créez votre compte en quelques secondes
|
|
136
|
+
</p>
|
|
137
|
+
</div>
|
|
138
|
+
|
|
139
|
+
{/* Form Card */}
|
|
140
|
+
<Card className="border border-default-200/60 bg-background/60 backdrop-blur-md backdrop-saturate-150 shadow-xl">
|
|
141
|
+
<CardBody className="gap-6 p-8">
|
|
142
|
+
<form onSubmit={handleSubmit} className="flex flex-col gap-6">
|
|
143
|
+
<Input
|
|
144
|
+
label="Nom complet"
|
|
145
|
+
type="text"
|
|
146
|
+
placeholder="Jean Dupont"
|
|
147
|
+
value={fullName}
|
|
148
|
+
onChange={(e) => setFullName(e.target.value)}
|
|
149
|
+
startContent={<User className="h-4 w-4 text-default-400" />}
|
|
150
|
+
classNames={{
|
|
151
|
+
input: "text-base",
|
|
152
|
+
inputWrapper:
|
|
153
|
+
"border border-default-200/60 hover:border-secondary-400 focus-within:border-secondary-500",
|
|
154
|
+
}}
|
|
155
|
+
/>
|
|
156
|
+
|
|
157
|
+
<Input
|
|
158
|
+
label="Email"
|
|
159
|
+
type="email"
|
|
160
|
+
placeholder="votre@email.com"
|
|
161
|
+
value={email}
|
|
162
|
+
onChange={(e) => setEmail(e.target.value)}
|
|
163
|
+
required
|
|
164
|
+
startContent={<Mail className="h-4 w-4 text-default-400" />}
|
|
165
|
+
classNames={{
|
|
166
|
+
input: "text-base",
|
|
167
|
+
inputWrapper:
|
|
168
|
+
"border border-default-200/60 hover:border-secondary-400 focus-within:border-secondary-500",
|
|
169
|
+
}}
|
|
170
|
+
/>
|
|
171
|
+
|
|
172
|
+
<Input
|
|
173
|
+
label="Mot de passe"
|
|
174
|
+
type="password"
|
|
175
|
+
placeholder="••••••••"
|
|
176
|
+
value={password}
|
|
177
|
+
onChange={(e) => setPassword(e.target.value)}
|
|
178
|
+
required
|
|
179
|
+
minLength={6}
|
|
180
|
+
startContent={<Lock className="h-4 w-4 text-default-400" />}
|
|
181
|
+
classNames={{
|
|
182
|
+
input: "text-base",
|
|
183
|
+
inputWrapper:
|
|
184
|
+
"border border-default-200/60 hover:border-secondary-400 focus-within:border-secondary-500",
|
|
185
|
+
}}
|
|
186
|
+
description="Minimum 6 caractères"
|
|
187
|
+
/>
|
|
188
|
+
|
|
189
|
+
<Input
|
|
190
|
+
label="Confirmer le mot de passe"
|
|
191
|
+
type="password"
|
|
192
|
+
placeholder="••••••••"
|
|
193
|
+
value={confirmPassword}
|
|
194
|
+
onChange={(e) => setConfirmPassword(e.target.value)}
|
|
195
|
+
required
|
|
196
|
+
minLength={6}
|
|
197
|
+
startContent={<Lock className="h-4 w-4 text-default-400" />}
|
|
198
|
+
classNames={{
|
|
199
|
+
input: "text-base",
|
|
200
|
+
inputWrapper:
|
|
201
|
+
"border border-default-200/60 hover:border-secondary-400 focus-within:border-secondary-500",
|
|
202
|
+
}}
|
|
203
|
+
/>
|
|
204
|
+
|
|
205
|
+
{error && (
|
|
206
|
+
<div className="rounded-lg border border-danger-200 bg-danger-50/50 px-4 py-3 dark:border-danger-800 dark:bg-danger-950/50">
|
|
207
|
+
<p className="text-sm text-danger-600 dark:text-danger-400">
|
|
208
|
+
{error}
|
|
209
|
+
</p>
|
|
210
|
+
</div>
|
|
211
|
+
)}
|
|
212
|
+
|
|
213
|
+
{success && (
|
|
214
|
+
<div className="rounded-lg border border-success-200 bg-success-50/50 px-4 py-3 dark:border-success-800 dark:bg-success-950/50">
|
|
215
|
+
<div className="flex items-center gap-2">
|
|
216
|
+
<CheckCircle2 className="h-4 w-4 text-success-600 dark:text-success-400" />
|
|
217
|
+
<p className="text-sm text-success-600 dark:text-success-400">
|
|
218
|
+
{success}
|
|
219
|
+
</p>
|
|
220
|
+
</div>
|
|
221
|
+
</div>
|
|
222
|
+
)}
|
|
223
|
+
|
|
224
|
+
<Button
|
|
225
|
+
type="submit"
|
|
226
|
+
color="secondary"
|
|
227
|
+
size="lg"
|
|
228
|
+
className="w-full font-semibold"
|
|
229
|
+
endContent={
|
|
230
|
+
loading ? null : <ArrowRight className="h-5 w-5" />
|
|
231
|
+
}
|
|
232
|
+
isLoading={loading}
|
|
233
|
+
>
|
|
234
|
+
Créer mon compte
|
|
235
|
+
</Button>
|
|
236
|
+
</form>
|
|
237
|
+
|
|
238
|
+
{/* Divider */}
|
|
239
|
+
<div className="relative flex items-center gap-4 py-2">
|
|
240
|
+
<div className="h-px flex-1 bg-default-200" />
|
|
241
|
+
<span className="text-xs text-default-400">OU</span>
|
|
242
|
+
<div className="h-px flex-1 bg-default-200" />
|
|
243
|
+
</div>
|
|
244
|
+
|
|
245
|
+
{/* Sign in link */}
|
|
246
|
+
<div className="text-center">
|
|
247
|
+
<p className="text-sm text-default-600">
|
|
248
|
+
Déjà inscrit ?{" "}
|
|
249
|
+
<Link
|
|
250
|
+
href={
|
|
251
|
+
redirectUrl
|
|
252
|
+
? `/signin?redirect=${encodeURIComponent(redirectUrl)}`
|
|
253
|
+
: "/signin"
|
|
254
|
+
}
|
|
255
|
+
className="font-semibold text-secondary-600 hover:text-secondary-700"
|
|
256
|
+
>
|
|
257
|
+
Se connecter
|
|
258
|
+
</Link>
|
|
259
|
+
</p>
|
|
260
|
+
</div>
|
|
261
|
+
</CardBody>
|
|
262
|
+
</Card>
|
|
263
|
+
|
|
264
|
+
{/* Footer */}
|
|
265
|
+
<p className="mt-8 text-center text-xs text-default-400">
|
|
266
|
+
En créant un compte, vous acceptez nos{" "}
|
|
267
|
+
<Link
|
|
268
|
+
href="/legal/terms"
|
|
269
|
+
className="underline hover:text-secondary-500"
|
|
270
|
+
>
|
|
271
|
+
conditions d'utilisation
|
|
272
|
+
</Link>{" "}
|
|
273
|
+
et notre{" "}
|
|
274
|
+
<Link
|
|
275
|
+
href="/legal/privacy"
|
|
276
|
+
className="underline hover:text-secondary-500"
|
|
277
|
+
>
|
|
278
|
+
politique de confidentialité
|
|
279
|
+
</Link>
|
|
280
|
+
</p>
|
|
281
|
+
</div>
|
|
282
|
+
</div>
|
|
283
|
+
</div>
|
|
284
|
+
);
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
export function SignUpPage() {
|
|
288
|
+
return (
|
|
289
|
+
<Suspense fallback={<div>Chargement...</div>}>
|
|
290
|
+
<SignUpForm />
|
|
291
|
+
</Suspense>
|
|
292
|
+
);
|
|
293
|
+
}
|