Skip to content

Commit 70cb9ca

Browse files
committed
update
1 parent f818cda commit 70cb9ca

File tree

5 files changed

+118
-40
lines changed

5 files changed

+118
-40
lines changed

apps/desktop/src/components/devtool/seed/shared/calendar.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ export const createCalendar = () => {
2424
user_id: DEFAULT_USER_ID,
2525
name: template,
2626
created_at: faker.date.past({ years: 1 }).toISOString(),
27+
enabled: faker.datatype.boolean(),
2728
} satisfies Calendar,
2829
};
2930
};

apps/desktop/src/components/main/body/empty/index.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,7 @@ function EmptyView() {
4848
const newNote = useNewNote({ behavior: "current" });
4949
const openCurrent = useTabs((state) => state.openCurrent);
5050
const openCalendar = useCallback(
51-
() => openCurrent({ type: "extension", extensionId: "calendar" }),
51+
() => openCurrent({ type: "calendar" }),
5252
[openCurrent],
5353
);
5454
const openContacts = useCallback(

apps/desktop/src/components/settings/calendar/configure/apple.tsx

Lines changed: 96 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
1-
import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
1+
import { useMutation, useQuery } from "@tanstack/react-query";
22
import { AlertCircleIcon, ArrowRightIcon, CheckIcon } from "lucide-react";
3-
import { useMemo, useState } from "react";
3+
import { useEffect, useMemo } from "react";
44

55
import {
66
commands as appleCalendarCommands,
@@ -18,6 +18,7 @@ import {
1818
import { Button } from "@hypr/ui/components/ui/button";
1919
import { cn } from "@hypr/utils";
2020

21+
import * as main from "../../../../store/tinybase/main";
2122
import { PROVIDERS } from "../shared";
2223
import {
2324
type CalendarGroup,
@@ -131,24 +132,74 @@ function appleColorToCss(color?: CalendarColor | null): string | undefined {
131132
}
132133

133134
function useAppleCalendarSelection() {
134-
const queryClient = useQueryClient();
135-
const [enabledCalendars, setEnabledCalendars] = useState<Set<string>>(
136-
new Set(),
137-
);
135+
const { user_id } = main.UI.useValues(main.STORE_ID);
136+
const calendarsTable = main.UI.useTable("calendars", main.STORE_ID);
138137

139-
const { data: appleCalendars } = useQuery({
138+
const {
139+
data: appleCalendars,
140+
refetch,
141+
isFetching,
142+
} = useQuery({
140143
queryKey: ["appleCalendars"],
141144
queryFn: async () => {
142-
const result = await appleCalendarCommands.listCalendars();
143-
if (result.status === "ok") {
144-
return result.data;
145+
const operation = appleCalendarCommands.listCalendars();
146+
const minDelay = new Promise((resolve) => setTimeout(resolve, 500));
147+
148+
const [result] = await Promise.all([operation, minDelay]);
149+
if (result.status === "error") {
150+
throw new Error(result.error);
145151
}
146-
throw new Error(result.error);
152+
return result.data;
147153
},
148154
});
149155

156+
const createCalendarRow = main.UI.useSetRowCallback(
157+
"calendars",
158+
(p: { id: string; name: string }) => p.id,
159+
(p: { id: string; name: string }) => ({
160+
user_id: user_id ?? "",
161+
created_at: new Date().toISOString(),
162+
name: p.name,
163+
enabled: false,
164+
}),
165+
[user_id],
166+
main.STORE_ID,
167+
);
168+
169+
const updateCalendarName = main.UI.useSetCellCallback(
170+
"calendars",
171+
(p: { id: string; name: string }) => p.id,
172+
"name",
173+
(p: { id: string; name: string }) => p.name,
174+
[],
175+
main.STORE_ID,
176+
);
177+
178+
useEffect(() => {
179+
if (!appleCalendars || !user_id) {
180+
return;
181+
}
182+
183+
for (const cal of appleCalendars) {
184+
const existing = calendarsTable[cal.id];
185+
if (!existing) {
186+
createCalendarRow({ id: cal.id, name: cal.title });
187+
} else if (existing.name !== cal.title) {
188+
updateCalendarName({ id: cal.id, name: cal.title });
189+
}
190+
}
191+
}, [
192+
appleCalendars,
193+
user_id,
194+
calendarsTable,
195+
createCalendarRow,
196+
updateCalendarName,
197+
]);
198+
150199
const groups = useMemo((): CalendarGroup[] => {
151-
if (!appleCalendars) return [];
200+
if (!appleCalendars) {
201+
return [];
202+
}
152203

153204
const grouped = new Map<string, CalendarItem[]>();
154205
for (const cal of appleCalendars) {
@@ -170,30 +221,47 @@ function useAppleCalendarSelection() {
170221
}, [appleCalendars]);
171222

172223
const isCalendarEnabled = (calendarId: string): boolean => {
173-
return enabledCalendars.has(calendarId);
224+
const calendar = calendarsTable[calendarId];
225+
return calendar?.enabled === true;
174226
};
175227

176-
const handleToggle = (calendar: CalendarItem, enabled: boolean) => {
177-
setEnabledCalendars((prev) => {
178-
const next = new Set(prev);
179-
if (enabled) {
180-
next.add(calendar.id);
181-
} else {
182-
next.delete(calendar.id);
228+
const setCalendarRow = main.UI.useSetRowCallback(
229+
"calendars",
230+
(p: { calendarId: string; enabled: boolean }) => p.calendarId,
231+
(p: { calendarId: string; enabled: boolean }) => {
232+
const existing = calendarsTable[p.calendarId];
233+
if (!existing) {
234+
return {
235+
user_id: user_id ?? "",
236+
created_at: new Date().toISOString(),
237+
name: "",
238+
enabled: p.enabled,
239+
};
183240
}
184-
return next;
185-
});
186-
};
241+
return {
242+
...existing,
243+
enabled: p.enabled,
244+
};
245+
},
246+
[calendarsTable, user_id],
247+
main.STORE_ID,
248+
);
187249

188-
const handleRefresh = () => {
189-
queryClient.invalidateQueries({ queryKey: ["appleCalendars"] });
250+
const handleToggle = (calendar: CalendarItem, enabled: boolean) => {
251+
setCalendarRow({ calendarId: calendar.id, enabled });
190252
};
191253

192-
return { groups, isCalendarEnabled, handleToggle, handleRefresh };
254+
return {
255+
groups,
256+
isCalendarEnabled,
257+
handleToggle,
258+
handleRefresh: refetch,
259+
isLoading: isFetching,
260+
};
193261
}
194262

195263
function AppleCalendarSelection() {
196-
const { groups, isCalendarEnabled, handleToggle, handleRefresh } =
264+
const { groups, isCalendarEnabled, handleToggle, handleRefresh, isLoading } =
197265
useAppleCalendarSelection();
198266

199267
return (
@@ -202,6 +270,7 @@ function AppleCalendarSelection() {
202270
isCalendarEnabled={isCalendarEnabled}
203271
onToggle={handleToggle}
204272
onRefresh={handleRefresh}
273+
isLoading={isLoading}
205274
/>
206275
);
207276
}

apps/desktop/src/components/settings/calendar/configure/shared.tsx

Lines changed: 15 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import { RefreshCwIcon } from "lucide-react";
22

33
import { Button } from "@hypr/ui/components/ui/button";
44
import { Switch } from "@hypr/ui/components/ui/switch";
5+
import { cn } from "@hypr/utils";
56

67
export interface CalendarItem {
78
id: string;
@@ -19,13 +20,15 @@ interface CalendarSelectionProps {
1920
isCalendarEnabled: (id: string) => boolean;
2021
onToggle: (calendar: CalendarItem, enabled: boolean) => void;
2122
onRefresh: () => void;
23+
isLoading: boolean;
2224
}
2325

2426
export function CalendarSelection({
2527
groups,
2628
isCalendarEnabled,
2729
onToggle,
2830
onRefresh,
31+
isLoading,
2932
}: CalendarSelectionProps) {
3033
if (groups.length === 0) {
3134
return (
@@ -38,18 +41,27 @@ export function CalendarSelection({
3841
return (
3942
<div className="pt-4 border-t mt-2">
4043
<div className="flex items-center justify-between mb-3">
41-
<h4 className="text-sm font-medium">Select Calendars</h4>
44+
<h4 className="text-sm font-medium">Select Calendars </h4>
4245
<Button
4346
variant="ghost"
4447
size="icon"
4548
onClick={onRefresh}
4649
className="size-7"
47-
aria-label="Refresh calendars"
50+
disabled={isLoading}
4851
>
49-
<RefreshCwIcon className="size-4" />
52+
<RefreshCwIcon
53+
className={cn("size-4", isLoading && "animate-spin")}
54+
/>
5055
</Button>
5156
</div>
5257
<div className="space-y-4">
58+
<div className="mt-2 p-3 bg-amber-50 border border-amber-200 rounded-lg">
59+
<p className="text-xs text-amber-700">
60+
Event fetching is not implemented yet. Calendar selection will be
61+
used once event syncing is available.
62+
</p>
63+
</div>
64+
5365
{groups.map((group) => (
5466
<div key={group.sourceName}>
5567
<h5 className="text-xs font-medium text-neutral-500 mb-2">
@@ -68,12 +80,6 @@ export function CalendarSelection({
6880
</div>
6981
))}
7082
</div>
71-
<div className="mt-4 p-3 bg-amber-50 border border-amber-200 rounded-lg">
72-
<p className="text-xs text-amber-700">
73-
Note: Event fetching is not implemented yet. Calendar selection will
74-
be used once event syncing is available.
75-
</p>
76-
</div>
7783
</div>
7884
);
7985
}

packages/store/src/schema-external.ts

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -44,9 +44,10 @@ export const eventSchema = baseEventSchema.omit({ id: true }).extend({
4444
note: z.preprocess((val) => val ?? undefined, z.string().optional()),
4545
});
4646

47-
export const calendarSchema = baseCalendarSchema
48-
.omit({ id: true })
49-
.extend({ created_at: z.string() });
47+
export const calendarSchema = baseCalendarSchema.omit({ id: true }).extend({
48+
created_at: z.string(),
49+
enabled: z.preprocess((val) => val ?? false, z.boolean()),
50+
});
5051

5152
export const organizationSchema = baseOrganizationSchema
5253
.omit({ id: true })
@@ -262,6 +263,7 @@ export const externalTableSchemaForTinybase = {
262263
user_id: { type: "string" },
263264
created_at: { type: "string" },
264265
name: { type: "string" },
266+
enabled: { type: "boolean" },
265267
} as const satisfies InferTinyBaseSchema<typeof calendarSchema>,
266268
events: {
267269
user_id: { type: "string" },

0 commit comments

Comments
 (0)