diff --git a/src/api/client.js b/src/api/client.js index efd5202..bc56594 100644 --- a/src/api/client.js +++ b/src/api/client.js @@ -106,3 +106,26 @@ export async function createPost(payload, token) { return res.json().catch(() => ({})); } + +/** + * Crée un nouvel utilisateur (backend doit exposer POST /api/register) + * payload: { username, email, password } + */ +export async function registerUser(payload) { + const res = await fetch(`${API_BASE}/register`, { + method: "POST", + headers: { + "Content-Type": "application/json", + }, + credentials: "include", + body: JSON.stringify(payload), + }); + + if (!res.ok) { + const text = await res.text().catch(() => ""); + console.error("registerUser failed", res.status, text); + throw new Error(text || "Failed to register user"); + } + + return res.json().catch(() => ({})); +} diff --git a/src/auth/AuthContext.jsx b/src/auth/AuthContext.jsx index 4b16782..d2689ca 100644 --- a/src/auth/AuthContext.jsx +++ b/src/auth/AuthContext.jsx @@ -157,4 +157,3 @@ export function useAuth() { } return ctx; } -// modif 1 \ No newline at end of file diff --git a/src/components/Layout/TopBar.jsx b/src/components/Layout/TopBar.jsx index 186695e..92852cd 100644 --- a/src/components/Layout/TopBar.jsx +++ b/src/components/Layout/TopBar.jsx @@ -1,5 +1,6 @@ import React, { useState } from "react"; import { useAuth } from "../../auth/AuthContext"; +import { registerUser } from "../../api/client"; const THEME_LABELS = { dark: "Dark", @@ -17,18 +18,52 @@ export default function TopBar({ theme = "dark", onChangeTheme }) { lastError, } = useAuth(); + const [mode, setMode] = useState("login"); // "login" | "register" + + // login form const [user, setUser] = useState(""); const [pass, setPass] = useState(""); + // signup form + const [regUser, setRegUser] = useState(""); + const [regEmail, setRegEmail] = useState(""); + const [regPass, setRegPass] = useState(""); + const [signupError, setSignupError] = useState(""); + const [signupLoading, setSignupLoading] = useState(false); + let authLabel = "AUTH: anon"; if (loading) authLabel = "AUTH: loading…"; else if (authenticated) authLabel = `AUTH: user=${username || "?"}`; - const handleSubmit = (e) => { + const handleLoginSubmit = (e) => { e.preventDefault(); login(user.trim(), pass); }; + const handleRegisterSubmit = async (e) => { + e.preventDefault(); + setSignupError(""); + if (!regUser.trim() || !regEmail.trim() || !regPass) { + setSignupError("All fields required."); + return; + } + try { + setSignupLoading(true); + await registerUser({ + username: regUser.trim(), + email: regEmail.trim(), + password: regPass, + }); + // auto login après création + login(regUser.trim(), regPass); + } catch (err) { + console.error(err); + setSignupError(err.message || "Register error."); + } finally { + setSignupLoading(false); + } + }; + return (
SW
@@ -43,6 +78,11 @@ export default function TopBar({ theme = "dark", onChangeTheme }) { ({lastError}) )} + {signupError && ( + + ({signupError}) + + )} @@ -71,54 +111,169 @@ export default function TopBar({ theme = "dark", onChangeTheme }) { ) : ( -
- setUser(e.target.value)} +
- setPass(e.target.value)} - style={{ - fontSize: "0.7rem", - padding: "0.15rem 0.4rem", - borderRadius: 999, - border: "1px solid #4b5563", - background: "#020617", - color: "#e5e7eb", - }} - /> - - + + +
+ + {mode === "login" ? ( +
+ setUser(e.target.value)} + style={{ + fontSize: "0.7rem", + padding: "0.15rem 0.4rem", + borderRadius: 999, + border: "1px solid #4b5563", + background: "#020617", + color: "#e5e7eb", + }} + /> + setPass(e.target.value)} + style={{ + fontSize: "0.7rem", + padding: "0.15rem 0.4rem", + borderRadius: 999, + border: "1px solid #4b5563", + background: "#020617", + color: "#e5e7eb", + }} + /> + +
+ ) : ( +
+ setRegUser(e.target.value)} + style={{ + fontSize: "0.7rem", + padding: "0.15rem 0.4rem", + borderRadius: 999, + border: "1px solid #4b5563", + background: "#020617", + color: "#e5e7eb", + minWidth: "70px", + }} + /> + setRegEmail(e.target.value)} + style={{ + fontSize: "0.7rem", + padding: "0.15rem 0.4rem", + borderRadius: 999, + border: "1px solid #4b5563", + background: "#020617", + color: "#e5e7eb", + minWidth: "90px", + }} + /> + setRegPass(e.target.value)} + style={{ + fontSize: "0.7rem", + padding: "0.15rem 0.4rem", + borderRadius: 999, + border: "1px solid #4b5563", + background: "#020617", + color: "#e5e7eb", + minWidth: "70px", + }} + /> + +
+ )} + )}
); } -// modif 2 \ No newline at end of file diff --git a/src/components/Map/MapView.jsx b/src/components/Map/MapView.jsx index 4d98ca4..c724dea 100644 --- a/src/components/Map/MapView.jsx +++ b/src/components/Map/MapView.jsx @@ -17,7 +17,7 @@ import PostList from "../Posts/PostList"; import { useAuth } from "../../auth/AuthContext"; export default function MapView({ theme = "dark" }) { - const { authenticated, username } = useAuth(); + const { authenticated, username, token } = useAuth(); const { containerRef, @@ -179,17 +179,19 @@ export default function MapView({ theme = "dark" }) { setIsSaving(true); // On récupère le post retourné par le backend - const newPost = await createPost({ - title: draftTitle.trim(), - snippet: draftBody.trim() || draftTitle.trim(), - category: draftCategory.toUpperCase(), - sub_category: draftSubCategory || "ALL", - lat, - lon: lng, - author: authorName, - }); - - // On l’injecte direct dans le moteur de posts, + const newPost = await createPost( + { + title: draftTitle.trim(), + snippet: draftBody.trim() || draftTitle.trim(), + category: draftCategory.toUpperCase(), + sub_category: draftSubCategory || "ALL", + lat, + lon: lng, + author: authorName, + }, + token + ); +// On l’injecte direct dans le moteur de posts, // en plus du broadcast WebSocket if (newPost && newPost.id) { handleIncomingPost(newPost);