// help.jsx — le bouton « i » : panneau d'aide (FAQ) + tutoriel guidé. // // Deux surfaces, une doctrine : // - La FAQ répond aux questions AVANT qu'elles ne bloquent (quels PDF, // qu'apprend la mémoire, qui s'engage en validant). Bilingue en clair // ({fr,en} par entrée, comme FIELD_GUIDES) — ces textes se relisent. // - Le TUTORIEL est un faux run ASSUMÉ : badgé « Tutoriel » à chaque écran, // données d'exemple confinées ici — jamais injectées dans les vrais // écrans (un mock qui fuit dans le réel a déjà coûté assez cher). // Sautable à tout moment, rejouable depuis l'aide. const { useState: useStateH, useEffect: useEffectH } = React; const TUTO_KEY = "grant4me.tutorial.v1"; /* =============================== FAQ =============================== */ const FAQ = [ { fr: { q: "Quels formulaires fonctionnent ?", a: "Les PDF avec des cases à remplir intégrées (on clique dans une case et on peut taper). Un scan, une photo ou l'édition « à imprimer » d'un formulaire est refusé immédiatement, sans frais. Cherchez alors la version remplissable (parfois appelée « dynamique ») du même document." }, en: { q: "Which forms work?", a: "PDFs with built-in fillable fields (you click a field and can type). A scan, a photo, or the print-only edition of a form is refused immediately, at no cost. Look for the fillable (sometimes called “dynamic”) version of the same document." }, }, { fr: { q: "D'où viennent les réponses proposées ?", a: "De trois sources, toujours dans cet ordre : votre profil d'entreprise, les réponses que vous avez déjà validées sur d'autres formulaires (votre mémoire d'entreprise), puis l'assistant qui rédige à partir de votre fiche projet. Rien n'est inventé : ce qui manque vous est demandé." }, en: { q: "Where do the suggested answers come from?", a: "Three sources, always in this order: your company profile, the answers you already confirmed on other forms (your company memory), then the assistant writing from your project sheet. Nothing is invented: whatever is missing is asked." }, }, { fr: { q: "Que retient la mémoire d'entreprise ?", a: "Uniquement des FAITS que vous avez explicitement validés : un numéro, une adresse, une date. Jamais un brouillon, jamais un « à compléter » ou un « S/O », même si vous l'avez validé pour le formulaire du jour. La mémoire sert vos prochaines demandes ; elle ne doit contenir que du solide." }, en: { q: "What does the company memory keep?", a: "Only FACTS you explicitly confirmed: a number, an address, a date. Never a draft, never a “to be completed” or “N/A”, even if you confirmed it for today's form. The memory serves your next requests; it must hold only solid ground." }, }, { fr: { q: "Qui s'engage quand je clique « OK » ?", a: "Vous. La validation est le geste qui vous engage : le document sera déposé en votre nom. C'est pourquoi l'assistant distingue toujours ce qu'il propose (à relire) de ce que vous avez confirmé, et pourquoi un formulaire rempli à 100 % vous demande quand même une relecture finale." }, en: { q: "Who commits when I click “OK”?", a: "You do. Confirmation is the act that commits you: the document will be submitted in your name. That's why the assistant always separates what it proposes (to review) from what you confirmed, and why a 100%-filled form still asks you for a final read-through." }, }, { fr: { q: "Dans quel format les dates sont-elles écrites ?", a: "Selon la convention du formulaire : AAAA-MM-JJ pour les formulaires québécois et canadiens, MM/JJ/AAAA pour les américains. Quand une date est ambiguë (03/04 : 3 avril ou 4 mars ?), l'assistant refuse de deviner et vous demande." }, en: { q: "In which format are dates written?", a: "Per the form's convention: YYYY-MM-DD for Québec and Canadian forms, MM/DD/YYYY for American ones. When a date is ambiguous (03/04: April 3rd or March 4th?), the assistant refuses to guess and asks you." }, }, { fr: { q: "Où sont mes données ?", a: "Hébergées et traitées au Québec, conformément à la Loi 25. Chaque entreprise est cloisonnée : vos dossiers, vos réponses et votre mémoire ne sont visibles que par votre compte. Les pièces d'export analytique ne contiennent aucune donnée client sans votre consentement." }, en: { q: "Where is my data?", a: "Hosted and processed in Québec, in compliance with Law 25. Each company is isolated: your files, answers and memory are visible only to your account. Analytics exports contain no client data without your consent." }, }, { fr: { q: "Un remplissage a échoué, je fais quoi ?", a: "La cause s'affiche sur l'écran du remplissage (fichier sans champs, document illisible, service indisponible…). Vos réponses déjà validées sont conservées. Corrigez la cause (souvent : prendre la version remplissable du PDF), puis relancez ; la mémoire vous évitera de tout resaisir." }, en: { q: "A filling failed, what do I do?", a: "The cause is shown on the filling screen (file without fields, unreadable document, service unavailable…). Your confirmed answers are kept. Fix the cause (often: get the fillable version of the PDF), then start again; the memory saves you from re-entering everything." }, }, ]; function HelpPanel({ open, onClose, onStartTutorial }) { const { t, lang } = useLang(); const [openIdx, setOpenIdx] = useStateH(null); useEffectH(() => { if (!open) return; const onKey = (e) => e.key === "Escape" && onClose(); window.addEventListener("keydown", onKey); return () => window.removeEventListener("keydown", onKey); }, [open]); if (!open) return null; return (
); } /* ============================ TUTORIEL ============================ */ // Un faux run ASSUMÉ, confiné à cet overlay. Les nombres sont des exemples // et l'écran le dit ; le seul geste réel est d'apprendre le geste. const TUTO_STEPS = [ { icon: "auto", fr: { h: "Bienvenue dans Grant4me", p: "En deux minutes, voici comment une demande se remplit, sur un exemple. Rien de ce que vous verrez ici ne touche vos dossiers.", note: "Vous pouvez passer ce tutoriel à tout moment." }, en: { h: "Welcome to Grant4me", p: "In two minutes, here is how a request gets filled, on an example. Nothing you see here touches your files.", note: "You can skip this tutorial at any time." }, }, { icon: "building", fr: { h: "1 · Votre profil", p: "NEQ, adresse, signataire, codes d'activité… Vous les saisissez une fois ; l'assistant s'en sert pour tous vos formulaires. Plus le profil est complet, moins on vous posera de questions.", demo: "profil" }, en: { h: "1 · Your profile", p: "Business number, address, signatory, activity codes… You enter them once; the assistant uses them for every form. The more complete the profile, the fewer questions you get.", demo: "profil" }, }, { icon: "edit", fr: { h: "2 · Votre projet, en langage courant", p: "Cinq questions simples : quoi, pourquoi, objectifs, étapes, budget. Pas de jargon : c'est cette fiche que l'assistant relira pour rédiger vos réponses.", demo: "fiche" }, en: { h: "2 · Your project, in plain language", p: "Five simple questions: what, why, objectives, steps, budget. No jargon: this is the sheet the assistant re-reads to write your answers.", demo: "fiche" }, }, { icon: "upload", fr: { h: "3 · Le formulaire à remplir", p: "Téléversez le PDF officiel. Seuls les PDF à cases remplissables fonctionnent. Pour vérifier : ouvrez-le, cliquez dans une case, tapez. Un scan est refusé immédiatement, sans frais.", demo: "upload" }, en: { h: "3 · The form to fill", p: "Upload the official PDF. Only PDFs with fillable fields work. To check: open it, click a field, type. A scan is refused immediately, at no cost.", demo: "upload" }, }, { icon: "auto", fr: { h: "4 · L'assistant remplit", p: "Il lit chaque champ, puise dans votre profil et votre fiche, et remplit tout ce qu'il sait. Ici, sur notre exemple :", demo: "run" }, en: { h: "4 · The assistant fills", p: "It reads every field, draws on your profile and sheet, and fills everything it knows. Here, on our example:", demo: "run" }, }, { icon: "flag", fr: { h: "5 · Vous confirmez, et c'est vous qui vous engagez", p: "Ce qui manque ou mérite votre jugement vous est présenté. Essayez : confirmez ce champ d'exemple.", demo: "confirm" }, en: { h: "5 · You confirm, and you are the one committing", p: "Whatever is missing or needs your judgment is shown to you. Try it: confirm this example field.", demo: "confirm" }, }, { icon: "check", fr: { h: "6 · Prêt à déposer", p: "Vous téléchargez le PDF rempli et la liste des pièces à joindre. Il ne reste qu'à signer et déposer, et tout ce que vous avez validé resservira la prochaine fois, sans ressaisie.", demo: "result" }, en: { h: "6 · Ready to submit", p: "You download the filled PDF and the list of documents to attach. All that's left is to sign and submit, and everything you confirmed will serve again next time, no re-typing.", demo: "result" }, }, ]; function TutoDemo({ kind, confirmed, onConfirm }) { const { t, lang } = useLang(); const [n, setN] = useStateH(0); // Petit compteur animé pour l'étape « run » — purement décoratif. useEffectH(() => { if (kind !== "run") return; setN(0); const id = setInterval(() => setN((x) => (x >= 47 ? 47 : x + 1)), 28); return () => clearInterval(id); }, [kind]); const B = ({ children }) => {children}; if (kind === "profil") return (
{t("Tutoriel (exemple)")}
NEQ1234567890
{t("Signataire")}Camille Exemple
); if (kind === "fiche") return (
{t("Tutoriel (exemple)")}
{t("Quoi ?")} {lang === "en" ? "Hire and train two cabinetmakers." : "Embaucher et former deux ébénistes."}
); if (kind === "upload") return (
{t("Tutoriel (exemple)")}
formulaire-exemple.pdf {t("Lu avec succès")}
); if (kind === "run") return (
{t("Tutoriel (exemple)")}
{n} {t("champs détectés")} {Math.max(0, n - 5)} {t("remplis pour vous")} {n >= 47 ? 5 : 0} {t("à confirmer")}
); if (kind === "confirm") return (
{t("Tutoriel (exemple)")}
{t("Salaire horaire proposé")} 22,00 $ {confirmed ? {t("Confirmé")} : {t("C'est exact")}}
{confirmed &&

{t("Voilà le geste : votre confirmation engage, et cette réponse resservira sur vos prochains formulaires.")}

}
); if (kind === "result") return (
{t("Tutoriel (exemple)")}
{t("PDF rempli")} + 3 {t("pièces à joindre")}
); return null; } function Tutorial({ open, onClose }) { const { t, lang } = useLang(); const [i, setI] = useStateH(0); const [confirmed, setConfirmed] = useStateH(false); useEffectH(() => { if (open) { setI(0); setConfirmed(false); } }, [open]); useEffectH(() => { if (!open) return; const onKey = (e) => e.key === "Escape" && finish(); window.addEventListener("keydown", onKey); return () => window.removeEventListener("keydown", onKey); }, [open]); if (!open) return null; const finish = () => { try { localStorage.setItem(TUTO_KEY, "done"); } catch (e) {} onClose(); }; const s = TUTO_STEPS[i]; const v = s[lang] || s.fr; const last = i === TUTO_STEPS.length - 1; return (
{t("Tutoriel")}

{v.h}

{v.p}

{v.demo && setConfirmed(true)} />} {v.note &&

{v.note}

}
setI(Math.max(0, i - 1))} disabled={i === 0}>{t("Précédent")} {TUTO_STEPS.map((_, k) => (
); } /** Le tutoriel doit-il se proposer tout seul ? (première visite, aucun run) */ function tutorialPending() { try { return localStorage.getItem(TUTO_KEY) !== "done"; } catch (e) { return false; } } Object.assign(window, { HelpPanel, Tutorial, tutorialPending });