feat: initialize Prisma schema and scaffold core application modules including auth, blogs, events, and business directories
Some checks failed
Build and Push App / build (push) Failing after 47s
Some checks failed
Build and Push App / build (push) Failing after 47s
This commit is contained in:
118
components/EventTimeline.tsx
Normal file
118
components/EventTimeline.tsx
Normal file
@@ -0,0 +1,118 @@
|
||||
import React from 'react';
|
||||
import { Calendar, MapPin, ArrowRight, Clock } from 'lucide-react';
|
||||
import Link from 'next/link';
|
||||
|
||||
interface Event {
|
||||
id: string;
|
||||
slug?: string | null;
|
||||
title: string;
|
||||
description: string;
|
||||
date: Date | string;
|
||||
location: string;
|
||||
thumbnailUrl: string;
|
||||
link?: string | null;
|
||||
}
|
||||
|
||||
interface EventTimelineProps {
|
||||
events: Event[];
|
||||
}
|
||||
|
||||
const EventTimeline = ({ events }: EventTimelineProps) => {
|
||||
// Sort events by date descending
|
||||
const sortedEvents = [...events].sort((a, b) =>
|
||||
new Date(b.date).getTime() - new Date(a.date).getTime()
|
||||
);
|
||||
|
||||
return (
|
||||
<div className="relative max-w-5xl mx-auto px-4 py-12">
|
||||
{/* Vertical Line */}
|
||||
<div className="absolute left-4 md:left-1/2 top-0 bottom-0 w-1 bg-gradient-to-b from-brand-500 via-brand-600 to-amber-500 transform md:-translate-x-1/2 rounded-full opacity-20"></div>
|
||||
|
||||
<div className="space-y-12">
|
||||
{sortedEvents.map((event, index) => {
|
||||
const eventDate = new Date(event.date);
|
||||
const isEven = index % 2 === 0;
|
||||
|
||||
return (
|
||||
<div key={event.id} className={`relative flex flex-col md:flex-row items-center ${isEven ? 'md:flex-row-reverse' : ''}`}>
|
||||
{/* Marker */}
|
||||
<div className="absolute left-4 md:left-1/2 w-4 h-4 bg-white border-4 border-brand-600 rounded-full transform md:-translate-x-1/2 z-10 shadow-[0_0_15px_rgba(234,88,12,0.5)]"></div>
|
||||
|
||||
{/* Content Card */}
|
||||
<div className={`w-full md:w-[45%] ml-12 md:ml-0 group`}>
|
||||
<div className="relative bg-white/80 backdrop-blur-xl p-6 rounded-3xl border border-white shadow-[0_8px_30px_rgb(0,0,0,0.04)] hover:shadow-[0_8px_30px_rgb(0,0,0,0.12)] transition-all duration-500 group-hover:-translate-y-1">
|
||||
{/* Image with overlay gradient */}
|
||||
<div className="relative h-48 mb-6 rounded-2xl overflow-hidden">
|
||||
<img
|
||||
src={event.thumbnailUrl}
|
||||
alt={event.title}
|
||||
className="w-full h-full object-cover group-hover:scale-105 transition-transform duration-700"
|
||||
/>
|
||||
<div className="absolute inset-0 bg-gradient-to-t from-black/60 to-transparent"></div>
|
||||
<div className="absolute bottom-4 left-4 right-4 flex justify-between items-end">
|
||||
<span className="bg-brand-600/90 backdrop-blur-md text-white px-3 py-1 rounded-full text-xs font-bold uppercase tracking-wider flex items-center">
|
||||
<Calendar className="w-3 h-3 mr-1" />
|
||||
{eventDate.toLocaleDateString('fr-FR', { day: 'numeric', month: 'short' })}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Event Details */}
|
||||
<div className="space-y-3">
|
||||
<div className="flex items-center text-brand-600 text-xs font-bold uppercase tracking-widest">
|
||||
<Clock className="w-3 h-3 mr-1" />
|
||||
{eventDate.toLocaleDateString('fr-FR', { year: 'numeric' })}
|
||||
</div>
|
||||
<h3 className="text-2xl font-serif font-bold text-gray-900 group-hover:text-brand-600 transition-colors leading-tight">
|
||||
{event.title}
|
||||
</h3>
|
||||
<div className="flex items-center text-sm text-gray-500">
|
||||
<MapPin className="w-4 h-4 mr-1 text-gray-400" />
|
||||
{event.location}
|
||||
</div>
|
||||
<div
|
||||
className="text-gray-600 text-sm line-clamp-2 prose-sm"
|
||||
dangerouslySetInnerHTML={{ __html: event.description }}
|
||||
/>
|
||||
|
||||
<div className="pt-4 flex items-center justify-between">
|
||||
<Link
|
||||
href={`/afrolife/${event.slug || event.id}`}
|
||||
className="inline-flex items-center text-brand-600 text-sm font-bold hover:underline group/link"
|
||||
>
|
||||
Voir les détails
|
||||
<ArrowRight className="w-4 h-4 ml-1 transform group-hover/link:translate-x-1 transition-transform" />
|
||||
</Link>
|
||||
{/* Decorative element */}
|
||||
<div className="h-px flex-1 bg-gradient-to-r from-transparent to-gray-100 ml-4"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Hover glow effect */}
|
||||
<div className="absolute -inset-px bg-gradient-to-br from-brand-500/10 to-amber-500/10 rounded-3xl opacity-0 group-hover:opacity-100 transition-opacity pointer-events-none"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Date Column (Desktop Only) */}
|
||||
<div className="hidden md:flex w-[45%] justify-center items-center px-8">
|
||||
<div className={`text-center ${isEven ? 'text-right' : 'text-left'}`}>
|
||||
<span className="block text-4xl font-serif font-bold text-gray-200 group-hover:text-brand-500/20 transition-colors">
|
||||
{eventDate.toLocaleDateString('fr-FR', { month: 'long' })}
|
||||
</span>
|
||||
<span className="block text-xl font-bold text-gray-400">
|
||||
{eventDate.getFullYear()}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
|
||||
{/* Decorative End Dot */}
|
||||
<div className="absolute left-4 md:left-1/2 bottom-0 w-4 h-4 bg-amber-500 rounded-full transform md:-translate-x-1/2 border-4 border-white shadow-lg"></div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default EventTimeline;
|
||||
@@ -6,11 +6,17 @@ import { usePathname } from 'next/navigation';
|
||||
import { Menu, X, LayoutDashboard, User as UserIcon } from 'lucide-react';
|
||||
import { useUser } from '../components/UserProvider';
|
||||
|
||||
const Navbar = () => {
|
||||
interface NavbarProps {
|
||||
settings?: any;
|
||||
}
|
||||
|
||||
const Navbar = ({ settings }: NavbarProps) => {
|
||||
const { user } = useUser();
|
||||
const [isOpen, setIsOpen] = useState(false);
|
||||
const pathname = usePathname() || '';
|
||||
|
||||
const siteName = settings?.siteName || "Afrohub";
|
||||
|
||||
// Hide Navbar on Dashboard to prevent double navigation, show only on public pages
|
||||
if (pathname.startsWith('/dashboard')) return null;
|
||||
|
||||
@@ -20,8 +26,10 @@ const Navbar = () => {
|
||||
<div className="flex justify-between h-16">
|
||||
<div className="flex items-center">
|
||||
<Link href="/" className="flex-shrink-0 flex items-center gap-2">
|
||||
<div className="w-8 h-8 bg-brand-600 rounded-lg flex items-center justify-center text-white font-bold font-serif">A</div>
|
||||
<span className="font-serif font-bold text-xl text-gray-900">Afrohub</span>
|
||||
<div className="w-8 h-8 bg-brand-600 rounded-lg flex items-center justify-center text-white font-bold font-serif">
|
||||
{siteName.charAt(0)}
|
||||
</div>
|
||||
<span className="font-serif font-bold text-xl text-gray-900">{siteName}</span>
|
||||
</Link>
|
||||
<div className="hidden sm:ml-8 sm:flex sm:space-x-8">
|
||||
<Link href="/" className={`inline-flex items-center px-1 pt-1 border-b-2 text-sm font-medium ${pathname === '/' ? 'border-brand-500 text-gray-900' : 'border-transparent text-gray-500 hover:text-gray-700 hover:border-gray-300'}`}>
|
||||
|
||||
@@ -6,6 +6,7 @@ import { useRouter, usePathname } from 'next/navigation';
|
||||
|
||||
interface UserContextType {
|
||||
user: User | null;
|
||||
settings: any | null;
|
||||
login: (user: User) => void;
|
||||
logout: () => void;
|
||||
}
|
||||
@@ -14,6 +15,7 @@ const UserContext = createContext<UserContextType | undefined>(undefined);
|
||||
|
||||
export function UserProvider({ children }: { children: ReactNode }) {
|
||||
const [user, setUser] = useState<User | null>(null);
|
||||
const [settings, setSettings] = useState<any>(null);
|
||||
const router = useRouter();
|
||||
const pathname = usePathname();
|
||||
|
||||
@@ -22,6 +24,12 @@ export function UserProvider({ children }: { children: ReactNode }) {
|
||||
if (savedUser) {
|
||||
setUser(JSON.parse(savedUser));
|
||||
}
|
||||
|
||||
// Fetch settings
|
||||
fetch('/api/settings')
|
||||
.then(res => res.json())
|
||||
.then(data => setSettings(data))
|
||||
.catch(err => console.error('Error fetching settings:', err));
|
||||
}, []);
|
||||
|
||||
// Global suspension check
|
||||
@@ -42,7 +50,7 @@ export function UserProvider({ children }: { children: ReactNode }) {
|
||||
};
|
||||
|
||||
return (
|
||||
<UserContext.Provider value={{ user, login, logout }}>
|
||||
<UserContext.Provider value={{ user, settings, login, logout }}>
|
||||
{children}
|
||||
</UserContext.Provider>
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user