Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions src/components/upload-zone.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -75,9 +75,9 @@ export function UploadZone() {
// Remove the redirect flag after restoring
sessionStorage.removeItem("schedulesync_oauth_redirect");
}
// eslint-disable-next-line react-hooks/exhaustive-deps
// Intentionally only depend on isSignedIn. We check preview/fileName/events for null
// to determine if restoration is needed, but don't want to re-run when they change.
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [isSignedIn]);

// Save state to sessionStorage before OAuth redirect
Expand Down Expand Up @@ -324,7 +324,7 @@ export function UploadZone() {
{analyzeSchedule.isPending ? (
<>
<Loader2 className="size-5 animate-spin" />
Analyzing schedule...
Analyzing schedule... Don&apos;t close the page.
</>
) : (
<>
Expand Down
4 changes: 3 additions & 1 deletion src/server/api/routers/schedule.ts
Original file line number Diff line number Diff line change
Expand Up @@ -82,15 +82,17 @@ export const scheduleRouter = createTRPCRouter({
}

// Create a new calendar
const timezone = input.timezone ?? "UTC";
const calendarId = await createCalendar(
accessToken,
input.calendarName,
timezone,
);

// Add events to the calendar
await addEventsToCalendar(accessToken, calendarId, input.events, {
repeatWeeks: input.repeatWeeks,
timezone: input.timezone ?? "UTC",
timezone,
startDate: input.startDate,
});

Expand Down
25 changes: 20 additions & 5 deletions src/server/services/google-calendar.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,27 @@ import { google } from "googleapis";
import type { ScheduleEvent } from "./schedule-analyzer";
import { parse, addWeeks } from "date-fns";

/**
* Formats a Date as a local datetime string without timezone suffix.
* Google Calendar will interpret this time using the provided timeZone parameter.
*/
function formatDateTimeLocal(date: Date): string {
const year = date.getFullYear();
const month = String(date.getMonth() + 1).padStart(2, "0");
const day = String(date.getDate()).padStart(2, "0");
const hours = String(date.getHours()).padStart(2, "0");
const minutes = String(date.getMinutes()).padStart(2, "0");
const seconds = String(date.getSeconds()).padStart(2, "0");
return `${year}-${month}-${day}T${hours}:${minutes}:${seconds}`;
}

/**
* Creates a new Google Calendar
*/
export async function createCalendar(
accessToken: string,
calendarName: string,
timezone = "UTC",
): Promise<string> {
const calendar = google.calendar({ version: "v3" });

Expand All @@ -18,7 +33,7 @@ export async function createCalendar(
auth,
requestBody: {
summary: calendarName,
timeZone: Intl.DateTimeFormat().resolvedOptions().timeZone,
timeZone: timezone,
},
});

Expand Down Expand Up @@ -95,11 +110,11 @@ async function createOneTimeEvent(
location: event.location,
description: buildEventDescription(event),
start: {
dateTime: startDateTime.toISOString(),
dateTime: formatDateTimeLocal(startDateTime),
timeZone: timezone,
},
end: {
dateTime: endDateTime.toISOString(),
dateTime: formatDateTimeLocal(endDateTime),
timeZone: timezone,
},
},
Expand Down Expand Up @@ -162,11 +177,11 @@ async function createRecurringEvent(
location: event.location,
description: buildEventDescription(event),
start: {
dateTime: startDateTime.toISOString(),
dateTime: formatDateTimeLocal(startDateTime),
timeZone: timezone,
},
end: {
dateTime: endDateTime.toISOString(),
dateTime: formatDateTimeLocal(endDateTime),
timeZone: timezone,
},
recurrence: [rrule],
Expand Down