diff --git a/src/components/CommunityPortal/Activities/Register/EventDescription.jsx b/src/components/CommunityPortal/Activities/Register/EventDescription.jsx index 7020dae976..97c944487b 100644 --- a/src/components/CommunityPortal/Activities/Register/EventDescription.jsx +++ b/src/components/CommunityPortal/Activities/Register/EventDescription.jsx @@ -1,57 +1,3 @@ -// import React, { useState } from 'react'; - -// function DescriptionSection() { -// // State to manage active tab -// const [activeTab, setActiveTab] = useState('Description'); - -// // Mock data for the Description section -// const descriptionData = [ -// 'Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.', -// 'Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.', -// 'Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur.', -// ]; - -// return ( -//
-// {/* Header Tabs */} -//
-// -// -// -// -//
- -// {/* Tab Content */} -// {activeTab === 'Description' && ( -//
-// {descriptionData.map((item, index) => ( -//

-// {item} -//

-// ))} -//
-// )} -//
-// ); -// } - -// export default DescriptionSection; - import { useState, useMemo } from 'react'; import { useSelector } from 'react-redux'; import styles from './DescriptionSection.module.css'; @@ -103,8 +49,8 @@ function DescriptionSection({ activity, registrants = [] }) { const participantEntries = useMemo(() => { const baseParticipants = - Array.isArray(activity?.participants) && activity.participants.length - ? activity.participants + Array.isArray(activity?.resources) && activity.resources.length + ? activity.resources : DEFAULT_TAB_CONTENT.Participates; const dynamicEntries = registrants @@ -144,7 +90,7 @@ function DescriptionSection({ activity, registrants = [] }) { return true; }) .map(({ label, isNew }) => ({ label, isNew })); - }, [activity?.participants, registrants]); + }, [activity?.resources, registrants]); const commentEntries = useMemo(() => { if (Array.isArray(activity?.comments) && activity.comments.length) { diff --git a/src/components/CommunityPortal/Activities/Register/Register.jsx b/src/components/CommunityPortal/Activities/Register/Register.jsx index 03392e163f..b156f40d55 100644 --- a/src/components/CommunityPortal/Activities/Register/Register.jsx +++ b/src/components/CommunityPortal/Activities/Register/Register.jsx @@ -1,27 +1,30 @@ import { useState, useEffect, useRef, useMemo } from 'react'; import { useParams } from 'react-router-dom'; import { useSelector } from 'react-redux'; -// import Calendar from 'react-calendar'; -// import 'react-calendar/dist/Calendar.css'; import styles from './Register.module.css'; -import EventDescription from './EventDescription'; +// import EventDescription from './EventDescription'; +import axios from 'axios'; +import { ENDPOINTS } from '../../../../utils/URL'; function Register() { const { activityId } = useParams(); const darkMode = useSelector(state => state.theme?.darkMode); const userProfile = useSelector(state => state.userProfile); const authUser = useSelector(state => state.auth?.user); + const authProfilePic = useSelector(state => state.auth?.profilePic); const [activity, setActivity] = useState(null); const [loading, setLoading] = useState(true); const [error, setError] = useState(null); // eslint-disable-next-line no-unused-vars - const [selectedDate, setSelectedDate] = useState(new Date()); + // const [selectedDate, setSelectedDate] = useState(new Date()); + const [activityDate, setActivityDate] = useState(''); + const [activityStartTime, setActivityStartTime] = useState(''); + const [activityEndTime, setActivityEndTime] = useState(''); const [feedbackMessage, setFeedbackMessage] = useState(null); const [availability, setAvailability] = useState(0); const [isRegistering, setIsRegistering] = useState(false); - const [registrants, setRegistrants] = useState([]); + const [isAlreadyRegistered, setIsAlreadyRegistered] = useState(false); const feedbackTimeoutRef = useRef(null); - const storageKey = useMemo(() => `activity-${activityId}-registrants`, [activityId]); const tokenPayload = useMemo(() => { if (typeof window === 'undefined' || typeof window.atob !== 'function') { return null; @@ -51,125 +54,12 @@ function Register() { }, []); useEffect(() => { - if (typeof window === 'undefined') return; - try { - const stored = window.localStorage.getItem(storageKey); - if (!stored) { - setRegistrants([]); - return; - } - const parsed = JSON.parse(stored); - if (Array.isArray(parsed)) { - setRegistrants(parsed); - } else { - setRegistrants([]); - } - } catch (err) { - // eslint-disable-next-line no-console - console.error('Failed to load saved registrants', err); - setRegistrants([]); - } - }, [storageKey]); - - useEffect(() => { - if (typeof window === 'undefined') return; - try { - window.localStorage.setItem(storageKey, JSON.stringify(registrants)); - } catch (err) { - // eslint-disable-next-line no-console - console.error('Failed to persist registrants', err); - } - }, [registrants, storageKey]); - - useEffect(() => { - // Simulate API call with loading and error handling const fetchActivity = async () => { try { setLoading(true); setError(null); - - // Simulate network delay - await new Promise(resolve => setTimeout(resolve, 1000)); - - const fetchedActivities = [ - { - id: 1, - name: 'Yoga Class', - rating: 4, - type: 'Fitness', - date: '03-10-2025', - time: '10:00 AM', - organizer: 'Alex Brian', - location: 'Community Center', - capacity: 10, - image: 'https://cdn.pixabay.com/photo/2024/06/21/07/46/yoga-8843808_1280.jpg', - description: 'A relaxing yoga session to improve flexibility and mindfulness.', - faqs: [ - { question: 'What should I bring?', answer: 'A yoga mat and a water bottle.' }, - { - question: 'Is it beginner-friendly?', - answer: 'Yes, it is suitable for all levels.', - }, - ], - participants: ['John Doe', 'Jane Smith', 'Alice Brown'], - comments: ['Looking forward to this!', 'This will be my first yoga session!'], - }, - { - id: 2, - name: 'Book Club', - rating: 5, - type: 'Social', - date: '03-15-2025', - time: '5:00 PM', - organizer: 'Bob', - location: 'Library', - capacity: 5, - image: 'https://cdn.pixabay.com/photo/2019/01/30/08/30/book-3964050_1280.jpg', - description: 'A book club discussion on the latest bestsellers.', - faqs: [ - { - question: 'Do I need to read the book beforehand?', - answer: "Yes, it's recommended.", - }, - { - question: 'Are snacks provided?', - answer: 'Yes, light refreshments will be available.', - }, - ], - participants: ['Emily White', 'Michael Green'], - comments: ['Excited to discuss my favorite book!', 'What book are we reading?'], - }, - ]; - - const selectedActivity = fetchedActivities.find( - fetchedActivity => fetchedActivity.id === parseInt(activityId, 10), - ); - - if (selectedActivity) { - setActivity(selectedActivity); - let savedCount = 0; - if (typeof window !== 'undefined') { - try { - const existing = window.localStorage.getItem(storageKey); - if (existing) { - const parsed = JSON.parse(existing); - if (Array.isArray(parsed)) { - savedCount = parsed.length; - } - } - } catch (storageErr) { - // eslint-disable-next-line no-console - console.error('Failed to read saved registrants during fetch', storageErr); - } - } - const baseCount = Array.isArray(selectedActivity.participants) - ? selectedActivity.participants.length - : 0; - const remaining = selectedActivity.capacity - baseCount - savedCount; - setAvailability(remaining > 0 ? remaining : 0); - } else { - setError('Activity not found'); - } + const response = await axios.get(ENDPOINTS.EVENTS_BY_ID(activityId)); + setActivity(response.data || []); } catch (err) { setError('Failed to load activity details. Please try again.'); } finally { @@ -182,10 +72,40 @@ function Register() { useEffect(() => { if (!activity) return; - const baseCount = Array.isArray(activity.participants) ? activity.participants.length : 0; - const remaining = activity.capacity - baseCount - registrants.length; + const eventDateTime = new Date(activity.startTime); + const eventDate = new Date( + new Intl.DateTimeFormat('en-US', { + timeZone: Intl.DateTimeFormat().resolvedOptions().timeZone, + year: 'numeric', + month: '2-digit', + day: '2-digit', + }).format(eventDateTime), + ); + setActivityDate(eventDate.toLocaleDateString()); + + const timeString = new Intl.DateTimeFormat('en-US', { + hour: '2-digit', + minute: '2-digit', + hour12: true, + timeZone: Intl.DateTimeFormat().resolvedOptions().timeZone, + }).format(new Date(eventDateTime)); + setActivityStartTime(timeString); + + const endTimeString = new Intl.DateTimeFormat('en-US', { + hour: '2-digit', + minute: '2-digit', + hour12: true, + timeZone: Intl.DateTimeFormat().resolvedOptions().timeZone, + }).format(new Date(activity.endTime)); + setActivityEndTime(endTimeString); + }, [activity]); + + useEffect(() => { + if (!activity) return; + const baseCount = activity.currentAttendees; + const remaining = activity.maxAttendees - baseCount; setAvailability(remaining > 0 ? remaining : 0); - }, [activity, registrants]); + }, [activity]); const resolveUserId = () => authUser?.userid || authUser?._id || @@ -256,15 +176,22 @@ function Register() { setFeedbackMessage(null); }, 5000); }; - const isAlreadyRegistered = useMemo(() => { - if (!registrants.length) return false; + + useEffect(() => { + if (!activity?.resources?.length) { + setIsAlreadyRegistered(false); + return; + } const userId = resolveUserId(); const name = resolveUserName().toLowerCase(); - return registrants.some(reg => { - if (userId) return reg.userId === userId; - return reg.name.toLowerCase() === name; - }); - }, [registrants, authUser, userProfile, tokenPayload]); + setIsAlreadyRegistered( + activity.resources.some(person => { + // if (userId) return person._id === userId; + return person.name?.toLowerCase() === name; + }), + ); + }, [activity, authUser, userProfile, tokenPayload]); + const handleRegister = async () => { if (!activity) return; if (availability === 0) { @@ -277,7 +204,6 @@ function Register() { const userId = resolveUserId(); const displayName = resolveUserName(); - const jobTitle = resolveJobTitle(); if (isAlreadyRegistered) { setFeedbackMessage({ type: 'error', text: 'You are already registered for this event.' }); @@ -285,27 +211,54 @@ function Register() { return; } + if (!userId) { + setFeedbackMessage({ + type: 'error', + text: + 'Unable to register: user identity could not be resolved. Please log in and try again.', + }); + scheduleFeedbackClear(); + return; + } + setIsRegistering(true); try { - // Simulate API call delay - await new Promise(resolve => setTimeout(resolve, 1500)); - - setRegistrants(prev => [ - ...prev, - { - userId: userId || `guest-${Date.now()}`, - name: displayName, - jobTitle, - registeredAt: new Date().toISOString(), - }, - ]); - - setFeedbackMessage({ - type: 'success', - text: 'Registration successful! See you at the event.', + const response = await axios.post(ENDPOINTS.REGISTER_FOR_EVENT(activity._id), { + userId: userId, + name: displayName, + profilePic: null, + location: activity.location || 'TBD', }); - scheduleFeedbackClear(); + + if (response.status === 200) { + const updatedActivity = response.data?.activity || response.data; + if (updatedActivity?.currentAttendees !== undefined) { + setActivity(updatedActivity); + } else { + setActivity(prev => ({ + ...prev, + currentAttendees: prev.currentAttendees + 1, + resources: [ + ...(prev.resources || []), + { + _id: userId, + name: displayName, + profilePic: '', + location: activity.location || 'Virtual', + }, + ], + })); + } + + setIsAlreadyRegistered(true); + setFeedbackMessage({ + type: 'success', + text: 'Registration successful! See you at the event.', + }); + + scheduleFeedbackClear(); + } } catch (err) { setFeedbackMessage({ type: 'error', @@ -317,13 +270,6 @@ function Register() { } }; - const handleRetry = () => { - setError(null); - setLoading(true); - // Re-fetch activity - window.location.reload(); - }; - // Loading state if (loading) { return ( @@ -342,28 +288,8 @@ function Register() { ); } - // Error state - if (error) { - return ( -
-
-
-
⚠️
-

