/** * Oveyo PWA — Application principale (style Vivinter) * * 5 onglets : Accueil, Carte, Partager, Pharmacie, Moi * + Vue Espace Commerçant (depuis profil) * * Toutes les pages sont branchées sur l'API V4.1+PWA d'Oveyo. */ const { useState, useEffect, useRef, useCallback, useMemo } = React; // Import lucide-react via UMD global const Lu = window.lucide || {}; const { Home, MapPin, Camera, Cross, User, ChevronRight, ChevronLeft, TrendingDown, TrendingUp, AlertTriangle, Bell, Plus, Check, Clock, Phone, Navigation, Award, Gift, Eye, Sparkles, Store, Users, ShieldCheck, Edit3, Wifi, WifiOff, Shield, X, RefreshCw, Search, Filter, Loader2, Send, ArrowRight, Lock, ChevronDown, ChevronUp, Pill, } = window.LucideReact || {}; // Components partagés const { GradientBar, Card, IconBubble, FlashProvider, useFlash, Sheet, EmptyState, Spinner } = window.OveyoUI; const { loadState, saveState, defaultState, api, usePrices, useCatalog, usePharmaciesOnDuty, useShops, usePulseFeed, useAmbassador, useTrustIndex, useGroupBuys, useGeolocation, useOnlineStatus, formatRelative, formatPhone, compressImage, } = window.OveyoState; // ============================================================ // HEADER GRADIENT (top bar Oveyo) // ============================================================ const TopBar = ({ state, onBellClick }) => { const online = useOnlineStatus(); const neighborhood = state.user.neighborhood_slug ? state.user.neighborhood_slug.replace(/-/g, ' ').replace(/\b\w/g, (c) => c.toUpperCase()) : 'Dakar'; return (
Oveyo
{neighborhood} {online ? : } {online ? 'Synchro' : 'Hors-ligne'}
); }; // ============================================================ // PAGE : ACCUEIL // ============================================================ const HomePage = ({ state, setActiveTab, onOpenSheet }) => { const { feed, loading: pulseLoading } = usePulseFeed(state.user.neighborhood_slug); const { data: pricesData } = usePrices(state.user.neighborhood_slug, 7); const trustIndex = useTrustIndex(state.user.neighborhood_slug); const groupBuys = useGroupBuys(state.user.neighborhood_slug); const products = (pricesData?.products || []).slice(0, 4).filter((p) => p.avg_price); return (
{/* Pulse du quartier */}

Pulse de ton quartier

en direct
{pulseLoading ? (
) : feed.length === 0 ? (

Pas encore d'activité dans ton quartier — sois la première à signaler !

) : (
{feed.slice(0, 3).map((p, i) => (
{p.anonymous ? : (p.user || '?')[0].toUpperCase()} {!!p.ambassador && (
)}
{p.anonymous ? ( <> Anonyme{' '} · {p.action}{' '} {p.place} ) : ( <> {p.user} de {p.area} {p.action}{' '} {p.place} )}
{formatRelative(p.created_at) || p.time}
))}
)}
{/* Quick actions */}
{/* Groupe d'achat (si présent) — cliquable */} {groupBuys.length > 0 && ( )} {/* Prix du jour */}

Prix du jour

moyenne {state.user.neighborhood_slug ? 'quartier' : 'Dakar'}
{products.length === 0 ? (
Pas encore de prix cette semaine. Sois la première à en signaler un !
) : ( products.map((p, i) => { const diff = Math.round(p.avg_price - (p.price_official || p.avg_price)); const overpriced = diff > 0; return ( ); }) )}
{/* CTA Comparer — gros bouton pleine largeur */}
{/* Indice de confiance */} {trustIndex && (
= 0.7 ? '#15803D' : trustIndex.score >= 0.4 ? '#F4632B' : '#DC2626'} strokeWidth="4" fill="none" strokeDasharray={`${150 * trustIndex.score} 150`} strokeLinecap="round" /> {Math.round(trustIndex.score * 100)}%
Indice de confiance
{trustIndex.neighborhood_name || 'Dakar'} cette semaine
{trustIndex.honest_shops} boutiques honnêtes · {trustIndex.abuses} abus signalés
)}
); }; // Export window.OveyoPages = window.OveyoPages || {}; window.OveyoPages.HomePage = HomePage; window.OveyoPages.TopBar = TopBar;