const { NavBar, Button, Dialog, Toast } = window.PilatesLifeStudioDesignSystem_f49721; const TIPO_LABEL = { grupal: 'Clase grupal', privada: 'Clase privada', valoracion: 'Valoración (primera vez)' }; // ── Rutas ──────────────────────────────────────────────────────────────────── // El sitio es una sola página de React, pero cada vista tiene su propia URL. Así la // barra de direcciones acompaña a la pestaña activa, el botón "atrás" del navegador // funciona, y se puede compartir un enlace directo (studiopilateslife.com/reservar // sirve para la bio de Instagram, por ejemplo). // // OJO: esto depende de la regla de reescritura del .htaccess, que sirve index.html en // estas rutas. Sin ella, entrar directo a /reservar o recargar la página da 404. const VISTAS = ['home', 'precios', 'reservar', 'login', 'registro', 'cuenta', 'restablecer', ...LEGAL.map(l => l.id)]; const TITULOS = { home: 'Pilates LifeStudio' }; TITULOS.precios = 'Planes y precios · Pilates LifeStudio'; TITULOS.reservar = 'Reservar · Pilates LifeStudio'; TITULOS.login = 'Iniciar sesión · Pilates LifeStudio'; TITULOS.registro = 'Crear cuenta · Pilates LifeStudio'; TITULOS.cuenta = 'Mi cuenta · Pilates LifeStudio'; TITULOS.restablecer = 'Restablecer contraseña · Pilates LifeStudio'; LEGAL.forEach(l => { TITULOS[l.id] = l.label + ' · Pilates LifeStudio'; }); function rutaDeVista(vista) { return vista === 'home' ? '/' : '/' + vista; } // Lee la vista desde la URL actual. Cualquier ruta que no reconozcamos cae en 'home'. function vistaDeRuta() { const slug = window.location.pathname.replace(/^\/+|\/+$/g, ''); if (slug === '') return 'home'; return VISTAS.includes(slug) ? slug : 'home'; } function Site() { const [view, setViewState] = React.useState(vistaDeRuta); const [pending, setPending] = React.useState(null); const [enviando, setEnviando] = React.useState(false); const [toast, setToast] = React.useState(null); const [refreshSignal, setRefreshSignal] = React.useState(0); const [resetSignal, setResetSignal] = React.useState(0); // null = todavía no sabemos (consultando al servidor), false = sin sesión, // objeto = logueado (payload completo de mi_cuenta.php: cliente + reservas + movimientos). const [sesion, setSesion] = React.useState(null); const refrescarSesion = React.useCallback(() => { return consultarSesion().then(datos => setSesion(datos || false)).catch(() => setSesion(false)); }, []); React.useEffect(() => { refrescarSesion(); }, [refrescarSesion]); // Login/registro/restablecer navegan a /cuenta justo después de autenticar. Si eso // pasara con un setView('cuenta') suelto, el efecto de arriba (view==='cuenta' && // sesion===false) alcanzaría a rebotar a /login porque `sesion` todavía no se ha // actualizado (refrescarSesion() es async) — por eso primero se espera esa respuesta // y solo entonces se cambia de vista. const irACuentaTrasAutenticar = React.useCallback(() => { return refrescarSesion().then(() => setView('cuenta')); }, [refrescarSesion]); // El link de WhatsApp para cancelar apunta siempre a /cuenta. Si el cliente todavía // no inició sesión, lo mandamos a /login primero; LoginPage lo trae de vuelta aquí // apenas entra. React.useEffect(() => { if (view === 'cuenta' && sesion === false) { setView('login'); } }, [view, sesion]); // Cambiar de pestaña escribe la ruta en la barra de direcciones, sin recargar nada. const setView = (siguiente) => { if (siguiente !== view) { window.history.pushState({ view: siguiente }, '', rutaDeVista(siguiente)); } setViewState(siguiente); }; // Botones atrás/adelante del navegador: aquí NO se hace pushState, solo se lee la URL // a la que el navegador ya nos movió (si no, se duplicarían entradas del historial). React.useEffect(() => { const alNavegar = () => setViewState(vistaDeRuta()); window.addEventListener('popstate', alNavegar); return () => window.removeEventListener('popstate', alNavegar); }, []); // El título de la pestaña del navegador acompaña a la vista. React.useEffect(() => { document.title = TITULOS[view] || TITULOS.home; }, [view]); React.useEffect(() => { window.scrollTo({ top: 0 }); }, [view]); React.useEffect(() => { if (!toast) return; const t = setTimeout(() => setToast(null), 4000); return () => clearTimeout(t); }, [toast]); const confirmar = async () => { if (!pending) return; setEnviando(true); try { const datos = await crearReserva({ fecha: pending.fecha, hora: pending.hora, tipo: pending.tipo, notas: pending.notas, }); setToast({ tone: 'success', message: `Reserva confirmada · ${datos.reserva.etiqueta} · ${datos.reserva.fecha} · Te quedan ${datos.saldo_tiquetera} tiquete(s)` }); setPending(null); setResetSignal(v => v + 1); refrescarSesion(); } catch (err) { setToast({ tone: 'danger', message: err.message }); setPending(null); } finally { setEnviando(false); setRefreshSignal(v => v + 1); } }; const navItems = sesion ? [...NAV, { id: 'cuenta', label: 'Mi cuenta' }] : [...NAV, { id: 'login', label: 'Iniciar sesión' }]; return (
setView('reservar')}>Reservar} /> {view === 'home' && (<> setView('reservar')} /> {/* Testimonial de Linda Palma desactivado temporalmente: pendiente conseguir su permiso. Volver a agregar aquí cuando esté confirmado. */} setView('reservar')} /> )} {view === 'precios' && setView('reservar')} />} {view === 'reservar' && setView('precios')} sesion={sesion} onNavigate={setView} refreshSignal={refreshSignal} resetSignal={resetSignal} />} {view === 'login' && } {view === 'registro' && } {view === 'restablecer' && } {view === 'cuenta' && sesion && { cerrarSesion().then(() => { setSesion(false); setView('home'); }); }} />} {LEGAL.some(l => l.id === view) && }
); }