diff --git a/src/Pages/Events/CalendarView.js b/src/Pages/Events/CalendarView.js index 6ed00ee6b..d63d1dec0 100644 --- a/src/Pages/Events/CalendarView.js +++ b/src/Pages/Events/CalendarView.js @@ -10,7 +10,8 @@ import { PinIcon, ClockIcon, XIcon, - PlusIcon + PlusIcon, + ScheduleIcon, } from './EventIcons'; const DAYS = ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat']; @@ -71,11 +72,27 @@ function formatTime(timeStr) { }); } +function isScheduledEvent(event) { + if (event.status !== 'draft' || !event.publish_date) return false; + const publishDate = new Date(event.publish_date); + return !isNaN(publishDate) && publishDate > new Date(); +} + function pillColors(event, isAdminView) { const status = event.status || 'draft'; const visibility = event.visibility || 'public'; if (isAdminView) { + if (isScheduledEvent(event)) { + return { + bg: 'bg-blue-500/12', + text: 'text-blue-200', + border: 'border-blue-400/40', + dot: 'bg-blue-300', + accent: 'text-blue-300', + }; + } + if (status === 'draft') { return { bg: 'bg-amber-500/12', @@ -149,6 +166,7 @@ function getBadgeText(event, isAdminView) { const visibility = event.visibility || 'public'; if (isAdminView) { + if (isScheduledEvent(event)) return 'Scheduled'; if (status === 'draft') return 'Draft'; if (status === 'closed') return 'Closed'; if (visibility === 'private') return 'Private'; @@ -532,6 +550,21 @@ function EventPopup({ event, onClose, isAdminView, user }) { {waitlistMessage && (

