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
|
||||
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é."
|
||||
|
|
|
|||
|
|
@ -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 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 =
|
||||
p.preferred_username ||
|
||||
p.email ||
|
||||
p.name ||
|
||||
(p.sub ? String(p.sub) : "");
|
||||
payload.preferred_username ||
|
||||
payload.email ||
|
||||
payload.name ||
|
||||
(payload.sub ? String(payload.sub) : "");
|
||||
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 {
|
||||
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 = () => {
|
||||
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 logout = () => {
|
||||
keycloak.logout({
|
||||
redirectUri: window.location.origin + "/",
|
||||
});
|
||||
};
|
||||
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(),
|
||||
}
|
||||
);
|
||||
|
||||
return (
|
||||
<AuthContext.Provider
|
||||
value={{
|
||||
keycloak: kc,
|
||||
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,
|
||||
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
|
||||
|
|
@ -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
|
||||
<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
|
||||
Loading…
Reference in New Issue