- Fixed theme selector dropdown positioning issues with proper Tailwind classes - Replaced custom CSS classes with standard Tailwind utilities for v4 compatibility - Implemented dynamic theme-aware styling system for dropdown appearance - Added theme-specific styling for Light, Dark, High Contrast Light, High Contrast Dark - Enhanced UX with professional dropdown interface and visual consistency - Maintained 100% theme compliance and accessibility standards - Updated memory bank documentation with Phase 7 completion Key improvements: - Professional dropdown with proper shadows, borders, and backgrounds - Dynamic styling that adapts to current website theme - Enhanced accessibility with ARIA support and keyboard navigation - Seamless visual integration across all theme modes - Production-ready theme selector with polished UI
265 lines
9.1 KiB
TypeScript
265 lines
9.1 KiB
TypeScript
'use client';
|
|
|
|
import React, { useState, useEffect } from 'react';
|
|
import { useParams, useRouter } from 'next/navigation';
|
|
import Link from 'next/link';
|
|
import { useEvents } from '../../../../hooks/useEvents';
|
|
|
|
export default function EventDetailsPage() {
|
|
const params = useParams();
|
|
const router = useRouter();
|
|
const eventId = params.id as string;
|
|
|
|
const { getEventById, deleteEvent } = useEvents();
|
|
|
|
const [event, setEvent] = useState<any>(null);
|
|
const [isLoading, setIsLoading] = useState(true);
|
|
const [error, setError] = useState('');
|
|
const [isDeleting, setIsDeleting] = useState(false);
|
|
|
|
useEffect(() => {
|
|
const loadEvent = async () => {
|
|
setIsLoading(true);
|
|
setError('');
|
|
|
|
try {
|
|
const eventData = await getEventById(eventId);
|
|
|
|
if (eventData) {
|
|
setEvent(eventData);
|
|
} else {
|
|
setError('Event not found');
|
|
}
|
|
} catch (err) {
|
|
console.error('Error loading event:', err);
|
|
setError('Failed to load event data. Please try again.');
|
|
} finally {
|
|
setIsLoading(false);
|
|
}
|
|
};
|
|
|
|
loadEvent();
|
|
}, [eventId, getEventById]);
|
|
|
|
const handleDelete = async () => {
|
|
if (!window.confirm('Are you sure you want to delete this event?')) {
|
|
return;
|
|
}
|
|
|
|
setIsDeleting(true);
|
|
|
|
try {
|
|
const success = await deleteEvent(eventId);
|
|
|
|
if (success) {
|
|
router.push('/admin/events');
|
|
} else {
|
|
setError('Failed to delete event. Please try again.');
|
|
}
|
|
} catch (err) {
|
|
console.error('Error deleting event:', err);
|
|
setError('An unexpected error occurred. Please try again later.');
|
|
} finally {
|
|
setIsDeleting(false);
|
|
}
|
|
};
|
|
|
|
if (isLoading) {
|
|
return (
|
|
<div className="container mx-auto px-4 py-8">
|
|
<div className="flex justify-center items-center h-64">
|
|
<div className="animate-spin rounded-full h-12 w-12 border-b-2 border-primary"></div>
|
|
<span className="ml-3 text-lg admin-text">Loading event data...</span>
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|
|
|
|
if (error) {
|
|
return (
|
|
<div className="container mx-auto px-4 py-8">
|
|
<div className="admin-card" style={{backgroundColor: 'var(--admin-error)', color: 'white', border: '1px solid var(--admin-error)'}}>
|
|
<p className="font-medium">Error</p>
|
|
<p>{error}</p>
|
|
<div className="mt-4">
|
|
<Link href="/admin/events" className="admin-btn-primary">
|
|
Return to Events
|
|
</Link>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|
|
|
|
if (!event) {
|
|
return null;
|
|
}
|
|
|
|
// Format date for display
|
|
const formatDate = (dateString: string) => {
|
|
const date = new Date(dateString);
|
|
return date.toLocaleDateString('en-US', {
|
|
weekday: 'long',
|
|
month: 'long',
|
|
day: 'numeric',
|
|
year: 'numeric'
|
|
});
|
|
};
|
|
|
|
return (
|
|
<div className="container mx-auto px-4 py-8">
|
|
<div className="flex items-center justify-between mb-6">
|
|
<h1 className="admin-page-header-title">{event.title}</h1>
|
|
<div className="flex space-x-3">
|
|
<Link
|
|
href="/admin/events"
|
|
className="admin-btn-secondary"
|
|
>
|
|
Back to Events
|
|
</Link>
|
|
<Link
|
|
href={`/admin/events/${eventId}/edit`}
|
|
className="admin-btn-primary"
|
|
>
|
|
Edit Event
|
|
</Link>
|
|
</div>
|
|
</div>
|
|
|
|
<div className="admin-card mb-6">
|
|
<div className="grid grid-cols-1 md:grid-cols-3 gap-6">
|
|
<div className="md:col-span-2 space-y-6">
|
|
{/* Event details */}
|
|
<div>
|
|
<h2 className="text-xl font-semibold mb-4 admin-text">Event Details</h2>
|
|
|
|
<div className="prose dark:prose-invert max-w-none">
|
|
<p className="admin-text">{event.description}</p>
|
|
</div>
|
|
</div>
|
|
|
|
{/* Tags */}
|
|
{event.tags && event.tags.length > 0 && (
|
|
<div>
|
|
<h3 className="text-lg font-semibold mb-2 admin-text">Tags</h3>
|
|
<div className="flex flex-wrap gap-2">
|
|
{event.tags.map((tag: string, index: number) => (
|
|
<span
|
|
key={index}
|
|
className="admin-tag"
|
|
>
|
|
{tag}
|
|
</span>
|
|
))}
|
|
</div>
|
|
</div>
|
|
)}
|
|
|
|
{/* Recurring info */}
|
|
{event.recurring && (
|
|
<div>
|
|
<h3 className="text-lg font-semibold mb-2 admin-text">Recurrence</h3>
|
|
<p className="admin-text">{event.recurrencePattern}</p>
|
|
</div>
|
|
)}
|
|
</div>
|
|
|
|
<div className="space-y-6">
|
|
{/* Event metadata */}
|
|
<div className="admin-info-panel">
|
|
<h3 className="text-lg font-semibold mb-4 admin-text">Event Information</h3>
|
|
|
|
<div className="space-y-3">
|
|
<div>
|
|
<p className="text-sm admin-text-light">Date</p>
|
|
<p className="font-medium admin-text">{formatDate(event.date)}</p>
|
|
</div>
|
|
|
|
<div>
|
|
<p className="text-sm admin-text-light">Time</p>
|
|
<p className="font-medium admin-text">{event.time}</p>
|
|
</div>
|
|
|
|
<div>
|
|
<p className="text-sm admin-text-light">Location</p>
|
|
<p className="font-medium admin-text">{event.location}</p>
|
|
</div>
|
|
|
|
<div>
|
|
<p className="text-sm admin-text-light">Category</p>
|
|
<p className="font-medium capitalize admin-text">{event.category}</p>
|
|
</div>
|
|
|
|
<div>
|
|
<p className="text-sm admin-text-light">Status</p>
|
|
<span className={`inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium ${
|
|
event.status === 'published' ? 'admin-status-published' :
|
|
event.status === 'draft' ? 'admin-status-draft' :
|
|
'admin-status-archived'
|
|
}`}>
|
|
{event.status.charAt(0).toUpperCase() + event.status.slice(1)}
|
|
</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
{/* Registration info */}
|
|
<div className="admin-info-panel">
|
|
<h3 className="text-lg font-semibold mb-4 admin-text">Registration</h3>
|
|
|
|
{event.registrationRequired ? (
|
|
<div className="space-y-3">
|
|
<p className="admin-text">Registration is required for this event.</p>
|
|
|
|
<div>
|
|
<p className="text-sm admin-text-light">Current Registrations</p>
|
|
<p className="font-medium admin-text">{event.registeredCount}</p>
|
|
</div>
|
|
|
|
{event.maxAttendees && (
|
|
<div>
|
|
<p className="text-sm admin-text-light">Maximum Attendees</p>
|
|
<p className="font-medium admin-text">{event.maxAttendees}</p>
|
|
</div>
|
|
)}
|
|
|
|
{event.maxAttendees && (
|
|
<div className="mt-2">
|
|
<div className="w-full admin-progress-bg rounded-full h-2.5">
|
|
<div
|
|
className="bg-primary h-2.5 rounded-full"
|
|
style={{ width: `${Math.min(100, (event.registeredCount / event.maxAttendees) * 100)}%` }}
|
|
></div>
|
|
</div>
|
|
<p className="text-sm admin-text-light mt-1">
|
|
{event.registeredCount} of {event.maxAttendees} spots filled
|
|
({Math.round((event.registeredCount / event.maxAttendees) * 100)}%)
|
|
</p>
|
|
</div>
|
|
)}
|
|
</div>
|
|
) : (
|
|
<p className="admin-text">Registration is not required for this event.</p>
|
|
)}
|
|
</div>
|
|
|
|
{/* Danger zone */}
|
|
<div className="admin-danger-zone">
|
|
<h3 className="text-lg font-semibold admin-danger-text mb-4">Danger Zone</h3>
|
|
<p className="text-sm admin-text-light mb-4">
|
|
Deleting this event will permanently remove it and cannot be undone.
|
|
</p>
|
|
<button
|
|
onClick={handleDelete}
|
|
disabled={isDeleting}
|
|
className="w-full bg-red-600 hover:bg-red-700 text-white py-2 px-4 rounded focus:outline-none focus:ring-2 focus:ring-red-500 focus:ring-offset-2 disabled:opacity-50"
|
|
>
|
|
{isDeleting ? 'Deleting...' : 'Delete Event'}
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|