import React, { useCallback, useEffect, useState } from "react"; import { Loader2 } from "lucide-react"; import { getApiBase } from "../lib/api"; import { frontendUrl } from "../lib/paths"; const GIS_SRC = "https://accounts.google.com/gsi/client"; const ENV_CLIENT_ID = (process.env.REACT_APP_GOOGLE_CLIENT_ID || "").trim(); /** Client OAuth public — fallback si le build n'a pas REACT_APP_GOOGLE_CLIENT_ID */ export const PROD_GOOGLE_CLIENT_ID = "791552572109-va37rj7pooi3opca3bqe61h15ka8gob9.apps.googleusercontent.com"; let gisLoadPromise = null; function loadGisScript() { if (window.google?.accounts?.id) return Promise.resolve(); if (gisLoadPromise) return gisLoadPromise; gisLoadPromise = new Promise((resolve, reject) => { const done = () => { if (window.google?.accounts?.id) resolve(); else reject(new Error("Google Identity Services indisponible")); }; const existing = document.querySelector(`script[src="${GIS_SRC}"]`); if (existing) { if (window.google?.accounts?.id) return resolve(); existing.addEventListener("load", done, { once: true }); existing.addEventListener("error", () => reject(new Error("Script Google bloqué")), { once: true }); return; } const script = document.createElement("script"); script.src = GIS_SRC; script.async = true; script.defer = true; script.onload = done; script.onerror = () => reject(new Error("Script Google bloqué")); document.head.appendChild(script); }); return gisLoadPromise; } export function getGoogleClientId(override) { return (override || ENV_CLIENT_ID || PROD_GOOGLE_CLIENT_ID).trim(); } export async function loadGoogleIdentity(clientId, onCredential) { const cid = getGoogleClientId(clientId); if (!cid) throw new Error("Google client_id manquant"); await loadGisScript(); window.google.accounts.id.initialize({ client_id: cid, callback: (response) => { if (response?.credential) onCredential(response.credential); }, auto_select: false, cancel_on_tap_outside: true, itp_support: true, ux_mode: "popup", }); return cid; } async function triggerGooglePopup(clientId, onCredential) { const cid = await loadGoogleIdentity(clientId, onCredential); const temp = document.createElement("div"); temp.style.cssText = "position:fixed;left:-9999px;top:0;width:320px;height:48px;opacity:0.01;"; document.body.appendChild(temp); try { window.google.accounts.id.renderButton(temp, { type: "standard", theme: "outline", size: "large", text: "continue_with", width: 320, locale: "fr", }); await new Promise((r) => setTimeout(r, 400)); const btn = temp.querySelector('[role="button"]') || temp.querySelector("div[tabindex]"); if (btn) { btn.click(); return true; } return false; } finally { setTimeout(() => temp.remove(), 8000); } } export function GoogleSignInButton({ clientId, onCredential, disabled, busy, onBusyChange }) { const cid = getGoogleClientId(clientId); const [gisReady, setGisReady] = useState(false); useEffect(() => { if (!cid) return; loadGoogleIdentity(cid, onCredential) .then(() => setGisReady(true)) .catch(() => setGisReady(false)); }, [cid, onCredential]); const handleClick = useCallback(async () => { if (disabled || busy || !cid) return; onBusyChange?.(true); try { const opened = await triggerGooglePopup(cid, onCredential); if (!opened) throw new Error("popup"); // Credential callback sets busy again; release after popup opens only if no callback within 90s window.setTimeout(() => onBusyChange?.(false), 90000); } catch (_) { onBusyChange?.(false); const redirectUrl = frontendUrl("/auth/google/callback"); window.location.assign( `${getApiBase()}/auth/google/login?redirect=${encodeURIComponent(redirectUrl)}` ); } }, [cid, disabled, busy, onCredential, onBusyChange]); if (!cid) return null; return ( ); } export default GoogleSignInButton;