feat: implement manageable homepage banner slider with support for businesses and custom slides
This commit is contained in:
@@ -8,14 +8,16 @@ import { generateSlug } from '@/lib/utils';
|
||||
import { Business, BlogPost } from '@/types';
|
||||
import BusinessCard from '@/components/BusinessCard';
|
||||
import { useUser } from '@/components/UserProvider';
|
||||
import HomeSlider from '@/components/HomeSlider';
|
||||
|
||||
interface Props {
|
||||
initialFeatured: Business[];
|
||||
initialPosts: BlogPost[];
|
||||
initialCategories: any[];
|
||||
initialSlides: any[];
|
||||
}
|
||||
|
||||
const HomeClient = ({ initialFeatured, initialPosts, initialCategories }: Props) => {
|
||||
const HomeClient = ({ initialFeatured, initialPosts, initialCategories, initialSlides }: Props) => {
|
||||
const router = useRouter();
|
||||
const { user, settings } = useUser();
|
||||
const [searchTerm, setSearchTerm] = useState('');
|
||||
@@ -23,11 +25,11 @@ const HomeClient = ({ initialFeatured, initialPosts, initialCategories }: Props)
|
||||
const [featuredBusinesses, setFeaturedBusinesses] = useState<Business[]>(initialFeatured);
|
||||
const [posts, setPosts] = useState<BlogPost[]>(initialPosts);
|
||||
const [categories, setCategories] = useState<any[]>(initialCategories);
|
||||
const [slides, setSlides] = useState<any[]>(initialSlides);
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [loadingPosts, setLoadingPosts] = useState(false);
|
||||
|
||||
const handleSearch = (e: React.FormEvent) => {
|
||||
e.preventDefault();
|
||||
const handleSearch = (searchTerm: string, locationTerm: string) => {
|
||||
const params = new URLSearchParams();
|
||||
if (searchTerm) params.append('q', searchTerm);
|
||||
if (locationTerm) params.append('location', locationTerm);
|
||||
@@ -36,47 +38,8 @@ const HomeClient = ({ initialFeatured, initialPosts, initialCategories }: Props)
|
||||
|
||||
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"
|
||||
value={locationTerm}
|
||||
onChange={(e) => setLocationTerm(e.target.value)}
|
||||
/>
|
||||
</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>
|
||||
{/* Dynamic Hero Slider */}
|
||||
<HomeSlider slides={slides} onSearch={handleSearch} />
|
||||
|
||||
{/* Featured Categories */}
|
||||
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-16">
|
||||
|
||||
@@ -44,7 +44,7 @@ export async function generateMetadata(): Promise<Metadata> {
|
||||
|
||||
export default async function HomePage() {
|
||||
// Fetch initial data for faster loading and SEO
|
||||
const [featuredBusinesses, posts, categories] = await Promise.all([
|
||||
const [featuredBusinesses, posts, categories, slides] = await Promise.all([
|
||||
prisma.business.findMany({
|
||||
where: { isFeatured: true, isActive: true, isSuspended: false },
|
||||
take: 4,
|
||||
@@ -58,6 +58,11 @@ export default async function HomePage() {
|
||||
prisma.businessCategory.findMany({
|
||||
where: { isActive: true },
|
||||
take: 8
|
||||
}),
|
||||
prisma.homeSlide.findMany({
|
||||
where: { isActive: true },
|
||||
orderBy: { order: 'asc' },
|
||||
include: { business: true }
|
||||
})
|
||||
]);
|
||||
|
||||
@@ -65,12 +70,14 @@ export default async function HomePage() {
|
||||
const initialFeatured = JSON.parse(JSON.stringify(featuredBusinesses));
|
||||
const initialPosts = JSON.parse(JSON.stringify(posts));
|
||||
const initialCategories = JSON.parse(JSON.stringify(categories));
|
||||
const initialSlides = JSON.parse(JSON.stringify(slides));
|
||||
|
||||
return (
|
||||
<HomeClient
|
||||
initialFeatured={initialFeatured}
|
||||
initialPosts={initialPosts}
|
||||
initialCategories={initialCategories}
|
||||
initialSlides={initialSlides}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user