feat: implement home page, business directory, and administrative management features with updated schema and API routes
Some checks failed
Build and Push App / build (push) Failing after 51s

This commit is contained in:
2026-04-23 14:40:50 +02:00
parent 88e4c13b9f
commit e6310f30de
23 changed files with 1124 additions and 222 deletions

View File

@@ -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, Country } from '../../types';
import { Business, Country } from '../../types';
import { generateBusinessDescription } from '../../lib/geminiService';
import { toast } from 'react-hot-toast';
import { useUser } from '../UserProvider';
@@ -18,7 +18,9 @@ const getYouTubeId = (url: string) => {
const normalizeBusinessData = (b: Business): Business => ({
...b,
name: b.name || 'Nouvelle Entreprise',
category: b.category || CATEGORIES[0],
category: b.category || 'Autre',
categoryId: b.categoryId || '',
suggestedCategory: b.suggestedCategory || '',
description: b.description || '',
location: b.location || '',
countryId: b.countryId || '',
@@ -46,23 +48,29 @@ const DashboardProfile = ({ business, setBusiness }: { business: Business, setBu
const { login } = useUser();
const [formData, setFormData] = useState<Business>(normalizeBusinessData(business));
const [countries, setCountries] = useState<Country[]>([]);
const [categories, setCategories] = useState<any[]>([]);
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
// Fetch countries and categories
React.useEffect(() => {
const fetchCountries = async () => {
const fetchData = async () => {
try {
const res = await fetch('/api/countries');
const data = await res.json();
if (!data.error) setCountries(data);
const [cRes, catRes] = await Promise.all([
fetch('/api/countries'),
fetch('/api/categories')
]);
const cData = await cRes.json();
const catData = await catRes.json();
if (!cData.error) setCountries(cData);
if (!catData.error) setCategories(catData);
} catch (error) {
console.error('Error fetching countries:', error);
console.error('Error fetching metadata:', error);
}
};
fetchCountries();
fetchData();
}, []);
// Sync formData when business prop changes (initial fetch)
@@ -333,10 +341,37 @@ const DashboardProfile = ({ business, setBusiness }: { business: Business, setBu
</div>
<div className="sm:col-span-3">
<label className="block text-sm font-medium text-gray-700">Secteur d'activité</label>
<select name="category" value={formData.category} onChange={handleInputChange} className="mt-1 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">
{CATEGORIES.map(c => <option key={c} value={c}>{c}</option>)}
<select
name="categoryId"
value={formData.categoryId || ''}
onChange={(e) => {
const catId = e.target.value;
const catName = categories.find(c => c.id === catId)?.name || 'Autre';
setFormData({ ...formData, categoryId: catId, category: catName });
}}
className="mt-1 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 secteur</option>
{categories.map(c => <option key={c.id} value={c.id}>{c.name}</option>)}
<option value="Autre">Autre (proposer...)</option>
</select>
</div>
{formData.category === 'Autre' && (
<div className="sm:col-span-3 animate-in fade-in slide-in-from-top-2 duration-300">
<label className="block text-sm font-medium text-brand-600 flex items-center gap-1">
<Sparkles className="w-3 h-3" /> Nom de la nouvelle catégorie
</label>
<input
type="text"
name="suggestedCategory"
value={formData.suggestedCategory || ''}
onChange={handleInputChange}
placeholder="ex: Intelligence Artificielle"
className="mt-1 block w-full border border-brand-300 bg-brand-50 rounded-md shadow-sm py-2 px-3 focus:ring-brand-500 focus:border-brand-500 sm:text-sm"
/>
<p className="mt-1 text-[10px] text-brand-500">Elle sera vérifiée par nos administrateurs avant d'être ajoutée officiellement.</p>
</div>
)}
<div className="sm:col-span-6">
<div className="flex justify-between mb-1">
<label className="block text-sm font-medium text-gray-700">Description</label>