feat: implement core platform features including business directory, admin dashboard, authentication, and API infrastructure
Some checks failed
Build and Push App / build (push) Failing after 16m2s
Some checks failed
Build and Push App / build (push) Failing after 16m2s
This commit is contained in:
@@ -3,7 +3,7 @@
|
||||
|
||||
import React, { useState } from 'react';
|
||||
import { Image as ImageIcon, Sparkles, Youtube, X, Globe, Facebook, Linkedin, Instagram, CheckCircle, AlertCircle } from 'lucide-react';
|
||||
import { Business, CATEGORIES } from '../../types';
|
||||
import { Business, CATEGORIES, Country } from '../../types';
|
||||
import { generateBusinessDescription } from '../../lib/geminiService';
|
||||
import { toast } from 'react-hot-toast';
|
||||
import { useUser } from '../UserProvider';
|
||||
@@ -15,17 +15,56 @@ const getYouTubeId = (url: string) => {
|
||||
return (match && match[2].length === 11) ? match[2] : null;
|
||||
};
|
||||
|
||||
const normalizeBusinessData = (b: Business): Business => ({
|
||||
...b,
|
||||
name: b.name || 'Nouvelle Entreprise',
|
||||
category: b.category || CATEGORIES[0],
|
||||
description: b.description || '',
|
||||
location: b.location || '',
|
||||
countryId: b.countryId || '',
|
||||
city: b.city || '',
|
||||
contactEmail: b.contactEmail || '',
|
||||
showEmail: b.showEmail ?? true,
|
||||
showPhone: b.showPhone ?? true,
|
||||
showSocials: b.showSocials ?? true,
|
||||
slug: b.slug || '',
|
||||
videoUrl: b.videoUrl || '',
|
||||
contactPhone: b.contactPhone || '',
|
||||
websiteUrl: b.websiteUrl || '',
|
||||
socialLinks: {
|
||||
facebook: b.socialLinks?.facebook || '',
|
||||
linkedin: b.socialLinks?.linkedin || '',
|
||||
instagram: b.socialLinks?.instagram || '',
|
||||
website: b.socialLinks?.website || '',
|
||||
}
|
||||
});
|
||||
|
||||
const DashboardProfile = ({ business, setBusiness }: { business: Business, setBusiness: (b: Business) => void }) => {
|
||||
const { login } = useUser();
|
||||
const [formData, setFormData] = useState(business);
|
||||
const [formData, setFormData] = useState<Business>(normalizeBusinessData(business));
|
||||
const [countries, setCountries] = useState<Country[]>([]);
|
||||
const [videoInput, setVideoInput] = useState(business.videoUrl || '');
|
||||
const [videoPreviewId, setVideoPreviewId] = useState<string | null>(getYouTubeId(business.videoUrl || ''));
|
||||
const [isGenerating, setIsGenerating] = useState(false);
|
||||
const [isMounted, setIsMounted] = useState(false);
|
||||
|
||||
// Fetch countries
|
||||
React.useEffect(() => {
|
||||
const fetchCountries = async () => {
|
||||
try {
|
||||
const res = await fetch('/api/countries');
|
||||
const data = await res.json();
|
||||
if (!data.error) setCountries(data);
|
||||
} catch (error) {
|
||||
console.error('Error fetching countries:', error);
|
||||
}
|
||||
};
|
||||
fetchCountries();
|
||||
}, []);
|
||||
|
||||
// Sync formData when business prop changes (initial fetch)
|
||||
React.useEffect(() => {
|
||||
setFormData(business);
|
||||
setFormData(normalizeBusinessData(business));
|
||||
setVideoInput(business.videoUrl || '');
|
||||
setVideoPreviewId(getYouTubeId(business.videoUrl || ''));
|
||||
}, [business]);
|
||||
@@ -91,6 +130,38 @@ const DashboardProfile = ({ business, setBusiness }: { business: Business, setBu
|
||||
}
|
||||
};
|
||||
|
||||
const handleDelete = async () => {
|
||||
if (!window.confirm("⚠️ ATTENTION : Êtes-vous sûr de vouloir supprimer définitivement votre boutique ? Cette action supprimera également vos offres, vos messages et vos statistiques. Cette action est irréversible.")) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const res = await fetch(`/api/businesses/${business.id}`, {
|
||||
method: 'DELETE',
|
||||
headers: {
|
||||
'x-user-id': business.ownerId
|
||||
}
|
||||
});
|
||||
const data = await res.json();
|
||||
if (data.success) {
|
||||
toast.success('Boutique supprimée avec succès.');
|
||||
// Upgrade local storage user if role changed
|
||||
const localUser = JSON.parse(localStorage.getItem('afro_user') || '{}');
|
||||
if (localUser.id === business.ownerId) {
|
||||
localUser.role = 'VISITOR';
|
||||
localStorage.setItem('afro_user', JSON.stringify(localUser));
|
||||
if (login) login(localUser);
|
||||
}
|
||||
setTimeout(() => window.location.href = '/dashboard', 1500);
|
||||
} else {
|
||||
toast.error(data.error || 'Erreur lors de la suppression');
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Delete error:', error);
|
||||
toast.error('Erreur réseau lors de la suppression');
|
||||
}
|
||||
};
|
||||
|
||||
const isNameOk = formData.name && formData.name !== "Nouvelle Entreprise";
|
||||
const isDescOk = formData.description && formData.description.length >= 20;
|
||||
const isLocOk = formData.location && formData.location !== "Ma Ville";
|
||||
@@ -254,17 +325,35 @@ const DashboardProfile = ({ business, setBusiness }: { business: Business, setBu
|
||||
<div className="bg-white shadow rounded-lg p-6">
|
||||
<h3 className="text-lg font-medium text-gray-900 mb-4 flex items-center"><Globe className="w-5 h-5 mr-2 text-blue-500" /> Coordonnées & Réseaux</h3>
|
||||
<div className="grid grid-cols-1 gap-y-6 gap-x-4 sm:grid-cols-6">
|
||||
<div className="sm:col-span-6">
|
||||
<label className="block text-sm font-medium text-gray-700 mb-1">Localisation (Ville, Pays)</label>
|
||||
<div className="sm:col-span-3">
|
||||
<label className="block text-sm font-medium text-gray-700 mb-1">Pays</label>
|
||||
<select
|
||||
name="countryId"
|
||||
value={formData.countryId || ''}
|
||||
onChange={handleInputChange}
|
||||
className="block w-full border border-gray-300 rounded-md shadow-sm py-2 px-3 focus:ring-brand-500 focus:border-brand-500 sm:text-sm"
|
||||
>
|
||||
<option value="">Sélectionner un pays</option>
|
||||
{countries.map(c => (
|
||||
<option key={c.id} value={c.id}>{c.flag} {c.name}</option>
|
||||
))}
|
||||
</select>
|
||||
</div>
|
||||
<div className="sm:col-span-3">
|
||||
<label className="block text-sm font-medium text-gray-700 mb-1">Ville</label>
|
||||
<input
|
||||
type="text"
|
||||
name="location"
|
||||
value={formData.location}
|
||||
name="city"
|
||||
value={formData.city || ''}
|
||||
onChange={handleInputChange}
|
||||
placeholder="Ex: Abidjan, Côte d'Ivoire"
|
||||
placeholder="Ex: Abidjan"
|
||||
className="block w-full border border-gray-300 rounded-md shadow-sm py-2 px-3 focus:ring-brand-500 focus:border-brand-500 sm:text-sm"
|
||||
/>
|
||||
</div>
|
||||
<div className="sm:col-span-6 hidden">
|
||||
{/* Keep legacy field for compatibility if needed, but hide it */}
|
||||
<input type="hidden" name="location" value={`${formData.city || ''}, ${countries.find(c => c.id === formData.countryId)?.name || ''}`} />
|
||||
</div>
|
||||
<div className="sm:col-span-3">
|
||||
<div className="flex items-center justify-between mb-1">
|
||||
<label className="block text-sm font-medium text-gray-700">Email Contact</label>
|
||||
@@ -335,6 +424,29 @@ const DashboardProfile = ({ business, setBusiness }: { business: Business, setBu
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* D. Zone de Danger */}
|
||||
<div className="bg-red-50 border border-red-200 shadow-sm rounded-lg p-6">
|
||||
<div className="flex items-center gap-2 mb-4 text-red-800">
|
||||
<AlertCircle className="w-5 h-5" />
|
||||
<h3 className="text-lg font-bold">Zone de Danger</h3>
|
||||
</div>
|
||||
<div className="flex flex-col sm:flex-row sm:items-center justify-between gap-4">
|
||||
<div>
|
||||
<p className="text-sm font-bold text-red-900">Supprimer définitivement la boutique</p>
|
||||
<p className="text-xs text-red-700 mt-1">
|
||||
Une fois supprimée, votre boutique ne sera plus visible dans l'annuaire.
|
||||
Toutes vos données (offres, messages, avis) seront perdues.
|
||||
</p>
|
||||
</div>
|
||||
<button
|
||||
onClick={handleDelete}
|
||||
className="bg-red-600 text-white px-4 py-2 rounded-lg hover:bg-red-700 font-bold text-sm shadow-sm transition-all active:scale-95 whitespace-nowrap"
|
||||
>
|
||||
Supprimer ma boutique
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user