@agent-native/core 0.98.7 → 0.98.9

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (81) hide show
  1. package/corpus/README.md +1 -1
  2. package/corpus/core/CHANGELOG.md +12 -0
  3. package/corpus/core/docs/content/external-agents.mdx +17 -11
  4. package/corpus/core/docs/content/locales/ar-SA/external-agents.mdx +6 -2
  5. package/corpus/core/docs/content/locales/de-DE/external-agents.mdx +6 -2
  6. package/corpus/core/docs/content/locales/es-ES/external-agents.mdx +6 -2
  7. package/corpus/core/docs/content/locales/fr-FR/external-agents.mdx +6 -2
  8. package/corpus/core/docs/content/locales/hi-IN/external-agents.mdx +6 -2
  9. package/corpus/core/docs/content/locales/ja-JP/external-agents.mdx +6 -2
  10. package/corpus/core/docs/content/locales/ko-KR/external-agents.mdx +6 -2
  11. package/corpus/core/docs/content/locales/pt-BR/external-agents.mdx +6 -2
  12. package/corpus/core/docs/content/locales/zh-CN/external-agents.mdx +6 -2
  13. package/corpus/core/docs/content/locales/zh-TW/external-agents.mdx +6 -2
  14. package/corpus/core/package.json +1 -1
  15. package/corpus/core/src/client/session-replay.ts +29 -7
  16. package/corpus/core/src/integrations/adapters/slack.ts +13 -2
  17. package/corpus/core/src/integrations/pending-tasks-retry-job.ts +2 -1
  18. package/corpus/core/src/integrations/pending-tasks-store.ts +1 -1
  19. package/corpus/core/src/integrations/plugin.ts +183 -48
  20. package/corpus/core/src/integrations/types.ts +7 -0
  21. package/corpus/core/src/mcp/build-server.ts +99 -26
  22. package/corpus/templates/calendar/.agents/skills/event-management/SKILL.md +31 -2
  23. package/corpus/templates/calendar/AGENTS.md +11 -0
  24. package/corpus/templates/calendar/README.md +2 -1
  25. package/corpus/templates/calendar/actions/create-event.ts +7 -6
  26. package/corpus/templates/calendar/actions/event-action-helpers.ts +49 -0
  27. package/corpus/templates/calendar/actions/update-event.ts +225 -14
  28. package/corpus/templates/calendar/app/components/calendar/DayView.tsx +185 -70
  29. package/corpus/templates/calendar/app/components/calendar/EventCard.tsx +26 -4
  30. package/corpus/templates/calendar/app/components/calendar/EventDetailPanel.tsx +112 -69
  31. package/corpus/templates/calendar/app/components/calendar/EventDetailPopover.tsx +621 -524
  32. package/corpus/templates/calendar/app/components/calendar/WeekView.tsx +275 -164
  33. package/corpus/templates/calendar/app/components/calendar/WorkingLocationEditor.tsx +222 -0
  34. package/corpus/templates/calendar/app/hooks/use-events.ts +151 -79
  35. package/corpus/templates/calendar/app/i18n/zh-TW.ts +6 -0
  36. package/corpus/templates/calendar/app/i18n-data.ts +60 -0
  37. package/corpus/templates/calendar/app/lib/all-day-layout.ts +126 -0
  38. package/corpus/templates/calendar/app/lib/event-form-utils.ts +18 -1
  39. package/corpus/templates/calendar/app/lib/event-mutation-inputs.ts +1 -1
  40. package/corpus/templates/calendar/app/lib/working-location.ts +163 -0
  41. package/corpus/templates/calendar/app/pages/CalendarView.tsx +21 -2
  42. package/corpus/templates/calendar/changelog/2026-07-07-working-locations-from-google-calendar-now-appear-as-native-.md +6 -0
  43. package/corpus/templates/calendar/server/lib/calendar-availability.ts +2 -0
  44. package/corpus/templates/calendar/server/lib/google-api.ts +6 -1
  45. package/corpus/templates/calendar/server/lib/google-calendar.ts +49 -19
  46. package/corpus/templates/calendar/shared/api.ts +2 -0
  47. package/dist/client/session-replay.d.ts.map +1 -1
  48. package/dist/client/session-replay.js +24 -7
  49. package/dist/client/session-replay.js.map +1 -1
  50. package/dist/collab/routes.d.ts +2 -2
  51. package/dist/integrations/adapters/slack.js +11 -2
  52. package/dist/integrations/adapters/slack.js.map +1 -1
  53. package/dist/integrations/pending-tasks-retry-job.d.ts.map +1 -1
  54. package/dist/integrations/pending-tasks-retry-job.js +2 -1
  55. package/dist/integrations/pending-tasks-retry-job.js.map +1 -1
  56. package/dist/integrations/pending-tasks-store.js +1 -1
  57. package/dist/integrations/pending-tasks-store.js.map +1 -1
  58. package/dist/integrations/plugin.d.ts.map +1 -1
  59. package/dist/integrations/plugin.js +140 -34
  60. package/dist/integrations/plugin.js.map +1 -1
  61. package/dist/integrations/types.d.ts +7 -0
  62. package/dist/integrations/types.d.ts.map +1 -1
  63. package/dist/integrations/types.js.map +1 -1
  64. package/dist/mcp/build-server.d.ts.map +1 -1
  65. package/dist/mcp/build-server.js +82 -28
  66. package/dist/mcp/build-server.js.map +1 -1
  67. package/dist/notifications/routes.d.ts +1 -1
  68. package/dist/observability/routes.d.ts +5 -5
  69. package/dist/secrets/routes.d.ts +9 -9
  70. package/docs/content/external-agents.mdx +17 -11
  71. package/docs/content/locales/ar-SA/external-agents.mdx +6 -2
  72. package/docs/content/locales/de-DE/external-agents.mdx +6 -2
  73. package/docs/content/locales/es-ES/external-agents.mdx +6 -2
  74. package/docs/content/locales/fr-FR/external-agents.mdx +6 -2
  75. package/docs/content/locales/hi-IN/external-agents.mdx +6 -2
  76. package/docs/content/locales/ja-JP/external-agents.mdx +6 -2
  77. package/docs/content/locales/ko-KR/external-agents.mdx +6 -2
  78. package/docs/content/locales/pt-BR/external-agents.mdx +6 -2
  79. package/docs/content/locales/zh-CN/external-agents.mdx +6 -2
  80. package/docs/content/locales/zh-TW/external-agents.mdx +6 -2
  81. package/package.json +1 -1
@@ -47,6 +47,7 @@ import {
47
47
  } from "@/components/calendar/EventOptionControls";
48
48
  import { FindTimeTakeover } from "@/components/calendar/FindTimePanel";
49
49
  import { useGuestNotificationPrompt } from "@/components/calendar/GuestNotificationDialog";
50
+ import { WorkingLocationEditor } from "@/components/calendar/WorkingLocationEditor";
50
51
  import { useCalendarContext } from "@/components/layout/AppLayout";
51
52
  import { TimezoneCombobox } from "@/components/TimezoneCombobox";
52
53
  import { Button } from "@/components/ui/button";
