diff options
Diffstat (limited to 'src/frontend')
| -rw-r--r-- | src/frontend/app/components/EnMarchaAnnouncement.tsx | 119 | ||||
| -rw-r--r-- | src/frontend/app/components/layout/AppShell.tsx | 5 | ||||
| -rw-r--r-- | src/frontend/app/hooks/useEnMarchaAnnouncement.ts | 29 | ||||
| -rw-r--r-- | src/frontend/app/i18n/locales/en-GB.json | 17 | ||||
| -rw-r--r-- | src/frontend/app/i18n/locales/es-ES.json | 17 | ||||
| -rw-r--r-- | src/frontend/app/i18n/locales/gl-ES.json | 17 |
6 files changed, 204 insertions, 0 deletions
diff --git a/src/frontend/app/components/EnMarchaAnnouncement.tsx b/src/frontend/app/components/EnMarchaAnnouncement.tsx new file mode 100644 index 0000000..0e7d0dd --- /dev/null +++ b/src/frontend/app/components/EnMarchaAnnouncement.tsx @@ -0,0 +1,119 @@ +import { AlertCircle, CheckCircle2, ExternalLink, Smartphone, X } from "lucide-react"; +import React from "react"; +import { useTranslation } from "react-i18next"; +import { Sheet } from "react-modal-sheet"; + +interface EnMarchaAnnouncementProps { + isOpen: boolean; + onClose: () => void; +} + +export const EnMarchaAnnouncement: React.FC<EnMarchaAnnouncementProps> = ({ + isOpen, + onClose, +}) => { + const { t } = useTranslation(); + + const features = [ + "feature_planner", + "feature_realtime", + "feature_operators", + "feature_ui", + "feature_more", + ]; + + return ( + <Sheet isOpen={isOpen} onClose={onClose} detent="content"> + <Sheet.Container className="bg-white! dark:bg-black! !rounded-t-[20px]"> + <Sheet.Header className="bg-white! dark:bg-black! !rounded-t-[20px]" /> + <Sheet.Content> + <div className="p-6 pb-10 flex flex-col gap-6 overflow-y-auto max-h-[85vh] text-slate-900 dark:text-slate-100"> + <div className="flex justify-between items-start"> + <h2 className="text-2xl font-bold tracking-tight"> + {t("enmarcha_announcement.title")} + </h2> + <button + onClick={onClose} + className="p-2 rounded-full hover:bg-slate-100 dark:hover:bg-slate-800 transition-colors" + aria-label={t("enmarcha_announcement.close")} + > + <X className="w-6 h-6 text-slate-400" /> + </button> + </div> + + <div className="space-y-4"> + <div className="p-4 rounded-xl bg-amber-50 dark:bg-amber-900/20 border border-amber-100 dark:border-amber-900/30 flex gap-3 items-center"> + <AlertCircle className="w-5 h-5 text-amber-600 dark:text-amber-400 shrink-0" /> + <p className="text-sm font-semibold text-amber-800 dark:text-amber-300"> + {t("enmarcha_announcement.discontinuation_notice")} + </p> + </div> + + <p className="text-slate-600 dark:text-slate-300 leading-relaxed"> + {t("enmarcha_announcement.description")} + </p> + + <div className="space-y-3 py-2"> + <h3 className="font-bold text-sm uppercase tracking-wider text-slate-500 dark:text-slate-400"> + {t("enmarcha_announcement.features_title")} + </h3> + <ul className="space-y-2"> + {features.map((feature) => ( + <li key={feature} className="flex gap-3 items-start text-sm"> + <CheckCircle2 className="w-4 h-4 text-blue-600 dark:text-blue-400 shrink-0 mt-0.5" /> + <span>{t(`enmarcha_announcement.${feature}`)}</span> + </li> + ))} + </ul> + </div> + + <a + href="https://enmarcha.app" + target="_blank" + rel="noopener noreferrer" + className="flex items-center justify-center gap-2 w-full py-3 px-6 bg-blue-600 hover:bg-blue-700 text-white font-bold rounded-xl transition-all active:scale-[0.98]" + > + {t("enmarcha_announcement.link_text")} + <ExternalLink className="w-5 h-5" /> + </a> + </div> + + <div className="space-y-4 pt-2"> + <div className="flex items-center gap-2"> + <Smartphone className="w-5 h-5 text-slate-400" /> + <h3 className="font-bold text-lg"> + {t("enmarcha_announcement.install_title")} + </h3> + </div> + + <div className="grid gap-3"> + <div className="p-4 rounded-xl bg-slate-50 dark:bg-slate-900 border border-slate-100 dark:border-slate-800"> + <h4 className="font-bold text-slate-700 dark:text-slate-300 mb-1">Android</h4> + <div className="space-y-2 text-sm text-slate-600 dark:text-slate-400"> + <p>{t("enmarcha_announcement.android_chrome")}</p> + <p>{t("enmarcha_announcement.android_firefox")}</p> + </div> + </div> + + <div className="p-4 rounded-xl bg-slate-50 dark:bg-slate-900 border border-slate-100 dark:border-slate-800"> + <h4 className="font-bold text-slate-700 dark:text-slate-300 mb-1">iOS</h4> + <p className="text-sm text-slate-600 dark:text-slate-400"> + {t("enmarcha_announcement.ios_safari")} + </p> + </div> + </div> + </div> + + <button + onClick={onClose} + className="w-full py-2 text-slate-500 dark:text-slate-400 text-sm font-medium hover:text-slate-700 dark:hover:text-slate-200 transition-colors" + > + {t("enmarcha_announcement.close")} + </button> + </div> + </Sheet.Content> + </Sheet.Container> + <Sheet.Backdrop onTap={onClose} /> + </Sheet> + ); +}; diff --git a/src/frontend/app/components/layout/AppShell.tsx b/src/frontend/app/components/layout/AppShell.tsx index afc19f3..1088035 100644 --- a/src/frontend/app/components/layout/AppShell.tsx +++ b/src/frontend/app/components/layout/AppShell.tsx @@ -1,9 +1,11 @@ import React, { useState } from "react"; import { Outlet } from "react-router"; +import { useEnMarchaAnnouncement } from "~/hooks/useEnMarchaAnnouncement"; import { PageTitleProvider, usePageTitleContext, } from "~/contexts/PageTitleContext"; +import { EnMarchaAnnouncement } from "../EnMarchaAnnouncement"; import { ThemeColorManager } from "../ThemeColorManager"; import "./AppShell.css"; import { Drawer } from "./Drawer"; @@ -13,6 +15,7 @@ import NavBar from "./NavBar"; const AppShellContent: React.FC = () => { const { title } = usePageTitleContext(); const [isDrawerOpen, setIsDrawerOpen] = useState(false); + const { isOpen, closeAnnouncement } = useEnMarchaAnnouncement(); return ( <div className="app-shell"> @@ -31,6 +34,8 @@ const AppShellContent: React.FC = () => { <footer className="app-shell__bottom-nav"> <NavBar /> </footer> + + <EnMarchaAnnouncement isOpen={isOpen} onClose={closeAnnouncement} /> </div> ); }; diff --git a/src/frontend/app/hooks/useEnMarchaAnnouncement.ts b/src/frontend/app/hooks/useEnMarchaAnnouncement.ts new file mode 100644 index 0000000..d135465 --- /dev/null +++ b/src/frontend/app/hooks/useEnMarchaAnnouncement.ts @@ -0,0 +1,29 @@ +import { useEffect, useState } from "react"; + +export const useEnMarchaAnnouncement = () => { + const [isOpen, setIsOpen] = useState(false); + + useEffect(() => { + const lastShown = localStorage.getItem("enmarcha_announcement_last_shown"); + const today = new Date().toISOString().split("T")[0]; + + if (lastShown !== today) { + // Show after a short delay to not overwhelm the user immediately on load + const timer = setTimeout(() => { + setIsOpen(true); + }, 2000); + return () => clearTimeout(timer); + } + }, []); + + const closeAnnouncement = () => { + const today = new Date().toISOString().split("T")[0]; + localStorage.setItem("enmarcha_announcement_last_shown", today); + setIsOpen(false); + }; + + return { + isOpen, + closeAnnouncement, + }; +}; diff --git a/src/frontend/app/i18n/locales/en-GB.json b/src/frontend/app/i18n/locales/en-GB.json index 819329e..d8a3134 100644 --- a/src/frontend/app/i18n/locales/en-GB.json +++ b/src/frontend/app/i18n/locales/en-GB.json @@ -166,6 +166,23 @@ "lines": { "description": "Below is a list of Vigo urban bus lines with their respective routes and links to official timetables." }, + "enmarcha_announcement": { + "title": "Busurbano is now EnMarcha", + "description": "Busurbano is evolving into EnMarcha. An updated version with new features, UI improvements, and designed to guide you across Galicia.", + "discontinuation_notice": "Notice: Busurbano will be discontinued on January 31, 2026.", + "features_title": "What's new?", + "feature_planner": "Plan your journeys across Galicia: city buses (Vigo, Coruña, and soon Santiago), rail services, and Xunta interurban buses.", + "feature_realtime": "Real-time arrival estimates for Vigo, A Coruña, and Santiago (currently in development).", + "feature_operators": "Comprehensive transit data from multiple operators in a single app.", + "feature_ui": "A modern, fast, and polished interface built for your daily commute.", + "feature_more": "Exciting new features are already on the way.", + "link_text": "Try EnMarcha now", + "install_title": "How to install the app", + "android_chrome": "In Chrome (Android): Tap ⋮ and select \"Install app\".", + "android_firefox": "In Firefox (Android): Tap ⋮ and select \"Install\".", + "ios_safari": "In Safari (iOS): Tap the share button ⎋ and select \"Add to Home Screen\".", + "close": "Stay on Busurbano" + }, "stop_help": { "title": "Estimates guide", "realtime_ok": "Reliable real-time", diff --git a/src/frontend/app/i18n/locales/es-ES.json b/src/frontend/app/i18n/locales/es-ES.json index 1e67073..4b55733 100644 --- a/src/frontend/app/i18n/locales/es-ES.json +++ b/src/frontend/app/i18n/locales/es-ES.json @@ -166,6 +166,23 @@ "lines": { "description": "A continuación se muestra una lista de las líneas de autobús urbano de Vigo con sus respectivas rutas y enlaces a los horarios oficiales." }, + "enmarcha_announcement": { + "title": "Busurbano ahora es EnMarcha", + "description": "Busurbano evoluciona para convertirse en EnMarcha. Una versión actualizada con nuevas funciones, mejoras en la interfaz y diseñada para acompañarte en tus viajes por Galicia.", + "discontinuation_notice": "Aviso: Busurbano dejará de estar disponible el 31 de enero de 2026.", + "features_title": "¿Qué hay de nuevo?", + "feature_planner": "Planifica tus rutas por toda Galicia: buses urbanos (Vigo, Coruña y pronto Santiago), trenes y buses interurbanos de la Xunta.", + "feature_realtime": "Tiempos de llegada en tiempo real para Vigo, A Coruña y Santiago (en fase beta).", + "feature_operators": "Toda la información de transporte de múltiples operadores en un solo lugar.", + "feature_ui": "Una interfaz moderna, rápida y optimizada para tu día a día.", + "feature_more": "Nuevas funcionalidades en camino para mejorar tu movilidad.", + "link_text": "Probar EnMarcha ahora", + "install_title": "Cómo instalar la aplicación", + "android_chrome": "En Chrome (Android): Pulsa ⋮ y selecciona \"Instalar aplicación\".", + "android_firefox": "En Firefox (Android): Pulsa ⋮ y selecciona \"Instalar\".", + "ios_safari": "En Safari (iOS): Pulsa el botón compartir ⎋ y selecciona \"Añadir a la pantalla de inicio\".", + "close": "Seguir en Busurbano" + }, "stop_help": { "title": "Guía de estimaciones", "realtime_ok": "Tiempo real fiable", diff --git a/src/frontend/app/i18n/locales/gl-ES.json b/src/frontend/app/i18n/locales/gl-ES.json index 4148bfa..0cb238d 100644 --- a/src/frontend/app/i18n/locales/gl-ES.json +++ b/src/frontend/app/i18n/locales/gl-ES.json @@ -166,6 +166,23 @@ "lines": { "description": "A continuación móstrase unha lista das liñas de autobús urbano de Vigo coas súas respectivas rutas e ligazóns ós horarios oficiais." }, + "enmarcha_announcement": { + "title": "Busurbano agora é EnMarcha", + "description": "Busurbano evoluciona para converterse en EnMarcha. Unha versión actualizada con novas funcións, melloras na interface e deseñada para acompañarte nas túas viaxes por Galicia.", + "discontinuation_notice": "Aviso: Busurbano deixará de estar dispoñible o 31 de xaneiro de 2026.", + "features_title": "Que hai de novo?", + "feature_planner": "Planifica as túas rutas por toda Galicia: buses urbanos (Vigo, Coruña e pronto Santiago), trens e buses interurbanos da Xunta.", + "feature_realtime": "Tempos de chegada en tempo real para Vigo, A Coruña e Santiago (en desenvolvemento).", + "feature_operators": "Toda a información de transporte de múltiples operadores nun só lugar.", + "feature_ui": "Unha interface moderna, rápida e optimizada para o teu día a día.", + "feature_more": "Novas funcionalidades en camiño para mellorar a túa mobilidade.", + "link_text": "Probar EnMarcha agora", + "install_title": "Como instalar a aplicación", + "android_chrome": "En Chrome (Android): Preme ⋮ e selecciona \"Instalar aplicación\".", + "android_firefox": "En Firefox (Android): Preme ⋮ e selecciona \"Instalar\".", + "ios_safari": "En Safari (iOS): Preme o botón compartir ⎋ e selecciona \"Engadir á pantalla de inicio\".", + "close": "Seguir en Busurbano" + }, "stop_help": { "title": "Guía de estimacións", "realtime_ok": "Tempo real fiable", |
