From 1068dbc6fa61f4282a9ffa2e0477442521e8e0cf Mon Sep 17 00:00:00 2001 From: Victor Date: Fri, 27 Mar 2026 21:21:56 +0100 Subject: [PATCH] feat: app store promotion banners and contextual download prompts Add mobile app promotion system for web users: - Smart banner (mobile-only): app-store-style banner with icon, rating stars, and platform-specific CTA, appears after 3s with slide-down animation - Promotional section: dark gradient banner with phone mockup between About and Stats sections on the home page - Footer store links: App Store and Google Play badges in a new column - Contextual download prompts: bottom-sheet popups triggered at key interaction points (search results, AED detail view, directions click, geolocation success) with anti-spam cooldowns - Fix: AED detail modal z-index and floating controls overlap on mobile Co-Authored-By: Claude Opus 4.6 (1M context) --- src/app/globals.css | 16 + src/app/page.tsx | 49 ++- src/components/AedDetailModal.tsx | 23 +- src/components/AppDownloadBanner.tsx | 589 +++++++++++++++++++++++++++ src/components/Footer.tsx | 6 +- 5 files changed, 672 insertions(+), 11 deletions(-) create mode 100644 src/components/AppDownloadBanner.tsx diff --git a/src/app/globals.css b/src/app/globals.css index 867444c7..5ecb4003 100644 --- a/src/app/globals.css +++ b/src/app/globals.css @@ -46,3 +46,19 @@ .animate-modal-content { animation: modal-slide-up 0.3s ease-out; } + +/* Smart banner slide-down */ +@keyframes slide-down { + from { + transform: translateY(-100%); + opacity: 0; + } + to { + transform: translateY(0); + opacity: 1; + } +} + +.animate-slide-down { + animation: slide-down 0.4s cubic-bezier(0.16, 1, 0.3, 1); +} diff --git a/src/app/page.tsx b/src/app/page.tsx index 92535b25..6e0a4909 100644 --- a/src/app/page.tsx +++ b/src/app/page.tsx @@ -17,6 +17,12 @@ import { useState, useEffect } from "react"; import Link from "next/link"; import AedDetailModal from "@/components/AedDetailModal"; +import { + AppDownloadPrompt, + AppDownloadSection, + AppSmartBanner, + useAppDownloadPrompt, +} from "@/components/AppDownloadBanner"; import { useAnalytics } from "@/hooks/useAnalytics"; import type { Aed } from "@/types/aed"; @@ -77,6 +83,8 @@ export default function Home() { trackButtonClick, } = useAnalytics(); + const appPrompt = useAppDownloadPrompt(); + // Debounce for address search const searchAddressSuggestions = async (query: string) => { if (query.length < 3) { @@ -136,6 +144,8 @@ export default function Home() { setNearbyAeds(nearbyData.data); if (nearbyData.data.length === 0) { setError("No se encontraron DEAs cerca de esta ubicación en un radio de 10 km."); + } else { + appPrompt.trigger("search_results", 2000); } } else { throw new Error(nearbyData.message || "Error al buscar DEAs"); @@ -196,6 +206,7 @@ export default function Home() { }); trackGeolocationRequest("success"); + appPrompt.trigger("geolocation", 3000); const { latitude, longitude } = position.coords; setSearchLocation({ lat: latitude, lng: longitude }); @@ -318,6 +329,17 @@ export default function Home() { return ( <> + {/* Smart App Banner for mobile users */} + + + {/* Contextual App Download Prompt */} + + {/* Fullscreen Map Section */}
- {/* Search Controls Overlay - Desktop: Top Left, Mobile: Top */} -
+ {/* Search Controls Overlay - Desktop: Top Left, Mobile: Top (hidden when modal open) */} +
{/* Search Box */}
@@ -419,8 +443,10 @@ export default function Home() {
- {/* Geolocation Button - Mobile: Bottom Center */} -
+ {/* Geolocation Button - Mobile: Bottom Center (hidden when modal open) */} +
+ +
+ {/* Icon + text */} +
+
+ +
+
+

{msg.title}

+

{msg.subtitle}

+
+
+ + {/* Features pills */} +
+ + + GPS nativo + + + + Navegacion + + + + Sin conexion + +
+ + {/* CTA button */} + { + trackExternalLink(storeUrl, `App Prompt - ${storeName}`, `app_prompt_${context}`); + trackButtonClick("app_prompt_download", context); + }} + className="flex items-center justify-center gap-2 w-full py-3 bg-gradient-to-r from-blue-600 to-blue-700 text-white font-semibold rounded-xl shadow-lg hover:shadow-xl transition-all active:scale-[0.98]" + > + + Descargar gratis en {storeName} + + + {/* Permanent dismiss */} + +
+
+
+
+ ); +} + +/** + * Sticky smart banner for mobile users — appears after 3s with app store styling. + * Dismissible with localStorage persistence. + */ +export function AppSmartBanner() { + const [show, setShow] = useState(false); + const platform = useDevicePlatform(); + const { trackExternalLink, trackButtonClick } = useAnalytics(); + + useEffect(() => { + if (platform === "desktop") return; + const dismissedAt = localStorage.getItem("app-banner-dismissed"); + if (dismissedAt) { + const ts = parseInt(dismissedAt, 10); + if (Date.now() - ts < COOLDOWN_24H) return; + } + + const timer = setTimeout(() => setShow(true), 3000); + return () => clearTimeout(timer); + }, [platform]); + + if (!show || platform === "desktop") return null; + + const storeUrl = platform === "ios" ? APP_STORE_URL : PLAY_STORE_URL; + const storeName = platform === "ios" ? "App Store" : "Google Play"; + const isIos = platform === "ios"; + + const handleDismiss = () => { + setShow(false); + localStorage.setItem("app-banner-dismissed", String(Date.now())); + trackButtonClick("smart_banner_dismiss", "smart_banner"); + }; + + return ( +
+ {/* Store-style banner */} +
+
+ {/* Close */} + + + {/* App icon */} +
+ DeaMap +
+ + {/* App info */} +
+

DeaMap - Desfibriladores

+

+ {isIos ? "En el App Store" : "En Google Play"} +

+ {/* Star rating */} +
+
+ {[1, 2, 3, 4, 5].map((star) => ( + + + + ))} +
+ GRATIS +
+
+ + {/* CTA button */} + + trackExternalLink(storeUrl, `Smart Banner - ${storeName}`, "smart_banner") + } + className={`flex-shrink-0 text-sm font-bold px-5 py-1.5 rounded-full transition-colors ${ + isIos + ? "bg-blue-500 text-white hover:bg-blue-600" + : "bg-green-600 text-white hover:bg-green-700" + }`} + > + VER + +
+
+
+ ); +} + +/** + * Full promotional section with store badges — for the home page info area. + */ +export function AppDownloadSection() { + const { trackExternalLink } = useAnalytics(); + + return ( +
+
+ {/* Decorative background elements */} +
+
+
+
+ +
+
+ {/* Text content */} +
+
+ + DISPONIBLE EN TU MOVIL +
+ +

+ Lleva DeaMap en tu bolsillo +

+ +

+ Encuentra el desfibrilador mas cercano al instante. +

+ +
    +
  • + + Busqueda por GPS en tiempo real +
  • +
  • + + Navegacion hasta el DEA mas cercano +
  • +
  • + + Funciona sin conexion +
  • +
+ + {/* Store badges */} + +
+ + {/* Phone mockup */} +
+
+ {/* Phone frame */} +
+ {/* Notch */} +
+ {/* Screen */} +
+ DeaMap app - Mapa de desfibriladores +
+
+
+
+
+
+
+
+ ); +} + +/** + * Compact store links for the footer. + */ +export function AppStoreFooterLinks() { + const { trackExternalLink } = useAnalytics(); + + return ( + + ); +} + +/* ---------- SVG Store Badges ---------- */ + +function AppStoreBadge({ className = "h-11" }: { className?: string }) { + return ( + + + + + Disponible en + + + App Store + + {/* Apple logo simplified */} + + + + + ); +} + +function GooglePlayBadge({ className = "h-11" }: { className?: string }) { + return ( + + + + + DISPONIBLE EN + + + Google Play + + {/* Play triangle */} + + + + + + + + + + + + + + + + + + + + + + + + + + ); +} diff --git a/src/components/Footer.tsx b/src/components/Footer.tsx index c303217c..fa3535d0 100644 --- a/src/components/Footer.tsx +++ b/src/components/Footer.tsx @@ -3,6 +3,7 @@ import { ExternalLink, MapPin, PlusCircle } from "lucide-react"; import Link from "next/link"; +import { AppStoreFooterLinks } from "@/components/AppDownloadBanner"; import { useAnalytics } from "@/hooks/useAnalytics"; export default function Footer() { @@ -11,7 +12,7 @@ export default function Footer() { return (