// app.jsx — PWA fullscreen mobile router const { useState, useEffect } = React; // Detect in-app browsers (WhatsApp, FB, Instagram, Messenger, LinkedIn, TikTok) function detectInAppBrowser() { try { const ua = (navigator.userAgent || '').toLowerCase(); if (ua.indexOf('whatsapp') !== -1 || ua.indexOf('wabusiness') !== -1 || ua.indexOf('wa-business') !== -1) return 'WhatsApp'; if (ua.indexOf('instagram') !== -1) return 'Instagram'; if (ua.indexOf('fbav') !== -1 || ua.indexOf('fban') !== -1 || ua.indexOf('fb_iab') !== -1 || ua.indexOf('fbios') !== -1) return 'Facebook'; if (ua.indexOf('messenger') !== -1) return 'Messenger'; if (ua.indexOf('linkedinapp') !== -1 || ua.indexOf('linkedin') !== -1) return 'LinkedIn'; if (ua.indexOf('musical_ly') !== -1 || ua.indexOf('bytedance') !== -1 || ua.indexOf('tiktok') !== -1) return 'TikTok'; // Fallback: many in-app browsers ship without Safari/Chrome in UA on iOS // Detect "in-app webview" via the absence of "Safari" on iOS but presence of "Mobile" const isIOS = /iphone|ipad|ipod/.test(ua); if (isIOS && ua.indexOf('safari') === -1 && ua.indexOf('crios') === -1 && ua.indexOf('fxios') === -1) { return 'Application'; } return null; } catch (e) { return null; } } function InAppBrowserBanner({ lang }) { const [dismissed, setDismissed] = useState(() => { try { return localStorage.getItem('trusta_inapp_dismiss') === '1'; } catch (e) { return false; } }); const [copied, setCopied] = useState(false); const browser = detectInAppBrowser(); if (!browser || dismissed) return null; const url = 'https://trusta.click/app'; const isKa = lang === 'ka'; const title = isKa ? `${browser}-ში ხარ — გახსენი ბრაუზერში` : `Tu es dans ${browser} — ouvre dans ton navigateur`; const subtitle = isKa ? 'უკეთესი გამოცდილებისთვის (Chrome / Safari)' : 'Pour une meilleure expérience (Chrome / Safari)'; const copyLabel = copied ? (isKa ? '✓ დაკოპირდა' : '✓ Copié') : (isKa ? 'ბმულის კოპირება' : 'Copier le lien'); const dismissLabel = isKa ? 'მესმის' : 'OK, j\'ai compris'; const onCopy = async () => { try { if (navigator.clipboard && navigator.clipboard.writeText) { await navigator.clipboard.writeText(url); } else { const ta = document.createElement('textarea'); ta.value = url; document.body.appendChild(ta); ta.select(); document.execCommand('copy'); document.body.removeChild(ta); } setCopied(true); setTimeout(() => setCopied(false), 2000); } catch (e) {} }; const onDismiss = () => { try { localStorage.setItem('trusta_inapp_dismiss', '1'); } catch (e) {} setDismissed(true); }; return (
⚠️ {title}
{subtitle}
); } function App() { // Language with localStorage persistence (default: Géorgien) const [lang, setLang] = useState(() => { try { return localStorage.getItem('trusta_lang') || 'ka'; } catch (e) { return 'ka'; } }); useEffect(() => { try { localStorage.setItem('trusta_lang', lang); } catch (e) {} document.documentElement.lang = lang; }, [lang]); const t = useT(lang); // Auto-login : on lit directement localStorage (résilient au timing de chargement des scripts) const initialRoute = (() => { try { const token = localStorage.getItem('trusta_token'); if (!token) return { name: 'splash' }; const userRaw = localStorage.getItem('trusta_user'); const u = userRaw ? JSON.parse(userRaw) : null; if (u && u.name && u.city) return { name: 'main' }; return { name: 'profileSetup' }; } catch (e) { return { name: 'splash' }; } })(); // Tous les useState d'abord (ordre important pour les hooks React) const [route, setRoute] = useState(initialRoute); const [tab, setTab] = useState('home'); const [query, setQuery] = useState(''); const [showReview, setShowReview] = useState(false); const [reviewUser, setReviewUser] = useState(null); const [authPhone, setAuthPhone] = useState(null); const [toast, setToast] = useState(null); const [showWelcome, setShowWelcome] = useState(false); // Helper: show a toast for 3s const showToast = (msg) => { setToast(msg); setTimeout(() => setToast(null), 3000); }; // Effets après tous les useState useEffect(() => { if (!window.TrustaAuth || !window.TrustaAuth.isLoggedIn()) return; if (!window.TrustaAPI || !window.TrustaAPI.getMe) return; window.TrustaAPI.getMe().then((u) => { if (u && u.name && u.city) { setTab('home'); setRoute({ name: 'main' }); } else if (u) { setRoute({ name: 'profileSetup' }); } }).catch(() => { // Erreur réseau → on garde l'état initial. 401 → apiFetch a déjà clear le token. }); if (window.TrustaFavStore) window.TrustaFavStore.refresh(); if (window.TrustaNotifStore) window.TrustaNotifStore.startPolling(30000); }, []); const go = (name, props = {}) => setRoute({ name, ...props }); const onTab = (id) => { if (id === 'post') { go('createListing'); return; } setTab(id); go('main'); }; let screen; if (route.name === 'splash') { screen = go('phone')} />; } else if (route.name === 'phone') { screen = { setAuthPhone(phone); go('otp'); }} />; } else if (route.name === 'otp') { screen = go('phone')} onNext={(result) => { // Si user existant et profil déjà rempli, skip profileSetup if (result?.user && result.user.name && result.user.city && !result.isNewUser) { const name = result.user.name; const msg = lang === 'ka' ? `კეთილი იყოს დაბრუნება, ${name}! 💛` : `Bon retour, ${name} ! 💛`; showToast(msg); setTab('home'); go('main'); } else { go('profileSetup'); } }} />; } else if (route.name === 'profileSetup') { screen = { setShowWelcome(true); setTab('home'); go('main'); }} />; } else if (route.name === 'main') { if (tab === 'home') { screen = go('listingDetail', { listing: l })} onOpenMap={() => go('map')} onCreateListing={() => go('createListing')} />; } else if (tab === 'chat') { screen = go('chatThread', { peer: c })} />; } else if (tab === 'alert') { screen = go('listingDetail', { listing: l })} onOpenChat={(c) => go('chatThread', { peer: c })} />; } else if (tab === 'profile') { screen = go('referral')} onOpenMyListings={() => go('myListings')} onOpenFavorites={() => go('favorites')} onOpenAbout={() => go('about')} onOpenContact={() => go('contact')} onOpenPrivacy={() => go('privacy')} onOpenTerms={() => go('terms')} onOpenDeleteAccount={() => go('deleteAccount')} onOpenBlocked={() => go('blocked')} onLogout={() => { setTab('home'); go('splash'); }} />; } } else if (route.name === 'listingDetail') { screen = go('main')} onDeleted={() => { setTab('home'); go('main', { _refresh: Date.now() }); }} onOpenChat={(u) => { // u = poster { id, name, avatar, verified } → format ChatThreadScreen const me = window.TrustaAuth && window.TrustaAuth.getUser(); if (!u || !u.id) return; if (me && u.id === me.id) return; // pas de chat avec soi-même go('chatThread', { peer: { peer_id: u.id, peer_name: u.name, peer_photo: u.avatar, peer_verified: u.verified, listing_id: route.listing && route.listing.id ? route.listing.id : null, } }); }} onOpenProfile={(u) => go('memberProfile', { user: u })} />; } else if (route.name === 'map') { screen = go('main')} onOpenListing={(l) => go('listingDetail', { listing: l })} />; } else if (route.name === 'chatThread') { screen = { setTab('chat'); go('main'); }} onOpenProfile={(u) => go('memberProfile', { user: u })} />; } else if (route.name === 'memberProfile') { screen = go('main')} onOpenChat={(u) => go('chatThread', { peer: u })} onLeaveReview={() => { setReviewUser(route.user); setShowReview(true); }} />; } else if (route.name === 'createListing') { screen = { setTab('home'); go('main'); }} onPublish={() => { setTab('home'); go('main'); }} />; } else if (route.name === 'referral') { screen = { setTab('profile'); go('main'); }} />; } else if (route.name === 'myListings') { screen = { setTab('profile'); go('main'); }} onOpenListing={(l) => go('listingDetail', { listing: l })} onCreateListing={() => go('createListing')} />; } else if (route.name === 'favorites') { screen = { setTab('profile'); go('main'); }} onOpenListing={(l) => go('listingDetail', { listing: l })} onOpenProfile={(u) => go('memberProfile', { user: u })} />; } else if (route.name === 'about') { screen = { setTab('profile'); go('main'); }} />; } else if (route.name === 'contact') { screen = { setTab('profile'); go('main'); }} />; } else if (route.name === 'privacy') { screen = { setTab('profile'); go('main'); }} />; } else if (route.name === 'terms') { screen = { setTab('profile'); go('main'); }} />; } else if (route.name === 'deleteAccount') { screen = { setTab('profile'); go('main'); }} onDeleted={() => { setTab('home'); go('splash'); }} />; } else if (route.name === 'blocked') { screen = { setTab('profile'); go('main'); }} />; } const showTabBar = route.name === 'main'; const showFloatingControls = route.name !== 'splash'; // Floating language toggle (top-right, respects safe area) const langToggle = showFloatingControls && ( ); return (
{langToggle} {/* In-app browser warning (WhatsApp/FB/Instagram) */} {/* Screen content fullscreen */}
{screen}
{/* Tab bar */} {showTabBar && } {/* Review modal */} {showReview && reviewUser && ( setShowReview(false)} /> )} {/* Welcome modal (post inscription beta) */} {showWelcome && (
setShowWelcome(false)} style={{ position: 'fixed', inset: 0, zIndex: 600, background: 'rgba(0,0,0,0.85)', backdropFilter: 'blur(8px)', WebkitBackdropFilter: 'blur(8px)', display: 'flex', alignItems: 'center', justifyContent: 'center', padding: 20, }} >
e.stopPropagation()} style={{ background: 'linear-gradient(160deg, #1a1a1a 0%, #0e0e0e 100%)', border: '1px solid rgba(212,175,55,0.4)', borderRadius: 20, padding: '32px 24px', maxWidth: 380, width: '100%', maxHeight: '85vh', overflowY: 'auto', fontFamily: 'Inter', color: '#fff', boxShadow: '0 20px 60px rgba(0,0,0,0.6), 0 0 0 1px rgba(212,175,55,0.15)', }} >
💛
{lang === 'ka' ? 'გმადლობთ ნდობისთვის!' : 'Merci pour ta confiance !'}
{lang === 'ka' ? 'შენ ხარ Trusta-ს ერთ-ერთი პირველი მომხმარებელი 🇬🇪' : 'Tu fais partie des tout premiers utilisateurs de Trusta 🇬🇪'}
⚠️ {lang === 'ka' ? 'ეს არის ბეტა ვერსია' : 'C\'est une version beta'}
{lang === 'ka' ? 'შესაძლოა იყოს პატარა ბაგები. შენი დახმარება გვჭირდება მათ აღმოსაჩენად 🛠️' : 'Il peut y avoir des petits bugs. On a besoin de toi pour les trouver 🛠️'}
🎁 {lang === 'ka' ? 'ჯილდოები ბეტა-ტესტერებისთვის' : 'Récompenses pour beta-testeurs'}
{lang === 'ka' ? 'ყველა ვინც დაგვეხმარება — უფასო თვეები, სპეციალური სტატუსები და საჩუქრები 💛' : 'Tous ceux qui nous aident : mois gratuits, statuts spéciaux et surprises 💛'}
)} {/* Toast notification */} {toast && (
{toast}
)}
); } ReactDOM.createRoot(document.getElementById('root')).render();