Skip to content

Commit d8ec064

Browse files
Merge pull request #4633 from OneCommunityGlobal/feature/standardize-event-timezone-display
Siva - feature for timezone standardization in EventCard
2 parents 9c082ee + 0aa4819 commit d8ec064

4 files changed

Lines changed: 322 additions & 25 deletions

File tree

src/components/CommunityPortal/CPDashboard.jsx

Lines changed: 46 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,8 @@ import {
1313
Label,
1414
} from 'reactstrap';
1515
import { FaCalendarAlt, FaMapMarkerAlt, FaUserAlt, FaSearch, FaTimes } from 'react-icons/fa';
16+
import { format } from 'date-fns';
17+
import { getUserTimezone, formatEventTimeWithTimezone } from '../../utils/timezoneUtils';
1618
import styles from './CPDashboard.module.css';
1719
import { ENDPOINTS } from '../../utils/URL';
1820
import DatePicker from 'react-datepicker';
@@ -132,7 +134,7 @@ export function CPDashboard() {
132134
total: response.data.events?.length || 0,
133135
}));
134136
} catch (err) {
135-
console.error('Failed to fetch events', err);
137+
console.error('Failed to load events:', err);
136138
setError('Failed to load events');
137139
} finally {
138140
setIsLoading(false);
@@ -180,34 +182,55 @@ export function CPDashboard() {
180182
};
181183

182184
const formatDate = dateStr => {
183-
if (!dateStr) {
185+
if (!dateStr) return 'Date TBD';
186+
try {
187+
const date = new Date(dateStr);
188+
if (Number.isNaN(date.getTime())) {
189+
return 'Invalid date';
190+
}
191+
// Format: "Saturday, February 15"
192+
return format(date, 'EEEE, MMMM d');
193+
} catch (err) {
194+
console.error('Error formatting date:', err);
184195
return 'Date TBD';
185196
}
197+
};
186198

187-
const date = new Date(dateStr);
188-
return date.toLocaleString('en-US', {
189-
weekday: 'long',
190-
month: 'long',
191-
day: 'numeric',
192-
year: 'numeric',
193-
hour: 'numeric',
194-
minute: '2-digit',
195-
});
199+
const formatTime = (eventDate, timeStr) => {
200+
if (!timeStr) return 'Time TBD';
201+
try {
202+
const userTimezone = getUserTimezone();
203+
return formatEventTimeWithTimezone(eventDate, timeStr, userTimezone);
204+
} catch (err) {
205+
console.error('Error formatting time:', err);
206+
return 'Time TBD';
207+
}
208+
};
209+
210+
const getDisplayLocation = location => {
211+
if (
212+
location == null ||
213+
String(location).trim() === '' ||
214+
String(location).toLowerCase() === 'tbd'
215+
) {
216+
return 'Location TBD';
217+
}
218+
return location;
196219
};
197220

198221
const parseEventDate = dateString => {
199222
if (!dateString) return null;
200223

201224
try {
202225
const parsedDate = new Date(dateString);
203-
if (!isNaN(parsedDate.getTime())) {
226+
if (!Number.isNaN(parsedDate.getTime())) {
204227
const year = parsedDate.getFullYear();
205228
const month = String(parsedDate.getMonth() + 1).padStart(2, '0');
206229
const day = String(parsedDate.getDate()).padStart(2, '0');
207230
return `${year}-${month}-${day}`;
208231
}
209232
} catch (err) {
210-
console.error('Error parsing date:', err);
233+
console.error('Error parsing event date:', err);
211234
}
212235
return null;
213236
};
@@ -298,11 +321,17 @@ export function CPDashboard() {
298321
</div>
299322
<CardBody>
300323
<h5 className={styles.eventTitle}>{event.title}</h5>
301-
<p className={styles.eventDate}>
302-
<FaCalendarAlt className={styles.eventIcon} /> {formatDate(event.date)}
303-
</p>
324+
<div className={styles.eventDate}>
325+
<FaCalendarAlt className={styles.eventIcon} />
326+
<div>
327+
<div>{formatDate(event.date)}</div>
328+
{event.startTime && (
329+
<div className={styles.eventTime}>{formatTime(event.date, event.startTime)}</div>
330+
)}
331+
</div>
332+
</div>
304333
<p className={styles.eventLocation}>
305-
<FaMapMarkerAlt className={styles.eventIcon} /> {event.location || 'Location TBD'}
334+
<FaMapMarkerAlt className={styles.eventIcon} /> {getDisplayLocation(event.location)}
306335
</p>
307336
<p className={styles.eventOrganizer}>
308337
{event.organizerLogo && !failedLogos.has(event._id) ? (

src/components/CommunityPortal/CPDashboard.module.css

Lines changed: 48 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -345,9 +345,39 @@
345345
font-size: 1rem;
346346
color: #555;
347347
display: flex;
348-
align-items: center;
348+
align-items: flex-start;
349349
gap: 8px;
350-
margin: 5px 0;
350+
margin: 8px 0;
351+
line-height: 1.5;
352+
}
353+
354+
.eventDate > svg,
355+
.eventLocation > svg,
356+
.eventOrganizer > svg {
357+
flex-shrink: 0;
358+
width: 16px;
359+
height: 16px;
360+
margin-top: 3px;
361+
align-self: flex-start;
362+
}
363+
364+
.eventDate > div {
365+
display: flex;
366+
flex-direction: column;
367+
gap: 4px;
368+
flex: 1;
369+
min-width: 0;
370+
}
371+
372+
.eventDate > div > div:first-child {
373+
line-height: 1.4;
374+
}
375+
376+
.eventTime {
377+
font-size: 0.9rem;
378+
color: #777;
379+
font-weight: 500;
380+
line-height: 1.4;
351381
}
352382

353383
.organizerLogo {
@@ -367,6 +397,22 @@
367397
font-size: 1rem;
368398
}
369399

400+
/* Dark mode: ensure date, time, location and organizer are readable */
401+
.darkMain .eventDate,
402+
.darkMain .eventDate .eventTime,
403+
.darkMain .eventLocation,
404+
.darkMain .eventOrganizer {
405+
color: #e4e6eb;
406+
}
407+
408+
.darkMain .eventTime {
409+
color: #b0b3b8;
410+
}
411+
412+
.darkMain .eventIcon {
413+
color: #e4e6eb;
414+
}
415+
370416
.noEvents {
371417
text-align: center;
372418
padding: 40px;

src/components/CommunityPortal/Event/EventCard/EventCard.jsx

Lines changed: 49 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import {
1010
faTag,
1111
} from '@fortawesome/free-solid-svg-icons';
1212
import { format } from 'date-fns';
13+
import { getUserTimezone, formatEventTimeWithTimezone } from '../../../../utils/timezoneUtils';
1314
import styles from './EventCard.module.css';
1415

1516
function EventCard(props) {
@@ -48,10 +49,48 @@ function EventCard(props) {
4849
return (locationType?.toLowerCase() || '') === 'virtual' ? 'virtual-tag' : 'in-person-tag';
4950
};
5051

51-
const formatDateTime = dateString => {
52+
const getDisplayLocation = () => {
53+
if (
54+
location == null ||
55+
String(location).trim() === '' ||
56+
String(location).toLowerCase() === 'tbd'
57+
) {
58+
return 'Location TBD';
59+
}
60+
return location;
61+
};
62+
63+
const formatDate = dateString => {
64+
if (!dateString) {
65+
return 'Date not set';
66+
}
67+
try {
68+
const date = new Date(dateString);
69+
if (Number.isNaN(date.getTime())) {
70+
return 'Invalid date';
71+
}
72+
return format(date, 'MMM dd, yyyy');
73+
} catch (error) {
74+
console.error('Error formatting date:', error);
75+
return 'Date not set';
76+
}
77+
};
78+
79+
const formatDateTime = (eventDate, timeString) => {
5280
try {
53-
return format(new Date(dateString), 'h:mm a');
81+
if (!timeString) {
82+
return 'Time not set';
83+
}
84+
// eventDate is required to correctly anchor a time-only string (e.g. "5:00 PM")
85+
// to a UTC datetime before conversion. Without it, toFullEventDatetime falls back
86+
// to parsing in the local machine timezone, producing inconsistent results.
87+
if (!eventDate) {
88+
return 'Date not set';
89+
}
90+
const userTimezone = getUserTimezone();
91+
return formatEventTimeWithTimezone(eventDate, timeString, userTimezone);
5492
} catch (error) {
93+
console.error('Error formatting date time:', error);
5594
return 'Time not set';
5695
}
5796
};
@@ -97,8 +136,12 @@ function EventCard(props) {
97136
<div className="d-flex align-items-center mb-2">
98137
<FontAwesomeIcon icon={faMapMarkerAlt} className="me-2 text-muted" />
99138
<span className="text-muted">Location:</span>
100-
<span className={`ms-2 ${styles['attendee-tag']} ${styles[getLocationTag(location)]}`}>
101-
{location}
139+
<span
140+
className={`ms-2 ${styles['attendee-tag']} ${
141+
styles[getLocationTag(getDisplayLocation())]
142+
}`}
143+
>
144+
{getDisplayLocation()}
102145
</span>
103146
</div>
104147
<div className={`${styles['event-description']} mb-2`}>
@@ -111,12 +154,12 @@ function EventCard(props) {
111154
<div className="mb-4">
112155
<div className="d-flex align-items-center mb-2">
113156
<FontAwesomeIcon icon={faCalendar} className="me-2" />
114-
<span>{format(new Date(date), 'MMM dd, yyyy')}</span>
157+
<span>{formatDate(date)}</span>
115158
</div>
116159
<div className="d-flex align-items-center mb-2">
117160
<FontAwesomeIcon icon={faClock} className="me-2" />
118161
<span>
119-
{formatDateTime(startTime)} - {formatDateTime(endTime)}
162+
{formatDateTime(date, startTime)} - {formatDateTime(date, endTime)}
120163
</span>
121164
</div>
122165
</div>

0 commit comments

Comments
 (0)