// screens.jsx — écrans du parcours (hors Run et Confirm)
//
// CÂBLAGE (2026-08-07, passe « enlever le static ») : plus AUCUNE donnée de
// démonstration. Chaque écran lit le tenant et n'affiche que ce qu'il sait ;
// vide quand il ne sait rien. Règle appliquée partout : un champ visible est
// un champ PERSISTÉ — un champ qu'on saisit et qui se perd en silence est un
// mensonge d'interface. (Revenus nets et bénéfice ont été retirés : la base
// n'a pas encore de colonne pour eux.)
// Langue : chrome via I18N.t(), noms du métier via le lexique du domaine.
const { useState: useStateS, useEffect: useEffectS } = React;
/* ============ CONNEXION ============ */
function LoginScreen({ onLogin }) {
const { t } = useLang();
const [email, setEmail] = useStateS(G4M.email || "");
const [password, setPassword] = useStateS("");
const [busy, setBusy] = useStateS(false);
const [err, setErr] = useStateS(null);
const enter = async () => {
setBusy(true); setErr(null);
try {
// AUTH BÊTA : le mot de passe échange contre un jeton — c'est lui qui
// prouve l'identité à chaque appel. Le serveur résout le tenant depuis
// le compte : le navigateur ne le choisit jamais.
const me = await G4M.login(email.trim(), password);
if (!me || !me.tenantId) throw new Error(t("Compte inconnu ou backend injoignable :") + " tenant absent");
onLogin();
} catch (e) {
setErr(e.message);
} finally { setBusy(false); }
};
return (
{t("Connexion sécurisée")}
{t("Bienvenue")}
{t("Connectez-vous pour reprendre vos {demandes} ou en commencer une nouvelle.")}
{active
? t("Votre abonnement est actif. Vous pouvez lancer des remplissages.")
: t("L'abonnement est inactif. Activez la facturation pour lancer des remplissages.")}
);
}
function DashboardScreen({ onNew, onOpen, onFirstVisit }) {
const { t, lang } = useLang();
const [runs, meta] = useG4M(() => G4M.runs(10), null, []);
const [session] = useG4M(() => G4M.me(), null, []);
// Premier passage d'un compte sans aucune demande : proposer le tutoriel.
// Déclenchement UNIQUE gardé par un ref — l'ancienne condition
// (`runs.length === 0` exact) ratait selon le minutage du chargement.
// Dès que le serveur a répondu (meta.live) et qu'il n'y a aucune demande,
// on propose, une seule fois.
const tutoFired = React.useRef(false);
useEffectS(() => {
if (tutoFired.current || !meta.live) return;
if (runs && runs.length > 0) return; // a déjà des demandes → pas de tuto
tutoFired.current = true;
if (onFirstVisit) onFirstVisit();
}, [meta.live, runs]);
const demandes = (runs || []).map((r) => {
const ui = RUN_STATUS_UI[r.status] || { statut: "brouillon", pct: 0 };
return {
id: r.id,
titre: r.projectTitle || t("{demande} sans titre"),
programme: r.companyName || "—",
statut: ui.statut,
pct: typeof r.progressPct === "number" && r.progressPct ? Math.round(r.progressPct) : ui.pct,
maj: r.createdAt ? new Date(r.createdAt).toLocaleDateString(lang === "en" ? "en-CA" : "fr-CA") : "—",
};
});
return (
{(session && session.companyName) &&
{session.companyName}
}
{t("Vos {demandes}")}
{t("Reprenez là où vous étiez, ou lancez une nouvelle {demande}.")}
{t("Nouvelle {demande}")}
e.key === "Enter" && onNew()}>
{t("Commencer une nouvelle {demande}")}
{t("Téléversez un {formulaire}. On s'occupe du reste.")}
{demandes.length === 0 && !meta.loading && (
{t("Aucune {demande} pour l'instant")}
{t("Votre première {demande} commence par le {formulaire} à remplir. Téléversez-le : on lit les champs, on remplit ce qu'on peut, et on ne vous demande que le reste.")}
{t("Ces informations serviront à remplir ")}{t("tous vos {formulaires}")}{t(" : ce que vous saisissez ici, on ne vous le redemandera plus. ")}
{t("Vous ne savez pas où trouver une information ?")} {t("Cliquez le « ? » à côté du champ.")}
{t("Ce profil ne se remplit ")}{t("qu'une fois")}{t(". Il sert à toutes vos {demandes} : plus vous le complétez, mieux on cible les bons {programmes} et plus le remplissage est précis.")}
{/* --- Identité --- */}
1 {t("Identité de l'entreprise")}
set("raison", v)} ariaLabel={t("Raison sociale")} />
set("neq", v)} ariaLabel="NEQ" />
{t("Tel qu'enregistré à votre dossier. Corrigez-le au besoin.")} set("forme", v)} ariaLabel={t("Statut juridique")} />
set("bn", v)} ariaLabel={t("Numéro d'entreprise fédéral (NE / ARC)")} />
{/* Une vraie date, pas une année : le moteur de dates refuse de
deviner un mois. AAAA-MM-JJ est le format pivot. */}
set("constitution", v)} ariaLabel={t("Date de constitution (AAAA-MM-JJ)")} />
set("employes", v)} ariaLabel={t("Nombre d'employés")} />
set("revenus_bruts", v)} ariaLabel={t("Revenus bruts (chiffre d'affaires)")} options={REVENUE_RANGES} />
set("secteur", v)} ariaLabel={t("Secteur d'activité")} />
{/* --- Adresse officielle --- */}
2 {t("Adresses")}
{t("Adresse officielle de l'entreprise")}
{t("Seule l'adresse officielle est enregistrée à votre dossier pour l'instant.")}
{/* --- Codes d'activité (multiples, persistés en jsonb) --- */}
3 {t("Codes d'activité")}
{t("Une entreprise peut en avoir plusieurs. Tapez votre activité dans le libellé : on cherche dans le référentiel officiel et le code se remplit seul. Indiquez le principal.")}
}
);
}
/* ============ BILLING MODAL (shown on 402 or from profile) ============ */
function BillingModal({ open, onClose }) {
const { t } = useLang();
const [sub, meta] = useG4M(() => open ? G4M.mySubscription() : null, null, [open]);
const [busy, setBusy] = useStateS(false);
const [err, setErr] = useStateS(null);
useEffectS(() => {
if (!open) return;
const esc = (e) => e.key === "Escape" && onClose();
window.addEventListener("keydown", esc);
return () => window.removeEventListener("keydown", esc);
}, [open]);
if (!open) return null;
const active = sub && sub.actif;
const status = (sub && sub.stripeStatus) || (active ? "active" : "inactive");
const go = async (fn) => {
setBusy(true); setErr(null);
try {
const r = await fn();
if (r && r.url) window.location.href = r.url;
} catch (e) { setErr(e.message); }
finally { setBusy(false); }
};
return (
e.stopPropagation()}>
{t("Abonnement")}
{!meta.live ? (
{t("Chargement…")}
) : (
<>
{active
? t("Votre abonnement est actif. Vous pouvez lancer des remplissages.")
: t("L'abonnement est inactif. Activez la facturation pour lancer des remplissages.")}
{t("Formulaires pris en charge : les PDF avec des cases à remplir intégrées.")}
{t(" Pour vérifier en 5 secondes : ouvrez le PDF et cliquez dans une case : si vous pouvez taper, c'est bon. Un scan ou un document à imprimer sera refusé immédiatement, sans frais d'analyse.")}
{state !== "done" ? (
{state === "idle" ? (
<>
{t("Choisissez votre {formulaire}")}
{t("PDF · jusqu'à 25 Mo · vos fichiers restent privés")}
{live
? <>{t("Remplissage automatique : ")}{run.pctAutoFilled ?? "—"} %{t(" · validé par vous : ")}{run.pctHumanValidated ?? "—"} %{t(". Il vous reste à joindre quelques pièces.")}>
: t("Le PDF n'est pas encore prêt.")}
{t("Votre {formulaire} rempli")}
{t("{formulaire} rempli")}
{t("PDF rempli, prêt à signer et à transmettre. Vous pourrez le rouvrir et le modifier à tout moment.")}
{t("Télécharger le PDF rempli")}
{dlErr &&
{dlErr}
}
→ {t("Comment soumettre")}
{t("Rassemblez les {pieces}")}{t(" listées à droite.")}
{t("Faites signer le {formulaire}")}{t(" par la personne autorisée de votre entreprise.")}
{t("Déposez le tout")}{t(" auprès de l'{organisme} avant l'échéance.")}
! {t("{pieces} à joindre")}
{docs.map((d, i) => (
{d.nom}
{d.note}
))}
1 ? "pieces" : "piece") })} />
{t("Retour")}{t("Revenir au tableau de bord")}
);
}
/* ============ helpers de mise en page ============ */
function ScreenShell({ kick, icon, title, lede, hideLede, children }) {
return (