millieux de tout brisrr

This commit is contained in:
Your Name 2025-12-10 17:59:44 -05:00
parent d2bde41aa4
commit 73b48dc728
3 changed files with 256 additions and 103 deletions

View File

@ -1,5 +1,55 @@
for f in $(find src -type f \( -name "*.js" -o -name "*.jsx" -o -name "*.css" \) ! -name "maplibre.css"); do
echo "================== FILE: $f =================="
cat "$f"
echo ""
done | termux-clipboard-set
#!/data/data/com.termux/files/usr/bin/bash
set -euo pipefail
# Nombre de parties voulues
PARTS=3
# Fichier temporaire qui contient tout le code
TMPFILE=$(mktemp)
cleanup() { rm -f "$TMPFILE" "${TMPFILE}"_part_* 2>/dev/null || true; }
trap cleanup EXIT
# 1) Construire le gros fichier avec tous les .js/.jsx/.css
find src -type f \( -name "*.js" -o -name "*.jsx" -o -name "*.css" \) ! -name "maplibre.css" -print0 |
while IFS= read -r -d '' f; do
printf '================== FILE: %s ==================\n' "$f" >> "$TMPFILE"
cat "$f" >> "$TMPFILE"
printf '\n' >> "$TMPFILE"
done
# 2) Découper en exactement PARTS morceaux (ou moins si très petit)
# -n l/3 = découpe en 3 blocs de lignes à peu près égaux
split -n l/"$PARTS" -d --additional-suffix=".txt" "$TMPFILE" "${TMPFILE}_part_"
# 3) Envoyer chaque morceau dans le presse-papier, ENTER pour le suivant
parts=( "${TMPFILE}_part_"*.txt )
total=${#parts[@]}
if [ "$total" -eq 0 ]; then
echo "Aucun contenu trouvé."
exit 0
fi
echo "📦 Total: $total partie(s) à envoyer dans ChatGPT."
idx=1
for p in "${parts[@]}"; do
# On ajoute une bannière claire DEBUT/FIN pour chaque partie
{
echo "===== SOCIOWIRE FRONTEND PART $idx / $total (BEGIN) ====="
cat "$p"
echo
echo "===== END OF SOCIOWIRE FRONTEND PART $idx / $total ====="
} | termux-clipboard-set
echo "➡️ Partie $idx / $total copiée dans le presse-papier."
if [ "$idx" -lt "$total" ]; then
echo "Colle cette partie dans ChatGPT, envoie le message,"
echo "puis reviens ici et appuie sur ENTER pour la suivante (ou 'q' + ENTER pour quitter)…"
read -r ans || true
[ "$ans" = "q" ] || [ "$ans" = "Q" ] && break
fi
idx=$((idx+1))
done
echo "✅ Terminé."

View File

@ -1,105 +1,153 @@
import React, { createContext, useContext, useEffect, useState } from "react";
import keycloak from "./keycloak";
// CONFIG KEYCLOAK
const KC_BASE = "https://web.sociowire.com:8443";
const KC_REALM = "sociowire";
const KC_CLIENT_ID = "sociowire-frontend";
const TOKEN_KEY = "sociowire:token";
const AuthContext = createContext(null);
function parseJwt(token) {
try {
const [, payload] = token.split(".");
const json = atob(payload.replace(/-/g, "+").replace(/_/g, "/"));
return JSON.parse(json);
} catch {
return {};
}
}
export function AuthProvider({ children }) {
const [kc, setKc] = useState(null);
const [loading, setLoading] = useState(true);
const [token, setToken] = useState(null);
const [authenticated, setAuthenticated] = useState(false);
const [username, setUsername] = useState("");
const [loading, setLoading] = useState(true);
const [lastError, setLastError] = useState("");
// charger token depuis localStorage au démarrage
useEffect(() => {
let isMounted = true;
async function init() {
try {
console.log("[AUTH] init keycloak…");
const ok = await keycloak.init({
onLoad: "check-sso", // ne force pas le login au chargement
checkLoginIframe: false,
});
if (!isMounted) return;
console.log("[AUTH] init result:", ok);
window.kc = keycloak; // debug global
setKc(keycloak);
setAuthenticated(ok);
if (ok && keycloak.tokenParsed) {
const p = keycloak.tokenParsed;
const name =
p.preferred_username ||
p.email ||
p.name ||
(p.sub ? String(p.sub) : "");
setUsername(name || "");
} else {
setUsername("");
}
} catch (err) {
console.error("[AUTH] keycloak init error", err);
if (isMounted) {
setLastError(String(err));
}
} finally {
if (isMounted) setLoading(false);
}
}
init();
// refresh token simplifié
const interval = setInterval(() => {
if (!keycloak || !keycloak.authenticated) return;
keycloak
.updateToken(60)
.then((refreshed) => {
if (refreshed) {
console.log("[AUTH] token refreshed");
try {
const saved = localStorage.getItem(TOKEN_KEY);
if (saved) {
const data = JSON.parse(saved);
if (data && data.access_token) {
const now = Date.now() / 1000;
const payload = parseJwt(data.access_token);
if (!payload.exp || payload.exp > now) {
setToken(data.access_token);
setAuthenticated(true);
const name =
payload.preferred_username ||
payload.email ||
payload.name ||
(payload.sub ? String(payload.sub) : "");
setUsername(name || "");
}
})
.catch((err) => {
console.warn("[AUTH] token refresh error", err);
});
}, 30000);
return () => {
isMounted = false;
clearInterval(interval);
};
}
}
} catch (e) {
console.warn("[AUTH] load token error", e);
} finally {
setLoading(false);
}
}, []);
const login = () => {
keycloak.login({
redirectUri: window.location.href,
});
async function login(user, pass) {
setLastError("");
if (!user || !pass) {
setLastError("Username & password required.");
return;
}
setLoading(true);
try {
const params = new URLSearchParams();
params.set("grant_type", "password");
params.set("client_id", KC_CLIENT_ID);
params.set("username", user);
params.set("password", pass);
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(),
}
);
if (!res.ok) {
const text = await res.text();
console.error("[AUTH] login failed", res.status, text);
setLastError("Invalid credentials or auth error.");
setAuthenticated(false);
setToken(null);
setUsername("");
try {
localStorage.removeItem(TOKEN_KEY);
} catch {}
return;
}
const data = await res.json();
const accessToken = data.access_token;
if (!accessToken) {
setLastError("No access token returned.");
return;
}
const payload = parseJwt(accessToken);
const name =
payload.preferred_username ||
payload.email ||
payload.name ||
(payload.sub ? String(payload.sub) : "");
setToken(accessToken);
setAuthenticated(true);
setUsername(name || "");
setLastError("");
try {
localStorage.setItem(TOKEN_KEY, JSON.stringify(data));
} catch (e) {
console.warn("[AUTH] save token error", e);
}
} catch (err) {
console.error("[AUTH] login error", err);
setLastError("Network/auth error.");
setAuthenticated(false);
setToken(null);
setUsername("");
} finally {
setLoading(false);
}
}
function logout() {
setToken(null);
setAuthenticated(false);
setUsername("");
setLastError("");
try {
localStorage.removeItem(TOKEN_KEY);
} catch {}
}
const value = {
loading,
authenticated,
username,
token,
lastError,
login,
logout,
};
const logout = () => {
keycloak.logout({
redirectUri: window.location.origin + "/",
});
};
return (
<AuthContext.Provider
value={{
keycloak: kc,
loading,
authenticated,
username,
login,
logout,
lastError,
}}
>
{children}
</AuthContext.Provider>
);
return <AuthContext.Provider value={value}>{children}</AuthContext.Provider>;
}
export function useAuth() {
@ -109,3 +157,4 @@ export function useAuth() {
}
return ctx;
}
// modif 1

View File

@ -1,4 +1,4 @@
import React from "react";
import React, { useState } from "react";
import { useAuth } from "../../auth/AuthContext";
const THEME_LABELS = {
@ -8,12 +8,27 @@ const THEME_LABELS = {
};
export default function TopBar({ theme = "dark", onChangeTheme }) {
const { loading, authenticated, username, login, logout, lastError } = useAuth();
const {
loading,
authenticated,
username,
login,
logout,
lastError,
} = useAuth();
const [user, setUser] = useState("");
const [pass, setPass] = useState("");
let authLabel = "AUTH: anon";
if (loading) authLabel = "AUTH: loading…";
else if (authenticated) authLabel = `AUTH: user=${username || "?"}`;
const handleSubmit = (e) => {
e.preventDefault();
login(user.trim(), pass);
};
return (
<header className="topbar">
<div className="logo-circle">SW</div>
@ -25,7 +40,7 @@ export default function TopBar({ theme = "dark", onChangeTheme }) {
{authLabel}
{lastError && (
<span style={{ color: "#f97373", marginLeft: "0.3rem" }}>
(auth err)
({lastError})
</span>
)}
</div>
@ -48,11 +63,7 @@ export default function TopBar({ theme = "dark", onChangeTheme }) {
))}
</div>
{loading ? (
<button className="btn-primary" disabled>
</button>
) : authenticated ? (
{authenticated ? (
<div style={{ display: "flex", alignItems: "center", gap: "0.4rem" }}>
<span style={{ fontSize: "0.8rem" }}>{username}</span>
<button className="btn-primary" onClick={logout}>
@ -60,11 +71,54 @@ export default function TopBar({ theme = "dark", onChangeTheme }) {
</button>
</div>
) : (
<button className="btn-primary" onClick={login}>
Login
</button>
<form
onSubmit={handleSubmit}
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>
)}
</div>
</header>
);
}
// modif 2