millieux de tout brisrr
This commit is contained in:
parent
d2bde41aa4
commit
73b48dc728
60
listfiles.sh
60
listfiles.sh
|
|
@ -1,5 +1,55 @@
|
||||||
for f in $(find src -type f \( -name "*.js" -o -name "*.jsx" -o -name "*.css" \) ! -name "maplibre.css"); do
|
#!/data/data/com.termux/files/usr/bin/bash
|
||||||
echo "================== FILE: $f =================="
|
set -euo pipefail
|
||||||
cat "$f"
|
|
||||||
echo ""
|
# Nombre de parties voulues
|
||||||
done | termux-clipboard-set
|
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é."
|
||||||
|
|
|
||||||
|
|
@ -1,105 +1,153 @@
|
||||||
import React, { createContext, useContext, useEffect, useState } from "react";
|
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);
|
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 }) {
|
export function AuthProvider({ children }) {
|
||||||
const [kc, setKc] = useState(null);
|
const [token, setToken] = useState(null);
|
||||||
const [loading, setLoading] = useState(true);
|
|
||||||
const [authenticated, setAuthenticated] = useState(false);
|
const [authenticated, setAuthenticated] = useState(false);
|
||||||
const [username, setUsername] = useState("");
|
const [username, setUsername] = useState("");
|
||||||
|
const [loading, setLoading] = useState(true);
|
||||||
const [lastError, setLastError] = useState("");
|
const [lastError, setLastError] = useState("");
|
||||||
|
|
||||||
|
// charger token depuis localStorage au démarrage
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
let isMounted = true;
|
|
||||||
|
|
||||||
async function init() {
|
|
||||||
try {
|
try {
|
||||||
console.log("[AUTH] init keycloak…");
|
const saved = localStorage.getItem(TOKEN_KEY);
|
||||||
const ok = await keycloak.init({
|
if (saved) {
|
||||||
onLoad: "check-sso", // ne force pas le login au chargement
|
const data = JSON.parse(saved);
|
||||||
checkLoginIframe: false,
|
if (data && data.access_token) {
|
||||||
});
|
const now = Date.now() / 1000;
|
||||||
|
const payload = parseJwt(data.access_token);
|
||||||
if (!isMounted) return;
|
if (!payload.exp || payload.exp > now) {
|
||||||
|
setToken(data.access_token);
|
||||||
console.log("[AUTH] init result:", ok);
|
setAuthenticated(true);
|
||||||
window.kc = keycloak; // debug global
|
|
||||||
|
|
||||||
setKc(keycloak);
|
|
||||||
setAuthenticated(ok);
|
|
||||||
|
|
||||||
if (ok && keycloak.tokenParsed) {
|
|
||||||
const p = keycloak.tokenParsed;
|
|
||||||
const name =
|
const name =
|
||||||
p.preferred_username ||
|
payload.preferred_username ||
|
||||||
p.email ||
|
payload.email ||
|
||||||
p.name ||
|
payload.name ||
|
||||||
(p.sub ? String(p.sub) : "");
|
(payload.sub ? String(payload.sub) : "");
|
||||||
setUsername(name || "");
|
setUsername(name || "");
|
||||||
} else {
|
|
||||||
setUsername("");
|
|
||||||
}
|
}
|
||||||
} catch (err) {
|
|
||||||
console.error("[AUTH] keycloak init error", err);
|
|
||||||
if (isMounted) {
|
|
||||||
setLastError(String(err));
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
console.warn("[AUTH] load token error", e);
|
||||||
} finally {
|
} finally {
|
||||||
if (isMounted) setLoading(false);
|
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");
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.catch((err) => {
|
|
||||||
console.warn("[AUTH] token refresh error", err);
|
|
||||||
});
|
|
||||||
}, 30000);
|
|
||||||
|
|
||||||
return () => {
|
|
||||||
isMounted = false;
|
|
||||||
clearInterval(interval);
|
|
||||||
};
|
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const login = () => {
|
async function login(user, pass) {
|
||||||
keycloak.login({
|
setLastError("");
|
||||||
redirectUri: window.location.href,
|
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 logout = () => {
|
const res = await fetch(
|
||||||
keycloak.logout({
|
`${KC_BASE}/realms/${KC_REALM}/protocol/openid-connect/token`,
|
||||||
redirectUri: window.location.origin + "/",
|
{
|
||||||
});
|
method: "POST",
|
||||||
};
|
headers: {
|
||||||
|
"Content-Type": "application/x-www-form-urlencoded",
|
||||||
|
},
|
||||||
|
body: params.toString(),
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
return (
|
if (!res.ok) {
|
||||||
<AuthContext.Provider
|
const text = await res.text();
|
||||||
value={{
|
console.error("[AUTH] login failed", res.status, text);
|
||||||
keycloak: kc,
|
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,
|
loading,
|
||||||
authenticated,
|
authenticated,
|
||||||
username,
|
username,
|
||||||
|
token,
|
||||||
|
lastError,
|
||||||
login,
|
login,
|
||||||
logout,
|
logout,
|
||||||
lastError,
|
};
|
||||||
}}
|
|
||||||
>
|
return <AuthContext.Provider value={value}>{children}</AuthContext.Provider>;
|
||||||
{children}
|
|
||||||
</AuthContext.Provider>
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export function useAuth() {
|
export function useAuth() {
|
||||||
|
|
@ -109,3 +157,4 @@ export function useAuth() {
|
||||||
}
|
}
|
||||||
return ctx;
|
return ctx;
|
||||||
}
|
}
|
||||||
|
// modif 1
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
import React from "react";
|
import React, { useState } from "react";
|
||||||
import { useAuth } from "../../auth/AuthContext";
|
import { useAuth } from "../../auth/AuthContext";
|
||||||
|
|
||||||
const THEME_LABELS = {
|
const THEME_LABELS = {
|
||||||
|
|
@ -8,12 +8,27 @@ const THEME_LABELS = {
|
||||||
};
|
};
|
||||||
|
|
||||||
export default function TopBar({ theme = "dark", onChangeTheme }) {
|
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";
|
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) => {
|
||||||
|
e.preventDefault();
|
||||||
|
login(user.trim(), pass);
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<header className="topbar">
|
<header className="topbar">
|
||||||
<div className="logo-circle">SW</div>
|
<div className="logo-circle">SW</div>
|
||||||
|
|
@ -25,7 +40,7 @@ export default function TopBar({ theme = "dark", onChangeTheme }) {
|
||||||
{authLabel}
|
{authLabel}
|
||||||
{lastError && (
|
{lastError && (
|
||||||
<span style={{ color: "#f97373", marginLeft: "0.3rem" }}>
|
<span style={{ color: "#f97373", marginLeft: "0.3rem" }}>
|
||||||
(auth err)
|
({lastError})
|
||||||
</span>
|
</span>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -48,11 +63,7 @@ export default function TopBar({ theme = "dark", onChangeTheme }) {
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{loading ? (
|
{authenticated ? (
|
||||||
<button className="btn-primary" disabled>
|
|
||||||
…
|
|
||||||
</button>
|
|
||||||
) : authenticated ? (
|
|
||||||
<div style={{ display: "flex", alignItems: "center", gap: "0.4rem" }}>
|
<div style={{ display: "flex", alignItems: "center", gap: "0.4rem" }}>
|
||||||
<span style={{ fontSize: "0.8rem" }}>{username}</span>
|
<span style={{ fontSize: "0.8rem" }}>{username}</span>
|
||||||
<button className="btn-primary" onClick={logout}>
|
<button className="btn-primary" onClick={logout}>
|
||||||
|
|
@ -60,11 +71,54 @@ export default function TopBar({ theme = "dark", onChangeTheme }) {
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<button className="btn-primary" onClick={login}>
|
<form
|
||||||
Login
|
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>
|
</button>
|
||||||
|
</form>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
</header>
|
</header>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
// modif 2
|
||||||
Loading…
Reference in New Issue