// sections-sim.jsx — Cadre téléphone + simulation interactive de l'app Mobile.
//
// Architecture hybride :
// 1. Tente de charger l'app réelle via iframe (mobile-demo/index.html?demo=1)
// 2. En fallback : affiche une démo interactive statique avec les écrans
// PhoneSearchScreen → PhoneSeatScreen → PhoneTicketScreen déjà définis
// dans sections.jsx. Pas besoin de build séparé — fonctionne toujours.
//
// Le composant `MobileDemoFrame` est exposé sur `window` et utilisé :
// - dans la section Features (taille "md" — intégré entre les cartes)
// - éventuellement comme section dédiée via le wrapper `BookingSimulator`
const { useState: simUseState, useEffect: simUseEffect, useRef: simUseRef } = React;
// ---------- Démo interactive statique (fallback / mode par défaut) ----------
// Affiche les vrais écrans de l'app avec navigation entre les étapes.
function StaticPhoneDemo({ lang = "fr" }) {
const [screen, setScreen] = simUseState(0); // 0=search, 1=seats, 2=ticket, 3=list
const screens = [
{ component: PhoneSearchScreen, label: lang === "fr" ? "Recherche" : "Search" },
{ component: PhoneSeatScreen, label: lang === "fr" ? "Siège" : "Seat" },
{ component: PhoneTicketScreen, label: "Billet" },
{ component: PhoneTicketsListScreen, label: lang === "fr" ? "Mes billets" : "My tickets" },
];
const Current = screens[screen].component;
// Auto-advance toutes les 5s si pas d'interaction
const [autoPlay, setAutoPlay] = simUseState(true);
simUseEffect(() => {
if (!autoPlay) return;
const id = setInterval(() => {
setScreen(s => (s + 1) % screens.length);
}, 5000);
return () => clearInterval(id);
}, [autoPlay, screens.length]);
const goTo = (idx) => {
setAutoPlay(false);
setScreen(idx);
};
const next = () => {
setAutoPlay(false);
setScreen(s => (s + 1) % screens.length);
};
return (
{/* Écran actuel */}
{/* Navigation dots */}
{screens.map((s, i) => (
))}
{/* Hint tap */}
{lang === "fr" ? "Tapez pour naviguer" : "Tap to navigate"}
);
}
// ---------- Composant principal : MobileDemoFrame ----------
function MobileDemoFrame({ size = "md", lang = "fr" }) {
const [status, setStatus] = simUseState("idle"); // idle | checking | ok | missing | loaded
const iframeRef = simUseRef(null);
const wrapRef = simUseRef(null);
// Lazy : on ne déclenche la vérification + chargement de l'iframe que lorsque
// le cadre entre dans le viewport. Évite de télécharger ~13 MB de bundle Vite +
// images au chargement initial de la page si l'utilisateur n'arrive jamais
// jusqu'à la section. rootMargin élargi pour pré-charger un peu en avance.
simUseEffect(() => {
const node = wrapRef.current;
if (!node) return;
if (typeof IntersectionObserver === "undefined") {
// Vieux navigateur : on charge tout de suite.
setStatus("checking");
return;
}
const obs = new IntersectionObserver((entries) => {
if (entries.some(e => e.isIntersecting)) {
setStatus("checking");
obs.disconnect();
}
}, { rootMargin: "400px" });
obs.observe(node);
return () => obs.disconnect();
}, []);
// Vérification présence + identité du build mobile-demo, déclenchée uniquement
// une fois passé en "checking" (donc après entrée dans le viewport).
simUseEffect(() => {
if (status !== "checking") return;
let cancelled = false;
// Cache standard : le HTML du mobile-demo change rarement et le navigateur
// saura le revalider via ETag si besoin. "no-store" forçait un download
// complet à chaque visite, avant même l'affichage de la simulation.
fetch("mobile-demo/index.html")
.then(async (res) => {
if (cancelled) return;
if (!res.ok) {
setStatus("missing");
return;
}
const text = await res.text();
// Signature attendue : assets/index-XXXXXX.js (build Vite)
const isMobileBuild = /assets\/index-[A-Za-z0-9_-]+\.js/.test(text);
if (!isMobileBuild) {
setStatus("missing");
return;
}
setStatus("ok");
})
.catch(() => {
if (!cancelled) setStatus("missing");
});
return () => { cancelled = true; };
}, [status]);
const reload = () => {
if (status === "loaded" && iframeRef.current) {
iframeRef.current.src = `mobile-demo/index.html?demo=1&t=${Date.now()}`;
setStatus("ok");
} else {
// Reset la démo statique
setStatus("missing");
}
};
return (
{(status === "idle" || status === "checking") && (
{lang === "fr" ? "Préparation…" : "Preparing…"}
)}
{/* Fallback : démo interactive statique avec les vrais écrans */}
{status === "missing" && (
)}
{(status === "ok" || status === "loaded") && (
);
}
// ---------- Wrapper de section optionnel — gardé pour compat si on veut
// remettre une section dédiée plus tard. Actuellement non utilisé dans
// app.jsx (la démo vit dans la section Features). ----------
function BookingSimulator({ t }) {
const lang = (window.FT_T === window.FT_I18N?.fr) ? "fr" : "en";
return (
{lang === "fr" ? "Essayez avant le lancement" : "Try before launch"}
{lang === "fr" ? "Réservez un billet en 30 secondes." : "Book a ticket in 30 seconds."}
{lang === "fr"
? "Démo interactive — l'application réelle FasoTravel embarquée. Cliquez à l'intérieur du téléphone."
: "Interactive demo — the real FasoTravel app embedded. Tap inside the phone."}
{lang === "fr"
? "C'est la vraie app, mode démo. Aucune réservation réelle, données fictives."
: "It's the real app in demo mode. No real booking, mock data."}