feat(smart-app): implement complete mobile app MVP

- App.tsx: full navigation (Auth stack + Main tabs with 5 screens)
- Auth: LoginScreen, RegisterScreen, ForgotPasswordScreen
- HomeScreen: dashboard with IoT metrics, weather widget, alerts, quick actions, sensors
- MapScreen: interactive map with layer toggles (6 layers)
- MarketplaceScreen: categories (6), products (5), search
- ChatScreen: AI chat with quick prompts (4), bot responses
- ProfileScreen: user info, stats, menu (9 items), logout
- AlertsScreen: alert list with severity, acknowledge
- SensorsScreen: sensor list with type filters (6 types), search
- ZonesScreen: zone cards with stats
- SettingsScreen: language picker (FR/EN/ES/DE), privacy, about
- Stores: iotStore (sensors, zones, alerts), notificationStore, uiStore + i18n
- Hooks: useSensors, useAlerts, useNotifications, useLocation
- Components: Card, Button, LoadingSpinner, ErrorBoundary, Header
- Services: iotService, notificationService (with axios API client)
- Utils: formatters (temp, AQI, noise, dates), validators (email, password, IBAN)
- Theme: colors.ts with full design system (Blue Ocean palette)
- Ditto: fixed MongoDB connection, new JWT secrets, official gateway image
This commit is contained in:
Eric FELIXINE
2026-06-01 18:00:35 -04:00
parent 08ca495bde
commit e30ae8ed09
35578 changed files with 3703534 additions and 43 deletions

View File

