feat(events): accept 'TBD' as a valid location

Events are often scheduled before the platform (Zoom link, Slack
channel) is chosen. The current workaround is a placeholder URL like
"https://us02web.zoom.us/j/TBD", which leaks to the public page as a
broken link.

Accept the literal "TBD" (case-insensitive) in both the Mongoose
validator and the form-side validator. The public detail page renders
"Platform TBD" instead of a link when the location matches.
This commit is contained in:
Jennie Robinson Faber 2026-05-19 10:35:49 +01:00
parent e1d224e260
commit 6fa3e08fe0
3 changed files with 16 additions and 12 deletions

View file

@ -114,7 +114,7 @@
<label> Location <span class="required">*</span> </label>
<UInput
v-model="eventForm.location"
placeholder="e.g., https://zoom.us/j/123... or #channel-name"
placeholder="e.g., https://zoom.us/j/123..., #channel-name, or TBD"
required
:color="fieldErrors.location ? 'error' : undefined"
class="w-full"
@ -123,7 +123,8 @@
{{ fieldErrors.location }}
</p>
<p v-if="!fieldErrors.location" class="help-text">
Enter a video conference link or Slack channel (starting with #)
Video conference link, Slack channel (#channel-name), or 'TBD' if
the platform is undecided
</p>
</div>
@ -840,7 +841,7 @@ const validateForm = () => {
if (!eventForm.location.trim()) {
formErrors.value.push("Location is required");
fieldErrors.value.location =
"Please enter a location (URL or Slack channel)";
"Please enter a URL, Slack channel, or 'TBD'";
}
// Date validation
@ -861,18 +862,17 @@ const validateForm = () => {
// Location format validation
if (eventForm.location.trim()) {
const value = eventForm.location.trim();
const urlPattern = /^https?:\/\/.+/;
const slackPattern = /^#[a-zA-Z0-9-_]+$/;
const isTbd = value.toUpperCase() === "TBD";
if (
!urlPattern.test(eventForm.location) &&
!slackPattern.test(eventForm.location)
) {
if (!isTbd && !urlPattern.test(value) && !slackPattern.test(value)) {
formErrors.value.push(
"Location must be a valid URL or Slack channel (starting with #)",
"Location must be a valid URL, Slack channel (starting with #), or 'TBD'",
);
fieldErrors.value.location =
"Enter a video conference link (https://...) or Slack channel (#channel-name)";
"Enter a URL (https://...), Slack channel (#channel-name), or 'TBD' if undecided";
}
}

View file

@ -22,7 +22,10 @@
</div>
<div class="event-meta-item">
<span class="meta-label">Location</span>
{{ event.location }}
<span v-if="event.location?.trim().toUpperCase() === 'TBD'">
Platform TBD
</span>
<template v-else>{{ event.location }}</template>
</div>
<div v-if="event.circle" class="event-meta-item">
<CircleBadge :circle="event.circle" />

View file

@ -25,13 +25,14 @@ const eventSchema = new mongoose.Schema({
// This will typically be a Slack channel or video conference link
validate: {
validator: function (v) {
// Must be either a valid URL or a Slack channel reference
// Accept a URL, a Slack channel, or the literal "TBD" (platform undecided).
if (typeof v === "string" && v.trim().toUpperCase() === "TBD") return true;
const urlPattern = /^https?:\/\/.+/;
const slackPattern = /^#[a-zA-Z0-9-_]+$/;
return urlPattern.test(v) || slackPattern.test(v);
},
message:
"Location must be a valid URL (video conference link) or Slack channel (starting with #)",
"Location must be a valid URL, Slack channel (starting with #), or 'TBD'",
},
},
isOnline: { type: Boolean, default: true }, // Default to online-first