/** * Oveyo PWA — Page Producteur (V9.4) * * Composants : * - AccountTypeChoice : écran de choix initial (Boutique / Producteur / Coopérative) * - ProducerRegisterForm : formulaire d'inscription producteur * - ProducerDashboard : accueil producteur (profil + annonces) * - NewListingForm : formulaire de publication d'annonce * - ProducerView : composant routeur principal (orchestration) * * Exposé : window.OveyoPages.ProducerView */ const LuPr = window.LucideReact || {}; const { Card: Cpr, Spinner: Sppr, EmptyState: ESpr, useFlash: uFpr, Sheet: ShPr } = window.OveyoUI; const { api: apiPr } = window.OveyoState; // ============================================================ // LISTE DES CULTURES (synchro avec backend producer_crops.py) // ============================================================ const CROPS_BY_CATEGORY = { cereales: { label: 'Céréales', icon: '🌾', items: ['riz', 'mil', 'mais', 'sorgho', 'fonio'] }, legumes: { label: 'Légumes', icon: '🥬', items: ['oignon', 'pomme-terre', 'tomate', 'carotte', 'chou', 'aubergine', 'navet', 'manioc', 'patate-douce', 'gombo', 'piment'] }, fruits: { label: 'Fruits', icon: '🥭', items: ['mangue', 'papaye', 'banane', 'pasteque', 'melon', 'citron'] }, legumineuses: { label: 'Légumineuses', icon: '🫘', items: ['arachide', 'niebe'] }, elevage: { label: 'Élevage', icon: '🐄', items: ['boeuf', 'mouton', 'volaille', 'oeuf', 'lait'] }, peche: { label: 'Pêche', icon: '🐟', items: ['poisson', 'poisson-fume', 'crevette'] }, }; const CROP_LABELS = { riz: 'Riz', mil: 'Mil', mais: 'Maïs', sorgho: 'Sorgho', fonio: 'Fonio', oignon: 'Oignon', 'pomme-terre': 'Pomme de terre', tomate: 'Tomate', carotte: 'Carotte', chou: 'Chou', aubergine: 'Aubergine', navet: 'Navet', manioc: 'Manioc', 'patate-douce': 'Patate douce', gombo: 'Gombo', piment: 'Piment', mangue: 'Mangue', papaye: 'Papaye', banane: 'Banane', pasteque: 'Pastèque', melon: 'Melon', citron: 'Citron', arachide: 'Arachide', niebe: 'Niébé', boeuf: 'Bétail', mouton: 'Mouton', volaille: 'Volaille', oeuf: 'Œufs', lait: 'Lait', poisson: 'Poisson frais', 'poisson-fume': 'Poisson fumé', crevette: 'Crevette', }; // ============================================================ // AccountTypeChoice — Écran initial : quel type de compte ? // ============================================================ const AccountTypeChoice = ({ onSelect, onBack }) => { const { ChevronLeft, Store, Wheat, Users } = LuPr; const types = [ { id: 'shop', icon: Store, color: '#F4632B', bgColor: '#FFF1E9', title: 'Boutique', desc: 'Tu tiens une boutique de quartier, station-service ou enseigne.' }, { id: 'producer', icon: Wheat, color: '#15803D', bgColor: '#E8F5E9', title: 'Producteur', desc: 'Tu cultives, élèves ou produis pour vendre en gros.' }, { id: 'cooperative', icon: Users, color: '#7C3AED', bgColor: '#F3EBFF', title: 'Coopérative', desc: 'Vous êtes plusieurs producteurs regroupés.' }, ]; return (
{/* Header */}
Espace Pro
Choisis ton type de compte
{/* Hero */}
Bienvenue 👋
Pour activer ton espace, dis-nous quel type d'acteur tu es. Tu pourras toujours ajouter d'autres profils plus tard.
{/* Cartes */}
{types.map((t) => { const Icon = t.icon; return ( ); })}
{/* Footer note */}
💡 Ton inscription est gratuite. Ton profil sera vérifié par notre équipe sous 48h pour activer le badge ✓.
); }; // ============================================================ // CropPicker — Multi-select avec accordéon par catégorie + "Autre" // ============================================================ const CropPicker = ({ value, onChange }) => { // value: { slugs: [...], others: [...] } const [expanded, setExpanded] = React.useState(null); const [otherInput, setOtherInput] = React.useState(''); const { Plus, X } = LuPr; const slugs = value?.slugs || []; const others = value?.others || []; const toggleSlug = (slug) => { if (slugs.includes(slug)) { onChange({ slugs: slugs.filter((s) => s !== slug), others }); } else { onChange({ slugs: [...slugs, slug], others }); } }; const addOther = () => { const t = otherInput.trim(); if (!t || others.includes(t)) return; onChange({ slugs, others: [...others, t] }); setOtherInput(''); }; const removeOther = (t) => { onChange({ slugs, others: others.filter((o) => o !== t) }); }; const totalSelected = slugs.length + others.length; return (
Tes productions
{totalSelected > 0 ? `${totalSelected} sélectionné(s)` : 'Aucune sélection'}
{/* Catégories */} {Object.entries(CROPS_BY_CATEGORY).map(([catSlug, cat]) => (
{expanded === catSlug && (
{cat.items.map((slug) => { const sel = slugs.includes(slug); return ( ); })}
)}
))} {/* Section "Autre" */}
Autre (texte libre)
setOtherInput(e.target.value)} onKeyDown={(e) => e.key === 'Enter' && (e.preventDefault(), addOther())} placeholder="Ex: Kinkéliba, Bissap, Moringa..." maxLength={60} className="flex-1 px-3 py-2 rounded-lg text-sm" style={{ background: '#F1F4F9', border: 'none' }} />
{others.length > 0 && (
{others.map((t) => ( {t} ))}
)}
); }; // ============================================================ // ProducerRegisterForm — Formulaire d'inscription producteur // ============================================================ const ProducerRegisterForm = ({ token, accountType, onSuccess, onBack }) => { const { ChevronLeft, MapPin, Loader2, Wheat, Users } = LuPr; const flash = uFpr(); const [busy, setBusy] = React.useState(false); const [form, setForm] = React.useState({ name: '', region: '', village: '', lat: 14.6928, // Dakar par défaut, l'utilisateur peut changer lng: -17.4467, crops: { slugs: [], others: [] }, surface_hectares: '', description: '', }); const [gpsLoading, setGpsLoading] = React.useState(false); const setField = (key, val) => setForm({ ...form, [key]: val }); const detectGPS = () => { if (!navigator.geolocation) { flash.show('error', 'GPS non disponible sur ce téléphone'); return; } setGpsLoading(true); navigator.geolocation.getCurrentPosition( (pos) => { setForm((f) => ({ ...f, lat: pos.coords.latitude, lng: pos.coords.longitude })); setGpsLoading(false); flash.show('success', 'Position détectée'); }, () => { setGpsLoading(false); flash.show('error', 'Impossible de détecter la position'); }, { enableHighAccuracy: true, timeout: 10000 } ); }; const submit = async () => { // Validation if (form.name.trim().length < 3) { flash.show('error', 'Le nom doit faire au moins 3 caractères'); return; } if (form.region.trim().length < 2) { flash.show('error', 'Indique ta région'); return; } if (form.crops.slugs.length + form.crops.others.length === 0) { flash.show('error', 'Sélectionne au moins une production'); return; } setBusy(true); try { const allCrops = [...form.crops.slugs, ...form.crops.others]; const payload = { name: form.name.trim(), region: form.region.trim(), village: form.village.trim() || null, lat: form.lat, lng: form.lng, crops: allCrops, surface_hectares: form.surface_hectares ? parseFloat(form.surface_hectares) : null, description: form.description.trim() || null, account_type: accountType, }; const res = await apiPr('/api/pwa/merchant/producer/register', { method: 'POST', headers: { Authorization: 'Bearer ' + token }, body: JSON.stringify(payload), }); flash.show('success', 'Inscription réussie ✓'); if (onSuccess) onSuccess(res); } catch (e) { const msg = String(e); if (msg.match(/409/)) { flash.show('error', 'Tu as déjà un profil — recharge la page'); } else { flash.show('error', 'Erreur lors de l\'inscription'); } } finally { setBusy(false); } }; const Icon = accountType === 'cooperative' ? Users : Wheat; const accentColor = accountType === 'cooperative' ? '#7C3AED' : '#15803D'; const accentBg = accountType === 'cooperative' ? '#F3EBFF' : '#E8F5E9'; return (
{/* Header */}
{accountType === 'cooperative' ? 'Inscription Coopérative' : 'Inscription Producteur'}
{/* Nom */}
setField('name', e.target.value)} placeholder={accountType === 'cooperative' ? "Ex: Coopérative Niayes" : "Ex: Ferme Diop & Fils"} maxLength={100} className="w-full px-3 py-2.5 rounded-xl text-sm" style={{ background: '#FFFFFF', border: '1px solid #EAEEF4' }} />
{/* Région */}
setField('region', e.target.value)} placeholder="Ex: Diourbel, Kaolack, Saint-Louis..." maxLength={80} className="w-full px-3 py-2.5 rounded-xl text-sm" style={{ background: '#FFFFFF', border: '1px solid #EAEEF4' }} />
{/* Village */}
setField('village', e.target.value)} placeholder="Ex: Touba Mosquée, Mboro..." maxLength={80} className="w-full px-3 py-2.5 rounded-xl text-sm" style={{ background: '#FFFFFF', border: '1px solid #EAEEF4' }} />
{/* GPS */}
{/* Cultures */} setField('crops', v)} /> {/* Surface */}
setField('surface_hectares', e.target.value)} placeholder="Ex: 5.5" className="w-full px-3 py-2.5 rounded-xl text-sm" style={{ background: '#FFFFFF', border: '1px solid #EAEEF4' }} />
{/* Description */}