{waitlistMessage}

)} + + {isAdminView && event.publish_date && ( +
+ + + Publish date: {new Date(event.publish_date).toLocaleString()} + +
+ )} + + {isAdminView && event.published_at && ( +
+ Published at: {new Date(event.published_at).toLocaleString()} +
+ )} @@ -935,6 +968,7 @@ export default function CalendarView({ {isAdminView ? (
{[ + { label: 'Scheduled', dot: 'bg-blue-300' }, { label: 'Published', dot: 'bg-cyan-300' }, { label: 'Private', dot: 'bg-violet-300' }, { label: 'Draft', dot: 'bg-amber-300' }, diff --git a/src/Pages/Events/CreateEventPage.js b/src/Pages/Events/CreateEventPage.js index c7b455c11..4c2c92c47 100644 --- a/src/Pages/Events/CreateEventPage.js +++ b/src/Pages/Events/CreateEventPage.js @@ -67,6 +67,7 @@ export default function CreateEventPage() { const [maxAttendees, setMaxAttendees] = useState(UNLIMITED_ATTENDEES); const [waitlistEnabled, setWaitlistEnabled] = useState(false); const [waitlistSize, setWaitlistSize] = useState(10); + const [publishDate, setPublishDate] = useState(''); const { questions, addQuestion, @@ -164,6 +165,12 @@ export default function CreateEventPage() { debounceRef.current = setTimeout(() => performAdminSearch(query), 300); } + function toPublishDateValue(status, publishDate) { + if (status === 'closed') return null; + if (!publishDate) return null; + return new Date(publishDate).toISOString(); + } + async function handleCreateEvent() { setSubmitError(''); if (!eventName.trim()) { @@ -190,6 +197,16 @@ export default function CreateEventPage() { setSubmitError('Please enter a valid waitlist size.'); return; } + if (status === 'closed' && publishDate) { + setSubmitError('Closed events cannot have a publish date.'); + return; + } + if (status === 'published' && publishDate) { + const confirmed = window.confirm( + 'This event is marked published and also has a publish date. It may publish immediately if that date is in the past. Continue?' + ); + if (!confirmed) return; + } const payload = { id: eventId, @@ -209,6 +226,7 @@ export default function CreateEventPage() { minimum_visible_role: visibility === 'private' ? minimumVisibleRole : '', waitlist_enabled: waitlistEnabled, waitlist_size: waitlistEnabled ? Number(waitlistSize) : 0, + publish_date: toPublishDateValue(status, publishDate), }; setSubmitting(true); @@ -280,6 +298,8 @@ export default function CreateEventPage() { setWaitlistEnabled, waitlistSize, setWaitlistSize, + publishDate, + setPublishDate, }} questionActions={{ questions, diff --git a/src/Pages/Events/EditEventPage.js b/src/Pages/Events/EditEventPage.js index d7cf31709..0de927f26 100644 --- a/src/Pages/Events/EditEventPage.js +++ b/src/Pages/Events/EditEventPage.js @@ -38,6 +38,7 @@ export default function EditEventPage() { const [waitlistSize, setWaitlistSize] = useState(10); const [eventAdmins, setEventAdmins] = useState([]); const [allOrgAdminsCanEdit, setAllOrgAdminsCanEdit] = useState(false); + const [publishDate, setPublishDate] = useState(''); const [adminSearch, setAdminSearch] = useState(''); const [adminSearchResults, setAdminSearchResults] = useState([]); const [adminSearchError, setAdminSearchError] = useState(''); @@ -104,6 +105,7 @@ export default function EditEventPage() { setWaitlistSize( typeof evt.waitlist_size === 'number' && evt.waitlist_size > 0 ? evt.waitlist_size : 10, ); + setPublishDate(evt.publish_date ? evt.publish_date.slice(0, 16) : ''); setQuestions(evt.registration_form || []); const adminIds = Array.isArray(evt.admins) ? evt.admins.map(String) : []; setAllOrgAdminsCanEdit(!!evt.all_org_admins_can_edit); @@ -175,6 +177,12 @@ export default function EditEventPage() { debounceRef.current = setTimeout(() => performAdminSearch(query), 300); } + function toPublishDateValue(status, publishDate) { + if (status === 'closed') return null; + if (!publishDate) return null; + return new Date(publishDate).toISOString(); + } + async function handleUpdateEvent() { setSubmitError(''); if (!eventName.trim()) { @@ -204,6 +212,18 @@ export default function EditEventPage() { } } + if (status === 'closed' && publishDate) { + setSubmitError('Closed events cannot have a publish date.'); + return; + } + + if (status === 'published' && publishDate) { + const confirmed = window.confirm( + 'This event is marked published and also has a publish date. It may publish immediately if that date is in the past. Continue?' + ); + if (!confirmed) return; + } + const payload = { name: eventName.trim(), date, @@ -220,6 +240,7 @@ export default function EditEventPage() { waitlist_size: waitlistEnabled ? Number(waitlistSize) : 0, admins: allOrgAdminsCanEdit ? [] : eventAdminIds, all_org_admins_can_edit: allOrgAdminsCanEdit, + publish_date: toPublishDateValue(status, publishDate), }; setSubmitting(true); @@ -331,6 +352,8 @@ export default function EditEventPage() { setWaitlistEnabled, waitlistSize, setWaitlistSize, + publishDate, + setPublishDate, }} questionActions={{ questions, diff --git a/src/Pages/Events/EventEditorForm.js b/src/Pages/Events/EventEditorForm.js index 72872fb67..9ff3ca1d2 100644 --- a/src/Pages/Events/EventEditorForm.js +++ b/src/Pages/Events/EventEditorForm.js @@ -47,6 +47,8 @@ export default function EventEditorForm({ setWaitlistEnabled, waitlistSize, setWaitlistSize, + publishDate, + setPublishDate, } = form; const { @@ -243,7 +245,14 @@ export default function EventEditorForm({ + +
{visibility === 'private' && ( diff --git a/src/Pages/Events/EventIcons.js b/src/Pages/Events/EventIcons.js index ca7a167cc..1b4e43adb 100644 --- a/src/Pages/Events/EventIcons.js +++ b/src/Pages/Events/EventIcons.js @@ -106,3 +106,13 @@ export function EditIcon() { ); } + +export function ScheduleIcon() { + return ( + + ); +}