diff --git a/src/components/CommunityPortal/CPDashboard.jsx b/src/components/CommunityPortal/CPDashboard.jsx index 7f9c5b2f9b8..e5b93c93624 100644 --- a/src/components/CommunityPortal/CPDashboard.jsx +++ b/src/components/CommunityPortal/CPDashboard.jsx @@ -1,99 +1,14 @@ -import { useState, useEffect, useRef, useCallback } from 'react'; +import { useState, useEffect, useRef } from 'react'; +import PropTypes from 'prop-types'; import { useSelector } from 'react-redux'; -import { Container, Row, Alert, Col, Card, CardBody, Button, Input } from 'reactstrap'; -import { - FaCalendarAlt, - FaMapMarkerAlt, - FaUserAlt, - FaSearch, - FaTimes, - FaHistory, -} from 'react-icons/fa'; +import { Container, Row, Alert, Col, Card, CardBody, Button, Input, Label } from 'reactstrap'; +import { FaCalendarAlt, FaMapMarkerAlt, FaUserAlt, FaSearch, FaTimes } from 'react-icons/fa'; import styles from './CPDashboard.module.css'; import { ENDPOINTS } from '../../utils/URL'; import { Link } from 'react-router-dom'; import axios from 'axios'; -import { isTomorrow, isComingWeekend } from './utils'; - -const RECENT_SEARCHES_KEY = 'cp_recent_searches'; -const MAX_RECENT_SEARCHES = 10; - -function useRecentSearches(key = RECENT_SEARCHES_KEY, maxItems = MAX_RECENT_SEARCHES) { - const [recentSearches, setRecentSearches] = useState(() => { - try { - const stored = localStorage.getItem(key); - return stored ? JSON.parse(stored) : []; - } catch { - return []; - } - }); - - const addSearch = useCallback( - query => { - const trimmed = query.trim(); - if (!trimmed) return; - setRecentSearches(prev => { - const updated = [trimmed, ...prev.filter(s => s !== trimmed)].slice(0, maxItems); - localStorage.setItem(key, JSON.stringify(updated)); - return updated; - }); - }, - [key, maxItems], - ); - - const removeSearch = useCallback( - query => { - setRecentSearches(prev => { - const updated = prev.filter(s => s !== query); - localStorage.setItem(key, JSON.stringify(updated)); - return updated; - }); - }, - [key], - ); - - return { recentSearches, addSearch, removeSearch }; -} - -function RecentSearchDropdown({ searches, onSelect, onRemove, darkMode }) { - if (!searches.length) return null; - return ( -
-
- - Recently Searched -
- {searches.map(term => ( -
- - -
- ))} -
- ); -} -const FixedRatioImage = ({ src, alt, fallback }) => ( +const FixedRatioImage = ({ src = '', alt = '', fallback }) => (
(
); +FixedRatioImage.propTypes = { + src: PropTypes.string, + alt: PropTypes.string, + fallback: PropTypes.string.isRequired, +}; + // Default filter values const DEFAULT_FILTERS = { dateFilter: '', @@ -128,35 +49,348 @@ const DEFAULT_FILTERS = { categories: '', }; -function passesFilters( - event, - { showPastEvents, isPastEvent, onlineOnly, dateFilter, searchQuery }, -) { - if (!showPastEvents && isPastEvent(event)) return false; - if (onlineOnly && event.location?.toLowerCase() !== 'virtual') return false; - if (dateFilter === 'tomorrow') return isTomorrow(event.date); - if (dateFilter === 'weekend') return isComingWeekend(event.date); - if (!searchQuery) return true; - const term = searchQuery.toLowerCase(); +// Shared shape for a community event +const eventShape = PropTypes.shape({ + id: PropTypes.oneOfType([PropTypes.string, PropTypes.number]), + image: PropTypes.string, + title: PropTypes.string, + date: PropTypes.string, + location: PropTypes.string, + organizer: PropTypes.string, +}); + +// Helper: combine base and dark variant class names +const cx = (base, darkMode) => { + if (!darkMode) return styles[base]; + const darkKey = `${base}-dark`; + return `${styles[base]} ${styles[darkKey]}`; +}; + +const formatDate = dateStr => { + if (!dateStr) return 'Date TBD'; + const date = new Date(dateStr); + return date.toLocaleString('en-US', { + weekday: 'long', + month: 'long', + day: 'numeric', + hour: 'numeric', + minute: '2-digit', + }); +}; + +const LoadingView = ({ darkMode }) => ( + +

Loading events...

+
+); + +LoadingView.propTypes = { + darkMode: PropTypes.bool.isRequired, +}; + +const ErrorView = ({ error, darkMode }) => ( + +

+ {error} +

+
+); + +ErrorView.propTypes = { + error: PropTypes.string.isRequired, + darkMode: PropTypes.bool.isRequired, +}; + +const SearchBar = ({ searchInput, setSearchInput, onSearch, onClear, onKeyDown, darkMode }) => ( +
+
+ setSearchInput(e.target.value)} + onKeyDown={onKeyDown} + className={cx('dashboard-search-input', darkMode)} + /> + + {searchInput && ( + + )} + + +
+
+); + +SearchBar.propTypes = { + searchInput: PropTypes.string.isRequired, + setSearchInput: PropTypes.func.isRequired, + onSearch: PropTypes.func.isRequired, + onClear: PropTypes.func.isRequired, + onKeyDown: PropTypes.func.isRequired, + darkMode: PropTypes.bool.isRequired, +}; + +const FiltersSidebar = ({ darkMode }) => ( +
+

Search Filters

+
+
+ +
+ + +
+ +
+ +
+ + +
+ +
+ + + + +
+ +
+ + + + +
+ +
+ + + + +
+
+
+); + +FiltersSidebar.propTypes = { + darkMode: PropTypes.bool.isRequired, +}; + +// Figma-consistent typography for event cards. +// Font properties apply in both modes; colors only in light mode so that +// dark-mode text classes (event-title-dark / event-text-dark) still win. +const EVENT_TITLE_FONT = { + textAlign: 'left', + margin: '0 0 8px', + fontWeight: 600, + fontSize: '1.1rem', + lineHeight: 1.3, +}; + +const EVENT_META_FONT = { + fontSize: '0.86rem', + gap: '6px', + margin: '4px 0', + lineHeight: 1.35, + fontWeight: 500, +}; + +const EVENT_ICON_FONT = { + marginTop: '1px', + fontSize: '0.78rem', +}; + +const EventCard = ({ event, darkMode, fallback }) => { + const titleStyle = darkMode ? EVENT_TITLE_FONT : { ...EVENT_TITLE_FONT, color: '#2f3f55' }; + const metaStyle = darkMode ? EVENT_META_FONT : { ...EVENT_META_FONT, color: '#5c6b7a' }; + const iconStyle = darkMode ? EVENT_ICON_FONT : { ...EVENT_ICON_FONT, color: '#6b7785' }; + return ( - event.title?.toLowerCase().includes(term) || - event.location?.toLowerCase().includes(term) || - event.organizer?.toLowerCase().includes(term) + + +
+ +
+ +
+ {event.title} +
+

