update frontend

This commit is contained in:
Your Name 2025-12-10 18:04:28 -05:00
parent 73b48dc728
commit c130ad9fe6
4 changed files with 231 additions and 52 deletions

View File

@ -106,3 +106,26 @@ export async function createPost(payload, token) {
return res.json().catch(() => ({})); 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(() => ({}));
}

View File

@ -157,4 +157,3 @@ export function useAuth() {
} }
return ctx; return ctx;
} }
// modif 1

View File

@ -1,5 +1,6 @@
import React, { useState } from "react"; import React, { useState } from "react";
import { useAuth } from "../../auth/AuthContext"; import { useAuth } from "../../auth/AuthContext";
import { registerUser } from "../../api/client";
const THEME_LABELS = { const THEME_LABELS = {
dark: "Dark", dark: "Dark",
@ -17,18 +18,52 @@ export default function TopBar({ theme = "dark", onChangeTheme }) {
lastError, lastError,
} = useAuth(); } = useAuth();
const [mode, setMode] = useState("login"); // "login" | "register"
// login form
const [user, setUser] = useState(""); const [user, setUser] = useState("");
const [pass, setPass] = 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"; let authLabel = "AUTH: anon";
if (loading) authLabel = "AUTH: loading…"; if (loading) authLabel = "AUTH: loading…";
else if (authenticated) authLabel = `AUTH: user=${username || "?"}`; else if (authenticated) authLabel = `AUTH: user=${username || "?"}`;
const handleSubmit = (e) => { const handleLoginSubmit = (e) => {
e.preventDefault(); e.preventDefault();
login(user.trim(), pass); 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 ( return (
<header className="topbar"> <header className="topbar">
<div className="logo-circle">SW</div> <div className="logo-circle">SW</div>
@ -43,6 +78,11 @@ export default function TopBar({ theme = "dark", onChangeTheme }) {
({lastError}) ({lastError})
</span> </span>
)} )}
{signupError && (
<span style={{ color: "#f97373", marginLeft: "0.3rem" }}>
({signupError})
</span>
)}
</div> </div>
</div> </div>
@ -71,54 +111,169 @@ export default function TopBar({ theme = "dark", onChangeTheme }) {
</button> </button>
</div> </div>
) : ( ) : (
<form <div
onSubmit={handleSubmit}
style={{ style={{
display: "flex", display: "flex",
alignItems: "center", flexDirection: "column",
alignItems: "flex-end",
gap: "0.25rem", gap: "0.25rem",
minWidth: "230px",
}} }}
> >
<input <div
type="text"
placeholder="user"
value={user}
onChange={(e) => setUser(e.target.value)}
style={{ style={{
fontSize: "0.7rem", display: "flex",
padding: "0.15rem 0.4rem", gap: "0.25rem",
borderRadius: 999, fontSize: "0.65rem",
border: "1px solid #4b5563",
background: "#020617",
color: "#e5e7eb",
}} }}
/>
<input
type="password"
placeholder="pass"
value={pass}
onChange={(e) => setPass(e.target.value)}
style={{
fontSize: "0.7rem",
padding: "0.15rem 0.4rem",
borderRadius: 999,
border: "1px solid #4b5563",
background: "#020617",
color: "#e5e7eb",
}}
/>
<button
className="btn-primary"
type="submit"
disabled={loading}
style={{ fontSize: "0.7rem" }}
> >
{loading ? "..." : "Login"} <button
</button> type="button"
</form> onClick={() => setMode("login")}
style={{
padding: "0.15rem 0.45rem",
borderRadius: 999,
border: "1px solid #4b5563",
background:
mode === "login" ? "#1e293b" : "transparent",
color: "#e5e7eb",
cursor: "pointer",
fontSize: "0.65rem",
}}
>
Login
</button>
<button
type="button"
onClick={() => setMode("register")}
style={{
padding: "0.15rem 0.45rem",
borderRadius: 999,
border: "1px solid #4b5563",
background:
mode === "register" ? "#1e293b" : "transparent",
color: "#e5e7eb",
cursor: "pointer",
fontSize: "0.65rem",
}}
>
Sign up
</button>
</div>
{mode === "login" ? (
<form
onSubmit={handleLoginSubmit}
style={{
display: "flex",
alignItems: "center",
gap: "0.25rem",
}}
>
<input
type="text"
placeholder="user"
value={user}
onChange={(e) => setUser(e.target.value)}
style={{
fontSize: "0.7rem",
padding: "0.15rem 0.4rem",
borderRadius: 999,
border: "1px solid #4b5563",
background: "#020617",
color: "#e5e7eb",
}}
/>
<input
type="password"
placeholder="pass"
value={pass}
onChange={(e) => setPass(e.target.value)}
style={{
fontSize: "0.7rem",
padding: "0.15rem 0.4rem",
borderRadius: 999,
border: "1px solid #4b5563",
background: "#020617",
color: "#e5e7eb",
}}
/>
<button
className="btn-primary"
type="submit"
disabled={loading}
style={{ fontSize: "0.7rem" }}
>
{loading ? "..." : "Login"}
</button>
</form>
) : (
<form
onSubmit={handleRegisterSubmit}
style={{
display: "flex",
alignItems: "center",
gap: "0.25rem",
}}
>
<input
type="text"
placeholder="user"
value={regUser}
onChange={(e) => 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",
}}
/>
<input
type="email"
placeholder="email"
value={regEmail}
onChange={(e) => 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",
}}
/>
<input
type="password"
placeholder="pass"
value={regPass}
onChange={(e) => 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",
}}
/>
<button
className="btn-primary"
type="submit"
disabled={signupLoading}
style={{ fontSize: "0.7rem" }}
>
{signupLoading ? "..." : "Create"}
</button>
</form>
)}
</div>
)} )}
</div> </div>
</header> </header>
); );
} }
// modif 2

View File

@ -17,7 +17,7 @@ import PostList from "../Posts/PostList";
import { useAuth } from "../../auth/AuthContext"; import { useAuth } from "../../auth/AuthContext";
export default function MapView({ theme = "dark" }) { export default function MapView({ theme = "dark" }) {
const { authenticated, username } = useAuth(); const { authenticated, username, token } = useAuth();
const { const {
containerRef, containerRef,
@ -179,17 +179,19 @@ export default function MapView({ theme = "dark" }) {
setIsSaving(true); setIsSaving(true);
// On récupère le post retourné par le backend // On récupère le post retourné par le backend
const newPost = await createPost({ const newPost = await createPost(
title: draftTitle.trim(), {
snippet: draftBody.trim() || draftTitle.trim(), title: draftTitle.trim(),
category: draftCategory.toUpperCase(), snippet: draftBody.trim() || draftTitle.trim(),
sub_category: draftSubCategory || "ALL", category: draftCategory.toUpperCase(),
lat, sub_category: draftSubCategory || "ALL",
lon: lng, lat,
author: authorName, lon: lng,
}); author: authorName,
},
// On linjecte direct dans le moteur de posts, token
);
// On linjecte direct dans le moteur de posts,
// en plus du broadcast WebSocket // en plus du broadcast WebSocket
if (newPost && newPost.id) { if (newPost && newPost.id) {
handleIncomingPost(newPost); handleIncomingPost(newPost);