@@ -0,0 +1,334 @@
<!DOCTYPE html>
<html lang="fr">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Smart App City — 05 AI Chat Assistant</title>
<link rel="stylesheet" href="shared.css">
<style>
.chat-header {
padding: 14px 16px;
background: white;
border-bottom: 1px solid var(--neutral-200);
display: flex; align-items: center; gap: 12px;
}
.chat-avatar {
width: 40px; height: 40px;
border-radius: var(--radius-full);
background: linear-gradient(135deg, #7C4DFF, #B388FF);
display: flex; align-items: center; justify-content: center;
color: white; font-size: 20px;
flex-shrink: 0;
}
.chat-status {
width: 10px; height: 10px;
background: var(--alert-success);
border-radius: 50%;
position: absolute;
bottom: 0; right: 0;
border: 2px solid white;
}
.chat-messages {
padding: 16px;
display: flex; flex-direction: column;
gap: 12px;
min-height: 400px;
}
.msg-row {
display: flex; gap: 8px;
align-items: flex-end;
}
.msg-row.user { flex-direction: row-reverse; }
.msg-avatar {
width: 28px; height: 28px;
border-radius: var(--radius-full);
display: flex; align-items: center; justify-content: center;
font-size: 14px; flex-shrink: 0;
}
.msg-bubble {
max-width: 75%;
padding: 10px 14px;
border-radius: var(--radius-lg);
font-size: var(--text-sm);
line-height: 1.45;
position: relative;
}
.msg-row:not(.user) .msg-bubble {
background: var(--neutral-100);
color: var(--neutral-800);
border-bottom-left-radius: 4px;
}
.msg-row.user .msg-bubble {
background: linear-gradient(135deg, var(--primary-500), var(--primary-600));
color: white;
border-bottom-right-radius: 4px;
}
.msg-time {
font-size: 9px;
color: var(--neutral-400);
margin-top: 4px;
}
.msg-row.user .msg-time { text-align: right; }
.quick-replies {
display: flex; flex-wrap: wrap; gap: 6px;
padding: 0 16px 12px;
}
.quick-reply {
padding: 8px 14px;
border-radius: var(--radius-full);
background: var(--primary-50);
color: var(--primary-600);
font-size: var(--text-sm);
font-weight: var(--weight-medium);
cursor: pointer;
border: 1px solid var(--primary-100);
white-space: nowrap;
}
.typing-indicator {
display: flex; gap: 4px;
padding: 12px 16px;
background: var(--neutral-100);
border-radius: var(--radius-lg);
border-bottom-left-radius: 4px;
width: fit-content;
}
.typing-dot {
width: 6px; height: 6px;
background: var(--neutral-400);
border-radius: 50%;
animation: typingBounce 1.4s infinite;
}
.typing-dot:nth-child(2) { animation-delay: 0.2s; }
.typing-dot:nth-child(3) { animation-delay: 0.4s; }
@keyframes typingBounce {
0%, 60%, 100% { transform: translateY(0); }
30% { transform: translateY(-6px); }
}
.chat-input-area {
position: absolute;
bottom: 80px; left: 0; right: 0;
background: white;
border-top: 1px solid var(--neutral-200);
padding: 10px 12px;
display: flex; gap: 8px; align-items: flex-end;
}
.chat-input {
flex: 1;
border: 2px solid var(--neutral-200);
border-radius: var(--radius-full);
padding: 10px 16px;
font-size: var(--text-sm);
resize: none;
max-height: 80px;
font-family: var(--font-family);
outline: none;
}
.chat-input:focus { border-color: var(--primary-500); }
.send-btn {
width: 42px; height: 42px;
border-radius: var(--radius-full);
background: var(--primary-500);
border: none;
display: flex; align-items: center; justify-content: center;
cursor: pointer;
flex-shrink: 0;
}
.inline-card {
background: var(--neutral-0);
border-radius: var(--radius-md);
overflow: hidden;
border: 1px solid var(--neutral-200);
margin-top: 8px;
}
.inline-card-map {
height: 80px;
background: linear-gradient(135deg,#E8F5E9,#E0F7FA);
display: flex; align-items: center; justify-content: center;
font-size: 12px; color: var(--neutral-500);
}
.inline-card-info { padding: 8px 12px; }
/* Fixed viewport for PDF/iframe embedding */
html, body { width: 390px !important; height: 844px !important; overflow: hidden !important; margin: 0 !important; padding: 0 !important; }
</style>
<style>
/* Fixed viewport for PDF/iframe embedding */
html, body { width: 390px !important; height: 844px !important; overflow: hidden !important; margin: 0 !important; padding: 0 !important; }
</style>
</head>
<body>
<div class="mobile-frame">
<!-- Status Bar -->
<div class="status-bar">
<span>9:41</span>
<div style="display:flex;gap:4px;align-items:center;">
<svg width="16" height="12" viewBox="0 0 16 12"><rect x="0" y="4" width="3" height="8" rx="1" fill="#333"/><rect x="4.5" y="2.5" width="3" height="9.5" rx="1" fill="#333"/><rect x="9" y="0" width="3" height="12" rx="1" fill="#333"/><rect x="13.5" y="0" width="2.5" height="12" rx="1" fill="#ccc"/></svg>
</div>
</div>
<!-- Chat Header -->
<div class="chat-header">
<div style="position:relative;">
<div class="chat-avatar">🤖</div>
<div class="chat-status"></div>
</div>
<div style="flex:1;">
<div style="font-weight:700;font-size:var(--text-base);">City Assistant</div>
<div style="font-size:var(--text-xs);color:var(--alert-success);font-weight:500;">En ligne · Propulsé par RAG</div>
</div>
<div style="display:flex;gap:8px;">
<svg class="icon" viewBox="0 0 24 24"><path d="M12 1a3 3 0 00-3 3v8a3 3 0 006 0V4a3 3 0 00-3-3z"/><path d="M19 10v2a7 7 0 01-14 0v-2"/><line x1="12" y1="19" x2="12" y2="23"/><line x1="8" y1="23" x2="16" y2="23"/></svg>
<svg class="icon" viewBox="0 0 24 24"><circle cx="12" cy="12" r="1"/><circle cx="12" cy="5" r="1"/><circle cx="12" cy="19" r="1"/></svg>
</div>
</div>
<!-- Messages -->
<div class="content-area" style="top:71px;bottom:140px;background:var(--neutral-50);">
<div class="chat-messages">
<!-- Welcome -->
<div class="msg-row">
<div class="msg-avatar" style="background:linear-gradient(135deg,#7C4DFF,#B388FF);">🤖</div>
<div>
<div class="msg-bubble">
Bonjour Eric ! 👋 Je suis votre assistant Smart City. Je peux vous aider avec :
<br><br>
🌡️ Les données en temps réel<br>
🗺️ Les infos géolocalisées<br>
🏪 Les services du marché local<br>
📊 Les statistiques de la ville<br><br>
Comment puis-je vous aider ?
</div>
<div class="msg-time">09:30</div>
</div>
</div>
<!-- User question -->
<div class="msg-row user">
<div>
<div class="msg-bubble">Quelle est la qualité de l'air à Fort-de-France ?</div>
<div class="msg-time">09:32</div>
</div>
</div>
<!-- AI response with data -->
<div class="msg-row">
<div class="msg-avatar" style="background:linear-gradient(135deg,#7C4DFF,#B388FF);">🤖</div>
<div>
<div class="msg-bubble">
📊 <strong>Qualité de l'air — Fort-de-France</strong><br><br>
Indice AQI : <strong>42 (Bon)</strong> 🟢<br><br>
🌡️ Temp : 24.3°C<br>
💧 Humidité : 65%<br>
💨 PM2.5 : 8 µg/m³<br>
🔄 Dernière mise à jour : il y a 3 min<br><br>
<em>Source : Capteur AirQ-012 · IoT Network</em>
</div>
<div class="inline-card">
<div class="inline-card-map">
<span>🗺️ Aperçu zone FDF Centre — <span style="color:var(--alert-success);font-weight:700;">BON</span></span>
</div>
<div class="inline-card-info" style="display:flex;gap:8px;">
<div style="flex:1;text-align:center;background:var(--neutral-50);border-radius:6px;padding:6px;">
<div style="font-size:var(--text-sm);font-weight:700;color:var(--alert-success);">42</div>
<div style="font-size:9px;color:var(--neutral-500);">AQI</div>
</div>
<div style="flex:1;text-align:center;background:var(--neutral-50);border-radius:6px;padding:6px;">
<div style="font-size:var(--text-sm);font-weight:700;color:var(--ocean-500);">24°</div>
<div style="font-size:9px;color:var(--neutral-500);">Temp</div>
</div>
<div style="flex:1;text-align:center;background:var(--neutral-50);border-radius:6px;padding:6px;">
<div style="font-size:var(--text-sm);font-weight:700;color:var(--neutral-700);">65%</div>
<div style="font-size:9px;color:var(--neutral-500);">Humidité</div>
</div>
</div>
</div>
<div class="msg-time">09:32</div>
</div>
</div>
<!-- User question 2 -->
<div class="msg-row user">
<div>
<div class="msg-bubble">Où trouver des fruits locaux près de chez moi ?</div>
<div class="msg-time">09:35</div>
</div>
</div>
<!-- AI response with map card -->
<div class="msg-row">
<div class="msg-avatar" style="background:linear-gradient(135deg,#7C4DFF,#B388FF);">🤖</div>
<div>
<div class="msg-bubble">
🍎 J'ai trouvé 3 producteurs près de vous !<br><br>
<strong>1. Marché Sainte-Marie</strong> 📍 2.3 km<br>
Mangue José, Banane... ⭐ 4.8<br><br>
<strong>2. Jardin du Lamentin</strong> 📍 3.7 km<br>
Tomates locales, herbes... ⭐ 4.9<br><br>
<strong>3. Habitation Limbé</strong> 📍 5.1 km<br>
Plantains, ignames... ⭐ 4.6<br><br>
Souhaitez-vous voir sur la carte ? 🗺️
</div>
<div class="msg-time">09:35</div>
</div>
</div>
<!-- Typing -->
<div class="msg-row">
<div class="msg-avatar" style="background:linear-gradient(135deg,#7C4DFF,#B388FF);">🤖</div>
<div class="typing-indicator">
<div class="typing-dot"></div>
<div class="typing-dot"></div>
<div class="typing-dot"></div>
</div>
</div>
</div>
<!-- Quick Replies -->
<div class="quick-replies">
<div class="quick-reply">🚌 Prochain bus</div>
<div class="quick-reply">🌧️ Météo</div>
<div class="quick-re-signaler un problème</div>
<div class="quick-reply">📊 Statistiques</div>
</div>
</div>
<!-- Input -->
<div class="chat-input-area">
<textarea class="chat-input" placeholder="Posez votre question..." rows="1"></textarea>
<div class="send-btn">
<svg class="icon" viewBox="0 0 24 24" style="color:white;"><line x1="22" y1="2" x2="11" y2="13"/><polygon points="22 2 15 22 11 13 2 9 22 2"/></svg>
</div>
</div>
<!-- Bottom Nav -->
<div class="bottom-nav">
<div class="bottom-nav-item">
<svg viewBox="0 0 24 24" fill="currentColor"><path d="M3 9l9-7 9 7v11a2 2 0 01-2 2H5a2 2 0 01-2-2z"/><polyline points="9 22 9 12 15 12 15 22" fill="none" stroke="currentColor" stroke-width="2"/></svg>
<span>Accueil</span>
</div>
<div class="bottom-nav-item">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><polygon points="1 6 1 22 8 18 16 22 23 18 23 2 16 6 8 2 1 6"/><line x1="8" y1="2" x2="8" y2="18"/><line x1="16" y1="6" x2="16" y2="22"/></svg>
<span>Carte</span>
</div>
<div class="bottom-nav-item active">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="12" cy="12" r="10"/><path d="M8 14s1.5 2 4 2 4-2 4-2"/><line x1="9" y1="9" x2="9.01" y2="9"/><line x1="15" y1="9" x2="15.01" y2="9"/></svg>
<span>AI Chat</span>
</div>
<div class="bottom-nav-item">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><rect x="2" y="3" width="20" height="14" rx="2"/><line x1="8" y1="21" x2="16" y2="21"/><line x1="12" y1="17" x2="12" y2="21"/></svg>
<span>Marché</span>
</div>
<div class="bottom-nav-item">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M20 21v-2a4 4 0 00-4-4H8a4 4 0 00-4 4v2"/><circle cx="12" cy="7" r="4"/></svg>
<span>Profil</span>
</div>
</div>
<div style="position:absolute;bottom:216px;left:0;right:0;text-align:center;font-size:10px;color:var(--neutral-300);">05 — AI Chat Assistant (RAG)</div>
</div>
</body>
</html>