migration correction et maj
Some checks failed
Build and Push App / build (push) Failing after 11m56s
Some checks failed
Build and Push App / build (push) Failing after 11m56s
This commit is contained in:
270
app/page.tsx
270
app/page.tsx
@@ -1,208 +1,76 @@
|
||||
"use client";
|
||||
import React from 'react';
|
||||
import { prisma } from '@/lib/prisma';
|
||||
import HomeClient from './HomeClient';
|
||||
import { Metadata } from 'next';
|
||||
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import Link from 'next/link';
|
||||
import { useRouter } from 'next/navigation';
|
||||
import { Search, MapPin, Briefcase, TrendingUp, Loader2, ArrowRight } from 'lucide-react';
|
||||
import { generateSlug } from '../lib/utils';
|
||||
import { Business, BlogPost } from '../types';
|
||||
import BusinessCard from '../components/BusinessCard';
|
||||
import { useUser } from '../components/UserProvider';
|
||||
export async function generateMetadata(): Promise<Metadata> {
|
||||
const settings = await prisma.siteSetting.findUnique({
|
||||
where: { id: 'singleton' }
|
||||
});
|
||||
|
||||
const HomePage = () => {
|
||||
const router = useRouter();
|
||||
const { user, settings } = useUser();
|
||||
const [searchTerm, setSearchTerm] = useState('');
|
||||
const [featuredBusinesses, setFeaturedBusinesses] = useState<Business[]>([]);
|
||||
const [posts, setPosts] = useState<BlogPost[]>([]);
|
||||
const [categories, setCategories] = useState<any[]>([]);
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [loadingPosts, setLoadingPosts] = useState(true);
|
||||
const title = settings?.siteName || 'Afroprenariat';
|
||||
const description = settings?.siteSlogan || "La plateforme de référence pour l'entrepreneuriat africain.";
|
||||
|
||||
useEffect(() => {
|
||||
const fetchFeatured = async () => {
|
||||
try {
|
||||
const res = await fetch('/api/businesses?featured=true', {
|
||||
headers: user ? { 'x-user-id': user.id } : {}
|
||||
});
|
||||
const data = await res.json();
|
||||
if (Array.isArray(data)) {
|
||||
setFeaturedBusinesses(data.slice(0, 4)); // Show top 4
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error fetching featured businesses:', error);
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
fetchFeatured();
|
||||
|
||||
const fetchPosts = async () => {
|
||||
try {
|
||||
const res = await fetch('/api/blog');
|
||||
const data = await res.json();
|
||||
if (Array.isArray(data)) {
|
||||
setPosts(data);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error fetching blog posts:', error);
|
||||
} finally {
|
||||
setLoadingPosts(false);
|
||||
}
|
||||
};
|
||||
const fetchCategories = async () => {
|
||||
try {
|
||||
const res = await fetch('/api/categories');
|
||||
const data = await res.json();
|
||||
if (Array.isArray(data)) setCategories(data);
|
||||
} catch (e) {}
|
||||
};
|
||||
fetchPosts();
|
||||
fetchCategories();
|
||||
}, []);
|
||||
|
||||
const handleSearch = (e: React.FormEvent) => {
|
||||
e.preventDefault();
|
||||
router.push(`/annuaire?q=${searchTerm}`);
|
||||
return {
|
||||
title: {
|
||||
default: title,
|
||||
template: `%s | ${title}`
|
||||
},
|
||||
description: description,
|
||||
openGraph: {
|
||||
title: title,
|
||||
description: description,
|
||||
url: 'https://afroprenariat.com',
|
||||
siteName: title,
|
||||
images: [
|
||||
{
|
||||
url: 'https://images.unsplash.com/photo-1522071820081-009f0129c71c?ixlib=rb-4.0.3&auto=format&fit=crop&w=1200&h=630&q=80',
|
||||
width: 1200,
|
||||
height: 630,
|
||||
alt: title,
|
||||
},
|
||||
],
|
||||
locale: 'fr_FR',
|
||||
type: 'website',
|
||||
},
|
||||
twitter: {
|
||||
card: 'summary_large_image',
|
||||
title: title,
|
||||
description: description,
|
||||
images: ['https://images.unsplash.com/photo-1522071820081-009f0129c71c?ixlib=rb-4.0.3&auto=format&fit=crop&w=1200&h=630&q=80'],
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
export default async function HomePage() {
|
||||
// Fetch initial data for faster loading and SEO
|
||||
const [featuredBusinesses, posts, categories] = await Promise.all([
|
||||
prisma.business.findMany({
|
||||
where: { isFeatured: true, isActive: true, isSuspended: false },
|
||||
take: 4,
|
||||
include: { categoryRef: true }
|
||||
}),
|
||||
prisma.blogPost.findMany({
|
||||
where: { status: 'PUBLISHED', publishedAt: { lte: new Date() } },
|
||||
orderBy: { date: 'desc' },
|
||||
take: 6
|
||||
}),
|
||||
prisma.businessCategory.findMany({
|
||||
where: { isActive: true },
|
||||
take: 8
|
||||
})
|
||||
]);
|
||||
|
||||
// Convert to plain objects for client component
|
||||
const initialFeatured = JSON.parse(JSON.stringify(featuredBusinesses));
|
||||
const initialPosts = JSON.parse(JSON.stringify(posts));
|
||||
const initialCategories = JSON.parse(JSON.stringify(categories));
|
||||
|
||||
return (
|
||||
<div>
|
||||
{/* Hero Section */}
|
||||
<div className="relative bg-dark-900 overflow-hidden">
|
||||
<div className="absolute inset-0 opacity-40">
|
||||
<img src="https://images.unsplash.com/photo-1522071820081-009f0129c71c?ixlib=rb-4.0.3&auto=format&fit=crop&w=1740&q=80" className="w-full h-full object-cover" alt="African entrepreneurs team" width={1740} height={1160} />
|
||||
</div>
|
||||
<div className="relative max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-24 lg:py-32">
|
||||
<h1 className="text-4xl md:text-6xl font-serif font-bold text-white mb-6 tracking-tight">
|
||||
Boostez votre visibilité dans <br />
|
||||
<span className="text-brand-500">l'écosystème africain</span>
|
||||
</h1>
|
||||
<p className="text-xl text-gray-300 mb-8 max-w-2xl">
|
||||
L'annuaire 2.0 qui connecte les talents, les entrepreneurs et les entreprises de la diaspora et du continent.
|
||||
</p>
|
||||
|
||||
<form onSubmit={handleSearch} className="max-w-3xl bg-white p-2 rounded-lg shadow-xl flex flex-col md:flex-row gap-2">
|
||||
<div className="flex-1 relative">
|
||||
<Search className="absolute left-3 top-3 text-gray-400 w-5 h-5" />
|
||||
<input
|
||||
type="text"
|
||||
placeholder="Que recherchez-vous ? (ex: Développeur, Traiteur...)"
|
||||
className="w-full pl-10 pr-4 py-3 rounded-md focus:outline-none text-gray-900"
|
||||
value={searchTerm}
|
||||
onChange={(e) => setSearchTerm(e.target.value)}
|
||||
/>
|
||||
</div>
|
||||
<div className="md:w-1/3 relative border-t md:border-t-0 md:border-l border-gray-200">
|
||||
<MapPin className="absolute left-3 top-3 text-gray-400 w-5 h-5" />
|
||||
<input type="text" placeholder="Localisation (ex: Abidjan)" className="w-full pl-10 pr-4 py-3 rounded-md focus:outline-none text-gray-900" />
|
||||
</div>
|
||||
<button type="submit" className="bg-brand-600 text-white px-8 py-3 rounded-md font-semibold hover:bg-brand-700 transition-colors">
|
||||
Rechercher
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Featured Categories */}
|
||||
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-16">
|
||||
<h2 className="text-3xl font-bold text-gray-900 mb-10 font-serif text-center">Secteurs en vedette</h2>
|
||||
<div className="flex flex-wrap justify-center gap-4 sm:gap-6">
|
||||
{(settings?.homeCategories || categories.slice(0, 4).map(c => c.name)).map((cat: string, idx: number) => (
|
||||
<Link
|
||||
key={idx}
|
||||
href={`/annuaire?category=${encodeURIComponent(cat)}`}
|
||||
className="group cursor-pointer bg-white p-6 rounded-xl border border-gray-100 shadow-sm hover:shadow-md hover:border-brand-200 transition-all text-center w-[calc(50%-0.5rem)] sm:w-48 lg:w-56 flex flex-col items-center"
|
||||
>
|
||||
<div className="w-12 h-12 bg-brand-50 text-brand-600 rounded-full flex items-center justify-center mb-4 group-hover:bg-brand-600 group-hover:text-white transition-colors">
|
||||
<Briefcase className="w-6 h-6" />
|
||||
</div>
|
||||
<h3 className="font-semibold text-gray-900 text-sm sm:text-base line-clamp-2">{cat}</h3>
|
||||
</Link>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Featured Businesses */}
|
||||
<div className="bg-gray-50 py-16">
|
||||
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
|
||||
<div className="flex justify-between items-end mb-8">
|
||||
<div>
|
||||
<h2 className="text-3xl font-bold text-gray-900 font-serif">Entreprises à la une</h2>
|
||||
<p className="text-gray-500 mt-2">Découvrez les pépites de notre communauté.</p>
|
||||
</div>
|
||||
<Link href="/annuaire" className="text-brand-600 font-medium hover:text-brand-700 flex items-center">
|
||||
Voir tout l'annuaire <TrendingUp className="w-4 h-4 ml-1" />
|
||||
</Link>
|
||||
</div>
|
||||
|
||||
{loading ? (
|
||||
<div className="flex justify-center py-12">
|
||||
<Loader2 className="w-10 h-10 text-brand-600 animate-spin" />
|
||||
</div>
|
||||
) : (
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-6">
|
||||
{featuredBusinesses.map(biz => (
|
||||
<BusinessCard key={biz.id} business={biz} />
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Blog Section */}
|
||||
{settings?.homeBlogShow !== false && (
|
||||
<div className="py-16">
|
||||
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
|
||||
<div className="flex justify-between items-end mb-8">
|
||||
<div>
|
||||
<h2 className="text-3xl font-bold text-gray-900 font-serif">{settings?.homeBlogTitle || "Derniers Articles"}</h2>
|
||||
<p className="text-gray-500 mt-2">{settings?.homeBlogSubtitle || "Toutes les actualités de l'entrepreneuriat africain."}</p>
|
||||
</div>
|
||||
<Link href="/blog" className="text-brand-600 font-medium hover:text-brand-700 flex items-center">
|
||||
Voir tout le blog <ArrowRight className="w-4 h-4 ml-1" />
|
||||
</Link>
|
||||
</div>
|
||||
|
||||
{loadingPosts ? (
|
||||
<div className="flex justify-center py-12">
|
||||
<Loader2 className="w-10 h-10 text-brand-600 animate-spin" />
|
||||
</div>
|
||||
) : (
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-8">
|
||||
{posts
|
||||
.filter(post => {
|
||||
if (settings?.homeBlogSelection === 'manual') {
|
||||
return settings.homeBlogIds?.includes(post.id);
|
||||
}
|
||||
if (settings?.homeBlogSelection === 'category') {
|
||||
return post.tags?.some((tag: string) => settings.homeBlogCategories?.includes(tag));
|
||||
}
|
||||
return true;
|
||||
})
|
||||
.slice(0, settings?.homeBlogCount || 3)
|
||||
.map(post => (
|
||||
<Link key={post.id} href={`/blog/${post.slug ? generateSlug(post.slug) : post.id}`} className="group bg-white rounded-xl shadow-sm border border-gray-100 overflow-hidden hover:shadow-md transition-all">
|
||||
<div className="h-48 overflow-hidden">
|
||||
<img src={post.imageUrl} alt={post.title} className="w-full h-full object-cover group-hover:scale-105 transition-transform duration-500" width={400} height={300} loading="lazy" />
|
||||
</div>
|
||||
<div className="p-6">
|
||||
<h3 className="text-xl font-bold text-gray-900 mb-2 line-clamp-2 group-hover:text-brand-600 transition-colors">{post.title}</h3>
|
||||
<p className="text-gray-500 text-sm line-clamp-3 mb-4">{post.excerpt}</p>
|
||||
<div className="flex items-center justify-between mt-auto pt-4 border-t border-gray-50">
|
||||
<span className="text-xs font-medium text-gray-400">{new Date(post.date).toLocaleDateString('fr-FR')}</span>
|
||||
<span className="text-brand-600 font-bold text-xs">Lire la suite →</span>
|
||||
</div>
|
||||
</div>
|
||||
</Link>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
<HomeClient
|
||||
initialFeatured={initialFeatured}
|
||||
initialPosts={initialPosts}
|
||||
initialCategories={initialCategories}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export default HomePage;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user