@@ -89,7 +90,9 @@ import {
89
90
  getEventEndValidationMessage,
90
91
  getLocalTimezone,
91
92
  getRecurrencePreset,
93
+ normalizeAllDayEditEndDate,
92
94
  remindersToDraftState,
95
+ resolveTimeEditScope,
93
96
  type AttachmentDraft,
94
97
  type RecurrencePreset,
95
98
  type ReminderDraft,
@@ -102,6 +105,13 @@ import {
102
105
  setEventDetailPopoverOpen,
103
106
  } from "@/lib/popover-click-guard";
104
107
  import { shortcutModifierLabel } from "@/lib/utils";
108
+ import {
109
+ buildWorkingLocationUpdate,
110
+ createWorkingLocationDisplayLabels,
111
+ getWorkingLocationTitle,
112
+ isWorkingLocationEvent,
113
+ type WorkingLocationSelection,
114
+ } from "@/lib/working-location";
105
115
 
106
116
  const ZOOM_AFTER_CONNECT_EVENT_ID_KEY = "calendar.zoomAfterConnectEventId";
107
117
  const ZOOM_AFTER_CONNECT_MAX_AGE_MS = 10 * 60 * 1000;
@@ -299,6 +309,8 @@ type EventUpdatePatch = Partial<CalendarEvent> & {
299
309
  addZoom?: boolean;
300
310
  addAttendees?: CalendarEvent["attendees"];
301
311
  scope?: UpdateEventScope;
312
+ workingLocationType?: "homeOffice" | "officeLocation" | "customLocation";
313
+ workingLocationLabel?: string;
302
314
  };
303
315
 
304
316
  function mergeAttendeesForPrompt(
@@ -495,6 +507,7 @@ export function EventDetailPopover({
495
507
  onDraftDiscard,
496
508
  }: EventDetailPopoverProps) {
497
509
  const t = useT();
510
+ const workingLocationLabels = createWorkingLocationDisplayLabels(t);
498
511
  const isMobile = useIsMobile();
499
512
  const [open, setOpen] = useState(defaultOpen);
500
513
  const [editingTitle, setEditingTitle] = useState(
@@ -513,6 +526,9 @@ export function EventDetailPopover({
513
526
  setSidebarEvent,
514
527
  setFocusedEvent,
515
528
  } = useCalendarContext();
529
+ const isWorkingLocation = isWorkingLocationEvent(event);
530
+ const isSingleDayWorkingLocation = isWorkingLocation && event.allDay;
531
+ const editableLocationValue = event.location || "";
516
532
 
517
533
  // Inline editing state
518
534
  const [editingField, setEditingField] = useState<string | null>(null);
@@ -520,7 +536,7 @@ export function EventDetailPopover({
520
536
  const [editDescription, setEditDescription] = useState(
521
537
  event.description || "",
522
538
  );
523
- const [editLocation, setEditLocation] = useState(event.location || "");
539
+ const [editLocation, setEditLocation] = useState(editableLocationValue);
524
540
  const [editDate, setEditDate] = useState(() => toDateInputValue(event.start));
525
541
  const [editEndDate, setEditEndDate] = useState(() =>
526
542
  event.allDay
@@ -588,7 +604,7 @@ export function EventDetailPopover({
588
604
  useEffect(() => {
589
605
  if (editingField !== "description")
590
606
  setEditDescription(event.description || "");
591
- if (editingField !== "location") setEditLocation(event.location || "");
607
+ if (editingField !== "location") setEditLocation(editableLocationValue);
592
608
  if (editingField !== "time") {
593
609
  setEditDate(toDateInputValue(event.start));
594
610
  setEditEndDate(
@@ -618,6 +634,7 @@ export function EventDetailPopover({
618
634
  event.id,
619
635
  event.description,
620
636
  event.location,
637
+ event.workingLocationProperties,
621
638
  event.start,
622
639
  event.end,
623
640
  event.allDay,
@@ -625,6 +642,7 @@ export function EventDetailPopover({
625
642
  event.reminders,
626
643
  event.remindersUseDefault,
627
644
  event.attachments,
645
+ editableLocationValue,
628
646
  ]);
629
647
 
630
648
  useEffect(() => {
@@ -673,7 +691,6 @@ export function EventDetailPopover({
673
691
  ? event.visibility
674
692
  : "default";
675
693
  const reminderValue = getReminderValue(event);
676
-
677
694
  // Save a field update
678
695
  const saveField = useCallback(
679
696
  (updates: EventUpdatePatch) => {
@@ -958,13 +975,14 @@ export function EventDetailPopover({
958
975
  const locationContainsMeetingLink =
959
976
  !!meetingLink && event.location?.includes(meetingLink.url);
960
977
  if (locationContainsMeetingLink && !trimmed) {
961
- setEditLocation(event.location || "");
978
+ setEditLocation(editableLocationValue);
962
979
  setEditingField(null);
963
980
  return false;
964
981
  }
965
982
  let saved = false;
966
- if (trimmed !== (event.location || "").trim()) {
967
- const updates: Partial<CalendarEvent> = { location: trimmed };
983
+ const currentValue = (event.location || "").trim();
984
+ if (trimmed !== currentValue.trim()) {
985
+ const updates: EventUpdatePatch = { location: trimmed };
968
986
  if (
969
987
  locationContainsMeetingLink &&
970
988
  meetingLink &&
@@ -982,10 +1000,38 @@ export function EventDetailPopover({
982
1000
  }
983
1001
  setEditingField(null);
984
1002
  return saved;
985
- }, [editLocation, event.description, event.location, meetingLink, saveField]);
1003
+ }, [
1004
+ editLocation,
1005
+ editableLocationValue,
1006
+ event.description,
1007
+ event.location,
1008
+ meetingLink,
1009
+ saveField,
1010
+ t,
1011
+ ]);
1012
+
1013
+ const handleSaveWorkingLocation = useCallback(
1014
+ (selection: WorkingLocationSelection) => {
1015
+ const update = buildWorkingLocationUpdate(event, selection);
1016
+ if (isDraft) {
1017
+ const { id: _id, scope: _scope, ...draftUpdate } = update;
1018
+ onDraftUpdate?.(event.id, draftUpdate);
1019
+ return;
1020
+ }
1021
+ updateEvent.mutate(update, {
1022
+ onError: () => toast.error(t("calendarView.failedUpdateEvent")),
1023
+ });
1024
+ },
1025
+ [event, isDraft, onDraftUpdate, t, updateEvent],
1026
+ );
986
1027
 
987
1028
  const handleSaveTime = useCallback(() => {
988
- const allDayEnd = new Date(`${editEndDate}T00:00:00`);
1029
+ const normalizedEndDate = normalizeAllDayEditEndDate(
1030
+ isSingleDayWorkingLocation,
1031
+ editDate,
1032
+ editEndDate,
1033
+ );
1034
+ const allDayEnd = new Date(`${normalizedEndDate}T00:00:00`);
989
1035
  allDayEnd.setDate(allDayEnd.getDate() + 1);
990
1036
  const newStart = event.allDay
991
1037
  ? new Date(`${editDate}T00:00:00`).toISOString()
@@ -1013,7 +1059,11 @@ export function EventDetailPopover({
1013
1059
  allDay: event.allDay,
1014
1060
  startTimeZone: event.allDay ? undefined : editTimezone,
1015
1061
  endTimeZone: event.allDay ? undefined : editTimezone,
1016
- scope: isRecurringEvent ? editTimeScope : "single",
1062
+ scope: resolveTimeEditScope(
1063
+ isRecurringEvent,
1064
+ isSingleDayWorkingLocation,
1065
+ editTimeScope,
1066
+ ),
1017
1067
  });
1018
1068
  }
1019
1069
  setEditTimeScope("single");
@@ -1028,6 +1078,7 @@ export function EventDetailPopover({
1028
1078
  event.start,
1029
1079
  event.end,
1030
1080
  event.allDay,
1081
+ isSingleDayWorkingLocation,
1031
1082
  isRecurringEvent,
1032
1083
  editTimeScope,
1033
1084
  saveField,
@@ -1346,7 +1397,11 @@ export function EventDetailPopover({
1346
1397
  <div className="flex items-center justify-between px-4 py-2.5 border-b border-border">
1347
1398
  <div className="text-[11px] font-medium uppercase tracking-wider text-muted-foreground">
1348
1399
  <span>
1349
- {isDraft ? t("eventForm.draftEvent") : t("eventForm.event")}
1400
+ {isWorkingLocation
1401
+ ? t("eventForm.workingLocation")
1402
+ : isDraft
1403
+ ? t("eventForm.draftEvent")
1404
+ : t("eventForm.event")}
1350
1405
  </span>
1351
1406
  </div>
1352
1407
  <div className="flex items-center gap-0.5">
@@ -1382,7 +1437,7 @@ export function EventDetailPopover({
1382
1437
  <div className="flex-1 overflow-y-auto">
1383
1438
  <div className="px-4 pt-4 pb-1">
1384
1439
  {/* Title — always editable */}
1385
- {isEditingTitle ? (
1440
+ {isEditingTitle && !isWorkingLocation ? (
1386
1441
  <input
1387
1442
  ref={titleInputRef}
1388
1443
  value={editingTitle}
@@ -1432,14 +1487,14 @@ export function EventDetailPopover({
1432
1487
  />
1433
1488
  ) : (
1434
1489
  <h2
1435
- className="text-lg font-semibold text-foreground leading-tight mb-4 cursor-text rounded px-0.5 -mx-0.5 hover:bg-muted/50"
1490
+ className={`mb-4 -mx-0.5 rounded px-0.5 text-lg font-semibold leading-tight text-foreground ${!isOverlay && !isWorkingLocation ? "cursor-text hover:bg-muted/50" : ""}`}
1436
1491
  onClick={() => {
1437
- if (isOverlay) return;
1492
+ if (isOverlay || isWorkingLocation) return;
1438
1493
  setEditingTitle(event.title);
1439
1494
  setIsEditingTitle(true);
1440
1495
  }}
1441
1496
  >
1442
- {event.title}
1497
+ {getWorkingLocationTitle(event, workingLocationLabels)}
1443
1498
  </h2>
1444
1499
  )}
1445
1500
  </div>
@@ -1459,7 +1514,13 @@ export function EventDetailPopover({
1459
1514
  <div className="flex items-start gap-3 py-1.5">
1460
1515
  <IconClock className="mt-2 h-4 w-4 shrink-0 text-muted-foreground" />
1461
1516
  <div className="flex-1 space-y-2">
1462
- <div className="grid grid-cols-2 gap-2">
1517
+ <div
1518
+ className={`grid gap-2 ${
1519
+ isSingleDayWorkingLocation
1520
+ ? "grid-cols-1"
1521
+ : "grid-cols-2"
1522
+ }`}
1523
+ >
1463
1524
  <input
1464
1525
  type="date"
1465
1526
  value={editDate}
@@ -1467,40 +1528,49 @@ export function EventDetailPopover({
1467
1528
  const next = e.target.value;
1468
1529
  setEditDate(next);
1469
1530
  setEditEndDate((current) =>
1470
- current < next ? next : current,
1531
+ // i18n-ignore -- this expression selects a date; it is not visible copy.
1532
+ isSingleDayWorkingLocation
1533
+ ? next
1534
+ : current < next
1535
+ ? next
1536
+ : current,
1471
1537
  );
1472
1538
  }}
1473
1539
  className="w-full rounded-md border border-border bg-transparent px-2 py-1 text-sm text-foreground"
1474
1540
  aria-label={t("eventForm.startDate")}
1475
1541
  />
1476
- <input
1477
- type="date"
1478
- min={editDate}
1479
- value={editEndDate}
1480
- onChange={(e) =>
1481
- setEditEndDate(e.target.value || editDate)
1482
- }
1483
- className="w-full rounded-md border border-border bg-transparent px-2 py-1 text-sm text-foreground"
1484
- aria-label={t("eventForm.endDate")}
1485
- />
1486
- </div>
1487
- <div className="flex items-center gap-2">
1488
- <input
1489
- type="time"
1490
- value={editStartTime}
1491
- onChange={(e) => setEditStartTime(e.target.value)}
1492
- className="flex-1 rounded-md border border-border bg-transparent px-2 py-1 text-sm text-foreground"
1493
- />
1494
- <span className="text-muted-foreground/50 text-xs">
1495
- &rarr;
1496
- </span>
1497
- <input
1498
- type="time"
1499
- value={editEndTime}
1500
- onChange={(e) => setEditEndTime(e.target.value)}
1501
- className="flex-1 rounded-md border border-border bg-transparent px-2 py-1 text-sm text-foreground"
1502
- />
1542
+ {!isSingleDayWorkingLocation && (
1543
+ <input
1544
+ type="date"
1545
+ min={editDate}
1546
+ value={editEndDate}
1547
+ onChange={(e) =>
1548
+ setEditEndDate(e.target.value || editDate)
1549
+ }
1550
+ className="w-full rounded-md border border-border bg-transparent px-2 py-1 text-sm text-foreground"
1551
+ aria-label={t("eventForm.endDate")}
1552
+ />
1553
+ )}
1503
1554
  </div>
1555
+ {!event.allDay && (
1556
+ <div className="flex items-center gap-2">
1557
+ <input
1558
+ type="time"
1559
+ value={editStartTime}
1560
+ onChange={(e) => setEditStartTime(e.target.value)}
1561
+ className="flex-1 rounded-md border border-border bg-transparent px-2 py-1 text-sm text-foreground"
1562
+ />
1563
+ <span className="text-muted-foreground/50 text-xs">
1564
+ &rarr;
1565
+ </span>
1566
+ <input
1567
+ type="time"
1568
+ value={editEndTime}
1569
+ onChange={(e) => setEditEndTime(e.target.value)}
1570
+ className="flex-1 rounded-md border border-border bg-transparent px-2 py-1 text-sm text-foreground"
1571
+ />
1572
+ </div>
1573
+ )}
1504
1574
  {!event.allDay && (
1505
1575
  <TimezoneCombobox
1506
1576
  id={`event-timezone-${event.id}`}
@@ -1508,31 +1578,37 @@ export function EventDetailPopover({
1508
1578
  onChange={setEditTimezone}
1509
1579
  />
1510
1580
  )}
1511
- {isRecurringEvent && !isDraft && (
1512
- <div className="flex items-center gap-2">
1513
- <span className="text-xs text-muted-foreground">
1514
- {t("eventForm.applyTo")}
1515
- </span>
1516
- <Select
1517
- value={editTimeScope}
1518
- onValueChange={(value) =>
1519
- setEditTimeScope(value as UpdateEventScope)
1520
- }
1521
- >
1522
- <SelectTrigger className="h-7 flex-1 text-xs">
1523
- <SelectValue />
1524
- </SelectTrigger>
1525
- <SelectContent>
1526
- <SelectItem value="single">
1527
- {t("eventForm.thisEvent")}
1528
- </SelectItem>
1529
- <SelectItem value="all">
1530
- {t("eventForm.allEvents")}
1531
- </SelectItem>
1532
- </SelectContent>
1533
- </Select>
1534
- </div>
1535
- )}
1581
+ {isRecurringEvent &&
1582
+ !isDraft &&
1583
+ !isSingleDayWorkingLocation && (
1584
+ <div className="flex items-center gap-2">
1585
+ <span className="text-xs text-muted-foreground">
1586
+ {t("eventForm.applyTo")}
1587
+ </span>
1588
+ <Select
1589
+ value={editTimeScope}
1590
+ onValueChange={(value) =>
1591
+ setEditTimeScope(value as UpdateEventScope)
1592
+ }
1593
+ >
1594
+ <SelectTrigger className="h-7 flex-1 text-xs">
1595
+ <SelectValue />
1596
+ </SelectTrigger>
1597
+ <SelectContent>
1598
+ <SelectItem value="single">
1599
+ {isWorkingLocation
1600
+ ? t("eventForm.thisDayOnly")
1601
+ : t("eventForm.thisEvent")}
1602
+ </SelectItem>
1603
+ <SelectItem value="all">
1604
+ {isWorkingLocation
1605
+ ? t("eventForm.allDays")
1606
+ : t("eventForm.allEvents")}
1607
+ </SelectItem>
1608
+ </SelectContent>
1609
+ </Select>
1610
+ </div>
1611
+ )}
1536
1612
  <div className="flex justify-end gap-1.5">
1537
1613
  <Button
1538
1614
  variant="ghost"
@@ -1614,7 +1690,7 @@ export function EventDetailPopover({
1614
1690
  </div>
1615
1691
  )}
1616
1692
 
1617
- {!event.allDay && !isOverlay && (
1693
+ {!event.allDay && !isOverlay && !isWorkingLocation && (
1618
1694
  <div className="flex items-center gap-3 py-1">
1619
1695
  <div className="h-4 w-4 shrink-0" />
1620
1696
  <Button
@@ -1630,7 +1706,7 @@ export function EventDetailPopover({
1630
1706
  </div>
1631
1707
  )}
1632
1708
 
1633
- {!event.allDay && !isOverlay && (
1709
+ {!event.allDay && !isOverlay && !isWorkingLocation && (
1634
1710
  <FindTimeTakeover
1635
1711
  open={findTimeOpen}
1636
1712
  onOpenChange={setFindTimeOpen}
@@ -1742,222 +1818,237 @@ export function EventDetailPopover({
1742
1818
  )}
1743
1819
  </button>
1744
1820
  ) : null}
1821
+
1822
+ {isWorkingLocation && (
1823
+ <WorkingLocationEditor
1824
+ event={event}
1825
+ isRecurring={isRecurringEvent}
1826
+ readOnly={isOverlay}
1827
+ disabled={updateEvent.isPending}
1828
+ onSave={handleSaveWorkingLocation}
1829
+ />
1830
+ )}
1745
1831
  </div>
1746
1832
 
1747
- {/* Separator */}
1748
- <div className="mx-4 my-2 border-t border-border/50" />
1749
-
1750
- {/* Attendees always shown */}
1751
- {event.attendees && event.attendees.length > 0 ? (
1752
- <EventAttendeesSection
1753
- event={event}
1754
- canEditOptional={!isOverlay}
1755
- onToggleOptional={handleToggleAttendeeOptional}
1756
- />
1757
- ) : !isOverlay ? (
1758
- <div className="px-4 py-1">
1759
- <div className="flex items-start gap-3">
1760
- <IconUser className="mt-0.5 h-4 w-4 shrink-0 text-muted-foreground" />
1761
- <span className="text-sm text-muted-foreground/60">
1762
- {t("eventForm.noGuests")}
1763
- </span>
1764
- </div>
1765
- </div>
1766
- ) : null}
1767
-
1768
- {/* Add guest input */}
1769
- {!isOverlay && (
1770
- <div className="px-4 py-1">
1771
- <div className="flex items-center gap-3">
1772
- <IconPlus className="h-4 w-4 shrink-0 text-muted-foreground/40" />
1773
- <AttendeeAutocomplete
1774
- selectedEmails={(event.attendees || []).map(
1775
- (attendee) => attendee.email,
1776
- )}
1777
- onAdd={handleAddAttendee}
1778
- placeholder={t("eventForm.addGuests")}
1779
- variant="inline"
1780
- showChips={false}
1781
- showAddButton
1782
- inputClassName="text-foreground placeholder:text-muted-foreground/40"
1833
+ {!isWorkingLocation && (
1834
+ <>
1835
+ {/* Separator */}
1836
+ <div className="mx-4 my-2 border-t border-border/50" />
1837
+
1838
+ {/* Attendees — always shown */}
1839
+ {event.attendees && event.attendees.length > 0 ? (
1840
+ <EventAttendeesSection
1841
+ event={event}
1842
+ canEditOptional={!isOverlay}
1843
+ onToggleOptional={handleToggleAttendeeOptional}
1783
1844
  />
1784
- </div>
1785
- </div>
1786
- )}
1845
+ ) : !isOverlay ? (
1846
+ <div className="px-4 py-1">
1847
+ <div className="flex items-start gap-3">
1848
+ <IconUser className="mt-0.5 h-4 w-4 shrink-0 text-muted-foreground" />
1849
+ <span className="text-sm text-muted-foreground/60">
1850
+ {t("eventForm.noGuests")}
1851
+ </span>
1852
+ </div>
1853
+ </div>
1854
+ ) : null}
1855
+
1856
+ {/* Add guest input */}
1857
+ {!isOverlay && (
1858
+ <div className="px-4 py-1">
1859
+ <div className="flex items-center gap-3">
1860
+ <IconPlus className="h-4 w-4 shrink-0 text-muted-foreground/40" />
1861
+ <AttendeeAutocomplete
1862
+ selectedEmails={(event.attendees || []).map(
1863
+ (attendee) => attendee.email,
1864
+ )}
1865
+ onAdd={handleAddAttendee}
1866
+ placeholder={t("eventForm.addGuests")}
1867
+ variant="inline"
1868
+ showChips={false}
1869
+ showAddButton
1870
+ inputClassName="text-foreground placeholder:text-muted-foreground/40"
1871
+ />
1872
+ </div>
1873
+ </div>
1874
+ )}
1875
+
1876
+ {/* Research Meeting button */}
1877
+ {event.attendees && event.attendees.length > 0 && (
1878
+ <>
1879
+ <div className="mx-4 my-2 border-t border-border/50" />
1880
+ <div className="px-4 py-1">
1881
+ <ResearchMeetingButton event={event} />
1882
+ </div>
1883
+ </>
1884
+ )}
1787
1885
 
1788
- {/* Research Meeting button */}
1789
- {event.attendees && event.attendees.length > 0 && (
1790
- <>
1791
1886
  <div className="mx-4 my-2 border-t border-border/50" />
1792
1887
  <div className="px-4 py-1">
1793
- <ResearchMeetingButton event={event} />
1888
+ <ExtensionSlot
1889
+ id="calendar.event-detail.bottom"
1890
+ context={buildEventDetailSlotContext(event)}
1891
+ showEmptyAffordance
1892
+ />
1794
1893
  </div>
1795
1894
  </>
1796
1895
  )}
1797
1896
 
1798
- <div className="mx-4 my-2 border-t border-border/50" />
1799
- <div className="px-4 py-1">
1800
- <ExtensionSlot
1801
- id="calendar.event-detail.bottom"
1802
- context={buildEventDetailSlotContext(event)}
1803
- showEmptyAffordance
1804
- />
1805
- </div>
1806
-
1807
1897
  {/* Meeting link */}
1808
- {meetingLink ? (
1809
- <>
1810
- <div className="mx-4 my-2 border-t border-border/50" />
1811
- <div className="px-4 py-1.5">
1812
- <a
1813
- href={meetingLink.url}
1814
- target="_blank"
1815
- rel="noopener noreferrer"
1816
- className="flex items-center justify-center w-full rounded-xl bg-[#4965E0] hover:bg-[#5A75F0] text-white font-semibold py-2 px-4 text-[15px] relative"
1817
- >
1818
- <IconVideo className="h-5 w-5 mr-2 opacity-80" />
1819
- <span>{getMeetingLabel(meetingLink.type, t)}</span>
1820
- <span className="absolute right-4 hidden items-center gap-1 opacity-50 sm:flex">
1821
- <kbd className="text-xs font-normal">
1822
- {shortcutModifierLabel()}
1823
- </kbd>
1824
- <kbd className="inline-flex h-5 w-5 items-center justify-center rounded bg-white/20 text-[11px] font-medium">
1825
- J
1826
- </kbd>
1827
- </span>
1828
- </a>
1829
- {(meetingLink.pin || meetingLink.passcode) && (
1830
- <div className="mt-1.5 text-xs text-muted-foreground/60">
1831
- {meetingLink.pin && (
1832
- <span>
1833
- {t("eventForm.pin", { pin: meetingLink.pin })}
1834
- </span>
1835
- )}
1836
- {meetingLink.pin && meetingLink.passcode && (
1837
- <span className="mx-1">&middot;</span>
1838
- )}
1839
- {meetingLink.passcode && (
1840
- <span>
1841
- {t("eventForm.passcode", {
1842
- passcode: meetingLink.passcode,
1843
- })}
1844
- </span>
1845
- )}
1846
- </div>
1847
- )}
1848
- </div>
1849
- </>
1850
- ) : pendingConferenceProvider ? (
1851
- <>
1852
- <div className="mx-4 my-2 border-t border-border/50" />
1853
- <div className="px-4 py-1.5">
1854
- <div className="flex w-full items-center rounded-xl bg-[#4965E0] py-2 pl-4 pr-2 text-white">
1855
- {pendingConferenceProvider === "zoom" ? (
1856
- <IconBrandZoom className="mr-2 h-5 w-5 opacity-90" />
1857
- ) : (
1858
- <IconVideo className="mr-2 h-5 w-5 opacity-90" />
1859
- )}
1860
- <span className="text-[15px] font-semibold">
1861
- {pendingConferenceProvider === "zoom"
1862
- ? t("eventForm.zoom")
1863
- : t("eventForm.googleMeet")}
1864
- </span>
1865
- <button
1866
- type="button"
1867
- onClick={handleRemovePendingConference}
1868
- aria-label={`Remove ${
1869
- pendingConferenceProvider === "zoom"
1870
- ? t("eventForm.zoom")
1871
- : t("eventForm.googleMeet")
1872
- }`}
1873
- className="ml-auto rounded-md p-1 text-white/70 transition-colors hover:bg-white/15 hover:text-white"
1898
+ {!isWorkingLocation &&
1899
+ (meetingLink ? (
1900
+ <>
1901
+ <div className="mx-4 my-2 border-t border-border/50" />
1902
+ <div className="px-4 py-1.5">
1903
+ <a
1904
+ href={meetingLink.url}
1905
+ target="_blank"
1906
+ rel="noopener noreferrer"
1907
+ className="flex items-center justify-center w-full rounded-xl bg-[#4965E0] hover:bg-[#5A75F0] text-white font-semibold py-2 px-4 text-[15px] relative"
1874
1908
  >
1875
- <IconX className="h-4 w-4" />
1876
- </button>
1909
+ <IconVideo className="h-5 w-5 mr-2 opacity-80" />
1910
+ <span>{getMeetingLabel(meetingLink.type, t)}</span>
1911
+ <span className="absolute right-4 hidden items-center gap-1 opacity-50 sm:flex">
1912
+ <kbd className="text-xs font-normal">
1913
+ {shortcutModifierLabel()}
1914
+ </kbd>
1915
+ <kbd className="inline-flex h-5 w-5 items-center justify-center rounded bg-white/20 text-[11px] font-medium">
1916
+ J
1917
+ </kbd>
1918
+ </span>
1919
+ </a>
1920
+ {(meetingLink.pin || meetingLink.passcode) && (
1921
+ <div className="mt-1.5 text-xs text-muted-foreground/60">
1922
+ {meetingLink.pin && (
1923
+ <span>
1924
+ {t("eventForm.pin", { pin: meetingLink.pin })}
1925
+ </span>
1926
+ )}
1927
+ {meetingLink.pin && meetingLink.passcode && (
1928
+ <span className="mx-1">&middot;</span>
1929
+ )}
1930
+ {meetingLink.passcode && (
1931
+ <span>
1932
+ {t("eventForm.passcode", {
1933
+ passcode: meetingLink.passcode,
1934
+ })}
1935
+ </span>
1936
+ )}
1937
+ </div>
1938
+ )}
1877
1939
  </div>
1878
- <p className="mt-1.5 text-xs text-muted-foreground/60">
1879
- {t("eventForm.conferencingLinkOnSave")}
1880
- </p>
1881
- </div>
1882
- </>
1883
- ) : !isOverlay ? (
1884
- <>
1885
- <div className="mx-4 my-2 border-t border-border/50" />
1886
- {editingField === "meetingLink" ? (
1940
+ </>
1941
+ ) : pendingConferenceProvider ? (
1942
+ <>
1943
+ <div className="mx-4 my-2 border-t border-border/50" />
1887
1944
  <div className="px-4 py-1.5">
1888
- <div className="flex items-center gap-2">
1889
- <IconVideo className="h-4 w-4 shrink-0 text-muted-foreground" />
1890
- <input
1891
- ref={meetingLinkRef}
1892
- value={editMeetingLink}
1893
- onChange={(e) => setEditMeetingLink(e.target.value)}
1894
- onKeyDown={(e) => {
1895
- if (e.key === "Enter") {
1896
- e.preventDefault();
1897
- handleSaveMeetingLink();
1898
- }
1899
- if (e.key === "Escape") {
1900
- e.preventDefault();
1901
- setEditMeetingLink("");
1902
- setEditingField(null);
1903
- }
1904
- e.stopPropagation();
1905
- }}
1906
- onBlur={handleSaveMeetingLink}
1907
- placeholder={t("eventForm.pasteMeetingLink")}
1908
- className="flex-1 bg-transparent border-none outline-none text-sm text-foreground placeholder:text-muted-foreground/40 focus:ring-0"
1909
- />
1945
+ <div className="flex w-full items-center rounded-xl bg-[#4965E0] py-2 pl-4 pr-2 text-white">
1946
+ {pendingConferenceProvider === "zoom" ? (
1947
+ <IconBrandZoom className="mr-2 h-5 w-5 opacity-90" />
1948
+ ) : (
1949
+ <IconVideo className="mr-2 h-5 w-5 opacity-90" />
1950
+ )}
1951
+ <span className="text-[15px] font-semibold">
1952
+ {pendingConferenceProvider === "zoom"
1953
+ ? t("eventForm.zoom")
1954
+ : t("eventForm.googleMeet")}
1955
+ </span>
1956
+ <button
1957
+ type="button"
1958
+ onClick={handleRemovePendingConference}
1959
+ aria-label={`Remove ${
1960
+ pendingConferenceProvider === "zoom"
1961
+ ? t("eventForm.zoom")
1962
+ : t("eventForm.googleMeet")
1963
+ }`}
1964
+ className="ml-auto rounded-md p-1 text-white/70 transition-colors hover:bg-white/15 hover:text-white"
1965
+ >
1966
+ <IconX className="h-4 w-4" />
1967
+ </button>
1910
1968
  </div>
1969
+ <p className="mt-1.5 text-xs text-muted-foreground/60">
1970
+ {t("eventForm.conferencingLinkOnSave")}
1971
+ </p>
1911
1972
  </div>
1912
- ) : (
1913
- <div className="px-4 py-1.5">
1914
- {pendingVideoProvider ? (
1915
- <MeetingLinkSkeleton provider={pendingVideoProvider} />
1916
- ) : (
1973
+ </>
1974
+ ) : !isOverlay ? (
1975
+ <>
1976
+ <div className="mx-4 my-2 border-t border-border/50" />
1977
+ {editingField === "meetingLink" ? (
1978
+ <div className="px-4 py-1.5">
1917
1979
  <div className="flex items-center gap-2">
1918
- <Button
1919
- type="button"
1920
- variant="outline"
1921
- size="sm"
1922
- className="h-8 flex-1 justify-center gap-1.5 px-2 text-xs"
1923
- disabled={updateEvent.isPending}
1924
- onClick={handleAddGoogleMeet}
1925
- >
1926
- <IconVideo className="h-3.5 w-3.5" />
1927
- {t("eventForm.meet")}
1928
- </Button>
1929
- <Button
1930
- type="button"
1931
- variant="outline"
1932
- size="sm"
1933
- className="h-8 flex-1 justify-center gap-1.5 px-2 text-xs"
1934
- disabled={
1935
- updateEvent.isPending || connectZoom.isPending
1936
- }
1937
- onClick={handleAddZoom}
1938
- >
1939
- <IconBrandZoom className="h-3.5 w-3.5" />
1940
- {t("eventForm.zoom")}
1941
- </Button>
1942
- <Button
1943
- type="button"
1944
- variant="ghost"
1945
- size="sm"
1946
- className="h-8 flex-1 justify-center gap-1.5 px-2 text-xs text-muted-foreground"
1947
- onClick={() => setEditingField("meetingLink")}
1948
- >
1949
- <IconPlus className="h-3.5 w-3.5" />
1950
- {t("eventForm.pasteLink")}
1951
- </Button>
1980
+ <IconVideo className="h-4 w-4 shrink-0 text-muted-foreground" />
1981
+ <input
1982
+ ref={meetingLinkRef}
1983
+ value={editMeetingLink}
1984
+ onChange={(e) => setEditMeetingLink(e.target.value)}
1985
+ onKeyDown={(e) => {
1986
+ if (e.key === "Enter") {
1987
+ e.preventDefault();
1988
+ handleSaveMeetingLink();
1989
+ }
1990
+ if (e.key === "Escape") {
1991
+ e.preventDefault();
1992
+ setEditMeetingLink("");
1993
+ setEditingField(null);
1994
+ }
1995
+ e.stopPropagation();
1996
+ }}
1997
+ onBlur={handleSaveMeetingLink}
1998
+ placeholder={t("eventForm.pasteMeetingLink")}
1999
+ className="flex-1 bg-transparent border-none outline-none text-sm text-foreground placeholder:text-muted-foreground/40 focus:ring-0"
2000
+ />
1952
2001
  </div>
1953
- )}
1954
- </div>
1955
- )}
1956
- </>
1957
- ) : null}
2002
+ </div>
2003
+ ) : (
2004
+ <div className="px-4 py-1.5">
2005
+ {pendingVideoProvider ? (
2006
+ <MeetingLinkSkeleton provider={pendingVideoProvider} />
2007
+ ) : (
2008
+ <div className="flex items-center gap-2">
2009
+ <Button
2010
+ type="button"
2011
+ variant="outline"
2012
+ size="sm"
2013
+ className="h-8 flex-1 justify-center gap-1.5 px-2 text-xs"
2014
+ disabled={updateEvent.isPending}
2015
+ onClick={handleAddGoogleMeet}
2016
+ >
2017
+ <IconVideo className="h-3.5 w-3.5" />
2018
+ {t("eventForm.meet")}
2019
+ </Button>
2020
+ <Button
2021
+ type="button"
2022
+ variant="outline"
2023
+ size="sm"
2024
+ className="h-8 flex-1 justify-center gap-1.5 px-2 text-xs"
2025
+ disabled={
2026
+ updateEvent.isPending || connectZoom.isPending
2027
+ }
2028
+ onClick={handleAddZoom}
2029
+ >
2030
+ <IconBrandZoom className="h-3.5 w-3.5" />
2031
+ {t("eventForm.zoom")}
2032
+ </Button>
2033
+ <Button
2034
+ type="button"
2035
+ variant="ghost"
2036
+ size="sm"
2037
+ className="h-8 flex-1 justify-center gap-1.5 px-2 text-xs text-muted-foreground"
2038
+ onClick={() => setEditingField("meetingLink")}
2039
+ >
2040
+ <IconPlus className="h-3.5 w-3.5" />
2041
+ {t("eventForm.pasteLink")}
2042
+ </Button>
2043
+ </div>
2044
+ )}
2045
+ </div>
2046
+ )}
2047
+ </>
2048
+ ) : null)}
1958
2049
 
1959
2050
  {/* Attachments */}
1960
- {!isOverlay && (
2051
+ {!isOverlay && !isWorkingLocation && (
1961
2052
  <>
1962
2053
  <div className="mx-4 my-2 border-t border-border/50" />
1963
2054
  {editingField === "attachments" ? (
@@ -2051,85 +2142,104 @@ export function EventDetailPopover({
2051
2142
  </>
2052
2143
  )}
2053
2144
 
2054
- {/* Location — always shown, editable */}
2055
- <div className="mx-4 my-2 border-t border-border/50" />
2056
- {editingField === "location" ? (
2057
- <div className="flex items-start gap-3 px-4 py-1.5">
2058
- <IconMapPin className="mt-1.5 h-4 w-4 shrink-0 text-muted-foreground" />
2059
- <input
2060
- ref={locationRef}
2061
- value={editLocation}
2062
- onChange={(e) => setEditLocation(e.target.value)}
2063
- onKeyDown={(e) => {
2064
- if (e.key === "Enter") {
2065
- e.preventDefault();
2066
- handleSaveLocation();
2067
- }
2068
- if (e.key === "Escape") {
2069
- e.preventDefault();
2070
- setEditLocation(event.location || "");
2071
- setEditingField(null);
2072
- }
2073
- e.stopPropagation();
2074
- }}
2075
- onBlur={handleSaveLocation}
2076
- placeholder={t("eventForm.addLocation")}
2077
- className="flex-1 bg-transparent border-none outline-none text-sm text-foreground placeholder:text-muted-foreground/40 focus:ring-0"
2078
- />
2079
- </div>
2080
- ) : event.location && !locationIsMeetingLink ? (
2081
- <div
2082
- className={`flex items-start gap-3 px-4 py-1.5 ${!isOverlay ? "cursor-pointer hover:bg-muted/50 rounded-md" : ""}`}
2083
- onClick={() => {
2084
- if (isOverlay) return;
2085
- setEditingField("location");
2086
- }}
2087
- >
2088
- <IconMapPin className="mt-0.5 h-4 w-4 shrink-0 text-muted-foreground" />
2089
- {locationIsUrl ? (
2090
- <Tooltip>
2091
- <TooltipTrigger asChild>
2092
- <a
2093
- href={event.location}
2094
- target="_blank"
2095
- rel="noopener noreferrer"
2096
- className="text-sm text-primary hover:underline truncate block max-w-full"
2097
- onClick={(e) => e.stopPropagation()}
2098
- >
2099
- {event.location}
2100
- </a>
2101
- </TooltipTrigger>
2102
- <TooltipContent>{event.location}</TooltipContent>
2103
- </Tooltip>
2104
- ) : (
2105
- <span className="text-sm text-muted-foreground">
2106
- {event.location}
2107
- </span>
2108
- )}
2109
- </div>
2110
- ) : locationIsMeetingLink && meetingLink ? (
2145
+ {!isWorkingLocation && (
2111
2146
  <>
2112
- <div className="flex items-start gap-3 px-4 py-1.5 rounded-md">
2113
- <IconVideo className="mt-0.5 h-4 w-4 shrink-0 text-muted-foreground" />
2114
- <div className="min-w-0">
2115
- <a
2116
- href={meetingLink.url}
2117
- target="_blank"
2118
- rel="noopener noreferrer"
2119
- className="block max-w-full truncate text-sm text-primary hover:underline"
2120
- >
2121
- {getMeetingLabel(meetingLink.type, t)}
2122
- </a>
2123
- <div className="text-xs text-muted-foreground">
2124
- {t("eventForm.savedAsVideoLink")}
2125
- </div>
2147
+ {/* Location always shown, editable */}
2148
+ <div className="mx-4 my-2 border-t border-border/50" />
2149
+ {editingField === "location" ? (
2150
+ <div className="flex items-start gap-3 px-4 py-1.5">
2151
+ <IconMapPin className="mt-1.5 h-4 w-4 shrink-0 text-muted-foreground" />
2152
+ <input
2153
+ ref={locationRef}
2154
+ value={editLocation}
2155
+ onChange={(e) => setEditLocation(e.target.value)}
2156
+ onKeyDown={(e) => {
2157
+ if (e.key === "Enter") {
2158
+ e.preventDefault();
2159
+ handleSaveLocation();
2160
+ }
2161
+ if (e.key === "Escape") {
2162
+ e.preventDefault();
2163
+ setEditLocation(editableLocationValue);
2164
+ setEditingField(null);
2165
+ }
2166
+ e.stopPropagation();
2167
+ }}
2168
+ onBlur={handleSaveLocation}
2169
+ placeholder={t("eventForm.addLocation")}
2170
+ className="flex-1 bg-transparent border-none outline-none text-sm text-foreground placeholder:text-muted-foreground/40 focus:ring-0"
2171
+ />
2126
2172
  </div>
2127
- </div>
2128
- {!isOverlay && (
2173
+ ) : event.location && !locationIsMeetingLink ? (
2174
+ <div
2175
+ className={`flex items-start gap-3 px-4 py-1.5 ${!isOverlay ? "cursor-pointer hover:bg-muted/50 rounded-md" : ""}`}
2176
+ onClick={() => {
2177
+ if (isOverlay) return;
2178
+ setEditingField("location");
2179
+ }}
2180
+ >
2181
+ <IconMapPin className="mt-0.5 h-4 w-4 shrink-0 text-muted-foreground" />
2182
+ {locationIsUrl ? (
2183
+ <Tooltip>
2184
+ <TooltipTrigger asChild>
2185
+ <a
2186
+ href={event.location}
2187
+ target="_blank"
2188
+ rel="noopener noreferrer"
2189
+ className="text-sm text-primary hover:underline truncate block max-w-full"
2190
+ onClick={(e) => e.stopPropagation()}
2191
+ >
2192
+ {event.location}
2193
+ </a>
2194
+ </TooltipTrigger>
2195
+ <TooltipContent>{event.location}</TooltipContent>
2196
+ </Tooltip>
2197
+ ) : (
2198
+ <span className="text-sm text-muted-foreground">
2199
+ {event.location}
2200
+ </span>
2201
+ )}
2202
+ </div>
2203
+ ) : locationIsMeetingLink && meetingLink ? (
2204
+ <>
2205
+ <div className="flex items-start gap-3 px-4 py-1.5 rounded-md">
2206
+ <IconVideo className="mt-0.5 h-4 w-4 shrink-0 text-muted-foreground" />
2207
+ <div className="min-w-0">
2208
+ <a
2209
+ href={meetingLink.url}
2210
+ target="_blank"
2211
+ rel="noopener noreferrer"
2212
+ className="block max-w-full truncate text-sm text-primary hover:underline"
2213
+ >
2214
+ {getMeetingLabel(meetingLink.type, t)}
2215
+ </a>
2216
+ <div className="text-xs text-muted-foreground">
2217
+ {t("eventForm.savedAsVideoLink")}
2218
+ </div>
2219
+ </div>
2220
+ </div>
2221
+ {!isOverlay && (
2222
+ <div
2223
+ className="flex items-center gap-3 px-4 py-1.5 cursor-pointer hover:bg-muted/50 rounded-md"
2224
+ onClick={() => {
2225
+ setEditLocation(editableLocationValue);
2226
+ setEditingField("location");
2227
+ }}
2228
+ >
2229
+ <IconMapPin className="h-4 w-4 shrink-0 text-muted-foreground/40" />
2230
+ <span className="text-sm text-muted-foreground/40">
2231
+ {t("eventForm.addLocation")}
2232
+ </span>
2233
+ </div>
2234
+ )}
2235
+ </>
2236
+ ) : !isOverlay ? (
2129
2237
  <div
2130
2238
  className="flex items-center gap-3 px-4 py-1.5 cursor-pointer hover:bg-muted/50 rounded-md"
2131
2239
  onClick={() => {
2132
- setEditLocation("");
2240
+ setEditLocation(
2241
+ locationIsMeetingLink ? "" : editableLocationValue,
2242
+ );
2133
2243
  setEditingField("location");
2134
2244
  }}
2135
2245
  >
@@ -2138,27 +2248,12 @@ export function EventDetailPopover({
2138
2248
  {t("eventForm.addLocation")}
2139
2249
  </span>
2140
2250
  </div>
2141
- )}
2251
+ ) : null}
2142
2252
  </>
2143
- ) : !isOverlay ? (
2144
- <div
2145
- className="flex items-center gap-3 px-4 py-1.5 cursor-pointer hover:bg-muted/50 rounded-md"
2146
- onClick={() => {
2147
- setEditLocation(
2148
- locationIsMeetingLink ? "" : event.location || "",
2149
- );
2150
- setEditingField("location");
2151
- }}
2152
- >
2153
- <IconMapPin className="h-4 w-4 shrink-0 text-muted-foreground/40" />
2154
- <span className="text-sm text-muted-foreground/40">
2155
- {t("eventForm.addLocation")}
2156
- </span>
2157
- </div>
2158
- ) : null}
2253
+ )}
2159
2254
 
2160
2255
  {/* Description — always shown for editable events; hidden for overlay events with no description */}
2161
- {(!isOverlay || event.description) && (
2256
+ {!isWorkingLocation && (!isOverlay || event.description) && (
2162
2257
  <>
2163
2258
  <div className="mx-4 my-2 border-t border-border/50" />
2164
2259
  <div className="px-4 py-1.5">
@@ -2210,180 +2305,182 @@ export function EventDetailPopover({
2210
2305
  )}
2211
2306
 
2212
2307
  {/* Reminders */}
2213
- {!isOverlay && editingField === "reminders" ? (
2214
- <>
2215
- <div className="mx-4 my-2 border-t border-border/50" />
2216
- <div className="px-4 py-1.5">
2217
- <div className="mb-2 flex items-center gap-3">
2218
- <IconBell className="h-4 w-4 shrink-0 text-muted-foreground" />
2219
- <span className="text-sm font-medium text-foreground">
2220
- {t("eventForm.eventAlerts")}
2221
- </span>
2222
- </div>
2223
- <ReminderControls
2224
- idPrefix={`event-${event.id}`}
2225
- mode={editReminderMode}
2226
- reminders={editReminders}
2227
- onModeChange={setEditReminderMode}
2228
- onRemindersChange={setEditReminders}
2229
- />
2230
- <div className="mt-2 flex justify-end gap-1.5">
2231
- <Button
2232
- variant="ghost"
2233
- size="sm"
2234
- className="h-6 text-xs"
2235
- onClick={() => {
2236
- const reminderState = remindersToDraftState(event);
2237
- setEditReminderMode(reminderState.mode);
2238
- setEditReminders(reminderState.reminders);
2239
- setEditingField(null);
2240
- }}
2241
- >
2242
- {t("eventForm.cancel")}
2243
- </Button>
2244
- <Button
2245
- size="sm"
2246
- className="h-6 text-xs"
2247
- onClick={handleSaveReminders}
2248
- >
2249
- {t("eventForm.save")}
2250
- </Button>
2308
+ {!isWorkingLocation &&
2309
+ (!isOverlay && editingField === "reminders" ? (
2310
+ <>
2311
+ <div className="mx-4 my-2 border-t border-border/50" />
2312
+ <div className="px-4 py-1.5">
2313
+ <div className="mb-2 flex items-center gap-3">
2314
+ <IconBell className="h-4 w-4 shrink-0 text-muted-foreground" />
2315
+ <span className="text-sm font-medium text-foreground">
2316
+ {t("eventForm.eventAlerts")}
2317
+ </span>
2318
+ </div>
2319
+ <ReminderControls
2320
+ idPrefix={`event-${event.id}`}
2321
+ mode={editReminderMode}
2322
+ reminders={editReminders}
2323
+ onModeChange={setEditReminderMode}
2324
+ onRemindersChange={setEditReminders}
2325
+ />
2326
+ <div className="mt-2 flex justify-end gap-1.5">
2327
+ <Button
2328
+ variant="ghost"
2329
+ size="sm"
2330
+ className="h-6 text-xs"
2331
+ onClick={() => {
2332
+ const reminderState = remindersToDraftState(event);
2333
+ setEditReminderMode(reminderState.mode);
2334
+ setEditReminders(reminderState.reminders);
2335
+ setEditingField(null);
2336
+ }}
2337
+ >
2338
+ {t("eventForm.cancel")}
2339
+ </Button>
2340
+ <Button
2341
+ size="sm"
2342
+ className="h-6 text-xs"
2343
+ onClick={handleSaveReminders}
2344
+ >
2345
+ {t("eventForm.save")}
2346
+ </Button>
2347
+ </div>
2251
2348
  </div>
2252
- </div>
2253
- </>
2254
- ) : event.reminders && event.reminders.length > 0 ? (
2255
- <>
2256
- <div className="mx-4 my-2 border-t border-border/50" />
2257
- <div className="flex items-start gap-3 px-4 py-1.5">
2258
- <IconBell className="mt-0.5 h-4 w-4 shrink-0 text-muted-foreground" />
2259
- <div className="space-y-0.5">
2260
- {event.reminders.map((r, i) => (
2261
- <div key={i} className="text-sm text-muted-foreground">
2262
- {formatReminderText(r.minutes)}
2263
- </div>
2264
- ))}
2349
+ </>
2350
+ ) : event.reminders && event.reminders.length > 0 ? (
2351
+ <>
2352
+ <div className="mx-4 my-2 border-t border-border/50" />
2353
+ <div className="flex items-start gap-3 px-4 py-1.5">
2354
+ <IconBell className="mt-0.5 h-4 w-4 shrink-0 text-muted-foreground" />
2355
+ <div className="space-y-0.5">
2356
+ {event.reminders.map((r, i) => (
2357
+ <div key={i} className="text-sm text-muted-foreground">
2358
+ {formatReminderText(r.minutes)}
2359
+ </div>
2360
+ ))}
2361
+ </div>
2265
2362
  </div>
2266
- </div>
2267
- </>
2268
- ) : null}
2363
+ </>
2364
+ ) : null)}
2269
2365
 
2270
2366
  {/* Availability, visibility, and alerts */}
2271
- {!isOverlay ? (
2272
- <>
2273
- <div className="mx-4 my-2 border-t border-border/50" />
2274
- <div className="px-4 py-1.5">
2275
- <div className="grid grid-cols-3 gap-2">
2276
- <div className="space-y-1">
2277
- <span className="text-[10px] text-muted-foreground">
2278
- {t("eventForm.showAs")}
2279
- </span>
2280
- <Select
2281
- value={availabilityValue}
2282
- onValueChange={(value) =>
2283
- handleAvailabilityChange(value as AvailabilityValue)
2284
- }
2285
- disabled={updateEvent.isPending}
2286
- >
2287
- <SelectTrigger className="h-8 text-xs">
2288
- <SelectValue />
2289
- </SelectTrigger>
2290
- <SelectContent>
2291
- <SelectItem value="opaque">
2292
- {t("eventForm.busy")}
2293
- </SelectItem>
2294
- <SelectItem value="transparent">
2295
- {t("eventForm.free")}
2296
- </SelectItem>
2297
- </SelectContent>
2298
- </Select>
2299
- </div>
2300
- <div className="space-y-1">
2301
- <span className="text-[10px] text-muted-foreground">
2302
- {t("eventForm.visibility")}
2303
- </span>
2304
- <Select
2305
- value={visibilityValue}
2306
- onValueChange={(value) =>
2307
- handleVisibilityChange(value as VisibilityValue)
2308
- }
2309
- disabled={updateEvent.isPending}
2310
- >
2311
- <SelectTrigger className="h-8 text-xs">
2312
- <SelectValue />
2313
- </SelectTrigger>
2314
- <SelectContent>
2315
- <SelectItem value="default">
2316
- {t("eventForm.default")}
2317
- </SelectItem>
2318
- <SelectItem value="public">
2319
- {t("eventForm.public")}
2320
- </SelectItem>
2321
- <SelectItem value="private">
2322
- {t("eventForm.private")}
2323
- </SelectItem>
2324
- </SelectContent>
2325
- </Select>
2367
+ {!isWorkingLocation &&
2368
+ (!isOverlay ? (
2369
+ <>
2370
+ <div className="mx-4 my-2 border-t border-border/50" />
2371
+ <div className="px-4 py-1.5">
2372
+ <div className="grid grid-cols-3 gap-2">
2373
+ <div className="space-y-1">
2374
+ <span className="text-[10px] text-muted-foreground">
2375
+ {t("eventForm.showAs")}
2376
+ </span>
2377
+ <Select
2378
+ value={availabilityValue}
2379
+ onValueChange={(value) =>
2380
+ handleAvailabilityChange(value as AvailabilityValue)
2381
+ }
2382
+ disabled={updateEvent.isPending}
2383
+ >
2384
+ <SelectTrigger className="h-8 text-xs">
2385
+ <SelectValue />
2386
+ </SelectTrigger>
2387
+ <SelectContent>
2388
+ <SelectItem value="opaque">
2389
+ {t("eventForm.busy")}
2390
+ </SelectItem>
2391
+ <SelectItem value="transparent">
2392
+ {t("eventForm.free")}
2393
+ </SelectItem>
2394
+ </SelectContent>
2395
+ </Select>
2396
+ </div>
2397
+ <div className="space-y-1">
2398
+ <span className="text-[10px] text-muted-foreground">
2399
+ {t("eventForm.visibility")}
2400
+ </span>
2401
+ <Select
2402
+ value={visibilityValue}
2403
+ onValueChange={(value) =>
2404
+ handleVisibilityChange(value as VisibilityValue)
2405
+ }
2406
+ disabled={updateEvent.isPending}
2407
+ >
2408
+ <SelectTrigger className="h-8 text-xs">
2409
+ <SelectValue />
2410
+ </SelectTrigger>
2411
+ <SelectContent>
2412
+ <SelectItem value="default">
2413
+ {t("eventForm.default")}
2414
+ </SelectItem>
2415
+ <SelectItem value="public">
2416
+ {t("eventForm.public")}
2417
+ </SelectItem>
2418
+ <SelectItem value="private">
2419
+ {t("eventForm.private")}
2420
+ </SelectItem>
2421
+ </SelectContent>
2422
+ </Select>
2423
+ </div>
2424
+ <div className="space-y-1">
2425
+ <span className="text-[10px] text-muted-foreground">
2426
+ {t("eventForm.alerts")}
2427
+ </span>
2428
+ <Select
2429
+ value={reminderValue}
2430
+ onValueChange={(value) =>
2431
+ handleReminderChange(value as ReminderValue)
2432
+ }
2433
+ disabled={updateEvent.isPending}
2434
+ >
2435
+ <SelectTrigger className="h-8 text-xs">
2436
+ <SelectValue />
2437
+ </SelectTrigger>
2438
+ <SelectContent>
2439
+ <SelectItem value="default">
2440
+ {t("eventForm.default")}
2441
+ </SelectItem>
2442
+ <SelectItem value="none">
2443
+ {t("eventForm.none")}
2444
+ </SelectItem>
2445
+ <SelectItem value="0">
2446
+ {t("eventForm.atStart")}
2447
+ </SelectItem>
2448
+ <SelectItem value="10">10 min</SelectItem>
2449
+ <SelectItem value="30">30 min</SelectItem>
2450
+ <SelectItem value="60">1 hour</SelectItem>
2451
+ <SelectItem value="1440">1 day</SelectItem>
2452
+ <SelectItem value="custom">
2453
+ {t("eventForm.customEllipsis")}
2454
+ </SelectItem>
2455
+ </SelectContent>
2456
+ </Select>
2457
+ </div>
2326
2458
  </div>
2327
- <div className="space-y-1">
2328
- <span className="text-[10px] text-muted-foreground">
2329
- {t("eventForm.alerts")}
2330
- </span>
2331
- <Select
2332
- value={reminderValue}
2333
- onValueChange={(value) =>
2334
- handleReminderChange(value as ReminderValue)
2335
- }
2336
- disabled={updateEvent.isPending}
2337
- >
2338
- <SelectTrigger className="h-8 text-xs">
2339
- <SelectValue />
2340
- </SelectTrigger>
2341
- <SelectContent>
2342
- <SelectItem value="default">
2343
- {t("eventForm.default")}
2344
- </SelectItem>
2345
- <SelectItem value="none">
2346
- {t("eventForm.none")}
2347
- </SelectItem>
2348
- <SelectItem value="0">
2349
- {t("eventForm.atStart")}
2350
- </SelectItem>
2351
- <SelectItem value="10">10 min</SelectItem>
2352
- <SelectItem value="30">30 min</SelectItem>
2353
- <SelectItem value="60">1 hour</SelectItem>
2354
- <SelectItem value="1440">1 day</SelectItem>
2355
- <SelectItem value="custom">
2356
- {t("eventForm.customEllipsis")}
2357
- </SelectItem>
2358
- </SelectContent>
2359
- </Select>
2459
+ <div className="mt-2 flex items-center gap-3">
2460
+ <IconPalette className="h-4 w-4 shrink-0 text-muted-foreground" />
2461
+ <EventColorSwatches
2462
+ value={event.colorId}
2463
+ onChange={handleColorChange}
2464
+ />
2360
2465
  </div>
2361
2466
  </div>
2362
- <div className="mt-2 flex items-center gap-3">
2363
- <IconPalette className="h-4 w-4 shrink-0 text-muted-foreground" />
2364
- <EventColorSwatches
2365
- value={event.colorId}
2366
- onChange={handleColorChange}
2367
- />
2467
+ </>
2468
+ ) : event.status || event.visibility ? (
2469
+ <>
2470
+ <div className="mx-4 my-2 border-t border-border/50" />
2471
+ <div className="flex items-center gap-3 px-4 py-1.5 text-sm text-muted-foreground">
2472
+ <div className="h-4 w-4 shrink-0" />
2473
+ <span>
2474
+ {event.transparency === "transparent"
2475
+ ? t("eventForm.free")
2476
+ : t("eventForm.busy")}
2477
+ {event.visibility && event.visibility !== "default"
2478
+ ? ` · ${event.visibility} visibility`
2479
+ : ""}
2480
+ </span>
2368
2481
  </div>
2369
- </div>
2370
- </>
2371
- ) : event.status || event.visibility ? (
2372
- <>
2373
- <div className="mx-4 my-2 border-t border-border/50" />
2374
- <div className="flex items-center gap-3 px-4 py-1.5 text-sm text-muted-foreground">
2375
- <div className="h-4 w-4 shrink-0" />
2376
- <span>
2377
- {event.transparency === "transparent"
2378
- ? t("eventForm.free")
2379
- : t("eventForm.busy")}
2380
- {event.visibility && event.visibility !== "default"
2381
- ? ` · ${event.visibility} visibility`
2382
- : ""}
2383
- </span>
2384
- </div>
2385
- </>
2386
- ) : null}
2482
+ </>
2483
+ ) : null)}
2387
2484
 
2388
2485
  {/* Overlay person badge */}
2389
2486
  {event.overlayEmail && (