fix: handle null dates in dashboard and gallery pages
Add null checks for expires_at and event_date fields to prevent TypeError when calling parseISO() on null values. This fixes crashes that occurred after making event dates optional. - AdminDashboard: skip events with null expires_at in expiring filter - GalleryPage: handle null expires_at in expiration calculation - GalleryView: make daysUntilExpiration nullable with explicit checks - EventDetailsPage: return null from safeParseDate for null inputs
This commit is contained in:
@@ -310,10 +310,12 @@ export const GalleryView: React.FC<GalleryViewProps> = ({ slug, event }) => {
|
||||
}
|
||||
}, [settingsData, data, setTheme]); // Use data instead of event prop
|
||||
|
||||
// Calculate days until expiration
|
||||
const daysUntilExpiration = differenceInDays(parseISO(event.expires_at), new Date());
|
||||
const showUrgentWarning = daysUntilExpiration <= 7;
|
||||
const isExpired = daysUntilExpiration < 0;
|
||||
// Calculate days until expiration (null means never expires)
|
||||
const daysUntilExpiration = event.expires_at
|
||||
? differenceInDays(parseISO(event.expires_at), new Date())
|
||||
: null;
|
||||
const showUrgentWarning = daysUntilExpiration !== null && daysUntilExpiration <= 7;
|
||||
const isExpired = daysUntilExpiration !== null && daysUntilExpiration < 0;
|
||||
|
||||
// Filter and sort photos
|
||||
const filteredPhotos = useMemo(() => {
|
||||
@@ -602,7 +604,7 @@ export const GalleryView: React.FC<GalleryViewProps> = ({ slug, event }) => {
|
||||
headerExtra={(() => {
|
||||
const items = [];
|
||||
|
||||
if (daysUntilExpiration <= 1 && daysUntilExpiration > 0) {
|
||||
if (daysUntilExpiration !== null && daysUntilExpiration <= 1 && daysUntilExpiration > 0) {
|
||||
items.push(
|
||||
<CountdownTimer key="countdown" expiresAt={event.expires_at} className="mr-2" />
|
||||
);
|
||||
|
||||
@@ -187,8 +187,8 @@ export const GalleryPage: React.FC = () => {
|
||||
}
|
||||
}, [galleryInfo, isAuthenticated, autoLoginAttempted, login, resolvedSlug, isResolvingIdentifier]);
|
||||
|
||||
// Calculate days until expiration
|
||||
const daysUntilExpiration = galleryInfo
|
||||
// Calculate days until expiration (null if no expiration set)
|
||||
const daysUntilExpiration = galleryInfo?.expires_at
|
||||
? differenceInDays(parseISO(galleryInfo.expires_at), new Date())
|
||||
: null;
|
||||
|
||||
@@ -394,9 +394,11 @@ export const GalleryPage: React.FC = () => {
|
||||
<CardContent className="text-center py-12">
|
||||
<Clock className="w-16 h-16 text-amber-500 mx-auto mb-4" />
|
||||
<h2 className="text-xl font-semibold mb-2">{t('gallery.expired')}</h2>
|
||||
<p className="text-neutral-600 mb-4">
|
||||
{t('gallery.expiredOn', { date: format(parseISO(galleryInfo.expires_at), 'PP') })}
|
||||
</p>
|
||||
{galleryInfo.expires_at && (
|
||||
<p className="text-neutral-600 mb-4">
|
||||
{t('gallery.expiredOn', { date: format(parseISO(galleryInfo.expires_at), 'PP') })}
|
||||
</p>
|
||||
)}
|
||||
<p className="text-sm text-neutral-500">
|
||||
{t('gallery.contactOrganizer')}
|
||||
</p>
|
||||
|
||||
@@ -73,6 +73,7 @@ export const AdminDashboard: React.FC = () => {
|
||||
// Calculate expiring events
|
||||
const activeEvents = eventsData?.events.filter(e => e.is_active && !e.is_archived) || [];
|
||||
const expiringEvents = activeEvents.filter(e => {
|
||||
if (!e.expires_at) return false;
|
||||
const days = differenceInDays(parseISO(e.expires_at), new Date());
|
||||
return days <= 7 && days > 0;
|
||||
});
|
||||
@@ -204,9 +205,11 @@ export const AdminDashboard: React.FC = () => {
|
||||
>
|
||||
<div>
|
||||
<h3 className="font-medium text-neutral-900">{event.event_name}</h3>
|
||||
<p className="text-sm text-neutral-600">
|
||||
{format(parseISO(event.event_date), 'PP')}
|
||||
</p>
|
||||
{event.event_date && (
|
||||
<p className="text-sm text-neutral-600">
|
||||
{format(parseISO(event.event_date), 'PP')}
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
<div className="text-right">
|
||||
<p className="text-sm font-medium text-orange-600">
|
||||
|
||||
@@ -31,9 +31,9 @@ import {
|
||||
import { parseISO, differenceInDays, isValid } from 'date-fns';
|
||||
|
||||
// Helper to safely parse dates that might be strings, Date objects, or timestamps
|
||||
const safeParseDate = (dateValue: unknown): Date => {
|
||||
const safeParseDate = (dateValue: unknown): Date | null => {
|
||||
if (!dateValue) {
|
||||
return new Date();
|
||||
return null;
|
||||
}
|
||||
if (dateValue instanceof Date) {
|
||||
return dateValue;
|
||||
@@ -45,7 +45,7 @@ const safeParseDate = (dateValue: unknown): Date => {
|
||||
const parsed = parseISO(dateValue);
|
||||
return isValid(parsed) ? parsed : new Date(dateValue);
|
||||
}
|
||||
return new Date();
|
||||
return null;
|
||||
};
|
||||
import { toast } from 'react-toastify';
|
||||
import { useLocalizedDate } from '../../hooks/useLocalizedDate';
|
||||
@@ -389,16 +389,17 @@ export const EventDetailsPage: React.FC = () => {
|
||||
);
|
||||
}
|
||||
|
||||
const daysUntilExpiration = differenceInDays(safeParseDate(event.expires_at), new Date());
|
||||
const isExpired = daysUntilExpiration <= 0;
|
||||
const isExpiring = daysUntilExpiration > 0 && daysUntilExpiration <= 7;
|
||||
const expiresAtDate = safeParseDate(event.expires_at);
|
||||
const daysUntilExpiration = expiresAtDate ? differenceInDays(expiresAtDate, new Date()) : null;
|
||||
const isExpired = daysUntilExpiration !== null && daysUntilExpiration <= 0;
|
||||
const isExpiring = daysUntilExpiration !== null && daysUntilExpiration > 0 && daysUntilExpiration <= 7;
|
||||
|
||||
const handleStartEdit = () => {
|
||||
setEditForm({
|
||||
welcome_message: event.welcome_message || '',
|
||||
color_theme: event.color_theme || '',
|
||||
css_template_id: event.css_template_id || null,
|
||||
expires_at: format(safeParseDate(event.expires_at), 'yyyy-MM-dd'),
|
||||
expires_at: expiresAtDate ? format(expiresAtDate, 'yyyy-MM-dd') : '',
|
||||
allow_user_uploads: event.allow_user_uploads || false,
|
||||
upload_category_id: event.upload_category_id || null,
|
||||
hero_photo_id: event.hero_photo_id || null,
|
||||
@@ -616,10 +617,12 @@ export const EventDetailsPage: React.FC = () => {
|
||||
<div>
|
||||
<h1 className="text-2xl font-bold text-neutral-900">{event.event_name}</h1>
|
||||
<div className="flex items-center gap-4 mt-2 text-sm text-neutral-600">
|
||||
<span className="flex items-center">
|
||||
<Calendar className="w-4 h-4 mr-1" />
|
||||
{format(safeParseDate(event.event_date), 'PPP')}
|
||||
</span>
|
||||
{event.event_date && (
|
||||
<span className="flex items-center">
|
||||
<Calendar className="w-4 h-4 mr-1" />
|
||||
{format(safeParseDate(event.event_date)!, 'PPP')}
|
||||
</span>
|
||||
)}
|
||||
<span className="capitalize">{event.event_type}</span>
|
||||
<span
|
||||
className={`inline-flex items-center gap-1 px-2 py-0.5 rounded-full text-xs font-medium ${
|
||||
@@ -1181,18 +1184,24 @@ export const EventDetailsPage: React.FC = () => {
|
||||
<div>
|
||||
<dt className="text-sm font-medium text-neutral-500">{t('events.created')}</dt>
|
||||
<dd className="mt-1 text-sm text-neutral-900">
|
||||
{format(safeParseDate(event.created_at), 'PP')}
|
||||
{event.created_at && format(safeParseDate(event.created_at)!, 'PP')}
|
||||
</dd>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<dt className="text-sm font-medium text-neutral-500">{t('events.expires')}</dt>
|
||||
<dd className="mt-1 text-sm text-neutral-900">
|
||||
{format(safeParseDate(event.expires_at), 'PP')}
|
||||
{!event.is_archived && daysUntilExpiration > 0 && (
|
||||
<span className="text-neutral-500 ml-1">
|
||||
{t('events.daysLeft', { count: daysUntilExpiration })}
|
||||
</span>
|
||||
{event.expires_at ? (
|
||||
<>
|
||||
{format(safeParseDate(event.expires_at)!, 'PP')}
|
||||
{!event.is_archived && daysUntilExpiration !== null && daysUntilExpiration > 0 && (
|
||||
<span className="text-neutral-500 ml-1">
|
||||
{t('events.daysLeft', { count: daysUntilExpiration })}
|
||||
</span>
|
||||
)}
|
||||
</>
|
||||
) : (
|
||||
<span className="text-neutral-500">{t('events.neverExpires', 'Never')}</span>
|
||||
)}
|
||||
</dd>
|
||||
</div>
|
||||
@@ -1517,7 +1526,7 @@ export const EventDetailsPage: React.FC = () => {
|
||||
<div>
|
||||
<p className="text-sm font-medium text-neutral-500">{t('events.archivedOn')}</p>
|
||||
<p className="text-sm text-neutral-900">
|
||||
{event.archived_at && format(safeParseDate(event.archived_at), 'PPp')}
|
||||
{event.archived_at && format(safeParseDate(event.archived_at)!, 'PPp')}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user