aboutsummaryrefslogtreecommitdiff
path: root/src/frontend/app
diff options
context:
space:
mode:
Diffstat (limited to 'src/frontend/app')
-rw-r--r--src/frontend/app/components/EnMarchaAnnouncement.tsx119
-rw-r--r--src/frontend/app/components/layout/AppShell.tsx5
-rw-r--r--src/frontend/app/hooks/useEnMarchaAnnouncement.ts29
-rw-r--r--src/frontend/app/i18n/locales/en-GB.json17
-rw-r--r--src/frontend/app/i18n/locales/es-ES.json17
-rw-r--r--src/frontend/app/i18n/locales/gl-ES.json17
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",