update frontend
This commit is contained in:
parent
73b48dc728
commit
c130ad9fe6
|
|
@ -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(() => ({}));
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -157,4 +157,3 @@ export function useAuth() {
|
||||||
}
|
}
|
||||||
return ctx;
|
return ctx;
|
||||||
}
|
}
|
||||||
// modif 1
|
|
||||||
|
|
@ -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,8 +111,59 @@ export default function TopBar({ theme = "dark", onChangeTheme }) {
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
display: "flex",
|
||||||
|
flexDirection: "column",
|
||||||
|
alignItems: "flex-end",
|
||||||
|
gap: "0.25rem",
|
||||||
|
minWidth: "230px",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
display: "flex",
|
||||||
|
gap: "0.25rem",
|
||||||
|
fontSize: "0.65rem",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
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
|
<form
|
||||||
onSubmit={handleSubmit}
|
onSubmit={handleLoginSubmit}
|
||||||
style={{
|
style={{
|
||||||
display: "flex",
|
display: "flex",
|
||||||
alignItems: "center",
|
alignItems: "center",
|
||||||
|
|
@ -116,9 +207,73 @@ export default function TopBar({ theme = "dark", onChangeTheme }) {
|
||||||
{loading ? "..." : "Login"}
|
{loading ? "..." : "Login"}
|
||||||
</button>
|
</button>
|
||||||
</form>
|
</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
|
|
||||||
|
|
@ -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,7 +179,8 @@ 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(),
|
title: draftTitle.trim(),
|
||||||
snippet: draftBody.trim() || draftTitle.trim(),
|
snippet: draftBody.trim() || draftTitle.trim(),
|
||||||
category: draftCategory.toUpperCase(),
|
category: draftCategory.toUpperCase(),
|
||||||
|
|
@ -187,9 +188,10 @@ export default function MapView({ theme = "dark" }) {
|
||||||
lat,
|
lat,
|
||||||
lon: lng,
|
lon: lng,
|
||||||
author: authorName,
|
author: authorName,
|
||||||
});
|
},
|
||||||
|
token
|
||||||
// On l’injecte direct dans le moteur de posts,
|
);
|
||||||
|
// On l’injecte 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);
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue