@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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lastbrain/module-auth",
3
- "version": "0.1.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
- "scripts": {
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 &lt;p&gt;Non connecté&lt;/p&gt;;
299
+ </span>
300
+ <span>
301
+ return &lt;div&gt;Bonjour {"{user.email}"}&lt;/div&gt;;
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,2 @@
1
+ // Server-only exports (Route Handlers, Server Actions, etc.)
2
+ export { POST as signInApi } from "./api/public/signin.js";
@@ -0,0 +1,3 @@
1
+ export function AdminUsersPage() {
2
+ return <div>Admin Users Page</div>;
3
+ }
@@ -0,0 +1,3 @@
1
+ export function DashboardPage() {
2
+ return <div className="pt-12">Welcome to your dashboard!</div>;
3
+ }
@@ -0,0 +1,3 @@
1
+ export function ProfilePage() {
2
+ return <div className="pt-12">Welcome to your Profile!</div>;
3
+ }
@@ -0,0 +1,3 @@
1
+ export function ReglagePage() {
2
+ return <div className="pt-12">Welcome to your Reglage!</div>;
3
+ }
@@ -0,0 +1,3 @@
1
+ export function ResetPassword() {
2
+ return <div>Reset Password Page</div>;
3
+ }
@@ -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
+ }