diff --git a/.patch_backups/20251219-142134/src/auth/AuthContext.jsx b/.patch_backups/20251219-142134/src/auth/AuthContext.jsx deleted file mode 100644 index 6f58d5f..0000000 --- a/.patch_backups/20251219-142134/src/auth/AuthContext.jsx +++ /dev/null @@ -1,226 +0,0 @@ -import React, { createContext, useContext, useEffect, useRef, useState } from "react"; - -const AuthContext = createContext(null); - -// Backend API (same origin) -const API_BASE = "/api"; - -const TOKEN_KEY = "sociowire:access_token"; -const REFRESH_KEY = "sociowire:refresh_token"; -const USERNAME_KEY = "sociowire:username"; - -function decodeJwtPayload(token) { - try { - const parts = token.split("."); - if (parts.length < 2) return null; - const json = atob(parts[1].replace(/-/g, "+").replace(/_/g, "/")); - return JSON.parse(json); - } catch { - return null; - } -} - -function willExpireSoon(token, withinSeconds = 90) { - const p = decodeJwtPayload(token); - if (!p || !p.exp) return true; - const now = Math.floor(Date.now() / 1000); - return p.exp - now <= withinSeconds; -} - -async function backendRefresh(refreshToken) { - const res = await fetch(`${API_BASE}/refresh`, { - method: "POST", - headers: { "Content-Type": "application/json" }, - credentials: "include", - body: JSON.stringify({ refresh_token: refreshToken }), - }); - - const text = await res.text(); - if (!res.ok) { - throw new Error(`${res.status} ${text}`); - } - - const data = JSON.parse(text); - if (!data.access_token) throw new Error("No access_token in refresh response"); - return { - accessToken: data.access_token, - refreshToken: data.refresh_token || refreshToken, - }; -} - -export function AuthProvider({ children }) { - const [booting, setBooting] = useState(true); - const [loading, setLoading] = useState(false); - const [authenticated, setAuthenticated] = useState(false); - const [username, setUsername] = useState(""); - const [token, setToken] = useState(null); - const [lastError, setLastError] = useState(""); - - const usernameRef = useRef(""); - - function persistAuth({ accessToken, refreshToken, user }) { - setToken(accessToken); - setAuthenticated(true); - setUsername(user); - usernameRef.current = user; - try { - localStorage.setItem(TOKEN_KEY, accessToken); - if (refreshToken) localStorage.setItem(REFRESH_KEY, refreshToken); - localStorage.setItem(USERNAME_KEY, user); - } catch {} - } - - function clearAuth(msg = "") { - setAuthenticated(false); - setToken(null); - setUsername(""); - usernameRef.current = ""; - setLastError(msg); - try { - localStorage.removeItem(TOKEN_KEY); - localStorage.removeItem(REFRESH_KEY); - localStorage.removeItem(USERNAME_KEY); - } catch {} - } - - async function trySilentRefreshIfNeeded() { - try { - const storedToken = localStorage.getItem(TOKEN_KEY); - const storedRefresh = localStorage.getItem(REFRESH_KEY); - const storedUser = localStorage.getItem(USERNAME_KEY) || ""; - - if (!storedToken || !storedRefresh) return false; - - if (!willExpireSoon(storedToken, 30)) { - setToken(storedToken); - setUsername(storedUser); - usernameRef.current = storedUser; - setAuthenticated(true); - return true; - } - - const { accessToken, refreshToken } = await backendRefresh(storedRefresh); - persistAuth({ accessToken, refreshToken, user: storedUser }); - return true; - } catch (e) { - console.warn("[AUTH] silent refresh failed:", e); - clearAuth("Session expired. Please login again."); - return false; - } - } - - useEffect(() => { - (async () => { - try { - const ok = await trySilentRefreshIfNeeded(); - if (!ok) { - const storedToken = localStorage.getItem(TOKEN_KEY); - const storedUser = localStorage.getItem(USERNAME_KEY) || ""; - if (storedToken) { - setToken(storedToken); - setUsername(storedUser); - usernameRef.current = storedUser; - setAuthenticated(true); - } - } - } finally { - setBooting(false); - } - })(); - }, []); - - useEffect(() => { - if (!authenticated) return; - - const interval = setInterval(async () => { - try { - const t = localStorage.getItem(TOKEN_KEY); - const rt = localStorage.getItem(REFRESH_KEY); - if (!t || !rt) return; - - if (!willExpireSoon(t, 90)) return; - - const { accessToken, refreshToken } = await backendRefresh(rt); - const u = localStorage.getItem(USERNAME_KEY) || usernameRef.current || ""; - persistAuth({ accessToken, refreshToken, user: u }); - } catch (e) { - console.warn("[AUTH] auto refresh failed:", e); - clearAuth("Session expired. Please login again."); - } - }, 60_000); - - return () => clearInterval(interval); - }, [authenticated]); - - async function login(user, pass) { - setLastError(""); - const u = (user || "").trim(); - if (!u || !pass) { - setLastError("Username and password required."); - return; - } - - try { - setLoading(true); - - const res = await fetch(`${API_BASE}/login`, { - method: "POST", - headers: { "Content-Type": "application/json" }, - credentials: "include", - body: JSON.stringify({ username: u, password: pass }), - }); - - const text = await res.text(); - if (!res.ok) { - console.error("[AUTH] /api/login failed", res.status, text); - setLastError(`${res.status} login failed`); - return; - } - - const data = JSON.parse(text); - const accessToken = data.access_token; - const refreshToken = data.refresh_token; - - if (!accessToken) { - setLastError("No access token in response."); - return; - } - - persistAuth({ accessToken, refreshToken, user: u }); - setLastError(""); - } catch (e) { - console.error("[AUTH] network error", e); - setLastError("Network error during login."); - clearAuth("Network error during login."); - } finally { - setLoading(false); - } - } - - function logout() { - clearAuth(""); - } - - return ( - - {children} - - ); -} - -export function useAuth() { - const ctx = useContext(AuthContext); - if (!ctx) throw new Error("useAuth must be used inside "); - return ctx; -} diff --git a/.patch_backups/20251219-142134/src/auth/authErrors.js b/.patch_backups/20251219-142134/src/auth/authErrors.js deleted file mode 100644 index 3341f30..0000000 --- a/.patch_backups/20251219-142134/src/auth/authErrors.js +++ /dev/null @@ -1,47 +0,0 @@ -export function parseAuthError(text = "", status = 0) { - // Keycloak token error example: - // {"error":"invalid_grant","error_description":"Account is not fully set up"} - const raw = String(text || ""); - let err = ""; - let desc = ""; - - try { - const j = JSON.parse(raw); - err = String(j.error || ""); - desc = String(j.error_description || j.errorMessage || ""); - } catch { - // raw is not JSON - } - - const combined = (err + " " + desc + " " + raw).toLowerCase(); - - // Email verification / required action - if ( - combined.includes("not fully set up") || - combined.includes("verify") || - combined.includes("verification") || - combined.includes("required action") || - (combined.includes("email") && combined.includes("verified")) - ) { - return { - code: "email_verification_required", - message: - "Tu dois confirmer ton email avant de te connecter. Vérifie ta boîte mail (et le spam), puis réessaie.", - }; - } - - // Bad credentials - if ( - combined.includes("invalid user credentials") || - (combined.includes("invalid_grant") && !combined.includes("not fully set up")) - ) { - return { code: "invalid_credentials", message: "Mauvais username ou password." }; - } - - // Fallback - if (desc) return { code: "login_failed", message: desc }; - if (err) return { code: "login_failed", message: err }; - if (raw && raw.length < 220) return { code: "login_failed", message: raw }; - - return { code: "login_failed", message: status ? `Login failed (${status}).` : "Login failed." }; -} diff --git a/.patch_backups/20251219-142209/src/auth/AuthContext.jsx b/.patch_backups/20251219-142209/src/auth/AuthContext.jsx deleted file mode 100644 index 22a103e..0000000 --- a/.patch_backups/20251219-142209/src/auth/AuthContext.jsx +++ /dev/null @@ -1,312 +0,0 @@ -import React, { createContext, useContext, useEffect, useRef, useState } from "react"; -import { parseAuthError } from "./authErrors"; - -const AuthContext = createContext(null); - -// Backend API (same origin) -const API_BASE = "/api"; - -const TOKEN_KEY = "sociowire:access_token"; -const REFRESH_KEY = "sociowire:refresh_token"; -const USERNAME_KEY = "sociowire:username"; - -function decodeJwtPayload(token) { - try { - const parts = token.split("."); - if (parts.length < 2) return null; - const json = atob(parts[1].replace(/-/g, "+").replace(/_/g, "/")); - return JSON.parse(json); - } catch { - return null; - } -} - -function willExpireSoon(token, withinSeconds = 90) { - const p = decodeJwtPayload(token); - if (!p || !p.exp) return true; - const now = Math.floor(Date.now() / 1000); - return p.exp - now <= withinSeconds; -} - -async function backendRefresh(refreshToken) { - const res = await fetch(`${API_BASE}/refresh`, { - method: "POST", - headers: { "Content-Type": "application/json" }, - credentials: "include", - body: JSON.stringify({ refresh_token: refreshToken }), - }); - - const text = await res.text().catch(() => ""); - if (!res.ok) { - throw new Error(`${res.status} ${text}`); - } - - const data = JSON.parse(text || "{}"); - if (!data.access_token) throw new Error("No access_token in refresh response"); - return { - accessToken: data.access_token, - refreshToken: data.refresh_token || refreshToken, - }; -} - -async function backendWhoami(accessToken) { - const res = await fetch(`${API_BASE}/whoami`, { - method: "GET", - headers: accessToken ? { Authorization: `Bearer ${accessToken}` } : {}, - credentials: "include", - }); - - const text = await res.text().catch(() => ""); - if (!res.ok) return { ok: false, status: res.status, text }; - try { - const j = JSON.parse(text || "{}"); - return { ok: true, status: res.status, json: j, text }; - } catch { - return { ok: true, status: res.status, json: {}, text }; - } -} - -async function backendResendVerify(payload) { - const res = await fetch(`${API_BASE}/resend-verify`, { - method: "POST", - headers: { "Content-Type": "application/json" }, - credentials: "include", - body: JSON.stringify(payload), - }); - const text = await res.text().catch(() => ""); - return { ok: res.ok, status: res.status, text }; -} - -export function AuthProvider({ children }) { - const [booting, setBooting] = useState(true); - const [loading, setLoading] = useState(false); - - const [authenticated, setAuthenticated] = useState(false); - const [username, setUsername] = useState(""); - const [token, setToken] = useState(null); - - const [lastError, setLastError] = useState(""); - const [lastErrorCode, setLastErrorCode] = useState(""); - - const usernameRef = useRef(""); - - function persistAuth({ accessToken, refreshToken, user }) { - setToken(accessToken); - setAuthenticated(true); - setUsername(user || ""); - usernameRef.current = user || ""; - try { - localStorage.setItem(TOKEN_KEY, accessToken); - if (refreshToken) localStorage.setItem(REFRESH_KEY, refreshToken); - if (user) localStorage.setItem(USERNAME_KEY, user); - } catch {} - } - - function clearAuth(msg = "", code = "") { - setAuthenticated(false); - setToken(null); - setUsername(""); - usernameRef.current = ""; - setLastError(msg); - setLastErrorCode(code); - try { - localStorage.removeItem(TOKEN_KEY); - localStorage.removeItem(REFRESH_KEY); - localStorage.removeItem(USERNAME_KEY); - } catch {} - } - - async function trySilentRefreshIfNeeded() { - try { - const storedToken = localStorage.getItem(TOKEN_KEY) || ""; - const storedRefresh = localStorage.getItem(REFRESH_KEY) || ""; - const storedUser = localStorage.getItem(USERNAME_KEY) || ""; - - if (!storedToken) return false; - - // If token still valid, confirm with whoami (prevents stale local token) - if (!willExpireSoon(storedToken, 30)) { - const me = await backendWhoami(storedToken); - if (me.ok && me.json?.authenticated) { - setToken(storedToken); - setUsername(me.json.username || storedUser); - usernameRef.current = me.json.username || storedUser; - setAuthenticated(true); - return true; - } - } - - // Expired or rejected -> refresh - if (!storedRefresh) return false; - - const { accessToken, refreshToken } = await backendRefresh(storedRefresh); - - const me = await backendWhoami(accessToken); - const finalUser = (me.ok && me.json?.authenticated && (me.json.username || storedUser)) || storedUser; - - persistAuth({ accessToken, refreshToken, user: finalUser }); - return true; - } catch (e) { - console.warn("[AUTH] silent refresh failed:", e); - clearAuth("Session expired. Please login again.", "session_expired"); - return false; - } - } - - useEffect(() => { - (async () => { - try { - await trySilentRefreshIfNeeded(); - } finally { - setBooting(false); - } - })(); - }, []); - - useEffect(() => { - if (!authenticated) return; - - const interval = setInterval(async () => { - try { - const t = localStorage.getItem(TOKEN_KEY) || ""; - const rt = localStorage.getItem(REFRESH_KEY) || ""; - if (!t || !rt) return; - - if (!willExpireSoon(t, 90)) return; - - const { accessToken, refreshToken } = await backendRefresh(rt); - const me = await backendWhoami(accessToken); - const u = - (me.ok && me.json?.authenticated && (me.json.username || "")) || - localStorage.getItem(USERNAME_KEY) || - usernameRef.current || - ""; - - persistAuth({ accessToken, refreshToken, user: u }); - } catch (e) { - console.warn("[AUTH] auto refresh failed:", e); - clearAuth("Session expired. Please login again.", "session_expired"); - } - }, 60_000); - - return () => clearInterval(interval); - }, [authenticated]); - - async function login(user, pass) { - setLastError(""); - setLastErrorCode(""); - - const u = (user || "").trim(); - if (!u || !pass) { - setLastError("Username and password required."); - setLastErrorCode("missing_fields"); - return false; - } - - try { - setLoading(true); - - const res = await fetch(`${API_BASE}/login`, { - method: "POST", - headers: { "Content-Type": "application/json" }, - credentials: "include", - body: JSON.stringify({ username: u, password: pass }), - }); - - const text = await res.text().catch(() => ""); - if (!res.ok) { - const nice = parseAuthError(text, res.status); - setLastError(nice.message); - setLastErrorCode(nice.code); - return false; - } - - const data = JSON.parse(text || "{}"); - const accessToken = data.access_token; - const refreshToken = data.refresh_token; - - if (!accessToken) { - setLastError("No access token in response."); - setLastErrorCode("token_missing"); - return false; - } - - // confirm + fetch real username - const me = await backendWhoami(accessToken); - const finalUser = - (me.ok && me.json?.authenticated && (me.json.username || "")) || u; - - persistAuth({ accessToken, refreshToken, user: finalUser }); - setLastError(""); - setLastErrorCode(""); - return true; - } catch (e) { - console.error("[AUTH] network error", e); - clearAuth("Network error during login.", "network_error"); - return false; - } finally { - setLoading(false); - } - } - - async function resendVerifyEmail(usernameOrEmail) { - const v = String(usernameOrEmail || "").trim(); - if (!v) { - setLastError("Entre ton username ou email, puis clique renvoyer."); - setLastErrorCode("missing_fields"); - return false; - } - - try { - const res = await backendResendVerify({ value: v }); - if (!res.ok) { - setLastError(`Resend failed (${res.status})`); - setLastErrorCode("resend_failed"); - return false; - } - setLastError("Email de confirmation renvoyé. Vérifie ta boîte mail (et le spam)."); - setLastErrorCode("verify_sent"); - return true; - } catch (e) { - console.error(e); - setLastError("Network error while resending email."); - setLastErrorCode("network_error"); - return false; - } - } - - function logout() { - clearAuth("", ""); - } - - return ( - { - setLastError(""); - setLastErrorCode(""); - }, - }} - > - {children} - - ); -} - -export function useAuth() { - const ctx = useContext(AuthContext); - if (!ctx) throw new Error("useAuth must be used inside "); - return ctx; -} - diff --git a/.patch_backups/20251219-142209/src/auth/authErrors.js b/.patch_backups/20251219-142209/src/auth/authErrors.js deleted file mode 100644 index 3341f30..0000000 --- a/.patch_backups/20251219-142209/src/auth/authErrors.js +++ /dev/null @@ -1,47 +0,0 @@ -export function parseAuthError(text = "", status = 0) { - // Keycloak token error example: - // {"error":"invalid_grant","error_description":"Account is not fully set up"} - const raw = String(text || ""); - let err = ""; - let desc = ""; - - try { - const j = JSON.parse(raw); - err = String(j.error || ""); - desc = String(j.error_description || j.errorMessage || ""); - } catch { - // raw is not JSON - } - - const combined = (err + " " + desc + " " + raw).toLowerCase(); - - // Email verification / required action - if ( - combined.includes("not fully set up") || - combined.includes("verify") || - combined.includes("verification") || - combined.includes("required action") || - (combined.includes("email") && combined.includes("verified")) - ) { - return { - code: "email_verification_required", - message: - "Tu dois confirmer ton email avant de te connecter. Vérifie ta boîte mail (et le spam), puis réessaie.", - }; - } - - // Bad credentials - if ( - combined.includes("invalid user credentials") || - (combined.includes("invalid_grant") && !combined.includes("not fully set up")) - ) { - return { code: "invalid_credentials", message: "Mauvais username ou password." }; - } - - // Fallback - if (desc) return { code: "login_failed", message: desc }; - if (err) return { code: "login_failed", message: err }; - if (raw && raw.length < 220) return { code: "login_failed", message: raw }; - - return { code: "login_failed", message: status ? `Login failed (${status}).` : "Login failed." }; -} diff --git a/.patch_backups/20251219-142209/src/components/Layout/TopBar.jsx b/.patch_backups/20251219-142209/src/components/Layout/TopBar.jsx deleted file mode 100644 index 7d1cf2f..0000000 --- a/.patch_backups/20251219-142209/src/components/Layout/TopBar.jsx +++ /dev/null @@ -1,224 +0,0 @@ -import React, { useState, useEffect } from "react"; -import { useAuth } from "../../auth/AuthContext"; -import { registerUser } from "../../api/client"; - -const THEME_LABELS = { - dark: "Dark", - blue: "Blue", - light: "Light", -}; - -export default function TopBar({ theme = "dark", onChangeTheme }) { - const { loading, authenticated, username, login, logout, lastError } = useAuth(); - - const [showAuth, setShowAuth] = useState(false); - const [mode, setMode] = useState("login"); - - const [loginUser, setLoginUser] = useState(""); - const [loginPass, setLoginPass] = useState(""); - - const [firstName, setFirstName] = useState(""); - const [lastName, setLastName] = useState(""); - 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 openModal = (nextMode) => { - setMode(nextMode); - setShowAuth(true); - setSignupError(""); - }; - - const closeModal = () => { - setShowAuth(false); - setLoginPass(""); - setSignupError(""); - }; - - useEffect(() => { - if (authenticated) setShowAuth(false); - }, [authenticated]); - - const handleLoginSubmit = async (e) => { - e.preventDefault(); - await login(loginUser, loginPass); - }; - - const handleSignupSubmit = async (e) => { - e.preventDefault(); - setSignupError(""); - if ( - !firstName.trim() || - !lastName.trim() || - !regUser.trim() || - !regEmail.trim() || - !regPass - ) { - setSignupError("First name, last name, username, email and password are required."); - return; - } - try { - setSignupLoading(true); - await registerUser({ - username: regUser.trim(), - email: regEmail.trim(), - password: regPass, - firstName: firstName.trim(), - lastName: lastName.trim(), - }); - await login(regUser.trim(), regPass); - } catch (err) { - console.error(err); - setSignupError(err.message || "Registration error."); - } finally { - setSignupLoading(false); - } - }; - - return ( - <> -
-
-
SW
-
-
SOCIOWIRE.com
-
Wired to life
-
- {authLabel} - {lastError && ( - - ({lastError}) - - )} -
-
-
- -
-
- {["dark", "blue", "light"].map((t) => ( - - ))} -
- - {authenticated ? ( -
- {username} - -
- ) : ( -
- - -
- )} -
-
- - {showAuth && ( -
-
e.stopPropagation()}> -
- - - -
- - {mode === "login" ? ( -
- - - - {lastError &&
{lastError}
} -
- ) : ( -
-
- - -
- - - - - {signupError &&
{signupError}
} - - - {lastError &&
{lastError}
} -
- )} -
-
- )} - - ); -} diff --git a/src/auth/AuthContext.jsx.bak.1765947497 b/src/auth/AuthContext.jsx.bak.1765947497 deleted file mode 100644 index 695ed74..0000000 --- a/src/auth/AuthContext.jsx.bak.1765947497 +++ /dev/null @@ -1,246 +0,0 @@ -import React, { createContext, useContext, useEffect, useRef, useState } from "react"; - -const AuthContext = createContext(null); - -// Keycloak config -const KC_BASE = "https://web.sociowire.com:8443"; -const KC_REALM = "sociowire"; -const KC_CLIENT_ID = "sociowire-frontend"; - -const TOKEN_KEY = "sociowire:access_token"; -const REFRESH_KEY = "sociowire:refresh_token"; -const USERNAME_KEY = "sociowire:username"; - -// --- helpers --- -function decodeJwtPayload(token) { - try { - const parts = token.split("."); - if (parts.length < 2) return null; - const json = atob(parts[1].replace(/-/g, "+").replace(/_/g, "/")); - return JSON.parse(json); - } catch { - return null; - } -} - -function willExpireSoon(token, withinSeconds = 90) { - const p = decodeJwtPayload(token); - if (!p || !p.exp) return true; - const now = Math.floor(Date.now() / 1000); - return p.exp - now <= withinSeconds; -} - -async function refreshWithKeycloak(refreshToken) { - const params = new URLSearchParams(); - params.set("grant_type", "refresh_token"); - params.set("client_id", KC_CLIENT_ID); - params.set("refresh_token", refreshToken); - - const res = await fetch(`${KC_BASE}/realms/${KC_REALM}/protocol/openid-connect/token`, { - method: "POST", - headers: { "Content-Type": "application/x-www-form-urlencoded" }, - body: params.toString(), - }); - - const text = await res.text(); - if (!res.ok) { - let msg = text; - try { - const j = JSON.parse(text); - msg = j.error_description || j.error || text; - } catch {} - throw new Error(`${res.status} ${msg}`); - } - - const data = JSON.parse(text); - if (!data.access_token) throw new Error("No access_token in refresh response"); - return { - accessToken: data.access_token, - refreshToken: data.refresh_token || refreshToken, - }; -} - -export function AuthProvider({ children }) { - const [booting, setBooting] = useState(true); - const [loading, setLoading] = useState(false); - const [authenticated, setAuthenticated] = useState(false); - const [username, setUsername] = useState(""); - const [token, setToken] = useState(null); - const [lastError, setLastError] = useState(""); - - const usernameRef = useRef(""); - - function persistAuth({ accessToken, refreshToken, user }) { - setToken(accessToken); - setAuthenticated(true); - setUsername(user); - usernameRef.current = user; - try { - localStorage.setItem(TOKEN_KEY, accessToken); - if (refreshToken) localStorage.setItem(REFRESH_KEY, refreshToken); - localStorage.setItem(USERNAME_KEY, user); - } catch {} - } - - function clearAuth(msg = "") { - setAuthenticated(false); - setToken(null); - setUsername(""); - usernameRef.current = ""; - setLastError(msg); - try { - localStorage.removeItem(TOKEN_KEY); - localStorage.removeItem(REFRESH_KEY); - localStorage.removeItem(USERNAME_KEY); - } catch {} - } - - async function trySilentRefreshIfNeeded() { - try { - const storedToken = localStorage.getItem(TOKEN_KEY); - const storedRefresh = localStorage.getItem(REFRESH_KEY); - const storedUser = localStorage.getItem(USERNAME_KEY) || ""; - - if (!storedToken || !storedRefresh) return false; - - if (!willExpireSoon(storedToken, 30)) { - setToken(storedToken); - setUsername(storedUser); - usernameRef.current = storedUser; - setAuthenticated(true); - return true; - } - - const { accessToken, refreshToken } = await refreshWithKeycloak(storedRefresh); - persistAuth({ accessToken, refreshToken, user: storedUser }); - return true; - } catch (e) { - console.warn("[AUTH] silent refresh failed:", e); - clearAuth("Session expired. Please login again."); - return false; - } - } - - useEffect(() => { - (async () => { - try { - const ok = await trySilentRefreshIfNeeded(); - if (!ok) { - const storedToken = localStorage.getItem(TOKEN_KEY); - const storedUser = localStorage.getItem(USERNAME_KEY) || ""; - if (storedToken) { - setToken(storedToken); - setUsername(storedUser); - usernameRef.current = storedUser; - setAuthenticated(true); - } - } - } finally { - setBooting(false); - } - })(); - }, []); - - useEffect(() => { - if (!authenticated) return; - - const interval = setInterval(async () => { - try { - const t = localStorage.getItem(TOKEN_KEY); - const rt = localStorage.getItem(REFRESH_KEY); - if (!t || !rt) return; - - if (!willExpireSoon(t, 90)) return; - - const { accessToken, refreshToken } = await refreshWithKeycloak(rt); - const u = localStorage.getItem(USERNAME_KEY) || usernameRef.current || ""; - persistAuth({ accessToken, refreshToken, user: u }); - } catch (e) { - console.warn("[AUTH] auto refresh failed:", e); - clearAuth("Session expired. Please login again."); - } - }, 60_000); - - return () => clearInterval(interval); - }, [authenticated]); - - async function login(user, pass) { - setLastError(""); - const u = (user || "").trim(); - if (!u || !pass) { - setLastError("Username and password required."); - return; - } - - const params = new URLSearchParams(); - params.set("grant_type", "password"); - params.set("client_id", KC_CLIENT_ID); - params.set("username", u); - params.set("password", pass); - - try { - setLoading(true); - - const res = await fetch(`${KC_BASE}/realms/${KC_REALM}/protocol/openid-connect/token`, { - method: "POST", - headers: { "Content-Type": "application/x-www-form-urlencoded" }, - body: params.toString(), - }); - - const text = await res.text(); - if (!res.ok) { - let msg = "Invalid credentials or auth error."; - try { - const j = JSON.parse(text); - msg = j.error_description || j.error || msg; - } catch {} - clearAuth(`${res.status} ${msg}`); - return; - } - - const data = JSON.parse(text); - const accessToken = data.access_token; - const refreshToken = data.refresh_token; - - if (!accessToken) { - clearAuth("No access token in response."); - return; - } - - persistAuth({ accessToken, refreshToken, user: u }); - setLastError(""); - } catch (e) { - console.error("[AUTH] network error", e); - clearAuth("Network error during login."); - } finally { - setLoading(false); - } - } - - function logout() { - clearAuth(""); - } - - return ( - - {children} - - ); -} - -export function useAuth() { - const ctx = useContext(AuthContext); - if (!ctx) throw new Error("useAuth must be used inside "); - return ctx; -} diff --git a/src/auth/AuthContext.jsx.bak.1765948491 b/src/auth/AuthContext.jsx.bak.1765948491 deleted file mode 100644 index 037648e..0000000 --- a/src/auth/AuthContext.jsx.bak.1765948491 +++ /dev/null @@ -1,234 +0,0 @@ -import React, { createContext, useContext, useEffect, useRef, useState } from "react"; - -const AuthContext = createContext(null); - -// Keycloak config - -const TOKEN_KEY = "sociowire:access_token"; -const REFRESH_KEY = "sociowire:refresh_token"; -const USERNAME_KEY = "sociowire:username"; - -// --- helpers --- -function decodeJwtPayload(token) { - try { - const parts = token.split("."); - if (parts.length < 2) return null; - const json = atob(parts[1].replace(/-/g, "+").replace(/_/g, "/")); - return JSON.parse(json); - } catch { - return null; - } -} - -function willExpireSoon(token, withinSeconds = 90) { - const p = decodeJwtPayload(token); - if (!p || !p.exp) return true; - const now = Math.floor(Date.now() / 1000); - return p.exp - now <= withinSeconds; -} - -async function refreshViaBackend(refreshToken) { - const res = await fetch("/api/refresh", { - method: "POST", - headers: { "Content-Type": "application/json" }, - credentials: "include", - body: JSON.stringify({ refresh_token: refreshToken }), - }); - - const text = await res.text(); - if (!res.ok) { - let msg = text; - try { - const j = JSON.parse(text); - msg = j.error_description || j.error || text; - } catch {} - throw new Error(` `); - } - - const data = JSON.parse(text); - if (!data.access_token) throw new Error("No access_token in refresh response"); - return { - accessToken: data.access_token, - refreshToken: data.refresh_token || refreshToken, - }; -} - -export function AuthProvider({ children }) { - const [booting, setBooting] = useState(true); - const [loading, setLoading] = useState(false); - const [authenticated, setAuthenticated] = useState(false); - const [username, setUsername] = useState(""); - const [token, setToken] = useState(null); - const [lastError, setLastError] = useState(""); - - const usernameRef = useRef(""); - - function persistAuth({ accessToken, refreshToken, user }) { - setToken(accessToken); - setAuthenticated(true); - setUsername(user); - usernameRef.current = user; - try { - localStorage.setItem(TOKEN_KEY, accessToken); - if (refreshToken) localStorage.setItem(REFRESH_KEY, refreshToken); - localStorage.setItem(USERNAME_KEY, user); - } catch {} - } - - function clearAuth(msg = "") { - setAuthenticated(false); - setToken(null); - setUsername(""); - usernameRef.current = ""; - setLastError(msg); - try { - localStorage.removeItem(TOKEN_KEY); - localStorage.removeItem(REFRESH_KEY); - localStorage.removeItem(USERNAME_KEY); - } catch {} - } - - async function trySilentRefreshIfNeeded() { - try { - const storedToken = localStorage.getItem(TOKEN_KEY); - const storedRefresh = localStorage.getItem(REFRESH_KEY); - const storedUser = localStorage.getItem(USERNAME_KEY) || ""; - - if (!storedToken || !storedRefresh) return false; - - if (!willExpireSoon(storedToken, 30)) { - setToken(storedToken); - setUsername(storedUser); - usernameRef.current = storedUser; - setAuthenticated(true); - return true; - } - - const { accessToken, refreshToken } = await refreshViaBackend(storedRefresh); - persistAuth({ accessToken, refreshToken, user: storedUser }); - return true; - } catch (e) { - console.warn("[AUTH] silent refresh failed:", e); - clearAuth("Session expired. Please login again."); - return false; - } - } - - useEffect(() => { - (async () => { - try { - const ok = await trySilentRefreshIfNeeded(); - if (!ok) { - const storedToken = localStorage.getItem(TOKEN_KEY); - const storedUser = localStorage.getItem(USERNAME_KEY) || ""; - if (storedToken) { - setToken(storedToken); - setUsername(storedUser); - usernameRef.current = storedUser; - setAuthenticated(true); - } - } - } finally { - setBooting(false); - } - })(); - }, []); - - useEffect(() => { - if (!authenticated) return; - - const interval = setInterval(async () => { - try { - const t = localStorage.getItem(TOKEN_KEY); - const rt = localStorage.getItem(REFRESH_KEY); - if (!t || !rt) return; - - if (!willExpireSoon(t, 90)) return; - - const { accessToken, refreshToken } = await refreshViaBackend(rt); - const u = localStorage.getItem(USERNAME_KEY) || usernameRef.current || ""; - persistAuth({ accessToken, refreshToken, user: u }); - } catch (e) { - console.warn("[AUTH] auto refresh failed:", e); - clearAuth("Session expired. Please login again."); - } - }, 60_000); - - return () => clearInterval(interval); - }, [authenticated]); - - async function login(user, pass) { - setLastError(""); - const u = (user || "").trim(); - if (!u || !pass) { - setLastError("Username and password required."); - return; - } - - try { - setLoading(true); - - const res = await fetch("/api/login", { - method: "POST", - headers: { "Content-Type": "application/json" }, - credentials: "include", - body: JSON.stringify({ username: u, password: pass }), - }); - - const text = await res.text(); - if (!res.ok) { - let msg = "Invalid credentials or auth error."; - try { - const j = JSON.parse(text); - msg = j.error_description || j.error || msg; - } catch {} - clearAuth(` `); - return; - } - - const data = JSON.parse(text); - const accessToken = data.access_token; - const refreshToken = data.refresh_token; - - if (!accessToken) { - clearAuth("No access token in response."); - return; - } - - persistAuth({ accessToken, refreshToken, user: u }); - setLastError(""); - } catch (e) { - console.error("[AUTH] network error", e); - clearAuth("Network error during login."); - } finally { - setLoading(false); - } - } - - function logout() { - clearAuth(""); - } - - return ( - - {children} - - ); -} - -export function useAuth() { - const ctx = useContext(AuthContext); - if (!ctx) throw new Error("useAuth must be used inside "); - return ctx; -} diff --git a/src/auth/AuthContext.jsx.bak.1765948522 b/src/auth/AuthContext.jsx.bak.1765948522 deleted file mode 100644 index 037648e..0000000 --- a/src/auth/AuthContext.jsx.bak.1765948522 +++ /dev/null @@ -1,234 +0,0 @@ -import React, { createContext, useContext, useEffect, useRef, useState } from "react"; - -const AuthContext = createContext(null); - -// Keycloak config - -const TOKEN_KEY = "sociowire:access_token"; -const REFRESH_KEY = "sociowire:refresh_token"; -const USERNAME_KEY = "sociowire:username"; - -// --- helpers --- -function decodeJwtPayload(token) { - try { - const parts = token.split("."); - if (parts.length < 2) return null; - const json = atob(parts[1].replace(/-/g, "+").replace(/_/g, "/")); - return JSON.parse(json); - } catch { - return null; - } -} - -function willExpireSoon(token, withinSeconds = 90) { - const p = decodeJwtPayload(token); - if (!p || !p.exp) return true; - const now = Math.floor(Date.now() / 1000); - return p.exp - now <= withinSeconds; -} - -async function refreshViaBackend(refreshToken) { - const res = await fetch("/api/refresh", { - method: "POST", - headers: { "Content-Type": "application/json" }, - credentials: "include", - body: JSON.stringify({ refresh_token: refreshToken }), - }); - - const text = await res.text(); - if (!res.ok) { - let msg = text; - try { - const j = JSON.parse(text); - msg = j.error_description || j.error || text; - } catch {} - throw new Error(` `); - } - - const data = JSON.parse(text); - if (!data.access_token) throw new Error("No access_token in refresh response"); - return { - accessToken: data.access_token, - refreshToken: data.refresh_token || refreshToken, - }; -} - -export function AuthProvider({ children }) { - const [booting, setBooting] = useState(true); - const [loading, setLoading] = useState(false); - const [authenticated, setAuthenticated] = useState(false); - const [username, setUsername] = useState(""); - const [token, setToken] = useState(null); - const [lastError, setLastError] = useState(""); - - const usernameRef = useRef(""); - - function persistAuth({ accessToken, refreshToken, user }) { - setToken(accessToken); - setAuthenticated(true); - setUsername(user); - usernameRef.current = user; - try { - localStorage.setItem(TOKEN_KEY, accessToken); - if (refreshToken) localStorage.setItem(REFRESH_KEY, refreshToken); - localStorage.setItem(USERNAME_KEY, user); - } catch {} - } - - function clearAuth(msg = "") { - setAuthenticated(false); - setToken(null); - setUsername(""); - usernameRef.current = ""; - setLastError(msg); - try { - localStorage.removeItem(TOKEN_KEY); - localStorage.removeItem(REFRESH_KEY); - localStorage.removeItem(USERNAME_KEY); - } catch {} - } - - async function trySilentRefreshIfNeeded() { - try { - const storedToken = localStorage.getItem(TOKEN_KEY); - const storedRefresh = localStorage.getItem(REFRESH_KEY); - const storedUser = localStorage.getItem(USERNAME_KEY) || ""; - - if (!storedToken || !storedRefresh) return false; - - if (!willExpireSoon(storedToken, 30)) { - setToken(storedToken); - setUsername(storedUser); - usernameRef.current = storedUser; - setAuthenticated(true); - return true; - } - - const { accessToken, refreshToken } = await refreshViaBackend(storedRefresh); - persistAuth({ accessToken, refreshToken, user: storedUser }); - return true; - } catch (e) { - console.warn("[AUTH] silent refresh failed:", e); - clearAuth("Session expired. Please login again."); - return false; - } - } - - useEffect(() => { - (async () => { - try { - const ok = await trySilentRefreshIfNeeded(); - if (!ok) { - const storedToken = localStorage.getItem(TOKEN_KEY); - const storedUser = localStorage.getItem(USERNAME_KEY) || ""; - if (storedToken) { - setToken(storedToken); - setUsername(storedUser); - usernameRef.current = storedUser; - setAuthenticated(true); - } - } - } finally { - setBooting(false); - } - })(); - }, []); - - useEffect(() => { - if (!authenticated) return; - - const interval = setInterval(async () => { - try { - const t = localStorage.getItem(TOKEN_KEY); - const rt = localStorage.getItem(REFRESH_KEY); - if (!t || !rt) return; - - if (!willExpireSoon(t, 90)) return; - - const { accessToken, refreshToken } = await refreshViaBackend(rt); - const u = localStorage.getItem(USERNAME_KEY) || usernameRef.current || ""; - persistAuth({ accessToken, refreshToken, user: u }); - } catch (e) { - console.warn("[AUTH] auto refresh failed:", e); - clearAuth("Session expired. Please login again."); - } - }, 60_000); - - return () => clearInterval(interval); - }, [authenticated]); - - async function login(user, pass) { - setLastError(""); - const u = (user || "").trim(); - if (!u || !pass) { - setLastError("Username and password required."); - return; - } - - try { - setLoading(true); - - const res = await fetch("/api/login", { - method: "POST", - headers: { "Content-Type": "application/json" }, - credentials: "include", - body: JSON.stringify({ username: u, password: pass }), - }); - - const text = await res.text(); - if (!res.ok) { - let msg = "Invalid credentials or auth error."; - try { - const j = JSON.parse(text); - msg = j.error_description || j.error || msg; - } catch {} - clearAuth(` `); - return; - } - - const data = JSON.parse(text); - const accessToken = data.access_token; - const refreshToken = data.refresh_token; - - if (!accessToken) { - clearAuth("No access token in response."); - return; - } - - persistAuth({ accessToken, refreshToken, user: u }); - setLastError(""); - } catch (e) { - console.error("[AUTH] network error", e); - clearAuth("Network error during login."); - } finally { - setLoading(false); - } - } - - function logout() { - clearAuth(""); - } - - return ( - - {children} - - ); -} - -export function useAuth() { - const ctx = useContext(AuthContext); - if (!ctx) throw new Error("useAuth must be used inside "); - return ctx; -} diff --git a/src/auth/AuthContext.jsx.bak.1765948573 b/src/auth/AuthContext.jsx.bak.1765948573 deleted file mode 100644 index 037648e..0000000 --- a/src/auth/AuthContext.jsx.bak.1765948573 +++ /dev/null @@ -1,234 +0,0 @@ -import React, { createContext, useContext, useEffect, useRef, useState } from "react"; - -const AuthContext = createContext(null); - -// Keycloak config - -const TOKEN_KEY = "sociowire:access_token"; -const REFRESH_KEY = "sociowire:refresh_token"; -const USERNAME_KEY = "sociowire:username"; - -// --- helpers --- -function decodeJwtPayload(token) { - try { - const parts = token.split("."); - if (parts.length < 2) return null; - const json = atob(parts[1].replace(/-/g, "+").replace(/_/g, "/")); - return JSON.parse(json); - } catch { - return null; - } -} - -function willExpireSoon(token, withinSeconds = 90) { - const p = decodeJwtPayload(token); - if (!p || !p.exp) return true; - const now = Math.floor(Date.now() / 1000); - return p.exp - now <= withinSeconds; -} - -async function refreshViaBackend(refreshToken) { - const res = await fetch("/api/refresh", { - method: "POST", - headers: { "Content-Type": "application/json" }, - credentials: "include", - body: JSON.stringify({ refresh_token: refreshToken }), - }); - - const text = await res.text(); - if (!res.ok) { - let msg = text; - try { - const j = JSON.parse(text); - msg = j.error_description || j.error || text; - } catch {} - throw new Error(` `); - } - - const data = JSON.parse(text); - if (!data.access_token) throw new Error("No access_token in refresh response"); - return { - accessToken: data.access_token, - refreshToken: data.refresh_token || refreshToken, - }; -} - -export function AuthProvider({ children }) { - const [booting, setBooting] = useState(true); - const [loading, setLoading] = useState(false); - const [authenticated, setAuthenticated] = useState(false); - const [username, setUsername] = useState(""); - const [token, setToken] = useState(null); - const [lastError, setLastError] = useState(""); - - const usernameRef = useRef(""); - - function persistAuth({ accessToken, refreshToken, user }) { - setToken(accessToken); - setAuthenticated(true); - setUsername(user); - usernameRef.current = user; - try { - localStorage.setItem(TOKEN_KEY, accessToken); - if (refreshToken) localStorage.setItem(REFRESH_KEY, refreshToken); - localStorage.setItem(USERNAME_KEY, user); - } catch {} - } - - function clearAuth(msg = "") { - setAuthenticated(false); - setToken(null); - setUsername(""); - usernameRef.current = ""; - setLastError(msg); - try { - localStorage.removeItem(TOKEN_KEY); - localStorage.removeItem(REFRESH_KEY); - localStorage.removeItem(USERNAME_KEY); - } catch {} - } - - async function trySilentRefreshIfNeeded() { - try { - const storedToken = localStorage.getItem(TOKEN_KEY); - const storedRefresh = localStorage.getItem(REFRESH_KEY); - const storedUser = localStorage.getItem(USERNAME_KEY) || ""; - - if (!storedToken || !storedRefresh) return false; - - if (!willExpireSoon(storedToken, 30)) { - setToken(storedToken); - setUsername(storedUser); - usernameRef.current = storedUser; - setAuthenticated(true); - return true; - } - - const { accessToken, refreshToken } = await refreshViaBackend(storedRefresh); - persistAuth({ accessToken, refreshToken, user: storedUser }); - return true; - } catch (e) { - console.warn("[AUTH] silent refresh failed:", e); - clearAuth("Session expired. Please login again."); - return false; - } - } - - useEffect(() => { - (async () => { - try { - const ok = await trySilentRefreshIfNeeded(); - if (!ok) { - const storedToken = localStorage.getItem(TOKEN_KEY); - const storedUser = localStorage.getItem(USERNAME_KEY) || ""; - if (storedToken) { - setToken(storedToken); - setUsername(storedUser); - usernameRef.current = storedUser; - setAuthenticated(true); - } - } - } finally { - setBooting(false); - } - })(); - }, []); - - useEffect(() => { - if (!authenticated) return; - - const interval = setInterval(async () => { - try { - const t = localStorage.getItem(TOKEN_KEY); - const rt = localStorage.getItem(REFRESH_KEY); - if (!t || !rt) return; - - if (!willExpireSoon(t, 90)) return; - - const { accessToken, refreshToken } = await refreshViaBackend(rt); - const u = localStorage.getItem(USERNAME_KEY) || usernameRef.current || ""; - persistAuth({ accessToken, refreshToken, user: u }); - } catch (e) { - console.warn("[AUTH] auto refresh failed:", e); - clearAuth("Session expired. Please login again."); - } - }, 60_000); - - return () => clearInterval(interval); - }, [authenticated]); - - async function login(user, pass) { - setLastError(""); - const u = (user || "").trim(); - if (!u || !pass) { - setLastError("Username and password required."); - return; - } - - try { - setLoading(true); - - const res = await fetch("/api/login", { - method: "POST", - headers: { "Content-Type": "application/json" }, - credentials: "include", - body: JSON.stringify({ username: u, password: pass }), - }); - - const text = await res.text(); - if (!res.ok) { - let msg = "Invalid credentials or auth error."; - try { - const j = JSON.parse(text); - msg = j.error_description || j.error || msg; - } catch {} - clearAuth(` `); - return; - } - - const data = JSON.parse(text); - const accessToken = data.access_token; - const refreshToken = data.refresh_token; - - if (!accessToken) { - clearAuth("No access token in response."); - return; - } - - persistAuth({ accessToken, refreshToken, user: u }); - setLastError(""); - } catch (e) { - console.error("[AUTH] network error", e); - clearAuth("Network error during login."); - } finally { - setLoading(false); - } - } - - function logout() { - clearAuth(""); - } - - return ( - - {children} - - ); -} - -export function useAuth() { - const ctx = useContext(AuthContext); - if (!ctx) throw new Error("useAuth must be used inside "); - return ctx; -} diff --git a/src/auth/AuthContext.jsx.bak.1766173632 b/src/auth/AuthContext.jsx.bak.1766173632 deleted file mode 100644 index 717fa95..0000000 --- a/src/auth/AuthContext.jsx.bak.1766173632 +++ /dev/null @@ -1,343 +0,0 @@ -import React, { createContext, useContext, useEffect, useRef, useState } from "react"; - -const AuthContext = createContext(null); -const API_BASE = "/api"; - -const TOKEN_KEY = "sociowire:access_token"; -const REFRESH_KEY = "sociowire:refresh_token"; -const USERNAME_KEY = "sociowire:username"; - -function decodeJwtPayload(token) { - try { - const parts = String(token || "").split("."); - if (parts.length < 2) return null; - const json = atob(parts[1].replace(/-/g, "+").replace(/_/g, "/")); - return JSON.parse(json); - } catch { - return null; - } -} - -function willExpireSoon(token, withinSeconds = 90) { - const p = decodeJwtPayload(token); - if (!p || !p.exp) return true; - const now = Math.floor(Date.now() / 1000); - return p.exp - now <= withinSeconds; -} - -async function apiRefresh(refreshToken) { - const res = await fetch(`${API_BASE}/refresh`, { - method: "POST", - headers: { "Content-Type": "application/json" }, - credentials: "include", - body: JSON.stringify({ refresh_token: refreshToken }), - }); - - const text = await res.text().catch(() => ""); - if (!res.ok) throw new Error(`${res.status} ${text}`); - - const data = JSON.parse(text || "{}"); - if (!data.access_token) throw new Error("No access_token in refresh response"); - - return { - accessToken: data.access_token, - refreshToken: data.refresh_token || refreshToken, - }; -} - -async function apiMe(accessToken) { - const res = await fetch(`${API_BASE}/me`, { - method: "GET", - headers: { Authorization: `Bearer ${accessToken}` }, - credentials: "include", - }); - - const text = await res.text().catch(() => ""); - if (!res.ok) throw new Error(`${res.status} ${text || "me failed"}`); - - const data = JSON.parse(text || "{}"); - return data; -} - -async function apiResendVerify(accessToken) { - const res = await fetch(`${API_BASE}/resend-verification`, { - method: "POST", - headers: { Authorization: `Bearer ${accessToken}` }, - credentials: "include", - }); - - const text = await res.text().catch(() => ""); - if (!res.ok) throw new Error(`${res.status} ${text || "resend failed"}`); - - return text ? JSON.parse(text) : { ok: true }; -} - -export function AuthProvider({ children }) { - const [booting, setBooting] = useState(true); - const [loading, setLoading] = useState(false); - - const [authenticated, setAuthenticated] = useState(false); - const [username, setUsername] = useState(""); - const [token, setToken] = useState(null); - - const [emailVerified, setEmailVerified] = useState(true); - const [needsEmailVerification, setNeedsEmailVerification] = useState(false); - - const [lastError, setLastError] = useState(""); - const [lastInfo, setLastInfo] = useState(""); - - const usernameRef = useRef(""); - - function persistAuth({ accessToken, refreshToken, user }) { - setToken(accessToken); - setAuthenticated(true); - setUsername(user || ""); - usernameRef.current = user || ""; - - try { - localStorage.setItem(TOKEN_KEY, accessToken); - if (refreshToken) localStorage.setItem(REFRESH_KEY, refreshToken); - localStorage.setItem(USERNAME_KEY, user || ""); - } catch {} - } - - function clearAuth(msg = "") { - setAuthenticated(false); - setToken(null); - setUsername(""); - usernameRef.current = ""; - - setEmailVerified(true); - setNeedsEmailVerification(false); - - setLastError(msg || ""); - try { - localStorage.removeItem(TOKEN_KEY); - localStorage.removeItem(REFRESH_KEY); - localStorage.removeItem(USERNAME_KEY); - } catch {} - } - - async function syncWithBackend(accessToken, fallbackUser = "") { - // Confirme que le backend accepte vraiment le token - const me = await apiMe(accessToken); - - const u = - me.username || - me.preferred_username || - me.user || - fallbackUser || - localStorage.getItem(USERNAME_KEY) || - ""; - - const verified = me.email_verified !== false; // default true si absent - setEmailVerified(verified); - setNeedsEmailVerification(!verified); - - setAuthenticated(true); - setToken(accessToken); - setUsername(u); - usernameRef.current = u; - - // garde username en storage pour UI - try { localStorage.setItem(USERNAME_KEY, u); } catch {} - - if (!verified) { - setLastInfo("Check your inbox and verify your email to unlock posting."); - } else { - setLastInfo(""); - } - - return { me, verified }; - } - - async function bootRestore() { - const storedToken = (() => { - try { return localStorage.getItem(TOKEN_KEY); } catch { return null; } - })(); - const storedRefresh = (() => { - try { return localStorage.getItem(REFRESH_KEY); } catch { return null; } - })(); - const storedUser = (() => { - try { return localStorage.getItem(USERNAME_KEY) || ""; } catch { return ""; } - })(); - - if (!storedToken) return; - - // 1) essaie /me direct - try { - await syncWithBackend(storedToken, storedUser); - return; - } catch (e) { - // 2) si /me refuse, tente refresh si possible - const msg = String(e?.message || ""); - const is401 = msg.startsWith("401") || msg.includes(" 401 "); - if (!is401 || !storedRefresh) { - clearAuth("Session expired. Please login again."); - return; - } - } - - try { - const { accessToken, refreshToken } = await apiRefresh(storedRefresh); - persistAuth({ accessToken, refreshToken, user: storedUser }); - await syncWithBackend(accessToken, storedUser); - } catch (e) { - console.warn("[AUTH] restore refresh failed:", e); - clearAuth("Session expired. Please login again."); - } - } - - useEffect(() => { - (async () => { - try { - await bootRestore(); - } finally { - setBooting(false); - } - })(); - // eslint-disable-next-line react-hooks/exhaustive-deps - }, []); - - // Auto refresh (et resync backend) - useEffect(() => { - if (!authenticated) return; - - const interval = setInterval(async () => { - try { - const t = (() => { try { return localStorage.getItem(TOKEN_KEY); } catch { return null; } })(); - const rt = (() => { try { return localStorage.getItem(REFRESH_KEY); } catch { return null; } })(); - if (!t || !rt) return; - - if (!willExpireSoon(t, 90)) { - // même si pas proche d’expirer, on peut vérifier /me 1 fois de temps en temps - return; - } - - const { accessToken, refreshToken } = await apiRefresh(rt); - const u = (() => { - try { return localStorage.getItem(USERNAME_KEY) || usernameRef.current || ""; } catch { return usernameRef.current || ""; } - })(); - - persistAuth({ accessToken, refreshToken, user: u }); - await syncWithBackend(accessToken, u); - } catch (e) { - console.warn("[AUTH] auto refresh failed:", e); - clearAuth("Session expired. Please login again."); - } - }, 60_000); - - return () => clearInterval(interval); - }, [authenticated]); - - async function login(user, pass) { - setLastError(""); - setLastInfo(""); - const u = (user || "").trim(); - if (!u || !pass) { - setLastError("Username and password required."); - return; - } - - try { - setLoading(true); - - const res = await fetch(`${API_BASE}/login`, { - method: "POST", - headers: { "Content-Type": "application/json" }, - credentials: "include", - body: JSON.stringify({ username: u, password: pass }), - }); - - const text = await res.text().catch(() => ""); - if (!res.ok) { - // backend peut renvoyer un JSON {error:"email_not_verified"} - try { - const obj = JSON.parse(text || "{}"); - if (res.status === 403 && obj?.error === "email_not_verified") { - setLastError("Email not verified. Check your inbox (spam) and verify, then login again."); - return; - } - } catch {} - setLastError(`${res.status} login failed`); - return; - } - - const data = JSON.parse(text || "{}"); - const accessToken = data.access_token; - const refreshToken = data.refresh_token; - - if (!accessToken) { - setLastError("No access token in response."); - return; - } - - persistAuth({ accessToken, refreshToken, user: u }); - - // IMPORTANT: resync with backend right after login - try { - await syncWithBackend(accessToken, u); - } catch (e) { - console.warn("[AUTH] /me failed after login:", e); - clearAuth("Login desynced. Please login again."); - return; - } - - setLastError(""); - } catch (e) { - console.error("[AUTH] network error", e); - clearAuth("Network error during login."); - setLastError("Network error during login."); - } finally { - setLoading(false); - } - } - - function logout() { - clearAuth(""); - } - - async function resendVerificationEmail() { - setLastError(""); - setLastInfo(""); - const t = token || (() => { try { return localStorage.getItem(TOKEN_KEY); } catch { return null; } })(); - if (!t) { - setLastError("Not logged in."); - return; - } - try { - await apiResendVerify(t); - setLastInfo("Verification email sent. Check inbox/spam."); - } catch (e) { - console.error(e); - setLastError("Could not resend verification email."); - } - } - - return ( - - {children} - - ); -} - -export function useAuth() { - const ctx = useContext(AuthContext); - if (!ctx) throw new Error("useAuth must be used inside "); - return ctx; -} diff --git a/src/components/Layout/TopBar.jsx.bak.1765948462 b/src/components/Layout/TopBar.jsx.bak.1765948462 deleted file mode 100644 index 6be5434..0000000 --- a/src/components/Layout/TopBar.jsx.bak.1765948462 +++ /dev/null @@ -1,222 +0,0 @@ -import React, { useState, useEffect } from "react"; -import { useAuth } from "../../auth/AuthContext"; -import { registerUser } from "../../api/client"; - -const THEME_LABELS = { - dark: "Dark", - blue: "Blue", - light: "Light", -}; - -export default function TopBar({ theme = "dark", onChangeTheme }) { - const { loading, authenticated, username, login, logout, lastError } = useAuth(); - - const [showAuth, setShowAuth] = useState(false); - const [mode, setMode] = useState("login"); - - const [loginUser, setLoginUser] = useState(""); - const [loginPass, setLoginPass] = useState(""); - - const [firstName, setFirstName] = useState(""); - const [lastName, setLastName] = useState(""); - 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 openModal = (nextMode) => { - setMode(nextMode); - setShowAuth(true); - setSignupError(""); - }; - - const closeModal = () => { - setShowAuth(false); - setLoginPass(""); - setSignupError(""); - }; - - useEffect(() => { - if (authenticated) setShowAuth(false); - }, [authenticated]); - - const handleLoginSubmit = async (e) => { - e.preventDefault(); - await login(loginUser, loginPass); - }; - - const handleSignupSubmit = async (e) => { - e.preventDefault(); - setSignupError(""); - if ( - !firstName.trim() || - !lastName.trim() || - !regUser.trim() || - !regEmail.trim() || - !regPass - ) { - setSignupError("First name, last name, username, email and password are required."); - return; - } - try { - setSignupLoading(true); - await registerUser({ - username: regUser.trim(), - email: regEmail.trim(), - password: regPass, - firstName: firstName.trim(), - lastName: lastName.trim(), - }); - await login(regUser.trim(), regPass); - } catch (err) { - console.error(err); - setSignupError(err.message || "Registration error."); - } finally { - setSignupLoading(false); - } - }; - - return ( - <> -
-
-
SW
-
-
SOCIOWIRE.com
-
Wired to life
-
- {authLabel} - {lastError && ( - - ({lastError}) - - )} -
-
-
- -
-
- {["dark", "blue", "light"].map((t) => ( - - ))} -
- - {authenticated ? ( -
- {username} - -
- ) : ( -
- - -
- )} -
-
- - {showAuth && ( -
-
e.stopPropagation()}> -
- - - -
- - {mode === "login" ? ( -
- - - -
- ) : ( -
-
- - -
- - - - - {signupError &&
{signupError}
} - - -
- )} -
-
- )} - - ); -} diff --git a/src/components/Layout/TopBar.jsx.bak.1765948508 b/src/components/Layout/TopBar.jsx.bak.1765948508 deleted file mode 100644 index 228fe93..0000000 --- a/src/components/Layout/TopBar.jsx.bak.1765948508 +++ /dev/null @@ -1,223 +0,0 @@ -import React, { useState, useEffect } from "react"; -import { useAuth } from "../../auth/AuthContext"; -import { registerUser } from "../../api/client"; - -const THEME_LABELS = { - dark: "Dark", - blue: "Blue", - light: "Light", -}; - -export default function TopBar({ theme = "dark", onChangeTheme }) { - const { loading, authenticated, username, login, logout, lastError } = useAuth(); - - const [showAuth, setShowAuth] = useState(false); - const [mode, setMode] = useState("login"); - - const [loginUser, setLoginUser] = useState(""); - const [loginPass, setLoginPass] = useState(""); - - const [firstName, setFirstName] = useState(""); - const [lastName, setLastName] = useState(""); - 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 openModal = (nextMode) => { - setMode(nextMode); - setShowAuth(true); - setSignupError(""); - }; - - const closeModal = () => { - setShowAuth(false); - setLoginPass(""); - setSignupError(""); - }; - - useEffect(() => { - if (authenticated) setShowAuth(false); - }, [authenticated]); - - const handleLoginSubmit = async (e) => { - e.preventDefault(); - await login(loginUser, loginPass); - }; - - const handleSignupSubmit = async (e) => { - e.preventDefault(); - setSignupError(""); - if ( - !firstName.trim() || - !lastName.trim() || - !regUser.trim() || - !regEmail.trim() || - !regPass - ) { - setSignupError("First name, last name, username, email and password are required."); - return; - } - try { - setSignupLoading(true); - await registerUser({ - username: regUser.trim(), - email: regEmail.trim(), - password: regPass, - firstName: firstName.trim(), - lastName: lastName.trim(), - }); - await login(regUser.trim(), regPass); - } catch (err) { - console.error(err); - setSignupError(err.message || "Registration error."); - } finally { - setSignupLoading(false); - } - }; - - return ( - <> -
-
-
SW
-
-
SOCIOWIRE.com
-
Wired to life
-
- {authLabel} - {lastError && ( - - ({lastError}) - - )} -
-
-
- -
-
- {["dark", "blue", "light"].map((t) => ( - - ))} -
- - {authenticated ? ( -
- {username} - -
- ) : ( -
- - -
- )} -
-
- - {showAuth && ( -
-
e.stopPropagation()}> -
- - - -
- - {mode === "login" ? ( -
- - - - {lastError &&
{lastError}
} -
- ) : ( -
-
- - -
- - - - - {signupError &&
{signupError}
} - - -
- )} -
-
- )} - - ); -} diff --git a/src/components/Map/MapView.jsx.bak b/src/components/Map/MapView.jsx.bak deleted file mode 100644 index da4bebe..0000000 --- a/src/components/Map/MapView.jsx.bak +++ /dev/null @@ -1,423 +0,0 @@ -import React, { useEffect, useRef, useState } from "react"; -import "maplibre-gl/dist/maplibre-gl.css"; - -import "../../styles/mapMarkers.css"; -import "../../styles/overlays.css"; -import "../../styles/posts.css"; -import "../../styles/filters.css"; - -import { createPost } from "../../api/client"; -import { FILTER_MAIN_CATEGORIES, FILTER_CATEGORY_MAP, CREATE_CATEGORY_MAP } from "./mapConfig"; -import { useMapCore } from "./useMapCore"; -import { useUserPosition } from "./useUserPosition"; -import { usePostsEngine } from "./usePostsEngine"; -import { useAuth } from "../../auth/AuthContext"; -import FilterButtons from "../Filters/FilterButtons"; -import TimeFilterButtons from "../Filters/TimeFilterButtons"; - -export default function MapView({ - theme = "dark", - - // lift posts to App (so Sociowall is NOT inside the map) - onPostsState, - - // selection controlled by App - selectedPost, - onSelectPost, -}) { - const { authenticated, username, token } = useAuth(); - - const { containerRef, mapRef, markersRef, expandedElRef, viewParams, hasLastView } = - useMapCore(theme); - - const [mainFilter, setMainFilter] = useState("All"); - const TIME_FILTER_KEY = "sociowire:timeFilter"; - const [timeFilter, setTimeFilter] = useState(() => { - try { - const v = localStorage.getItem(TIME_FILTER_KEY); - const ok = ["NOW","TODAY","RECENT","PAST"].includes(v); - return ok ? v : "TODAY"; - } catch { - return "TODAY"; - } - }); - const [subFilter, setSubFilter] = useState("All"); - - useEffect(() => { - try { localStorage.setItem(TIME_FILTER_KEY, timeFilter); } catch {} - }, [timeFilter]); - - const stageRef = useRef(null); - const [showSubcats, setShowSubcats] = useState(true); - - const userPosition = useUserPosition(mapRef, hasLastView); - - const { status, visiblePosts, loadingPosts, loadError, handleIncomingPost } = usePostsEngine({ - mapRef, - viewParams, - mainFilter, - subFilter, - timeFilter, - markersRef, - expandedElRef, - }); - - const [isCreating, setIsCreating] = useState(false); - const [draftTitle, setDraftTitle] = useState(""); - const [draftBody, setDraftBody] = useState(""); - const [draftCategory, setDraftCategory] = useState("News"); - const [draftSubCategory, setDraftSubCategory] = useState(CREATE_CATEGORY_MAP.News[0]); - const [draftCoords, setDraftCoords] = useState(null); - const [isSaving, setIsSaving] = useState(false); - const [saveError, setSaveError] = useState(""); - - const bottomCategories = FILTER_CATEGORY_MAP[mainFilter] || ["All"]; - - /* SW_SUBCAT_ICONS:BEGIN */ - const getSubcatIcon = (main, sub) => { - const M = { - All: { - All: "fa-solid fa-layer-group", - }, - - News: { - All: "fa-solid fa-layer-group", - World: "fa-solid fa-globe", - Politics: "fa-solid fa-landmark-flag", - Tech: "fa-solid fa-microchip", - Finance: "fa-solid fa-chart-line", - Local: "fa-solid fa-location-dot", - }, - - Friends: { - All: "fa-solid fa-layer-group", - Nearby: "fa-solid fa-location-crosshairs", - Chats: "fa-solid fa-comments", - Groups: "fa-solid fa-user-group", - Stories: "fa-solid fa-book-open", - Favs: "fa-solid fa-heart", - }, - - Events: { - All: "fa-solid fa-layer-group", - Today: "fa-solid fa-calendar-day", - Weekend: "fa-solid fa-calendar-week", - Music: "fa-solid fa-music", - Sports: "fa-solid fa-football", - Local: "fa-solid fa-location-dot", - }, - - Market: { - All: "fa-solid fa-layer-group", - Actu: "fa-solid fa-newspaper", - Tech: "fa-solid fa-microchip", - Finan: "fa-solid fa-money-bill-trend-up", - Sport: "fa-solid fa-basketball", - Deals: "fa-solid fa-tags", - }, - }; - - return (M[main] && M[main][sub]) || ""; - }; - /* SW_SUBCAT_ICONS:END */ - - // keep subFilter valid - useEffect(() => { - if (!bottomCategories.length) return; - if (!subFilter || !bottomCategories.includes(subFilter)) setSubFilter("All"); - }, [mainFilter, bottomCategories, subFilter]); - - // push posts state up to App (for Sociowall) - useEffect(() => { - if (!onPostsState) return; - onPostsState({ - status, - posts: visiblePosts, - loading: loadingPosts, - error: loadError, - }); - }, [status, visiblePosts, loadingPosts, loadError, onPostsState]); - - // if App selects a post (from Sociowall), fly to it on map - useEffect(() => { - if (!selectedPost) return; - const map = mapRef.current; - if (!map) return; - - const lng = selectedPost.lon ?? selectedPost.lng; - const lat = selectedPost.lat ?? selectedPost.latitude; - - if (typeof lng === "number" && typeof lat === "number") { - map.flyTo({ center: [lng, lat], zoom: 8, speed: 1.4 }); - } - }, [selectedPost, mapRef]); - - // Websocket for new posts - useEffect(() => { - const el = stageRef.current; - if (!el || typeof IntersectionObserver === "undefined") return; - - const obs = new IntersectionObserver( - (entries) => { - const e = entries && entries[0]; - const ok = !!(e && e.isIntersecting && e.intersectionRatio > 0.15); - setShowSubcats(ok); - }, - { threshold: [0, 0.15, 0.3, 0.6] } - ); - - obs.observe(el); - return () => obs.disconnect(); - }, []); - - useEffect(() => { - const proto = window.location.protocol === "https:" ? "wss" : "ws"; - const host = - window.location.port === "5173" - ? `${window.location.hostname}:8081` - : window.location.host; - const wsUrl = `${proto}://${host}/ws`; - - let ws; - try { - ws = new WebSocket(wsUrl); - } catch { - return; - } - - ws.onmessage = (ev) => { - try { - const msg = JSON.parse(ev.data); - if (msg.type === "new_post" && msg.post) handleIncomingPost(msg.post); - } catch {} - }; - - return () => ws && ws.close(); - }, [handleIncomingPost]); - - const handleFlyToMe = () => { - const map = mapRef.current; - if (!map || !userPosition) return; - map.flyTo({ center: userPosition, zoom: 9, speed: 1.2 }); - }; - - const handleOpenCreate = () => { - if (!authenticated) { - alert("You must be logged in to place a wire."); - return; - } - setIsCreating(true); - setDraftTitle(""); - setDraftBody(""); - const main = mainFilter === "All" ? "News" : mainFilter; - setDraftCategory(main); - const list = CREATE_CATEGORY_MAP[main] || CREATE_CATEGORY_MAP.News; - setDraftSubCategory(list[0]); - setDraftCoords(null); - }; - - const handleSearchClick = () => { - alert("🔍 Search feature coming soon!"); - }; - - const handleSubmitPost = async () => { - if (isSaving) return; - setSaveError(""); - - const map = mapRef.current; - if (!map) return; - - if (!authenticated) { - setSaveError("You must be logged in to post."); - return; - } - - const authorName = (username || "").trim() || "anon"; - - let baseLng; - let baseLat; - - if (draftCoords && draftCoords.length === 2) { - baseLng = draftCoords[0]; - baseLat = draftCoords[1]; - } else { - const center = map.getCenter(); - baseLng = center.lng; - baseLat = center.lat; - } - - // keep marker visually above pointer - const pixelOffsetY = -20; - const screenPoint = map.project([baseLng, baseLat]); - const correctedPoint = { x: screenPoint.x, y: screenPoint.y + pixelOffsetY }; - const correctedLngLat = map.unproject([correctedPoint.x, correctedPoint.y]); - const lng = correctedLngLat.lng; - const lat = correctedLngLat.lat; - - if (!draftTitle.trim()) { - setSaveError("Title required."); - return; - } - - try { - setIsSaving(true); - - const newPost = await createPost( - { - title: draftTitle.trim(), - snippet: draftBody.trim() || draftTitle.trim(), - category: draftCategory === "Events" ? "EVENT" : draftCategory.toUpperCase(), - sub_category: draftSubCategory || "ALL", - lat, - lon: lng, - author: authorName, - }, - token - ); - - if (newPost && newPost.id) handleIncomingPost(newPost); - - setIsSaving(false); - setIsCreating(false); - } catch (e) { - console.error(e); - setIsSaving(false); - setSaveError("Error creating post."); - } - }; - - const handleMainFilterSelect = (code) => { - if (!FILTER_MAIN_CATEGORIES.includes(code)) return; - setMainFilter(code); - }; - - // allow selecting from map later (optional hook) - const handleSelectPost = (post) => { - if (onSelectPost) onSelectPost(post); - const map = mapRef.current; - if (!map) return; - const lng = post.lon ?? post.lng; - const lat = post.lat ?? post.latitude; - if (typeof lng === "number" && typeof lat === "number") { - map.flyTo({ center: [lng, lat], zoom: 8, speed: 1.4 }); - } - }; - - return ( -
-
-
- {status &&
{status}
} - -
- - -
- -
- setTimeFilter(code)} /> -
- -
- -
- -
-
- {bottomCategories.map((c) => ( - - ))} -
-
- -
- -
- - {isCreating && ( -
-
-
- )} - - {isCreating && ( -
-
- Create wire - -
- - - Move the map, aim with the crosshair — your wire will be pinned there. - - -
- - - -
- - setDraftTitle(e.target.value)} - /> - -