sw-fe/src/components/Layout/TopBar.jsx

430 lines
14 KiB
JavaScript
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import React, { useEffect, useMemo, useState } from "react";
import { useAuth } from "../../auth/AuthContext";
import { registerUser } from "../../api/client";
import AuthToast from "../Auth/AuthToast";
const THEME_LABELS = { dark: "Dark", blue: "Blue", light: "Light" };
const LAST_SIGNUP_KEY = "sociowire:lastSignup";
function initialLetter(name = "") {
const s = (name || "").trim();
return s ? s[0].toUpperCase() : "G";
}
function truthyParam(v) {
if (!v) return false;
const x = String(v).toLowerCase().trim();
return x === "1" || x === "true" || x === "yes" || x === "ok";
}
function readLastSignup() {
try {
const raw = localStorage.getItem(LAST_SIGNUP_KEY);
if (!raw) return null;
const j = JSON.parse(raw);
if (!j || typeof j !== "object") return null;
return {
username: (j.username || "").toString().trim(),
email: (j.email || "").toString().trim(),
};
} catch {
return null;
}
}
function parseMergedParams() {
const search = window.location.search || "";
const hash = window.location.hash || "";
const qs = new URLSearchParams(search.startsWith("?") ? search.slice(1) : search);
let hashQs = "";
if (hash.includes("?")) hashQs = hash.slice(hash.indexOf("?") + 1);
const hs = new URLSearchParams(hashQs);
const get = (k) => {
const a = qs.get(k);
if (a != null && a !== "") return a;
const b = hs.get(k);
return b != null ? b : null;
};
return { qs, hs, get, hash };
}
export default function TopBar({ theme = "dark", onChangeTheme }) {
const {
loading,
authenticated,
username,
login,
logout,
needsEmailVerification,
lastError,
lastInfo,
clearError,
clearInfo,
} = 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 [signupInfo, setSignupInfo] = useState("");
const [signupLoading, setSignupLoading] = useState(false);
const openModal = (nextMode) => {
setMode(nextMode);
setShowAuth(true);
setSignupError("");
};
const closeModal = () => {
setShowAuth(false);
setLoginPass("");
setSignupError("");
};
useEffect(() => {
if (authenticated) setShowAuth(false);
}, [authenticated]);
useEffect(() => {
try {
const { get, qs, hs, hash } = parseMergedParams();
const verified =
get("verified") ||
get("email_verified") ||
get("emailVerified") ||
get("kc_verified");
const signup = get("signup");
const notice = get("notice");
const email =
get("email") ||
get("user_email") ||
get("mail");
const uname =
get("username") ||
get("user") ||
get("preferred_username") ||
get("login");
const last = readLastSignup();
if (truthyParam(verified)) {
const who = (email || uname || last?.email || last?.username || "").trim();
setSignupInfo("✅ Email confirmé" + (who ? " (" + who + ")" : "") + ". Tu peux te connecter.");
setMode("login");
setShowAuth(true);
const prefill = (uname || email || last?.username || last?.email || "").trim();
if (prefill) setLoginUser(prefill);
} else if (truthyParam(signup)) {
const who = (email || uname || last?.email || last?.username || "").trim();
setSignupInfo("✅ Compte créé" + (who ? " (" + who + ")" : "") + ". Vérifie tes emails (et le spam), puis connecte-toi.");
setMode("login");
setShowAuth(true);
const prefill = (uname || email || last?.username || last?.email || "").trim();
if (prefill) setLoginUser(prefill);
} else if (notice) {
const msg = decodeURIComponent(notice);
if (msg && msg.trim()) {
setSignupInfo(msg.trim());
setMode("login");
setShowAuth(true);
}
}
const touched =
qs.has("verified") || qs.has("email_verified") || qs.has("emailVerified") || qs.has("signup") || qs.has("notice") ||
hs.has("verified") || hs.has("email_verified") || hs.has("emailVerified") || hs.has("signup") || hs.has("notice");
if (touched) {
const cleanHash = hash.includes("?") ? hash.split("?")[0] : hash;
const clean = window.location.pathname + cleanHash;
window.history.replaceState({}, "", clean);
}
} catch (e) {
// ignore
}
}, []);
const handleLoginSubmit = async (e) => {
e.preventDefault();
await login(loginUser, loginPass);
};
const handleSignupSubmit = async (e) => {
e.preventDefault();
setSignupError("");
setSignupInfo("");
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);
const u = regUser.trim();
const em = regEmail.trim();
await registerUser({
username: u,
email: em,
password: regPass,
firstName: firstName.trim(),
lastName: lastName.trim(),
});
try {
localStorage.setItem(LAST_SIGNUP_KEY, JSON.stringify({ username: u, email: em }));
} catch {}
setSignupInfo("✅ Compte créé (" + em + "). Vérifie tes emails (spam aussi), puis connecte-toi.");
setMode("login");
setLoginUser(u || em);
setLoginPass("");
} catch (err) {
console.error(err);
setSignupError(err.message || "Registration error.");
} finally {
setSignupLoading(false);
}
};
const toast = useMemo(() => {
if (lastError) {
const t = needsEmailVerification ? "warn" : "error";
const title = needsEmailVerification ? "Vérification email requise" : "Connexion impossible";
return { type: t, title, message: lastError };
}
if (lastInfo) {
return { type: "success", title: "", message: lastInfo };
}
return null;
}, [lastError, lastInfo, needsEmailVerification]);
// Fonction pour afficher le prompt PWA (appelée par le bouton)
const handleInstall = () => {
if ('beforeinstallprompt' in window) {
const e = new Event('beforeinstallprompt');
window.dispatchEvent(e);
} else {
alert('Installation PWA non supportée sur ce navigateur.');
}
};
return (
<>
{/* Toast area (under topbar) */}
{toast?.message ? (
<div className="sw-toast-wrap">
<AuthToast
type={toast.type}
title={toast.title}
message={toast.message}
onClose={() => {
if (lastError && typeof clearError === "function") clearError();
if (lastInfo && typeof clearInfo === "function") clearInfo();
}}
/>
</div>
) : null}
<header className="topbar">
<div className="brand-stack">
<div className="brand-toprow">
<div className="logo-circle">SW</div>
<div className="title-block">
<div className="main-title">SOCIOWIRE.com</div>
<div className="sub-title">Wired to life</div>
</div>
</div>
{/* Theme dots + BOUTON INSTALL PWA */}
<div className="brand-under">
<div className="theme-switch">
{["dark", "blue", "light"].map((t) => (
<button
key={t}
type="button"
className={"theme-dot" + (theme === t ? " theme-dot-active" : "")}
title={THEME_LABELS[t]}
onClick={() => onChangeTheme && onChangeTheme(t)}
>
{THEME_LABELS[t][0]}
</button>
))}
</div>
{/* BOUTON INSTALL PWA ici en haut */}
<button
onClick={handleInstall}
style={{
marginLeft: '8px',
padding: '5px 10px',
fontSize: '12px',
background: '#0ea5e9',
color: 'white',
border: 'none',
borderRadius: '999px',
cursor: 'pointer',
display: 'flex',
alignItems: 'center',
gap: '6px',
boxShadow: '0 2px 8px rgba(0,0,0,0.3)',
}}
>
<i className="fa-solid fa-download"></i> Installer
</button>
</div>
</div>
<div className="topbar-right">
{authenticated ? (
<div className="sw-userchip" title={username}>
<div className="sw-avatar">
<span>{initialLetter(username)}</span>
</div>
<div className="sw-usertext">
<div className="sw-userline">Online</div>
<div className="sw-userhandle">@{username || "user"}</div>
</div>
</div>
) : (
<div className="sw-userchip" title="Guest">
<div className="sw-avatar">
<i className="fa-solid fa-user" />
</div>
<div className="sw-usertext">
<div className="sw-userline">{loading ? "Loading…" : "Guest"}</div>
<div className="sw-userhandle">anon</div>
</div>
</div>
)}
<button
className="btn-primary"
type="button"
onClick={() => (authenticated ? logout() : openModal("login"))}
disabled={loading}
title={authenticated ? "Logoff" : "Logon"}
>
{authenticated ? "Logoff" : "Logon"}
</button>
</div>
</header>
{showAuth ? (
<div className="auth-modal-backdrop" onClick={closeModal}>
<div className="auth-modal" onClick={(e) => e.stopPropagation()}>
<div className="auth-modal-header">
<button
type="button"
className={"auth-tab" + (mode === "login" ? " auth-tab-active" : "")}
onClick={() => setMode("login")}
>
Login
</button>
<button
type="button"
className={"auth-tab" + (mode === "signup" ? " auth-tab-active" : "")}
onClick={() => setMode("signup")}
>
Sign up
</button>
<button type="button" className="auth-close" onClick={closeModal}>
</button>
</div>
{signupInfo ? <div className="auth-notice auth-notice-success">{signupInfo}</div> : null}
{mode === "login" && lastError ? (
<div className={"auth-notice " + (needsEmailVerification ? "auth-notice-warn" : "auth-notice-error")}>
{lastError}
</div>
) : null}
{mode === "login" ? (
<form className="auth-form" onSubmit={handleLoginSubmit}>
<label>
Username or email
<input
type="text"
value={loginUser}
onChange={(e) => setLoginUser(e.target.value)}
autoComplete="username"
/>
</label>
<label>
Password
<input
type="password"
value={loginPass}
onChange={(e) => setLoginPass(e.target.value)}
autoComplete="current-password"
/>
</label>
<button type="submit" className="btn-primary btn-full" disabled={loading}>
{loading ? "..." : "Sign in"}
</button>
{needsEmailVerification ? (
<div className="auth-notice auth-notice-warn" style={{ marginTop: 10 }}>
Ton compte demande une vérification email. Confirme ton email, puis réessaie de te connecter.
</div>
) : null}
</form>
) : (
<form className="auth-form" onSubmit={handleSignupSubmit}>
<div className="auth-row">
<label>
First name
<input type="text" value={firstName} onChange={(e) => setFirstName(e.target.value)} />
</label>
<label>
Last name
<input type="text" value={lastName} onChange={(e) => setLastName(e.target.value)} />
</label>
</div>
<label>
Username
<input type="text" value={regUser} onChange={(e) => setRegUser(e.target.value)} />
</label>
<label>
Email
<input type="email" value={regEmail} onChange={(e) => setRegEmail(e.target.value)} />
</label>
<label>
Password
<input type="password" value={regPass} onChange={(e) => setRegPass(e.target.value)} />
</label>
{signupError ? <div className="auth-notice auth-notice-error">{signupError}</div> : null}
<button type="submit" className="btn-primary btn-full" disabled={signupLoading}>
{signupLoading ? "..." : "Create account"}
</button>
</form>
)}
</div>
</div>
) : null}
</>
);
}