+ {' '} + {formatDate(event.date)} +

+

+ {event.location} +

+

+ {event.organizer} +

+
+
+ ); -} +}; + +EventCard.propTypes = { + event: eventShape.isRequired, + darkMode: PropTypes.bool.isRequired, + fallback: PropTypes.string.isRequired, +}; + +const EventsGrid = ({ events, darkMode, fallback }) => ( + + {events.length > 0 ? ( + events.map(ev => ) + ) : ( +
+ No events available +
+ )} +
+); + +EventsGrid.propTypes = { + events: PropTypes.arrayOf(eventShape).isRequired, + darkMode: PropTypes.bool.isRequired, + fallback: PropTypes.string.isRequired, +}; + +const PaginationControls = ({ pagination, totalPages, goToPage }) => + totalPages > 1 ? ( +
+ + + Page {pagination.currentPage} of {totalPages} + + +
+ ) : null; + +PaginationControls.propTypes = { + pagination: PropTypes.shape({ + currentPage: PropTypes.number, + limit: PropTypes.number, + total: PropTypes.number, + }).isRequired, + totalPages: PropTypes.number.isRequired, + goToPage: PropTypes.func.isRequired, +}; -export function CPDashboard() { +function CPDashboard() { + const darkMode = useSelector(state => state.theme.darkMode); const [events, setEvents] = useState([]); const [searchInput, setSearchInput] = useState(''); const [searchQuery, setSearchQuery] = useState(''); const [selectedDate, setSelectedDate] = useState(''); - const [onlineOnly] = useState(false); + const [onlineOnly, setOnlineOnly] = useState(false); const [isLoading, setIsLoading] = useState(false); const [error, setError] = useState(null); const [showPastEvents, setShowPastEvents] = useState(false); - const [showRecentSearches, setShowRecentSearches] = useState(false); - const darkMode = useSelector(state => state.theme.darkMode); - const { recentSearches, addSearch, removeSearch } = useRecentSearches(); + + // Darken the page body in dark mode (app-wide pattern) so the area around the + // dashboard isn't left white. + useEffect(() => { + document.body.classList.toggle('dark-mode-body', darkMode); + return () => document.body.classList.remove('dark-mode-body'); + }, [darkMode]); // Hide the global back-to-top button — not needed on this page useEffect(() => { @@ -202,13 +436,6 @@ export function CPDashboard() { fetchEvents(); }, []); - // Darken the page body in dark mode (app-wide pattern) so the area - // behind the dashboard isn't left white. - useEffect(() => { - document.body.classList.toggle('dark-mode-body', darkMode); - return () => document.body.classList.remove('dark-mode-body'); - }, [darkMode]); - useEffect(() => { const handler = setTimeout(() => { setSearchQuery(searchInput.trim()); @@ -221,13 +448,11 @@ export function CPDashboard() { const handleSearchClick = () => { const trimmed = searchInput.trim(); setSearchQuery(trimmed); - addSearch(trimmed); setPagination(prev => ({ ...prev, currentPage: 1 })); - setShowRecentSearches(false); }; // keep this near your refs/functions - const BASE_HEIGHT = 32; + const BASE_HEIGHT = 36; const autoGrow = el => { if (!el) return; @@ -236,9 +461,6 @@ export function CPDashboard() { }; const searchRef = useRef(null); - const recentDropdownRef = useRef(null); - const blurTimeoutRef = useRef(null); - useEffect(() => { autoGrow(searchRef.current); // ✅ runs even when you clear via button }, [searchInput]); @@ -248,36 +470,10 @@ export function CPDashboard() { e.preventDefault(); // ✅ stops newline const trimmed = searchInput.trim(); setSearchQuery(trimmed); - addSearch(trimmed); setPagination(prev => ({ ...prev, currentPage: 1 })); - setShowRecentSearches(false); - } - }; - - const handleSearchFocus = () => { - setShowRecentSearches(true); - }; - - const handleSearchBlur = () => { - blurTimeoutRef.current = setTimeout(() => { - setShowRecentSearches(false); - }, 200); - }; - - const handleRecentDropdownMouseDown = () => { - if (blurTimeoutRef.current) { - clearTimeout(blurTimeoutRef.current); } }; - const handleSelectRecent = term => { - setSearchInput(term); - setSearchQuery(term); - setPagination(prev => ({ ...prev, currentPage: 1 })); - setShowRecentSearches(false); - if (searchRef.current) searchRef.current.focus(); - }; - const formatDate = dateStr => { if (!dateStr) return 'Date TBD'; const date = new Date(dateStr); @@ -285,11 +481,35 @@ export function CPDashboard() { weekday: 'long', month: 'long', day: 'numeric', + year: 'numeric', hour: 'numeric', minute: '2-digit', }); }; + const isTomorrow = dateString => { + const input = new Date(dateString); + const today = new Date(); + today.setHours(0, 0, 0, 0); + const tomorrow = new Date(today); + tomorrow.setDate(today.getDate() + 1); + return input >= tomorrow && input < new Date(tomorrow.getTime() + 24 * 60 * 60 * 1000); + }; + + const isComingWeekend = dateString => { + const input = new Date(dateString); + const today = new Date(); + today.setHours(0, 0, 0, 0); + const day = today.getDay(); + const daysUntilSaturday = (6 - day + 7) % 7 || 7; + const saturday = new Date(today); + saturday.setDate(today.getDate() + daysUntilSaturday); + const sunday = new Date(saturday); + sunday.setDate(saturday.getDate() + 1); + sunday.setHours(23, 59, 59, 999); + return input >= saturday && input <= sunday; + }; + // Handler to update pending filter values const handleFilterChange = (filterName, value) => { setPendingFilters(prev => ({ @@ -298,6 +518,15 @@ export function CPDashboard() { })); }; + // Toggle the date radio: clicking the active option clears it; clicking another switches. + // Applies immediately (bypasses the Apply Filters button) so the user sees instant feedback. + const handleDateToggle = value => { + const next = appliedFilters.dateFilter === value ? '' : value; + setPendingFilters(prev => ({ ...prev, dateFilter: next })); + setAppliedFilters(prev => ({ ...prev, dateFilter: next })); + setPagination(prev => ({ ...prev, currentPage: 1 })); + }; + // Apply all pending filters const handleApplyFilters = () => { setAppliedFilters(pendingFilters); @@ -318,16 +547,32 @@ export function CPDashboard() { if (!ref) return false; return new Date(ref) < now; }; + // Filter events based on applied filters + const filteredEvents = events.filter(event => { + if (!showPastEvents && isPastEvent(event)) return false; + // Filter by online only + if (appliedFilters.onlineOnly) { + const isOnlineEvent = event.location?.toLowerCase() === 'virtual'; + if (!isOnlineEvent) return false; + } - const filteredEvents = events.filter(event => - passesFilters(event, { - showPastEvents, - isPastEvent, - onlineOnly: appliedFilters.onlineOnly, - dateFilter: appliedFilters.dateFilter, - searchQuery, - }), - ); + // Filter by date + if (appliedFilters.dateFilter === 'tomorrow') { + return isTomorrow(event.date); + } else if (appliedFilters.dateFilter === 'weekend') { + return isComingWeekend(event.date); + } + + // Filter by search query + if (!searchQuery) return true; + + const term = searchQuery.toLowerCase(); + return ( + event.title?.toLowerCase().includes(term) || + event.location?.toLowerCase().includes(term) || + event.organizer?.toLowerCase().includes(term) + ); + }); // Reset pagination to page 1 when filters change useEffect(() => { @@ -361,6 +606,7 @@ export function CPDashboard() { ); } + // isLoading and error are already handled by the early returns above. let eventsContent; if (displayedEvents.length > 0) { @@ -377,9 +623,7 @@ export function CPDashboard() { -
- {event.title} -
+
{event.title}

setSearchInput(e.target.value)} onKeyDown={handleSearchKeyDown} - onFocus={handleSearchFocus} - onBlur={handleSearchBlur} className={`${styles.dashboardSearchTextarea} ${ darkMode ? styles.darkSearchTextarea : '' }`} @@ -468,22 +710,6 @@ export function CPDashboard() { {searchInput.length >= 100 && ( Max 100 characters )} - {showRecentSearches && ( -

- -
- )} @@ -491,32 +717,30 @@ export function CPDashboard() {
-
+
-
+ Tomorrow + +
+ This Weekend +
-
-
- handleFilterChange('onlineOnly', e.target.checked)} - className={styles.radioInput} - /> - -
-
+
{/* Branches Filter */} diff --git a/src/components/CommunityPortal/CPDashboard.module.css b/src/components/CommunityPortal/CPDashboard.module.css index abbdaa81895..1664eb3f862 100644 --- a/src/components/CommunityPortal/CPDashboard.module.css +++ b/src/components/CommunityPortal/CPDashboard.module.css @@ -1,3 +1,4 @@ +/* stylelint-disable no-descending-specificity */ :global(body) { font-family: Poppins, sans-serif; background: #fff; @@ -5,6 +6,12 @@ padding: 0; } +/* In dark mode the page body is darkened (class toggled from the component) so + the area around the dashboard matches the app's dark theme instead of white. */ +:global(body.dark-mode-body) { + background: #1b2a41; +} + .dashboardContainer { padding: 40px 10px; margin: 0 auto; @@ -20,13 +27,15 @@ .darkContainer { background: #2b3e59 !important; color: #fff; + border-radius: 0; + box-shadow: none; } .dashboardHeader { display: flex; align-items: center; - justify-content: flex-start; - gap: 20px; + justify-content: flex-start; + gap: 20px; margin-bottom: 30px; padding: 16px 24px; background: linear-gradient(120deg, #fff, #f8f9fa); @@ -35,6 +44,11 @@ flex-wrap: nowrap; } +.darkHeader{ + background: #1b2a41; + color: #fff; +} + .dashboardHeader h1 { margin: 0; font-size: 2.5rem; @@ -44,8 +58,9 @@ flex-shrink: 0; } -.darkHeader { - background: #1b2a41; +/* In dark mode the header background is dark navy, so the navy h1 color above + has almost no contrast — force a light color. */ +.darkHeader h1 { color: #fff; } @@ -55,6 +70,7 @@ gap: 15px; } +/* pill input */ .dashboardSearchInput { width: 100%; border-radius: 999px; @@ -63,37 +79,41 @@ box-sizing: border-box; } +/* clear X button */ .dashboardClearBtn { + position: absolute; + right: 35px; + top: 45%; + transform: translateY(-50%); border: none; background: transparent; cursor: pointer; font-size: 0.9rem; color: #1b3c55; - display: flex; - align-items: center; - justify-content: center; - padding: 0; } +/* search icon button inside the bar */ .dashboardSearchIconBtn { + position: absolute; + right: 1%; + top: 48%; + transform: translateY(-50%); width: 34px; height: 34px; border-radius: 50%; cursor: pointer; background: transparent; - border: none; display: flex; align-items: center; justify-content: center; color: #1b3c55; font-size: 0.9rem; - padding: 0; } .cp_dashboard_header { display: flex; align-items: center; - justify-content: space-between; + justify-content: space-between; gap: 24px; padding: 16px 24px; margin-bottom: 30px; @@ -113,7 +133,12 @@ overflow: hidden; border: 2px solid #1b3c55; background: #fff; +} + +.darkHeader .dashboardSearchContainer { /* white when NOT focused */ outline: none; + border-color: #fff; + background: #1b2a41; } .dashboardSearchContainer { @@ -129,15 +154,12 @@ margin-top: 8px; } -.dashboardSearchContainer input, -.dashboardSearchContainer textarea { +.dashboardSearchContainer input { outline: none; } .dashboardSearchContainer input:focus, -.dashboardSearchContainer input:focus-visible, -.dashboardSearchContainer textarea:focus, -.dashboardSearchContainer textarea:focus-visible { +.dashboardSearchContainer input:focus-visible { outline: none; box-shadow: none; } @@ -147,11 +169,6 @@ outline: none; } -.darkHeader .dashboardSearchContainer { - border-color: #fff; - background: #1b2a41; -} - .dashboardSearchContainer:focus-within { border-color: #4da3ff; box-shadow: none; @@ -169,11 +186,11 @@ background: transparent; font-size: 1rem; line-height: 1.5; - padding: 8px 96px 8px 18px; - min-height: 32px; + padding: 10px 96px 10px 18px; + min-height: 36px; max-height: 120px; box-sizing: border-box; - overflow: hidden auto; + overflow: hidden auto; /* ✅ Brave-like wrapping */ /* ✅ prevents overflow */ white-space: pre-wrap; overflow-wrap: break-word; word-break: normal; @@ -187,6 +204,7 @@ color: #cbd5e1; } + @media (width <= 850px) { .dashboardSearchContainer { width: 250px; @@ -220,6 +238,12 @@ border-color: #4da3ff; } +.dashboardSearchContainer, +.dashboardSearchContainer:focus, +.dashboardSearchContainer:focus-visible { + outline: none; +} + .darkSearchTextarea { color: #fff; } @@ -247,12 +271,42 @@ } .darkSidebar { - background: #2b3e59; - border-right: 1px solid #1b2a41; + background: #1b2a41; } -.filterSection { - width: 100%; +.darkSidebar .filterItem input:not([type='checkbox'], [type='radio']), +.darkSidebar .filterItem select { + background-color: #34495e; + color: #fff; + border: 1px solid #4a6572; +} + +.darkSidebar .filterItem label { + background: transparent; + color: #ecf0f1; +} + +.darkSidebar input[type="date"] { + color-scheme: dark; +} + +.darkSidebar .inputGroup { + background-color: #34495e; + border-color: #4da3ff; +} + +.darkSidebar .inputGroup input { + color: #fff; +} + +.darkSidebar .inputGroup input::placeholder { + color: #cbd5e1; +} + +.darkSidebar .inputGroupText { + background: #2b3e59; + border-right: 1px solid #1b2a41; + color: #cbd5e1; } .filterSection h4 { @@ -261,8 +315,9 @@ font-weight: 600; } -.filterSectionHeader { - margin-bottom: 40px; +/* Dark sidebar background is dark navy; lighten the "Filters" heading for contrast. */ +.darkSidebar .filterSection h4 { + color: #ecf0f1; } .filterSectionDivider { @@ -271,16 +326,20 @@ gap: 24px; } -.filterItem { - margin-bottom: 0; +.filterSectionHeader{ + margin-bottom: 40px; } -.filterItem label { - display: block; - font-weight: 600; - color: #34495e; - margin-bottom: 8px; -} +.filterItem:has(select) label { + margin-bottom: 0; + line-height: 1.3; + } + + .filterItem:has(select) select { + margin-top: 4px; + padding-top: 10px; + padding-bottom: 10px; + } .filterOptionsVertical { display: flex; @@ -290,6 +349,13 @@ margin-top: 10px; } +.filterItem label { + display: block; + font-weight: 600; + color: #34495e; + margin-bottom: 8px; +} + .filterItem input:not([type='checkbox'], [type='radio'], [type='date']), .filterItem select { padding: 12px 15px; @@ -303,87 +369,103 @@ transition: all 0.3s ease; } -.filterItem input:focus, -.filterItem select:focus { - border-color: #2c3e50; - box-shadow: 0 0 5px rgb(44 62 80 / 40%); - outline: none; -} - .radioRow { - display: flex; - flex-direction: column; - gap: 12px; - margin-top: 10px; - padding: 0; -} - -.radioColumn { display: flex; flex-direction: column; gap: 8px; + margin-top: 8px; + align-items: flex-start; } -.radioGroup { - display: flex; +.radioOption, +.checkboxOption { + display: inline-flex; align-items: center; gap: 10px; margin: 0; + cursor: pointer; + font-weight: 500; + background: transparent !important; } -.radioLabel, -.checkboxLabel { - display: flex; - align-items: center; - gap: 6px; +.radioOption input[type='radio'], +.checkboxOption input[type='checkbox'] { + margin: 0; cursor: pointer; - font-weight: 400; - margin-bottom: 0; - color: #34495e; + flex-shrink: 0; } +/* Fixed, identical size for radios and checkbox so their left edges line up + on a straight left line and the control-to-text gap is uniform. */ .filterItem input[type='radio'], -.filterItem input[type='checkbox'], -.radioInput, -.checkboxInput { - width: 20px; - height: 20px; - cursor: pointer; - margin: 0; +.filterItem input[type='checkbox'] { + display: inline-block; /* <– keeps input on same line as text */ + width: 16px; + height: 16px; padding: 0; - accent-color: #1b3c55; - flex-shrink: 0; + margin: 0; vertical-align: middle; } -.darkSidebar .filterItem label, -.darkSidebar .radioLabel, -.darkSidebar .checkboxLabel { - color: #ecf0f1; +/* Tighter vertical spacing for dropdown filters (Branches/Themes/Categories) */ +.compact-dropdown-filter label { + margin-bottom: 4px; } -.darkSidebar .radioInput, -.darkSidebar .filterItem input[type='checkbox'] { - accent-color: #4da3ff; +.compact-dropdown-filter select { + margin-top: 4px; } -.darkSidebar .inputGroup input { - color: #fff; +.filter-item input:not([type="checkbox"], [type="radio"]):focus, +.filter-item select:focus { + border-color: #2c3e50; + box-shadow: 0 0 5px rgb(44 62 80 / 40%); + outline: none; } -.darkSidebar .filterItem input:not([type='checkbox'], [type='radio'], [type='date']), -.darkSidebar .filterItem select { - background-color: #34495e; - color: #fff; - border: 1px solid #4a6572; +.radioGroup { + margin-right: 8px; } -.darkSidebar input[type='date'] { - color-scheme: dark; +.radioInput { + margin-right: 6px; } -.dateFilter { +.radioLabel { + margin-bottom: 0; + vertical-align: middle; +} + +.filterItem input:not([type="checkbox"], [type="radio"]), +.filterItem select { + padding: 12px 15px; margin-top: 10px; + border: 1px solid #ddd; + border-radius: 8px; + width: 100%; + font-size: 1rem; + background: #fff; + transition: all 0.3s ease; +} + +/* Render a single dropdown chevron: hide the browser's native arrow and draw + one custom chevron so the selects don't show a doubled-up arrow. */ +.filterItem select { + appearance: none; + background-image: url("data:image/svg+xml;charset=UTF-8,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' viewBox='0 0 24 24' fill='none' stroke='%232c3e50' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpolyline points='6 9 12 15 18 9'/%3E%3C/svg%3E"); + background-repeat: no-repeat; + background-position: right 12px center; + background-size: 16px; + padding-right: 36px; +} + +/* In the dark sidebar the select text is white, so use a white chevron. */ +.darkSidebar .filterItem select { + background-image: url("data:image/svg+xml;charset=UTF-8,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' viewBox='0 0 24 24' fill='none' stroke='%23ffffff' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpolyline points='6 9 12 15 18 9'/%3E%3C/svg%3E"); +} + +.dateFilter { + margin-top: 8px; padding: 12px 15px; border: 1px solid #ddd; border-radius: 8px; @@ -391,27 +473,28 @@ height: auto; font-size: 1rem; transition: all 0.3s ease; - margin-bottom: 20px; + margin-bottom: 0; } .filterItem input[type='date'] { display: block; box-sizing: border-box; padding: 12px 15px; - margin-top: 10px; + margin-top: 8px; border: 1px solid #ddd; border-radius: 8px; width: 100%; height: auto; font-size: 1rem; transition: all 0.3s ease; - margin-bottom: 20px; + margin-bottom: 0; } .searchFilter { margin-bottom: 1.5rem; } +/* Search Input Container */ .inputGroup { display: flex; align-items: center; @@ -431,19 +514,6 @@ font-size: 1.1rem; } -.darkSidebar .inputGroup { - background-color: #34495e; - border-color: #4da3ff; -} - -.darkSidebar .inputGroup input::placeholder { - color: #cbd5e1; -} - -.darkSidebar .inputGroupText { - color: #cbd5e1; -} - .dashboardMain { width: 65%; padding: 30px; @@ -454,17 +524,11 @@ background: #2b3e59; } -.darkMain .sectionTitle { - color: #f5f6f7; -} - .eventsHeader { display: flex; align-items: center; justify-content: space-between; margin-bottom: 30px; - gap: 16px; - flex-wrap: wrap; } .sectionTitle { @@ -495,7 +559,7 @@ margin-bottom: 30px; } -.clearDateFilterBtn { +.clearDateFilterBtn{ margin-top: 10px; } @@ -504,17 +568,19 @@ margin-bottom: 1.5rem; } -.eventCardLink { +.eventCard:hover { + transform: scale(1.02); + box-shadow: 0 8px 20px rgb(0 0 0 / 20%); +} + +.eventCardImgContainer img { width: 100%; - display: flex; - text-decoration: none; - color: inherit; + height: auto; } .eventCard { width: 100%; height: 100%; - min-height: 100%; box-shadow: 0 4px 12px rgb(0 0 0 / 10%); display: flex; flex-direction: column; @@ -522,65 +588,19 @@ overflow: hidden; border: none; background: #fff; - transition: - transform 0.3s ease, - box-shadow 0.3s ease; + transition: transform 0.3s ease, box-shadow 0.3s ease; position: relative; } -.eventCard:hover { - transform: scale(1.02); - box-shadow: 0 8px 20px rgb(0 0 0 / 20%); -} - -.eventTitle::after { - content: attr(data-event-title); - position: absolute; - bottom: calc(100% + 8px); - left: 50%; - z-index: 2; - max-width: min(280px, calc(100vw - 48px)); - padding: 6px 10px; - color: #fff; - font-size: 0.9rem; - line-height: 1.3; - text-align: center; - white-space: normal; - overflow-wrap: break-word; - background-color: #000; - border-radius: 4px; - opacity: 0; - pointer-events: none; - transform: translate(-50%, -4px); - transition: - opacity 0.2s ease, - transform 0.2s ease; -} - -.eventTitle:hover::after { - opacity: 1; - transform: translate(-50%, 0); -} - .darkEventCard { background: #1b2a41; } -.darkEventCard .eventTitle { - color: #f5f6f7; -} - .eventCardImgContainer { width: 100%; overflow: hidden; background-color: #f0f0f0; position: relative; - flex-shrink: 0; -} - -.eventCardImgContainer img { - width: 100%; - height: auto; } .eventCardImg { @@ -597,22 +617,21 @@ } .eventTitle { - position: relative; text-align: center; color: #2c3e50; margin: 0 0 15px; font-weight: bold; font-size: 1.3rem; line-height: 1.4; - overflow: visible; - cursor: default; -} - -.eventTitleText { - display: block; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; + cursor: default; +} + +/* On the dark event card the navy title above is unreadable — use a light color. */ +.darkEventCard .eventTitle { + color: #fff; } .eventDate, @@ -658,19 +677,17 @@ .noEvents { text-align: center; padding: 40px; - color: #888; + color: #595959; /* darker gray to meet AA contrast on the white background */ font-size: 1.2rem; - width: 100%; } .darkMain .noEvents { - color: #9ca3af; + color: #e4e6eb; /* lighter gray to meet AA contrast on the dark background */ } .dashboardActions { text-align: center; - margin-top: 20px; - margin-bottom: 20px; + margin-top: 30px; } .dashboardActions button { @@ -695,11 +712,6 @@ display: flex; align-items: center; gap: 12px; - flex-wrap: wrap; -} - -.paginationInfo { - font-weight: 500; } .paginationBtn { @@ -708,31 +720,24 @@ color: #fff !important; } -.paginationBtn:disabled { - opacity: 0.65; -} - -.paginationBtn:hover:not(:disabled) { - background-color: #5a6268 !important; - border-color: #545b62 !important; -} +/* Hide browser's built-in clear icon for search inputs */ .filterActions { display: flex; - flex-direction: row; + flex-direction: row; gap: 10px; margin-top: 20px; padding-top: 15px; - width: 100%; + width: 100%; } .applyBtn { flex: 1; - background-color: #1e8449 !important; + background-color: #198754 !important; /* darker success green: white text meets AA contrast */ border: none; color: white; - padding: 10px 5px; - font-size: 0.9rem; + padding: 10px 5px; + font-size: 0.9rem; font-weight: 600; border-radius: 8px; cursor: pointer; @@ -740,16 +745,16 @@ } .applyBtn:hover { - background-color: #156b3a !important; + background-color: #146c43 !important; box-shadow: 0 4px 8px rgb(39 174 96 / 30%); } .clearBtn { flex: 1; - background-color: #6c757d !important; + background-color: #6c757d !important; /* darker secondary gray: white text meets AA contrast */ border: none; color: white; - padding: 10px 5px; + padding: 10px 5px; font-size: 0.9rem; font-weight: 600; border-radius: 8px; @@ -758,7 +763,7 @@ } .clearBtn:hover { - background-color: #7f8c8d !important; + background-color: #565e64 !important; box-shadow: 0 4px 8px rgb(149 165 166 / 30%); } @@ -766,113 +771,6 @@ border-top-color: #3a4e6a; } -/* Recent Search Dropdown */ -.recentSearchDropdown { - position: absolute; - top: 100%; - left: 0; - right: 0; - z-index: 10; - background: #fff; - border: 1px solid #ddd; - border-radius: 8px; - box-shadow: 0 4px 12px rgb(0 0 0 / 15%); - margin-top: 4px; - max-height: 240px; - overflow-y: auto; -} - -.darkRecentSearchDropdown { - background: #1b2a41; - border-color: #4a6572; -} - -.recentSearchHeader { - display: flex; - align-items: center; - gap: 6px; - padding: 8px 12px; - font-size: 0.75rem; - font-weight: 600; - color: #888; - text-transform: uppercase; - letter-spacing: 0.5px; - border-bottom: 1px solid #eee; -} - -.darkRecentSearchDropdown .recentSearchHeader { - color: #9ca3af; - border-bottom-color: #3a4e6a; -} - -.recentIcon { - color: #888; - font-size: 0.7rem; -} - -.recentIconDark { - color: #9ca3af; - font-size: 0.7rem; -} - -.recentSearchItem { - display: flex; - align-items: center; - justify-content: space-between; -} - -.recentSearchTerm { - flex: 1; - display: flex; - align-items: center; - gap: 8px; - padding: 8px 12px; - border: none; - background: transparent; - cursor: pointer; - font-size: 0.9rem; - color: #34495e; - text-align: left; - transition: background-color 0.15s ease; -} - -.recentSearchTerm:hover { - background: #f5f5f5; -} - -.darkRecentSearchTerm { - color: #ecf0f1; -} - -.darkRecentSearchTerm:hover { - background: #2b3e59; -} - -.recentSearchRemove { - border: none; - background: transparent; - cursor: pointer; - padding: 4px 8px; - color: #999; - font-size: 0.75rem; - display: flex; - align-items: center; - justify-content: center; - transition: color 0.15s ease; -} - -.recentSearchRemove:hover { - color: #e74c3c; -} - -.darkRecentSearchRemove { - color: #7f8c8d; -} - -.darkRecentSearchRemove:hover { - color: #e74c3c; -} - @media (width <= 768px) { .dashboardHeader { flex-wrap: wrap; @@ -881,26 +779,13 @@ .cp_dashboard_search_container { width: 100%; } +} - .centeredRow { - padding: 15px; - } - - .dashboardMain { - width: 100%; - padding: 20px 10px; - } - - .dashboardSidebar { - margin-bottom: 20px; - } - - .eventsHeader { - flex-direction: column; - align-items: flex-start; - } +.paginationBtn:disabled { + opacity: 0.65; +} - .filterActions { - flex-direction: column; - } +.paginationBtn:hover:not(:disabled) { + background-color: #5a6268 !important; + border-color: #545b62 !important; } diff --git a/src/utils/URL.js b/src/utils/URL.js index 29e9d924015..08f3545ce6e 100644 --- a/src/utils/URL.js +++ b/src/utils/URL.js @@ -563,7 +563,7 @@ export const ENDPOINTS = { SUPPLIER_PROJECTS: `${APIEndpoint}/suppliers/projects`, // event endpoint - EVENTS: `${APIEndpoint}/events`, + EVENTS: `${APIEndpoint}/communityportal/events`, EVENTS_BY_ID: (activityId) => `${APIEndpoint}/events/${activityId}`, REGISTER_FOR_EVENT: (activityId) => `${APIEndpoint}/events/${activityId}/register`, EVENT_TYPES: `${APIEndpoint}/events/types`,