From c859230862c0a7851c2d1e239f527e7c96a65a06 Mon Sep 17 00:00:00 2001
From: Linus Rath <139418639+rathlinus@users.noreply.github.com>
Date: Mon, 30 Mar 2026 15:19:47 +0200
Subject: [PATCH] feat: add virtual location input #121
---
components/calendar/event-modal.tsx | 36 ++++++++++++++++++++++++++++-
locales/de/common.json | 1 +
locales/en/common.json | 1 +
locales/es/common.json | 1 +
locales/fr/common.json | 1 +
locales/it/common.json | 1 +
locales/ja/common.json | 1 +
locales/nl/common.json | 1 +
locales/pt/common.json | 1 +
locales/ru/common.json | 1 +
10 files changed, 44 insertions(+), 1 deletion(-)
diff --git a/components/calendar/event-modal.tsx b/components/calendar/event-modal.tsx
index c6178d06..b0901965 100644
--- a/components/calendar/event-modal.tsx
+++ b/components/calendar/event-modal.tsx
@@ -188,6 +188,9 @@ export function EventModal({
const [location, setLocation] = useState(
event?.locations ? Object.values(event.locations)[0]?.name || "" : ""
);
+ const [virtualLocation, setVirtualLocation] = useState(
+ event?.virtualLocations ? Object.values(event.virtualLocations)[0]?.uri || "" : ""
+ );
const [startDate, setStartDate] = useState(formatDateInput(getInitialStart()));
const [startTime, setStartTime] = useState(formatTimeInput(getInitialStart()));
const [endDate, setEndDate] = useState(formatDateInput(getInitialEnd()));
@@ -313,6 +316,20 @@ export function EventModal({
data.locations = null;
}
+ if (virtualLocation.trim()) {
+ data.virtualLocations = {
+ vl1: {
+ "@type": "VirtualLocation",
+ name: null,
+ description: null,
+ uri: virtualLocation.trim(),
+ features: null,
+ },
+ };
+ } else if (event && event.virtualLocations && Object.keys(event.virtualLocations).length > 0) {
+ data.virtualLocations = null;
+ }
+
if (recurrence !== "none") {
data.recurrenceRules = [{
"@type": "RecurrenceRule",
@@ -372,7 +389,7 @@ export function EventModal({
} finally {
setIsSaving(false);
}
- }, [title, description, location, startDate, startTime, endDate, endTime, allDay, calendarId, recurrence, alert, attendees, sendInvitations, currentUserEmails, existingParticipants, event, onSave, isSaving]);
+ }, [title, description, location, virtualLocation, startDate, startTime, endDate, endTime, allDay, calendarId, recurrence, alert, attendees, sendInvitations, currentUserEmails, existingParticipants, event, onSave, isSaving]);
const handleRsvp = useCallback((status: CalendarParticipant['participationStatus']) => {
if (!event || !userParticipantId || !onRsvp) return;
@@ -401,6 +418,7 @@ export function EventModal({
privacy: event.privacy,
};
if (event.locations) data.locations = structuredClone(event.locations);
+ if (event.virtualLocations) data.virtualLocations = structuredClone(event.virtualLocations);
if (event.recurrenceRules) data.recurrenceRules = structuredClone(event.recurrenceRules);
if (event.alerts) data.alerts = structuredClone(event.alerts);
if (event.participants) data.participants = structuredClone(event.participants);
@@ -778,6 +796,22 @@ export function EventModal({
/>
+
+
+ setVirtualLocation(e.target.value)}
+ placeholder="https://meet.example.com/..."
+ maxLength={2000}
+ />
+
+