@agent-native/core 0.176.4 → 0.176.5-nightly-20260902205402
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.
- package/corpus/README.md +1 -1
- package/corpus/templates/calendar/.agents/skills/event-management/SKILL.md +6 -0
- package/corpus/templates/calendar/AGENTS.md +4 -0
- package/corpus/templates/calendar/actions/delete-event.ts +2 -2
- package/corpus/templates/calendar/actions/event-action-helpers.ts +7 -0
- package/corpus/templates/calendar/actions/get-event.ts +40 -3
- package/corpus/templates/calendar/actions/list-events.ts +49 -2
- package/corpus/templates/calendar/actions/list-google-calendars.ts +24 -0
- package/corpus/templates/calendar/actions/update-calendar-visual-preferences.ts +52 -1
- package/corpus/templates/calendar/actions/update-event.ts +2 -2
- package/corpus/templates/calendar/actions/view-screen.ts +54 -2
- package/corpus/templates/calendar/app/components/calendar/EventDetailPanel.tsx +33 -19
- package/corpus/templates/calendar/app/components/calendar/EventDetailPopover.tsx +31 -5
- package/corpus/templates/calendar/app/components/layout/Sidebar.tsx +102 -0
- package/corpus/templates/calendar/app/hooks/use-events.ts +45 -10
- package/corpus/templates/calendar/app/hooks/use-google-auth.ts +8 -1
- package/corpus/templates/calendar/app/hooks/use-google-calendars.ts +41 -0
- package/corpus/templates/calendar/app/hooks/use-view-preferences.ts +56 -4
- package/corpus/templates/calendar/app/lib/shared-calendar-demo.ts +187 -0
- package/corpus/templates/calendar/app/pages/CalendarView.tsx +50 -5
- package/corpus/templates/calendar/changelog/2026-09-01-calendars-shared-with-your-connected-google-accounts-can-now.md +6 -0
- package/corpus/templates/calendar/changelog/2026-09-02-updated-the-booking-link-og-preview-image-with-the-new-monoc.md +6 -0
- package/corpus/templates/calendar/server/lib/booking-og-image.ts +8 -18
- package/corpus/templates/calendar/server/lib/google-api.ts +10 -0
- package/corpus/templates/calendar/server/lib/google-calendar.ts +215 -21
- package/corpus/templates/calendar/server/plugins/agent-chat.ts +2 -0
- package/corpus/templates/calendar/server/plugins/feature-flags.ts +5 -0
- package/corpus/templates/calendar/shared/api.ts +20 -0
- package/corpus/templates/calendar/shared/calendar-view-preferences.ts +28 -1
- package/corpus/templates/calendar/shared/feature-flags.ts +8 -0
- package/corpus/templates/calendar/shared/google-calendar-sources.ts +46 -0
- package/corpus/templates/factory/.env.example +5 -0
- package/corpus/templates/factory/README.md +9 -0
- package/corpus/templates/factory/server/connectors/credentials.ts +28 -6
- package/corpus/templates/factory/server/triage/github-client.ts +144 -7
- package/corpus/templates/forms/server/lib/form-og-image.ts +7 -17
- package/corpus/templates/slides/actions/add-slide.ts +1 -0
- package/corpus/templates/slides/actions/create-deck.ts +1 -0
- package/corpus/templates/slides/actions/get-deck.ts +1 -0
- package/corpus/templates/slides/actions/navigate.ts +1 -0
- package/corpus/templates/slides/actions/patch-deck.ts +1 -0
- package/corpus/templates/slides/actions/update-slide.ts +1 -0
- package/corpus/templates/slides/actions/view-screen.ts +1 -0
- package/corpus/templates/slides/server/plugins/agent-chat.ts +2 -0
- package/dist/action.d.ts +4 -0
- package/dist/action.js +3 -0
- package/dist/agent/types.d.ts +1 -0
- package/dist/automation/index.d.ts +1 -1
- package/dist/client/webmcp.js +7 -2
- package/dist/collab/awareness.d.ts +2 -2
- package/dist/mcp/build-server.d.ts +2 -0
- package/dist/mcp/build-server.js +4 -0
- package/dist/notifications/routes.d.ts +3 -3
- package/dist/observability/routes.d.ts +6 -6
- package/dist/provider-api/actions/provider-api.d.ts +1 -1
- package/dist/secrets/routes.d.ts +9 -9
- package/dist/server/action-routes.d.ts +1 -0
- package/dist/server/action-routes.js +4 -0
- package/dist/server/agent-chat/mcp-options.d.ts +4 -0
- package/dist/server/agent-chat/mcp-options.js +1 -0
- package/dist/server/agent-chat-plugin.js +2 -0
- package/dist/server/realtime-token.d.ts +1 -1
- package/dist/server/social-og-image.js +6 -20
- package/dist/server/transcribe-voice.d.ts +1 -1
- package/dist/shared/agent-mcp-metadata.d.ts +3 -0
- package/dist/shared/agent-mcp-metadata.js +18 -0
- package/dist/shared/social-meta.d.ts +1 -1
- package/dist/shared/social-meta.js +1 -1
- package/package.json +1 -1
|
@@ -501,11 +501,17 @@ export function EventDetailPopover({
|
|
|
501
501
|
const workingLocationLabels = createWorkingLocationDisplayLabels(t);
|
|
502
502
|
const isMobile = useIsMobile();
|
|
503
503
|
const eventTimezone = timezone || event.startTimeZone || getLocalTimezone();
|
|
504
|
+
const isReadOnlySource =
|
|
505
|
+
!!event.overlayEmail ||
|
|
506
|
+
event.calendarPrimary === false ||
|
|
507
|
+
event.calendarReadOnly === true;
|
|
504
508
|
const [open, setOpen] = useState(defaultOpen);
|
|
505
509
|
const [editingTitle, setEditingTitle] = useState(
|
|
506
|
-
defaultOpen ? getEditableEventTitle(event) : "",
|
|
510
|
+
defaultOpen && !isReadOnlySource ? getEditableEventTitle(event) : "",
|
|
511
|
+
);
|
|
512
|
+
const [isEditingTitle, setIsEditingTitle] = useState(
|
|
513
|
+
defaultOpen && !isReadOnlySource,
|
|
507
514
|
);
|
|
508
|
-
const [isEditingTitle, setIsEditingTitle] = useState(defaultOpen);
|
|
509
515
|
const isNewEventRef = useRef(defaultOpen);
|
|
510
516
|
const titleInputRef = useRef<HTMLInputElement>(null);
|
|
511
517
|
const popoverTokenRef = useRef<symbol | null>(null);
|
|
@@ -570,13 +576,13 @@ export function EventDetailPopover({
|
|
|
570
576
|
string | null
|
|
571
577
|
>(() => getStoredZoomAfterConnectEventId());
|
|
572
578
|
const [showConferencingOptions, setShowConferencingOptions] = useState(false);
|
|
573
|
-
const isOverlay =
|
|
579
|
+
const isOverlay = isReadOnlySource;
|
|
574
580
|
const ownerLabel = event.ownerName || event.overlayEmail;
|
|
575
581
|
|
|
576
582
|
const updateEvent = useUpdateEvent();
|
|
577
583
|
const masterEventId =
|
|
578
584
|
open && event.recurringEventId ? `google-${event.recurringEventId}` : "";
|
|
579
|
-
const masterEvent = useEvent(masterEventId);
|
|
585
|
+
const masterEvent = useEvent(masterEventId, event.calendarSourceKey);
|
|
580
586
|
const recurrenceRules =
|
|
581
587
|
event.recurrence && event.recurrence.length > 0
|
|
582
588
|
? event.recurrence
|
|
@@ -1810,7 +1816,7 @@ export function EventDetailPopover({
|
|
|
1810
1816
|
<div ref={detailsScrollRef} className="flex-1 overflow-y-auto">
|
|
1811
1817
|
<div className="px-2 py-2">
|
|
1812
1818
|
{/* Title — always editable */}
|
|
1813
|
-
{isEditingTitle && !isWorkingLocation ? (
|
|
1819
|
+
{isEditingTitle && !isWorkingLocation && !isOverlay ? (
|
|
1814
1820
|
<input
|
|
1815
1821
|
ref={titleInputRef}
|
|
1816
1822
|
value={editingTitle}
|
|
@@ -2738,6 +2744,26 @@ export function EventDetailPopover({
|
|
|
2738
2744
|
</>
|
|
2739
2745
|
)}
|
|
2740
2746
|
|
|
2747
|
+
{(event.calendarPrimary === false || event.calendarReadOnly) &&
|
|
2748
|
+
event.calendarName &&
|
|
2749
|
+
!event.overlayEmail && (
|
|
2750
|
+
<>
|
|
2751
|
+
<div className={eventPopoverDivider} />
|
|
2752
|
+
<div className="flex items-center gap-2 px-4 py-1.5">
|
|
2753
|
+
<span
|
|
2754
|
+
aria-hidden="true"
|
|
2755
|
+
className="ml-1 size-2 shrink-0 rounded-full ring-1 ring-border"
|
|
2756
|
+
style={{ backgroundColor: event.color }}
|
|
2757
|
+
/>
|
|
2758
|
+
<span className="truncate text-muted-foreground">
|
|
2759
|
+
{t("eventForm.viewingOwnerCalendar", {
|
|
2760
|
+
owner: `${event.calendarName} · ${event.accountEmail ?? "Google"}`,
|
|
2761
|
+
})}
|
|
2762
|
+
</span>
|
|
2763
|
+
</div>
|
|
2764
|
+
</>
|
|
2765
|
+
)}
|
|
2766
|
+
|
|
2741
2767
|
{/* Bottom padding */}
|
|
2742
2768
|
<div className="h-3" />
|
|
2743
2769
|
</div>
|
|
@@ -73,6 +73,7 @@ import {
|
|
|
73
73
|
useGoogleAuthStatus,
|
|
74
74
|
useGoogleDesktopAuth,
|
|
75
75
|
} from "@/hooks/use-google-auth";
|
|
76
|
+
import { useGoogleCalendars } from "@/hooks/use-google-calendars";
|
|
76
77
|
import {
|
|
77
78
|
useOverlayPeople,
|
|
78
79
|
useRemoveOverlayPerson,
|
|
@@ -635,6 +636,106 @@ function GoogleAccountsSection({
|
|
|
635
636
|
);
|
|
636
637
|
}
|
|
637
638
|
|
|
639
|
+
function SharedGoogleCalendarsGroup() {
|
|
640
|
+
const t = useT();
|
|
641
|
+
const { data: calendars, enabled } = useGoogleCalendars();
|
|
642
|
+
const {
|
|
643
|
+
prefs: { googleCalendarVisibility },
|
|
644
|
+
updateGoogleCalendarVisibility,
|
|
645
|
+
} = useViewPreferences();
|
|
646
|
+
const sharedCalendars = (calendars ?? []).filter(
|
|
647
|
+
(calendar) => !calendar.primary && calendar.accessRole !== "freeBusyReader",
|
|
648
|
+
);
|
|
649
|
+
const grouped = useMemo(() => {
|
|
650
|
+
const groups = new Map<string, typeof sharedCalendars>();
|
|
651
|
+
for (const calendar of sharedCalendars) {
|
|
652
|
+
const current = groups.get(calendar.accountEmail) ?? [];
|
|
653
|
+
current.push(calendar);
|
|
654
|
+
groups.set(calendar.accountEmail, current);
|
|
655
|
+
}
|
|
656
|
+
return Array.from(groups.entries());
|
|
657
|
+
}, [sharedCalendars]);
|
|
658
|
+
|
|
659
|
+
if (!enabled || grouped.length === 0) return null;
|
|
660
|
+
|
|
661
|
+
return (
|
|
662
|
+
<div className="space-y-1">
|
|
663
|
+
{grouped.map(([accountEmail, accountCalendars]) => (
|
|
664
|
+
<div key={accountEmail}>
|
|
665
|
+
<div className="px-3 py-1 text-[10px] font-medium text-muted-foreground/60">
|
|
666
|
+
{accountEmail}
|
|
667
|
+
</div>
|
|
668
|
+
<div className="space-y-0.5">
|
|
669
|
+
{accountCalendars.map((calendar) => {
|
|
670
|
+
const visible =
|
|
671
|
+
googleCalendarVisibility[calendar.sourceKey] ??
|
|
672
|
+
calendar.selected;
|
|
673
|
+
return (
|
|
674
|
+
<div
|
|
675
|
+
key={calendar.sourceKey}
|
|
676
|
+
className="group flex min-h-7 items-center gap-2 px-3 text-xs"
|
|
677
|
+
>
|
|
678
|
+
<span
|
|
679
|
+
aria-hidden="true"
|
|
680
|
+
className={cn(
|
|
681
|
+
"block size-2.5 shrink-0 rounded-full ring-1 ring-border",
|
|
682
|
+
!visible && "opacity-40",
|
|
683
|
+
)}
|
|
684
|
+
style={{
|
|
685
|
+
backgroundColor: calendar.color || CALENDAR_COLORS[6],
|
|
686
|
+
}}
|
|
687
|
+
/>
|
|
688
|
+
<span
|
|
689
|
+
className={cn(
|
|
690
|
+
"min-w-0 flex-1 truncate",
|
|
691
|
+
visible
|
|
692
|
+
? "text-muted-foreground"
|
|
693
|
+
: "text-muted-foreground/40",
|
|
694
|
+
)}
|
|
695
|
+
>
|
|
696
|
+
{calendar.name}
|
|
697
|
+
</span>
|
|
698
|
+
<Tooltip>
|
|
699
|
+
<TooltipTrigger asChild>
|
|
700
|
+
<button
|
|
701
|
+
type="button"
|
|
702
|
+
onClick={() =>
|
|
703
|
+
updateGoogleCalendarVisibility(
|
|
704
|
+
calendar.sourceKey,
|
|
705
|
+
!visible,
|
|
706
|
+
)
|
|
707
|
+
}
|
|
708
|
+
className="flex size-6 shrink-0 items-center justify-center rounded text-muted-foreground/60 opacity-0 hover:text-foreground group-hover:opacity-100 focus-visible:opacity-100"
|
|
709
|
+
aria-label={
|
|
710
|
+
visible
|
|
711
|
+
? t("sidebar.hideCalendar")
|
|
712
|
+
: t("sidebar.showCalendar")
|
|
713
|
+
}
|
|
714
|
+
aria-pressed={visible}
|
|
715
|
+
>
|
|
716
|
+
{visible ? (
|
|
717
|
+
<IconEye className="size-3" />
|
|
718
|
+
) : (
|
|
719
|
+
<IconEyeOff className="size-3" />
|
|
720
|
+
)}
|
|
721
|
+
</button>
|
|
722
|
+
</TooltipTrigger>
|
|
723
|
+
<TooltipContent side="right">
|
|
724
|
+
{visible
|
|
725
|
+
? t("sidebar.hideCalendar")
|
|
726
|
+
: t("sidebar.showCalendar")}
|
|
727
|
+
</TooltipContent>
|
|
728
|
+
</Tooltip>
|
|
729
|
+
</div>
|
|
730
|
+
);
|
|
731
|
+
})}
|
|
732
|
+
</div>
|
|
733
|
+
</div>
|
|
734
|
+
))}
|
|
735
|
+
</div>
|
|
736
|
+
);
|
|
737
|
+
}
|
|
738
|
+
|
|
638
739
|
export function Sidebar({
|
|
639
740
|
open,
|
|
640
741
|
onClose,
|
|
@@ -929,6 +1030,7 @@ export function Sidebar({
|
|
|
929
1030
|
<IconPlus className="h-3.5 w-3.5" />
|
|
930
1031
|
</button>
|
|
931
1032
|
</div>
|
|
1033
|
+
<SharedGoogleCalendarsGroup />
|
|
932
1034
|
{(overlayPeople.length > 0 || externalCalendars.length > 0) && (
|
|
933
1035
|
<div className="mt-1 space-y-1">
|
|
934
1036
|
{overlayPeople.length > 0 && (
|
|
@@ -6,6 +6,7 @@ import {
|
|
|
6
6
|
import type { CalendarEvent, UpdateEventScope } from "@shared/api";
|
|
7
7
|
import {
|
|
8
8
|
useQueryClient,
|
|
9
|
+
useQuery,
|
|
9
10
|
keepPreviousData,
|
|
10
11
|
type QueryKey,
|
|
11
12
|
} from "@tanstack/react-query";
|
|
@@ -13,6 +14,10 @@ import { nanoid } from "nanoid";
|
|
|
13
14
|
import { useMemo } from "react";
|
|
14
15
|
|
|
15
16
|
import { dateTimeInTimezoneToIso } from "@/lib/event-form-utils";
|
|
17
|
+
import {
|
|
18
|
+
isSharedCalendarDemo,
|
|
19
|
+
SHARED_CALENDAR_DEMO_EVENTS,
|
|
20
|
+
} from "@/lib/shared-calendar-demo";
|
|
16
21
|
import {
|
|
17
22
|
buildWorkingLocationProperties,
|
|
18
23
|
getWorkingLocationEditableLabel,
|
|
@@ -83,13 +88,17 @@ function buildEventsParams(
|
|
|
83
88
|
from?: string,
|
|
84
89
|
to?: string,
|
|
85
90
|
overlayEmails?: string[],
|
|
86
|
-
|
|
87
|
-
|
|
91
|
+
calendarSourceKeys?: string[],
|
|
92
|
+
): Record<string, string | string[]> {
|
|
93
|
+
const params: Record<string, string | string[]> = {};
|
|
88
94
|
if (from) params.from = from;
|
|
89
95
|
if (to) params.to = to;
|
|
90
96
|
if (overlayEmails && overlayEmails.length > 0) {
|
|
91
97
|
params.overlayEmails = overlayEmails.join(",");
|
|
92
98
|
}
|
|
99
|
+
if (calendarSourceKeys && calendarSourceKeys.length > 0) {
|
|
100
|
+
params.calendarSourceKeys = calendarSourceKeys;
|
|
101
|
+
}
|
|
93
102
|
return params;
|
|
94
103
|
}
|
|
95
104
|
|
|
@@ -103,8 +112,12 @@ function getListEventsParams(
|
|
|
103
112
|
return params as Record<string, string>;
|
|
104
113
|
}
|
|
105
114
|
|
|
106
|
-
function getEventQueryKey(id: string) {
|
|
107
|
-
return [
|
|
115
|
+
function getEventQueryKey(id: string, calendarSourceKey?: string) {
|
|
116
|
+
return [
|
|
117
|
+
"action",
|
|
118
|
+
"get-event",
|
|
119
|
+
calendarSourceKey ? { id, calendarSourceKey } : { id },
|
|
120
|
+
] as const;
|
|
108
121
|
}
|
|
109
122
|
|
|
110
123
|
export function getOptimisticTitleIsGenerated(
|
|
@@ -279,15 +292,31 @@ export function useEvents(
|
|
|
279
292
|
from?: string,
|
|
280
293
|
to?: string,
|
|
281
294
|
overlayEmails?: string[],
|
|
295
|
+
calendarSourceKeys?: string[],
|
|
282
296
|
) {
|
|
283
|
-
const params = buildEventsParams(from, to, overlayEmails);
|
|
284
|
-
|
|
285
|
-
|
|
297
|
+
const params = buildEventsParams(from, to, overlayEmails, calendarSourceKeys);
|
|
298
|
+
const demo = isSharedCalendarDemo();
|
|
299
|
+
const live = useActionQuery<CalendarEvent[]>("list-events", params, {
|
|
300
|
+
enabled: !demo,
|
|
286
301
|
retry: false,
|
|
287
302
|
staleTime: 30_000,
|
|
288
303
|
gcTime: 30 * 60 * 1000,
|
|
289
304
|
placeholderData: keepPreviousData,
|
|
290
305
|
});
|
|
306
|
+
const fixture = useQuery({
|
|
307
|
+
queryKey: ["shared-calendar-demo-events", params],
|
|
308
|
+
queryFn: async () => {
|
|
309
|
+
const selected = new Set(calendarSourceKeys ?? []);
|
|
310
|
+
return SHARED_CALENDAR_DEMO_EVENTS.filter((event) => {
|
|
311
|
+
if (!event.calendarSourceKey) return true;
|
|
312
|
+
return selected.size === 0 || selected.has(event.calendarSourceKey);
|
|
313
|
+
});
|
|
314
|
+
},
|
|
315
|
+
enabled: demo,
|
|
316
|
+
staleTime: Infinity,
|
|
317
|
+
});
|
|
318
|
+
|
|
319
|
+
return demo ? fixture : live;
|
|
291
320
|
}
|
|
292
321
|
|
|
293
322
|
type OverlaySourceCoverage = {
|
|
@@ -351,8 +380,10 @@ export function prefetchEvents(
|
|
|
351
380
|
from: string,
|
|
352
381
|
to: string,
|
|
353
382
|
overlayEmails?: string[],
|
|
383
|
+
calendarSourceKeys?: string[],
|
|
354
384
|
) {
|
|
355
|
-
|
|
385
|
+
if (isSharedCalendarDemo()) return;
|
|
386
|
+
const params = buildEventsParams(from, to, overlayEmails, calendarSourceKeys);
|
|
356
387
|
return queryClient.prefetchQuery({
|
|
357
388
|
queryKey: ["action", "list-events", params],
|
|
358
389
|
queryFn: () =>
|
|
@@ -362,8 +393,12 @@ export function prefetchEvents(
|
|
|
362
393
|
});
|
|
363
394
|
}
|
|
364
395
|
|
|
365
|
-
export function useEvent(id: string) {
|
|
366
|
-
return useActionQuery<CalendarEvent>(
|
|
396
|
+
export function useEvent(id: string, calendarSourceKey?: string) {
|
|
397
|
+
return useActionQuery<CalendarEvent>(
|
|
398
|
+
"get-event",
|
|
399
|
+
calendarSourceKey ? { id, calendarSourceKey } : { id },
|
|
400
|
+
{ enabled: !!id },
|
|
401
|
+
);
|
|
367
402
|
}
|
|
368
403
|
|
|
369
404
|
export function useCreateEvent() {
|
|
@@ -7,6 +7,11 @@ import type { GoogleAuthStatus } from "@shared/api";
|
|
|
7
7
|
import { useQuery, useMutation, useQueryClient } from "@tanstack/react-query";
|
|
8
8
|
import { useCallback, useEffect, useMemo, useRef, useState } from "react";
|
|
9
9
|
|
|
10
|
+
import {
|
|
11
|
+
isSharedCalendarDemo,
|
|
12
|
+
SHARED_CALENDAR_DEMO_STATUS,
|
|
13
|
+
} from "@/lib/shared-calendar-demo";
|
|
14
|
+
|
|
10
15
|
export interface DesktopAuthIssue {
|
|
11
16
|
error?: string;
|
|
12
17
|
message?: string;
|
|
@@ -149,14 +154,16 @@ async function fetchJson<T>(input: string, init?: RequestInit): Promise<T> {
|
|
|
149
154
|
}
|
|
150
155
|
|
|
151
156
|
export function useGoogleAuthStatus() {
|
|
157
|
+
const demo = isSharedCalendarDemo();
|
|
152
158
|
return useQuery<GoogleAuthStatus>({
|
|
153
159
|
queryKey: ["google-status"],
|
|
154
160
|
queryFn: async () => {
|
|
161
|
+
if (demo) return SHARED_CALENDAR_DEMO_STATUS;
|
|
155
162
|
return fetchJson<GoogleAuthStatus>(
|
|
156
163
|
agentNativePath("/_agent-native/google/status"),
|
|
157
164
|
);
|
|
158
165
|
},
|
|
159
|
-
staleTime: 30_000,
|
|
166
|
+
staleTime: demo ? Infinity : 30_000,
|
|
160
167
|
});
|
|
161
168
|
}
|
|
162
169
|
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { useFeatureFlag } from "@agent-native/core/client/feature-flags";
|
|
2
|
+
import { useActionQuery } from "@agent-native/core/client/hooks";
|
|
3
|
+
import type { GoogleCalendarSource } from "@shared/api";
|
|
4
|
+
import { SHARED_GOOGLE_CALENDARS } from "@shared/feature-flags";
|
|
5
|
+
import { useQuery } from "@tanstack/react-query";
|
|
6
|
+
|
|
7
|
+
import {
|
|
8
|
+
isSharedCalendarDemo,
|
|
9
|
+
SHARED_CALENDAR_DEMO_SOURCES,
|
|
10
|
+
} from "@/lib/shared-calendar-demo";
|
|
11
|
+
|
|
12
|
+
export function useGoogleCalendars() {
|
|
13
|
+
const demo = isSharedCalendarDemo();
|
|
14
|
+
const flagEnabled = useFeatureFlag(SHARED_GOOGLE_CALENDARS.key);
|
|
15
|
+
const enabled = demo || flagEnabled;
|
|
16
|
+
const live = useActionQuery<{
|
|
17
|
+
calendars: GoogleCalendarSource[];
|
|
18
|
+
errors: Array<{ email: string; error: string }>;
|
|
19
|
+
}>(
|
|
20
|
+
"list-google-calendars",
|
|
21
|
+
{},
|
|
22
|
+
{
|
|
23
|
+
enabled: enabled && !demo,
|
|
24
|
+
retry: false,
|
|
25
|
+
staleTime: 30_000,
|
|
26
|
+
},
|
|
27
|
+
);
|
|
28
|
+
const fixture = useQuery({
|
|
29
|
+
queryKey: ["shared-calendar-demo-sources"],
|
|
30
|
+
queryFn: async () => SHARED_CALENDAR_DEMO_SOURCES,
|
|
31
|
+
enabled: demo,
|
|
32
|
+
staleTime: Infinity,
|
|
33
|
+
});
|
|
34
|
+
return {
|
|
35
|
+
...(demo ? fixture : live),
|
|
36
|
+
data: demo ? fixture.data : live.data?.calendars,
|
|
37
|
+
sourceErrors: demo ? [] : (live.data?.errors ?? []),
|
|
38
|
+
enabled,
|
|
39
|
+
demo,
|
|
40
|
+
};
|
|
41
|
+
}
|
|
@@ -13,6 +13,7 @@ import {
|
|
|
13
13
|
type CalendarColorMode,
|
|
14
14
|
type CalendarViewPreferences,
|
|
15
15
|
} from "@/lib/calendar-view-preferences";
|
|
16
|
+
import { isSharedCalendarDemo } from "@/lib/shared-calendar-demo";
|
|
16
17
|
|
|
17
18
|
export type ViewPreferences = CalendarViewPreferences;
|
|
18
19
|
|
|
@@ -129,6 +130,7 @@ async function readAppStatePreferences(): Promise<CalendarViewPreferences | null
|
|
|
129
130
|
}
|
|
130
131
|
|
|
131
132
|
export function useViewPreferences() {
|
|
133
|
+
const demo = isSharedCalendarDemo();
|
|
132
134
|
const [prefs, setPrefs] = useState<ViewPreferences>(load);
|
|
133
135
|
const accountColorRequestIds = useRef<Record<string, number>>({});
|
|
134
136
|
const accountModeRequestIds = useRef<Record<string, number>>({});
|
|
@@ -150,6 +152,7 @@ export function useViewPreferences() {
|
|
|
150
152
|
}, []);
|
|
151
153
|
|
|
152
154
|
useEffect(() => {
|
|
155
|
+
if (demo) return;
|
|
153
156
|
let cancelled = false;
|
|
154
157
|
let timeout: number | undefined;
|
|
155
158
|
|
|
@@ -220,7 +223,7 @@ export function useViewPreferences() {
|
|
|
220
223
|
document.removeEventListener("visibilitychange", handleVisibilityChange);
|
|
221
224
|
if (timeout) window.clearTimeout(timeout);
|
|
222
225
|
};
|
|
223
|
-
}, []);
|
|
226
|
+
}, [demo]);
|
|
224
227
|
|
|
225
228
|
const update = useCallback((patch: Partial<ViewPreferences>) => {
|
|
226
229
|
setPrefs((prev) => {
|
|
@@ -258,6 +261,8 @@ export function useViewPreferences() {
|
|
|
258
261
|
return next;
|
|
259
262
|
});
|
|
260
263
|
|
|
264
|
+
if (demo) return;
|
|
265
|
+
|
|
261
266
|
callAction("update-calendar-visual-preferences", {
|
|
262
267
|
accountEmail,
|
|
263
268
|
accountColor,
|
|
@@ -333,7 +338,7 @@ export function useViewPreferences() {
|
|
|
333
338
|
}
|
|
334
339
|
});
|
|
335
340
|
},
|
|
336
|
-
[],
|
|
341
|
+
[demo],
|
|
337
342
|
);
|
|
338
343
|
|
|
339
344
|
const updateAccountColorMode = useCallback(
|
|
@@ -356,6 +361,8 @@ export function useViewPreferences() {
|
|
|
356
361
|
return next;
|
|
357
362
|
});
|
|
358
363
|
|
|
364
|
+
if (demo) return;
|
|
365
|
+
|
|
359
366
|
callAction("update-calendar-visual-preferences", {
|
|
360
367
|
accountEmail,
|
|
361
368
|
accountColorMode,
|
|
@@ -416,8 +423,53 @@ export function useViewPreferences() {
|
|
|
416
423
|
}
|
|
417
424
|
});
|
|
418
425
|
},
|
|
419
|
-
[],
|
|
426
|
+
[demo],
|
|
427
|
+
);
|
|
428
|
+
|
|
429
|
+
const updateGoogleCalendarVisibility = useCallback(
|
|
430
|
+
(sourceKey: string, visible: boolean) => {
|
|
431
|
+
const rollbackPrefs = prefs;
|
|
432
|
+
const next = normalizeCalendarViewPreferences({
|
|
433
|
+
...prefs,
|
|
434
|
+
googleCalendarVisibility: {
|
|
435
|
+
...prefs.googleCalendarVisibility,
|
|
436
|
+
[sourceKey]: visible,
|
|
437
|
+
},
|
|
438
|
+
});
|
|
439
|
+
setPrefs(next);
|
|
440
|
+
save(next);
|
|
441
|
+
window.dispatchEvent(new Event(CALENDAR_VIEW_PREFERENCES_CHANGE_EVENT));
|
|
442
|
+
|
|
443
|
+
if (demo) return;
|
|
444
|
+
|
|
445
|
+
callAction("update-calendar-visual-preferences", {
|
|
446
|
+
googleCalendarSourceKey: sourceKey,
|
|
447
|
+
googleCalendarVisible: visible,
|
|
448
|
+
}).catch(() => {
|
|
449
|
+
setPrefs((current) => {
|
|
450
|
+
if (current.googleCalendarVisibility[sourceKey] !== visible) {
|
|
451
|
+
return current;
|
|
452
|
+
}
|
|
453
|
+
const next = normalizeCalendarViewPreferences({
|
|
454
|
+
...current,
|
|
455
|
+
googleCalendarVisibility: rollbackPrefs.googleCalendarVisibility,
|
|
456
|
+
});
|
|
457
|
+
save(next);
|
|
458
|
+
window.dispatchEvent(
|
|
459
|
+
new Event(CALENDAR_VIEW_PREFERENCES_CHANGE_EVENT),
|
|
460
|
+
);
|
|
461
|
+
return next;
|
|
462
|
+
});
|
|
463
|
+
});
|
|
464
|
+
},
|
|
465
|
+
[demo, prefs],
|
|
420
466
|
);
|
|
421
467
|
|
|
422
|
-
return {
|
|
468
|
+
return {
|
|
469
|
+
prefs,
|
|
470
|
+
update,
|
|
471
|
+
updateAccountColor,
|
|
472
|
+
updateAccountColorMode,
|
|
473
|
+
updateGoogleCalendarVisibility,
|
|
474
|
+
};
|
|
423
475
|
}
|
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
CalendarEvent,
|
|
3
|
+
GoogleAuthStatus,
|
|
4
|
+
GoogleCalendarSource,
|
|
5
|
+
} from "@shared/api";
|
|
6
|
+
import { CALENDAR_COLORS } from "@shared/calendar-view-preferences";
|
|
7
|
+
|
|
8
|
+
export function isSharedCalendarDemo(): boolean {
|
|
9
|
+
return (
|
|
10
|
+
import.meta.env.DEV &&
|
|
11
|
+
new URLSearchParams(window.location.search).get("calendarDemo") === "shared"
|
|
12
|
+
);
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export const SHARED_CALENDAR_DEMO_STATUS: GoogleAuthStatus = {
|
|
16
|
+
connected: true,
|
|
17
|
+
configured: true,
|
|
18
|
+
accounts: [
|
|
19
|
+
{ email: "alice@builder.io" },
|
|
20
|
+
{ email: "emdistal@gmail.com" },
|
|
21
|
+
{ email: "tempoimmaterial@gmail.com" },
|
|
22
|
+
],
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
export const SHARED_CALENDAR_DEMO_SOURCES: GoogleCalendarSource[] = [
|
|
26
|
+
{
|
|
27
|
+
sourceKey: "demo:builder-primary",
|
|
28
|
+
accountEmail: "alice@builder.io",
|
|
29
|
+
calendarId: "alice@builder.io",
|
|
30
|
+
name: "Alice · Builder",
|
|
31
|
+
color: CALENDAR_COLORS[1],
|
|
32
|
+
selected: true,
|
|
33
|
+
primary: true,
|
|
34
|
+
accessRole: "owner",
|
|
35
|
+
readOnly: false,
|
|
36
|
+
},
|
|
37
|
+
{
|
|
38
|
+
sourceKey: "demo:personal-primary",
|
|
39
|
+
accountEmail: "emdistal@gmail.com",
|
|
40
|
+
calendarId: "emdistal@gmail.com",
|
|
41
|
+
name: "Alice Alexandra",
|
|
42
|
+
color: CALENDAR_COLORS[2],
|
|
43
|
+
selected: true,
|
|
44
|
+
primary: true,
|
|
45
|
+
accessRole: "owner",
|
|
46
|
+
readOnly: false,
|
|
47
|
+
},
|
|
48
|
+
{
|
|
49
|
+
sourceKey: "demo:friends",
|
|
50
|
+
accountEmail: "emdistal@gmail.com",
|
|
51
|
+
calendarId: "friends@example.com",
|
|
52
|
+
name: "Friends",
|
|
53
|
+
color: CALENDAR_COLORS[3],
|
|
54
|
+
selected: true,
|
|
55
|
+
primary: false,
|
|
56
|
+
accessRole: "reader",
|
|
57
|
+
readOnly: true,
|
|
58
|
+
},
|
|
59
|
+
{
|
|
60
|
+
sourceKey: "demo:tempo-primary",
|
|
61
|
+
accountEmail: "tempoimmaterial@gmail.com",
|
|
62
|
+
calendarId: "tempoimmaterial@gmail.com",
|
|
63
|
+
name: "Tempo Immaterial",
|
|
64
|
+
color: CALENDAR_COLORS[5],
|
|
65
|
+
selected: true,
|
|
66
|
+
primary: true,
|
|
67
|
+
accessRole: "owner",
|
|
68
|
+
readOnly: false,
|
|
69
|
+
},
|
|
70
|
+
{
|
|
71
|
+
sourceKey: "demo:studio",
|
|
72
|
+
accountEmail: "tempoimmaterial@gmail.com",
|
|
73
|
+
calendarId: "studio@example.com",
|
|
74
|
+
name: "Studio schedule",
|
|
75
|
+
color: CALENDAR_COLORS[4],
|
|
76
|
+
selected: false,
|
|
77
|
+
primary: false,
|
|
78
|
+
accessRole: "reader",
|
|
79
|
+
readOnly: true,
|
|
80
|
+
},
|
|
81
|
+
];
|
|
82
|
+
|
|
83
|
+
const now = "2026-09-01T12:00:00.000Z";
|
|
84
|
+
|
|
85
|
+
export const SHARED_CALENDAR_DEMO_EVENTS: CalendarEvent[] = [
|
|
86
|
+
demoEvent({
|
|
87
|
+
id: "google-builder-enablement",
|
|
88
|
+
title: "GTM Enablement",
|
|
89
|
+
start: "2026-09-02T12:00:00-04:00",
|
|
90
|
+
end: "2026-09-02T13:00:00-04:00",
|
|
91
|
+
accountEmail: "alice@builder.io",
|
|
92
|
+
calendarId: "alice@builder.io",
|
|
93
|
+
calendarName: "Alice · Builder",
|
|
94
|
+
color: CALENDAR_COLORS[1],
|
|
95
|
+
}),
|
|
96
|
+
demoEvent({
|
|
97
|
+
id: "google-personal-dermatology",
|
|
98
|
+
title: "dermatology appt",
|
|
99
|
+
start: "2026-09-01T15:20:00-04:00",
|
|
100
|
+
end: "2026-09-01T16:20:00-04:00",
|
|
101
|
+
accountEmail: "emdistal@gmail.com",
|
|
102
|
+
calendarId: "emdistal@gmail.com",
|
|
103
|
+
calendarName: "Alice Alexandra",
|
|
104
|
+
color: CALENDAR_COLORS[2],
|
|
105
|
+
}),
|
|
106
|
+
demoEvent({
|
|
107
|
+
id: "google-friends-cottage",
|
|
108
|
+
title: "Magpie Cottage Sale",
|
|
109
|
+
start: "2026-09-05T10:00:00-04:00",
|
|
110
|
+
end: "2026-09-05T16:00:00-04:00",
|
|
111
|
+
accountEmail: "emdistal@gmail.com",
|
|
112
|
+
calendarId: "friends@example.com",
|
|
113
|
+
calendarName: "Friends",
|
|
114
|
+
calendarAccessRole: "reader",
|
|
115
|
+
calendarReadOnly: true,
|
|
116
|
+
color: CALENDAR_COLORS[3],
|
|
117
|
+
}),
|
|
118
|
+
demoEvent({
|
|
119
|
+
id: "google-friends-birthday",
|
|
120
|
+
title: "Matt’s 33⅓ Birthday",
|
|
121
|
+
start: "2026-09-04T18:00:00-04:00",
|
|
122
|
+
end: "2026-09-04T19:00:00-04:00",
|
|
123
|
+
accountEmail: "emdistal@gmail.com",
|
|
124
|
+
calendarId: "friends@example.com",
|
|
125
|
+
calendarName: "Friends",
|
|
126
|
+
calendarAccessRole: "reader",
|
|
127
|
+
calendarReadOnly: true,
|
|
128
|
+
color: CALENDAR_COLORS[3],
|
|
129
|
+
}),
|
|
130
|
+
demoEvent({
|
|
131
|
+
id: "google-personal-dinner",
|
|
132
|
+
title: "dinner with sarah",
|
|
133
|
+
start: "2026-09-03T17:30:00-04:00",
|
|
134
|
+
end: "2026-09-03T20:00:00-04:00",
|
|
135
|
+
accountEmail: "emdistal@gmail.com",
|
|
136
|
+
calendarId: "emdistal@gmail.com",
|
|
137
|
+
calendarName: "Alice Alexandra",
|
|
138
|
+
color: CALENDAR_COLORS[2],
|
|
139
|
+
}),
|
|
140
|
+
demoEvent({
|
|
141
|
+
id: "google-tempo-studio",
|
|
142
|
+
title: "Open studio",
|
|
143
|
+
start: "2026-09-03T14:00:00-04:00",
|
|
144
|
+
end: "2026-09-03T16:00:00-04:00",
|
|
145
|
+
accountEmail: "tempoimmaterial@gmail.com",
|
|
146
|
+
calendarId: "studio@example.com",
|
|
147
|
+
calendarName: "Studio schedule",
|
|
148
|
+
calendarAccessRole: "reader",
|
|
149
|
+
calendarReadOnly: true,
|
|
150
|
+
color: CALENDAR_COLORS[4],
|
|
151
|
+
}),
|
|
152
|
+
];
|
|
153
|
+
|
|
154
|
+
function demoEvent(
|
|
155
|
+
event: Pick<
|
|
156
|
+
CalendarEvent,
|
|
157
|
+
| "id"
|
|
158
|
+
| "title"
|
|
159
|
+
| "start"
|
|
160
|
+
| "end"
|
|
161
|
+
| "accountEmail"
|
|
162
|
+
| "calendarId"
|
|
163
|
+
| "calendarName"
|
|
164
|
+
| "calendarAccessRole"
|
|
165
|
+
| "calendarReadOnly"
|
|
166
|
+
| "color"
|
|
167
|
+
>,
|
|
168
|
+
): CalendarEvent {
|
|
169
|
+
const source = SHARED_CALENDAR_DEMO_SOURCES.find(
|
|
170
|
+
(candidate) =>
|
|
171
|
+
candidate.accountEmail === event.accountEmail &&
|
|
172
|
+
candidate.calendarId === event.calendarId,
|
|
173
|
+
);
|
|
174
|
+
return {
|
|
175
|
+
...event,
|
|
176
|
+
calendarSourceKey: source?.sourceKey ?? "",
|
|
177
|
+
calendarPrimary: source?.primary,
|
|
178
|
+
description: "",
|
|
179
|
+
location: "",
|
|
180
|
+
allDay: false,
|
|
181
|
+
source: "google",
|
|
182
|
+
googleEventId: event.id.replace(/^google-/, ""),
|
|
183
|
+
htmlLink: "https://calendar.google.com/calendar/u/0/r",
|
|
184
|
+
createdAt: now,
|
|
185
|
+
updatedAt: now,
|
|
186
|
+
};
|
|
187
|
+
}
|