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 && (
+ 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 (
+