{error}

- -
-
-
- ); - } - // No activity found - if (!activity) { + if (error || !activity) { return (
@@ -380,140 +306,128 @@ function Register() { return (
-
- {/* Left Column: Image + Register Button */} -
- {activity.name} - - {feedbackMessage && ( -
- {feedbackMessage.text} +
+
+
+ {activity.title} +
+ +
+
+ {activity.type} + {activity.status}
- )} -
- {/* Middle Column: Event Details */} -
-

- {activity.name} -

-
-
-

- Date: {activity.date} -

-

- Time: {activity.time} -

-

- Organizer: {activity.organizer || 'Not Available'} -

+

{activity.title}

+ +
+
+ Date: {activityDate} +
+
+ Time: {activityStartTime} - {activityEndTime} +
+
+ Location: {activity.location} +
+
+ Organizer: {activity.organizer || 'Organizer not specified'} +
+
+ +
+ + +
+ {availability} spots left +
+
+ + {feedbackMessage && ( +
+ {feedbackMessage.text} +
+ )} +
+
+ +
+
+
+

About this event

+

{activity.description}

+
+ +
+

Event details

+
+

+ Type: {activity.type} +

+

+ Location: {activity.location} +

+

+ Status: {activity.status} +

+

+ Active: {activity.isActive ? 'Yes' : 'No'} +

+

+ Organizer: {activity.organizer || 'Organizer not specified'} +

+
-
+ +
+

Attendees

+
+ {activity.resources?.map(person => ( +
+
+

{person.name}

+

{person.location}

+
+
+ ))} +
+
+
+ +
-
- - {/* Right Column: Calendar */} -
- {/* */} -

- Selected Date: {selectedDate.toDateString()} -

-
-
- - {/* Description Section */} -
- + +
diff --git a/src/components/CommunityPortal/Activities/Register/Register.module.css b/src/components/CommunityPortal/Activities/Register/Register.module.css index ca22e0e4f8..c57c804d92 100644 --- a/src/components/CommunityPortal/Activities/Register/Register.module.css +++ b/src/components/CommunityPortal/Activities/Register/Register.module.css @@ -3,14 +3,14 @@ width: 100%; min-height: 100vh; padding: 40px 0; - background-color: #ffffff; + background-color: #fff; box-sizing: border-box; transition: background-color 0.3s ease, color 0.3s ease; } .mainContainerDark { background-color: #0f172a; - color: #ffffff; + color: #fff; } .contentWrapper { @@ -21,503 +21,627 @@ } .contentWrapperDark { - color: #ffffff; + color: #fff; } -/* Register container */ -.registerContainer { +/* Loading state */ +.loadingContainer { display: flex; - justify-content: space-between; - align-items: flex-start; - padding: 20px; + justify-content: center; + align-items: center; + min-height: 200px; + flex-direction: column; gap: 20px; - margin-bottom: 20px; - background-color: #ffffff; - border-radius: 12px; - box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1); - color: #1f2937; } -.registerContainerDark { - background-color: rgba(30, 41, 59, 0.8); - box-shadow: 0 4px 16px rgba(15, 23, 42, 0.45); - color: #f8fafc; - border: 1px solid rgba(148, 163, 184, 0.25); +.loadingSpinner { + width: 40px; + height: 40px; + border: 4px solid #f3f3f3; + border-top: 4px solid #007bff; + border-radius: 50%; + animation: spin 1s linear infinite; +} + +.loadingSpinnerDark { + border-color: #495057; + border-top-color: #0d6efd; +} + +.loadingText { + font-size: 16px; + color: #666; + font-weight: 500; } -/* Left column */ -.leftColumn { - flex: 1; +.loadingTextDark { + color: #adb5bd; +} + +/* Error state */ +.errorContainer { display: flex; - flex-direction: column; + justify-content: center; align-items: center; - gap: 15px; + min-height: 200px; + flex-direction: column; + gap: 20px; + text-align: center; } -.eventImage { - width: 100%; - max-width: 450px; - height: 300px; - border-radius: 12px; - box-shadow: 0 4px 8px rgba(0, 0, 0, 0.2); - object-fit: cover; - margin: 10px auto; - display: block; +.errorIcon { + font-size: 48px; + color: #dc3545; } -.registerButton { - background-color: #007bff; - color: white; - padding: 12px 24px; - border: none; - border-radius: 8px; - cursor: pointer; - margin-top: 15px; - font-size: 16px; +.errorText { + font-size: 18px; + color: #dc3545; font-weight: 600; - transition: all 0.3s ease; - min-width: 120px; } -.registerButton:hover { - background-color: #0056b3; - transform: translateY(-2px); - box-shadow: 0 4px 12px rgba(0, 123, 255, 0.3); +.errorTextDark { + color: #ff6b6b; } -.registerButton:active { - transform: translateY(0); +.retryButton { + background-color: #007bff; + color: #fff; + padding: 10px 20px; + border: none; + border-radius: 6px; + cursor: pointer; + font-size: 14px; + font-weight: 500; + transition: background-color 0.3s ease; } -.registerButton:disabled { - background-color: #6c757d; - cursor: not-allowed; - transform: none; - box-shadow: none; +.retryButton:hover { + background-color: #0056b3; } -.registerButtonRegistering { +.retryButtonDark { background-color: #0d6efd; - cursor: wait; - position: relative; - overflow: hidden; } -.registerButtonRegistering::after { - content: ''; - position: absolute; - top: 0; - left: -100%; - width: 100%; - height: 100%; - background: linear-gradient(90deg, transparent, rgba(255, 255, 255, 0.2), transparent); - animation: shimmer 1.5s infinite; +.retryButtonDark:hover { + background-color: #0b5ed7; } -@keyframes shimmer { +/* Animations */ +@keyframes spin { 0% { - left: -100%; + transform: rotate(0deg); } + 100% { - left: 100%; + transform: rotate(360deg); } } -.registerButtonDark { - background-color: #0d6efd; +@keyframes slide-in { + from { + opacity: 0; + transform: translateY(-10px); + } + + to { + opacity: 1; + transform: translateY(0); + } } -.registerButtonDark:hover { - background-color: #0b5ed7; +/* Responsive design */ +@media (width <= 768px) { + .registerContainer { + flex-direction: column; + gap: 20px; + } + + .leftColumn, + .middleColumn, + .rightColumn { + flex: none; + width: 100%; + } + + .detailsRow { + flex-direction: column; + align-items: flex-start; + gap: 10px; + } + + .detailsRow p { + min-width: auto; + } + + .middleColumn h1 { + font-size: 1.5rem; + } } -.registerButtonDark:disabled { - background-color: #495057; +@media (width <= 480px) { + .mainContainer { + padding: 10px; + } + + .registerContainer { + padding: 15px; + } + + .eventImage { + height: 200px; + } + + .registerButton { + width: 100%; + padding: 15px; + } } -/* Middle column */ -.middleColumn { - flex: 2; - padding: 0 20px; +.eventPage { + width: 100%; + max-width: 1280px; + margin: 0 auto; + padding: 32px 20px 48px; + display: flex; + flex-direction: column; + gap: 32px; color: #1f2937; } -.middleColumnDark { - color: #f8fafc; +.eventPageDark { + color: #f3f4f6; } -.eventTitle { - margin-bottom: 20px; - font-size: 2rem; - font-weight: 700; - color: #1f2937; - transition: color 0.3s ease; +/* Hero */ +.heroSection { + display: grid; + grid-template-columns: minmax(140px, 420px) 1fr; + gap: 28px; + align-items: stretch; } -.eventTitleDark { - color: #ffffff; +.heroImageWrapper { + width: 100%; + min-height: 320px; + border-radius: 20px; + overflow: hidden; + background: transparent; + box-shadow: 0 10px 30px rgb(15 23 42 / 8%); } -.detailsGrid { +.heroImage { + width: 100%; + height: 100%; + min-height: 320px; + object-fit: cover; + display: block; +} + +.heroContent { + background: #fff; + border: 1px solid #e5e7eb; + border-radius: 20px; + padding: 28px; display: flex; flex-direction: column; gap: 20px; + box-shadow: 0 10px 30px rgb(15 23 42 / 6%); } -.detailsRow { +.eventPageDark .heroContent { + background: #111827; + border-color: #1f2937; +} + +.heroHeader { display: flex; - align-items: center; - gap: 20px; flex-wrap: wrap; + gap: 10px; } -.detailsRow p { - flex: 1; - min-width: 200px; - margin: 0; - font-size: 16px; - color: #555; +.typeBadge, +.statusBadge { + display: inline-flex; + align-items: center; + justify-content: center; + padding: 8px 14px; + border-radius: 999px; + font-size: 0.85rem; + font-weight: 700; + line-height: 1; } -.detailsRowDark p { - color: #e0e0e0; +.typeBadge { + background: #eff6ff; + color: #1d4ed8; + border: 1px solid #bfdbfe; } -.detailsRow p strong { - font-weight: 600; - color: #333; +.eventPageDark .typeBadge { + background: rgb(29 78 216 / 18%); + color: #93c5fd; + border-color: rgb(147 197 253 / 25%); } -.detailsRowDark p strong { - color: #ffffff; +.statusBadge { + background: #ecfdf5; + color: #047857; + border: 1px solid #a7f3d0; } -/* Availability styling */ -.availabilityText { - font-weight: 600; - color: #dc3545; - background-color: #fff5f5; - padding: 4px 8px; - border-radius: 6px; - border: 1px solid #fecaca; +.eventPageDark .statusBadge { + background: rgb(4 120 87 / 20%); + color: #6ee7b7; + border-color: rgb(110 231 183 / 22%); } -.availabilityTextDark { - color: #ff6b6b; - background-color: #2d1b1b; - border-color: #7f1d1d; +.eventTitle { + margin: 0; + font-size: clamp(2rem, 3vw, 2.8rem); + line-height: 1.1; + font-weight: 800; + letter-spacing: -0.02em; } -.availabilityContainer { - display: flex; - align-items: center; - gap: 8px; - flex-wrap: wrap; +.eventDescription { + margin: 0; + font-size: 1rem; + line-height: 1.7; + color: #4b5563; + overflow: hidden; + display: -webkit-box; + -webkit-line-clamp: 2; + line-clamp: 2; + -webkit-box-orient: vertical; } -.availabilityIcon { - font-size: 16px; +.eventPageDark .eventDescription { + color: #d1d5db; } -.availabilityLow { - color: #f59e0b; - background-color: #fffbeb; - border-color: #fde68a; +.metaGrid { + display: grid; + grid-template-columns: repeat(2, minmax(180px, 1fr)); + gap: 14px 20px; } -.availabilityLowDark { - color: #fbbf24; - background-color: #451a03; - border-color: #92400e; +.metaGrid > div { + padding: 14px 16px; + border-radius: 14px; + background: #f9fafb; + border: 1px solid #e5e7eb; + font-size: 0.95rem; + line-height: 1.5; } -.availabilityCritical { - color: #dc2626; - background-color: #fef2f2; - border-color: #fecaca; - animation: pulse 2s infinite; +.eventPageDark .metaGrid > div { + background: #0f172a; + border-color: #1f2937; } -.availabilityCriticalDark { - color: #ef4444; - background-color: #2d1b1b; - border-color: #7f1d1d; +.heroActions { + display: flex; + flex-wrap: wrap; + align-items: center; + justify-content: space-between; + gap: 16px; } -@keyframes pulse { - 0%, - 100% { - opacity: 1; - } - 50% { - opacity: 0.7; - } +.registerButton { + appearance: none; + border: none; + outline: none; + cursor: pointer; + border-radius: 14px; + padding: 14px 22px; + min-width: 160px; + background: #2563eb; + color: #fff; + font-size: 1rem; + font-weight: 700; + transition: + transform 0.18s ease, + box-shadow 0.18s ease, + background 0.18s ease, + opacity 0.18s ease; + box-shadow: 0 10px 20px rgb(37 99 235 / 22%); } -.statusText { - font-weight: 600; - color: #28a745; +.registerButton:disabled { + cursor: not-allowed; + opacity: 0.65; + box-shadow: none; + background-color: #6c757d; } -.statusTextDark { - color: #51cf66; +.registerButton:hover:not(:disabled) { + transform: translateY(-1px); + background: #1d4ed8; } -.statusTextFull { - color: #dc3545; +.registerButton:active:not(:disabled) { + transform: translateY(0); } -.statusTextFullDark { - color: #ff6b6b; +.registerButtonDark { + background: #3b82f6; } -/* Rating container */ -.ratingContainer { - display: flex; - align-items: center; - gap: 8px; - flex: 1; - min-width: 150px; +.registerButtonDark:hover:not(:disabled) { + background: #60a5fa; } -.starRating { +.quickStats { display: flex; - margin-left: 3px; + flex-wrap: wrap; + gap: 10px; } -.filledStar { - color: #ffc107; - font-size: 18px; - margin-right: 2px; +.quickStats span { + padding: 10px 14px; + border-radius: 999px; + background: #f3f4f6; + border: 1px solid #e5e7eb; + font-size: 0.9rem; + font-weight: 600; + color: #374151; } -.emptyStar { - color: #dee2e6; - font-size: 18px; - margin-right: 2px; +.eventPageDark .quickStats span { + background: #0f172a; + border-color: #1f2937; + color: #e5e7eb; +} + +.feedbackMessage { + border-radius: 14px; + padding: 14px 16px; + font-size: 0.95rem; + font-weight: 600; + border: 1px solid transparent; } -.emptyStarDark { - color: #6c757d; +.feedbackMessageSuccess { + background: #ecfdf5; + color: #065f46; + border-color: #a7f3d0; } -/* Right column */ -.rightColumn { - flex: 1; +.feedbackMessageError { + background: #fef2f2; + color: #991b1b; + border-color: #fecaca; +} + +/* Main layout */ +.contentSection { + display: grid; + grid-template-columns: minmax(0, 1fr) 320px; + gap: 24px; + align-items: start; +} + +.mainContent { display: flex; flex-direction: column; - align-items: center; + gap: 24px; } -.selectedDate { - margin-top: 10px; - font-size: 14px; - color: #333; - text-align: center; +.sidebar { + display: flex; + flex-direction: column; + gap: 20px; + position: sticky; + top: 24px; } -.selectedDateDark { - color: #e0e0e0; +.infoCard, +.sideCard { + background: #fff; + border: 1px solid #e5e7eb; + border-radius: 20px; + padding: 24px; + box-shadow: 0 10px 25px rgb(15 23 42 / 5%); } -/* Description section */ -.descriptionSection { - margin-top: 20px; - background-color: #ffffff; - border-radius: 12px; - box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1); - overflow: hidden; +.eventPageDark .infoCard, +.eventPageDark .sideCard { + background: #111827; + border-color: #1f2937; } -.descriptionSectionDark { - background-color: #2d2d2d; - box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3); +.infoCard h2, +.sideCard h3 { + margin: 0 0 16px; + font-size: 1.2rem; + font-weight: 800; + letter-spacing: -0.01em; } -/* Feedback messages */ -.feedbackMessage { - margin-top: 15px; - padding: 12px 16px; - border-radius: 8px; - font-size: 14px; - font-weight: 500; - text-align: center; - animation: slideIn 0.3s ease-out; +.infoCard p, +.sideCard p { + margin: 0; + line-height: 1.7; } -.feedbackMessageSuccess { - color: #155724; - background-color: #d4edda; - border: 1px solid #c3e6cb; +.detailsList { + display: grid; + grid-template-columns: repeat(2, minmax(180px, 1fr)); + gap: 14px 20px; } -.feedbackMessageSuccessDark { - color: #d1e7dd; - background-color: #0f5132; - border: 1px solid #0a4128; +.detailsList p { + padding: 14px 16px; + border-radius: 14px; + background: #f9fafb; + border: 1px solid #e5e7eb; } -.feedbackMessageError { - color: #721c24; - background-color: #f8d7da; - border: 1px solid #f5c6cb; +.eventPageDark .detailsList p { + background: #0f172a; + border-color: #1f2937; } -.feedbackMessageErrorDark { - color: #f8d7da; - background-color: #842029; - border: 1px solid #6a1a21; +/* Resources */ +.resourceList { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(220px, 1fr)); + gap: 16px; } -/* Loading state */ -.loadingContainer { +.resourceCard { display: flex; - justify-content: center; align-items: center; - min-height: 200px; - flex-direction: column; - gap: 20px; + gap: 14px; + padding: 16px; + border-radius: 16px; + background: #f9fafb; + border: 1px solid #e5e7eb; } -.loadingSpinner { - width: 40px; - height: 40px; - border: 4px solid #f3f3f3; - border-top: 4px solid #007bff; +.eventPageDark .resourceCard { + background: #0f172a; + border-color: #1f2937; +} + +.resourceAvatar { + width: 52px; + height: 52px; border-radius: 50%; - animation: spin 1s linear infinite; + object-fit: cover; + flex-shrink: 0; + background: #d1d5db; } -.loadingSpinnerDark { - border-color: #495057; - border-top-color: #0d6efd; +.resourceName { + margin: 0 0 4px; + font-size: 0.98rem; + font-weight: 700; } -.loadingText { - font-size: 16px; - color: #666; - font-weight: 500; +.resourceLocation { + margin: 0; + font-size: 0.9rem; + color: #6b7280; } -.loadingTextDark { - color: #adb5bd; +.eventPageDark .resourceLocation { + color: #9ca3af; } -/* Error state */ -.errorContainer { +/* Rating */ +.starRating { display: flex; - justify-content: center; - align-items: center; - min-height: 200px; - flex-direction: column; - gap: 20px; - text-align: center; + gap: 4px; + font-size: 1.4rem; + line-height: 1; } -.errorIcon { - font-size: 48px; - color: #dc3545; +.filledStar { + color: #f59e0b; } -.errorText { - font-size: 18px; - color: #dc3545; - font-weight: 600; +.emptyStar { + color: #d1d5db; } -.errorTextDark { - color: #ff6b6b; +.eventPageDark .emptyStar { + color: #4b5563; } -.retryButton { - background-color: #007bff; - color: white; - padding: 10px 20px; - border: none; - border-radius: 6px; - cursor: pointer; - font-size: 14px; - font-weight: 500; - transition: background-color 0.3s ease; +/* Availability / status helper styles if you want to reuse them */ +.availabilityBar { + width: 100%; + height: 10px; + border-radius: 999px; + background: #e5e7eb; + overflow: hidden; + margin-top: 10px; } -.retryButton:hover { - background-color: #0056b3; +.eventPageDark .availabilityBar { + background: #1f2937; } -.retryButtonDark { - background-color: #0d6efd; +.availabilityFill { + height: 100%; + border-radius: inherit; + background: linear-gradient(90deg, #2563eb, #60a5fa); } -.retryButtonDark:hover { - background-color: #0b5ed7; +.statusOpen { + color: #047857; } -/* Animations */ -@keyframes spin { - 0% { - transform: rotate(0deg); - } - 100% { - transform: rotate(360deg); - } +.statusLow { + color: #b45309; } -@keyframes slideIn { - from { - opacity: 0; - transform: translateY(-10px); - } - to { - opacity: 1; - transform: translateY(0); - } +.statusFull { + color: #b91c1c; } -/* Responsive design */ -@media (max-width: 768px) { - .registerContainer { - flex-direction: column; - gap: 20px; +/* Responsive */ +@media (width <= 1100px) { + .contentSection { + grid-template-columns: 1fr; } - .leftColumn, - .middleColumn, - .rightColumn { - flex: none; - width: 100%; + .sidebar { + position: static; } +} - .detailsRow { - flex-direction: column; - align-items: flex-start; - gap: 10px; +@media (width <= 860px) { + .heroSection { + grid-template-columns: 1fr; } - .detailsRow p { - min-width: auto; + .heroImageWrapper, + .heroImage { + min-height: 260px; } - .middleColumn h1 { - font-size: 1.5rem; + .metaGrid, + .detailsList { + grid-template-columns: 1fr; } } -@media (max-width: 480px) { - .mainContainer { - padding: 10px; +@media (width <= 640px) { + .eventPage { + padding: 20px 14px 36px; + gap: 24px; } - .registerContainer { - padding: 15px; + .heroContent, + .infoCard, + .sideCard { + padding: 18px; + border-radius: 16px; } - .eventImage { - height: 200px; + .heroActions { + align-items: stretch; } .registerButton { width: 100%; - padding: 15px; } -} + + .quickStats { + width: 100%; + } + + .quickStats span { + width: 100%; + justify-content: center; + text-align: center; + } + + .resourceList { + grid-template-columns: 1fr; + } +} \ No newline at end of file diff --git a/src/components/CommunityPortal/CPDashboard.jsx b/src/components/CommunityPortal/CPDashboard.jsx index 14a633f464..475c74f8a0 100644 --- a/src/components/CommunityPortal/CPDashboard.jsx +++ b/src/components/CommunityPortal/CPDashboard.jsx @@ -17,6 +17,7 @@ import { format } from 'date-fns'; import { getUserTimezone, formatEventTimeWithTimezone } from '../../utils/timezoneUtils'; import styles from './CPDashboard.module.css'; import { ENDPOINTS } from '../../utils/URL'; +import { Link } from 'react-router-dom'; import DatePicker from 'react-datepicker'; import 'react-datepicker/dist/react-datepicker.css'; import { toast } from 'react-toastify'; @@ -38,9 +39,7 @@ const FixedRatioImage = ({ src, alt, fallback }) => ( alt={alt} loading="lazy" onError={e => { - if (e.currentTarget.src !== fallback) { - e.currentTarget.src = fallback; - } + if (e.currentTarget.src !== fallback) e.currentTarget.src = fallback; }} style={{ width: '100%', @@ -134,7 +133,6 @@ export function CPDashboard() { total: response.data.events?.length || 0, })); } catch (err) { - console.error('Failed to load events:', err); setError('Failed to load events'); } finally { setIsLoading(false); @@ -192,7 +190,7 @@ export function CPDashboard() { // Format: "Saturday, February 15" return format(date, 'EEEE, MMMM d'); } catch (err) { - console.error('Error formatting date:', err); + setError('Error formatting date.'); return 'Date TBD'; } }; @@ -203,7 +201,7 @@ export function CPDashboard() { const userTimezone = getUserTimezone(); return formatEventTimeWithTimezone(eventDate, timeStr, userTimezone); } catch (err) { - console.error('Error formatting time:', err); + setError('Error formatting time.'); return 'Time TBD'; } }; @@ -231,7 +229,7 @@ export function CPDashboard() { return `${year}-${month}-${day}`; } } catch (err) { - console.error('Error parsing event date:', err); + setError('Error parsing event date.'); } return null; }; @@ -274,24 +272,8 @@ export function CPDashboard() { pagination.currentPage * pagination.limit, ); - const isFiltered = Boolean(searchQuery); - const totalFilteredCount = filteredEvents.length; - - let eventCountText = 'Showing all events'; - - if (isFiltered) { - if (totalFilteredCount > 0) { - eventCountText = `Showing ${totalFilteredCount} event${totalFilteredCount !== 1 ? 's' : ''}`; - } else { - eventCountText = 'No events found'; - } - } - const goToPage = newPage => { - if (newPage < 1 || newPage > totalPages) { - return; - } - + if (newPage < 1 || newPage > totalPages) return; setPagination(prev => ({ ...prev, currentPage: newPage })); }; @@ -316,40 +298,49 @@ export function CPDashboard() { if (displayedEvents.length > 0) { eventsContent = displayedEvents.map(event => ( - -
- -
- -
{event.title}
-
- -
-
{formatDate(event.date)}
- {event.startTime && ( -
{formatTime(event.date, event.startTime)}
- )} -
+ + +
+
-

- {getDisplayLocation(event.location)} -

-

- {event.organizerLogo && !failedLogos.has(event._id) ? ( - {normalizeOrganizer(event.organizer) handleLogoError(event._id)} - loading="lazy" - /> - ) : ( -

- -
+ +
{event.title}
+
+ +
+
{formatDate(event.date)}
+ {event.startTime && ( +
+ {formatTime(event.date, event.startTime)} +
+ )} +
+
+

+ {getDisplayLocation(event.location)} +

+

+ {event.organizerLogo && !failedLogos.has(event._id) ? ( + {normalizeOrganizer(event.organizer) handleLogoError(event._id)} + loading="lazy" + /> + ) : ( +

+
+ + )); } else { @@ -413,8 +404,7 @@ export function CPDashboard() {
-

Search Filters

- +

Search Filters

Dates
@@ -432,7 +422,6 @@ export function CPDashboard() { Tomorrow - - +
- +
- +
@@ -518,21 +507,8 @@ export function CPDashboard() {
- -
-

Events

- -
- -

- {isFiltered - ? totalFilteredCount > 0 - ? `Showing ${totalFilteredCount} event${totalFilteredCount !== 1 ? 's' : ''}` - : 'No events found' - : 'Showing all events'} -

+ +

Events

{eventsContent} diff --git a/src/components/CommunityPortal/CPDashboard.module.css b/src/components/CommunityPortal/CPDashboard.module.css index adea778861..ef7fb966f7 100644 --- a/src/components/CommunityPortal/CPDashboard.module.css +++ b/src/components/CommunityPortal/CPDashboard.module.css @@ -1,10 +1,26 @@ +:global(body) { + font-family: Poppins, sans-serif; + background: #fff; + margin: 0; + padding: 0; +} + .dashboardContainer { padding: 40px 20px; max-width: 1400px; margin: 0 auto; - background: #ffffff; + background: #fff; border-radius: 16px; - box-shadow: 0 6px 20px rgba(0, 0, 0, 0.1); + box-shadow: 0 6px 20px rgb(0 0 0 / 10%); + font-size: 16px; + line-height: 1.6; + letter-spacing: 0.2px; + width: 100%; +} + +.darkContainer { + background: #2b3e59 !important; + color: #fff; } .dashboardHeader { @@ -12,12 +28,13 @@ justify-content: space-between; align-items: center; margin-bottom: 30px; - padding: 20px; - background: linear-gradient(120deg, #ffffff, #f8f9fa); + padding: 16px 24px; + background: linear-gradient(120deg, #fff, #f8f9fa); border-radius: 12px; - box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1); + box-shadow: 0 3px 10px rgb(0 0 0 / 10%); } + .dashboardHeader h1 { font-size: 2.5rem; font-weight: bold; @@ -29,6 +46,7 @@ background: #1b2a41; } +/* clear X button */ .dashboardClearBtn { position: absolute; right: 35px; @@ -64,20 +82,33 @@ border-radius: 999px; overflow: hidden; border: 2px solid #1b3c55; - background: #ffffff; + background: #fff; +} + +.dashboardSearchContainer:focus, +.dashboardSearchContainer:focus-visible { + outline: none; +} + +.dashboardSearchContainer:focus-within { + border-color: #4da3ff; + box-shadow: none; } .darkHeader .dashboardSearchContainer { - border-color: #ffffff; + border-color: #fff; background: #1b2a41; } -.dashboardSearchInput { - width: 100%; - border-radius: 999px; - border: none; - background: transparent; - box-sizing: border-box; +.dashboardSearchContainer:focus-within:not(:focus-visible) { + outline: none; +} + +.dashboardSearchContainer input, +.dashboardSearchContainer input:focus, +.dashboardSearchContainer input:focus-visible { + outline: none; + box-shadow: none; } .dashboardSearchTextarea { @@ -92,33 +123,32 @@ min-height: 36px; max-height: 120px; box-sizing: border-box; - overflow-y: auto; - overflow-x: hidden; + overflow: hidden auto; white-space: pre-wrap; - word-wrap: break-word; - word-break: break-word; -} - -@media (max-width: 850px) { - .dashboardSearchContainer { - width: 250px; - } + overflow-wrap: break-word; + word-break: normal; } .darkHeader .dashboardSearchTextarea { - color: #ffffff; + color: #fff; } .darkHeader .dashboardSearchTextarea::placeholder { color: #cbd5e1; } +@media (width <= 850px) { + .dashboardSearchContainer { + width: 250px; + } +} + .charCountWarning { position: absolute; top: 100%; transform: translateY(4px); background-color: #e4b348 !important; - color: #333333 !important; + color: #333 !important; padding: 4px 8px; border-radius: 4px; font-size: 0.85rem; @@ -144,39 +174,45 @@ outline: none; } -.dashboardSearchContainer input:focus, -.dashboardSearchContainer input:focus-visible { - outline: none; - box-shadow: none; +.dashboardSidebar { + padding: 20px; + display: flex; + justify-content: flex-start; + background: #fff; + border-radius: 12px; + box-shadow: 0 4px 12px rgb(0 0 0 / 10%); } -.dashboardSearchContainer:focus-within { - border-color: #4da3ff; - box-shadow: none; +.darkSidebar { + background: #2b3e59; } -.dashboardSearchContainer:focus-within:not(:focus-visible) { - outline: none; +.filterItem input:not([type="checkbox"], [type="radio"]), +.filterItem select { + padding: 12px 15px; + margin-top: 10px; + border-radius: 8px; + width: 100%; + height: auto; + font-size: 1rem; + transition: all 0.3s ease; + margin-bottom: 20px; + appearance: none; + background-image: url("data:image/svg+xml;charset=UTF-8,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='currentColor' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3e%3cpolyline points='6 9 12 15 18 9'%3e%3c/polyline%3e%3c/svg%3e"); + background-repeat: no-repeat; + background-position: right 1rem center; + background-size: 1em; } -.dashboardSearchContainer, -.dashboardSearchContainer:focus, -.dashboardSearchContainer:focus-visible { - outline: none; +.filterItem input:not([type="checkbox"], [type="radio"]) { + margin-top: 10px; } -.dashboardSearchContainer input, -.dashboardSearchContainer input:focus, -.dashboardSearchContainer input:focus-visible { +.filterItem input:focus, +.filterItem select:focus { + border-color: #2c3e50; + box-shadow: 0 0 5px rgb(44 62 80 / 40%); outline: none; - box-shadow: none; -} - -.dashboardSidebar { - padding: 30px; - background: #f9f9f9; - border-radius: 12px; - box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1); } .darkSidebar { @@ -276,7 +312,6 @@ display: block; font-weight: 600; color: #34495e; - margin-bottom: 8px; } .radioLabel { @@ -340,10 +375,9 @@ } .dashboardMain { - padding: 30px; - background: #ffffff; + width: 65%; border-radius: 12px; - box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1); + box-shadow: 0 4px 12px rgb(0 0 0 / 10%); } .darkMain { @@ -366,32 +400,52 @@ text-align: left; } +.sidebarTitle { + color: #2c3e50; + margin-bottom: 30px; +} + +.clearDateFilterBtn{ + margin-top: 10px; +} + .eventCardCol { display: flex; - margin-bottom: 20px; + padding: 20px 12px; +} + +.eventCardLink { + display: flex; + width: 100%; + height: 100%; + text-decoration: none !important; + color: inherit; } .eventCard { width: 100%; - margin-bottom: 20px; - border-radius: 12px; - overflow: hidden; - box-shadow: 0 6px 15px rgba(0, 0, 0, 0.1); - transition: transform 0.3s ease, box-shadow 0.3s ease; + height: 100%; display: flex; flex-direction: column; + border-radius: 12px; + overflow: hidden; border: none; - background: #ffffff; + background: #fff; + transition: transform 0.3s ease, box-shadow 0.3s ease; + position: relative; +} + +.darkEventCard { + background: #1b2a41; } .eventCard:hover { transform: scale(1.05); - box-shadow: 0 8px 20px rgba(0, 0, 0, 0.2); + box-shadow: 0 8px 20px rgb(0 0 0 / 20%); } .eventCardImgContainer { width: 100%; - height: 220px; overflow: hidden; background-color: #f0f0f0; position: relative; @@ -400,13 +454,20 @@ .eventCardImgContainer img { width: 100%; height: auto; - border-bottom: 3px solid #34495e; +} + +.eventCardBody { + display: flex; + flex-direction: column; + flex-grow: 1; + padding: 20px; + min-height: 180px; } .eventTitle { text-align: center; color: #2c3e50; - margin: 0 0 15px 0; + margin: 0 0 15px; font-weight: bold; font-size: 1.3rem; line-height: 1.4; @@ -493,22 +554,9 @@ font-size: 1.2rem; } -.showPastEventsBtn { - background-color: #2c3e50; - color: #ffffff; - border: none; - padding: 12px 25px; - font-size: 1rem; - border-radius: 8px; - cursor: pointer; - transition: all 0.3s ease; - font-weight: bold; - white-space: nowrap; -} - -.showPastEventsBtn:hover { - background-color: #34495e; - box-shadow: 0 4px 12px rgba(0, 0, 0, 0.2); +.dashboardActions { + text-align: center; + margin-top: 30px; } diff --git a/src/utils/URL.js b/src/utils/URL.js index 1ffcbea76b..726cfede97 100644 --- a/src/utils/URL.js +++ b/src/utils/URL.js @@ -485,7 +485,8 @@ export const ENDPOINTS = { // event endpoint EVENTS: `${APIEndpoint}/events`, - EVENT_BY_ID: id => `${APIEndpoint}/events/${id}`, + EVENTS_BY_ID: (activityId) => `${APIEndpoint}/events/${activityId}`, + REGISTER_FOR_EVENT: (activityId) => `${APIEndpoint}/events/${activityId}/register`, EVENT_TYPES: `${APIEndpoint}/events/types`, EVENT_LOCATIONS: `${APIEndpoint}/events/locations`, EVENT_ATTENDANCE_STATS: `${APIEndpoint}/events/attendance/stats`,