Repository: tutur3u/platform
Pull request #2339: Sync all from google calendar
Author: login: coder1807
Created: 2025-04-13T14:01:59Z | Merged: 2025-04-13T21:51:28Z



<!-- This is an auto-generated comment: release notes by coderabbit.ai -->
## Summary by CodeRabbit

- **New Features**
	- After authentication, users are now redirected to a dedicated calendar onboarding page.
	- Google Calendar integration has been enhanced to fetch, update, and delete events.
	- A new sync button in the calendar settings lets you manually synchronize events, with real-time notifications on the process.
	- Calendar events now include additional details for improved integration with your Google Calendar.
	- Users can now synchronize their Google Calendar events directly from the settings interface.
- **Bug Fixes**
	- Improved error handling for missing `wsId` during authentication and event synchronization processes.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->


Diff:
diff --git a/apps/db/supabase/migrations/20250413110924_update_ws_calendar_events.sql b/apps/db/supabase/migrations/20250413110924_update_ws_calendar_events.sql
new file mode 100644
index 0000000000..035602bfd5
--- /dev/null
+++ b/apps/db/supabase/migrations/20250413110924_update_ws_calendar_events.sql
@@ -0,0 +1,3 @@
+alter table "public"."workspace_calendar_events" add column "google_event_id" text;
+
+
diff --git a/apps/web/src/app/[locale]/(dashboard)/[wsId]/calendar/client.tsx b/apps/web/src/app/[locale]/(dashboard)/[wsId]/calendar/client.tsx
index 3ad5412470..e7c7f1530e 100644
--- a/apps/web/src/app/[locale]/(dashboard)/[wsId]/calendar/client.tsx
+++ b/apps/web/src/app/[locale]/(dashboard)/[wsId]/calendar/client.tsx
@@ -1,6 +1,5 @@
 'use client';
 
-import { DEV_MODE } from '@/constants/common';
 import { useQuery, useQueryClient } from '@tanstack/react-query';
 import type { Workspace } from '@tuturuuu/types/primitives/Workspace';
 import { Calendar } from '@tuturuuu/ui/legacy/calendar/Calendar';
@@ -24,7 +23,7 @@ export default function CalendarClientPage({
       useQuery={useQuery}
       useQueryClient={useQueryClient}
       experimentalGoogleCalendarLinked={experimentalGoogleCalendarLinked}
-      enableExperimentalGoogleCalendar={DEV_MODE}
+      enableExperimentalGoogleCalendar={true}
     />
   );
 }
diff --git a/apps/web/src/app/api/v1/calendar/auth/callback/route.ts b/apps/web/src/app/api/v1/calendar/auth/callback/route.ts
index 650457867a..853fe3f61e 100644
--- a/apps/web/src/app/api/v1/calendar/auth/callback/route.ts
+++ b/apps/web/src/app/api/v1/calendar/auth/callback/route.ts
@@ -5,6 +5,11 @@ import { NextResponse } from 'next/server';
 export async function GET(request: Request) {
   const url = new URL(request.url);
   const code = url.searchParams.get('code');
+  const wsId = url.searchParams.get('state');
+
+  if (!wsId) {
+    return NextResponse.json({ error: 'wsId is required' }, { status: 400 });
+  }
 
   if (!code) {
     return NextResponse.json({ error: 'No code provided' }, { status: 400 });
@@ -49,6 +54,7 @@ export async function GET(request: Request) {
       .from('calendar_auth_tokens')
       .select('*')
       .eq('user_id', user.id)
+      .eq('ws_id', wsId)
       .single();
 
     if (fetchError && fetchError.code !== 'PGRST116') {
@@ -84,6 +90,7 @@ export async function GET(request: Request) {
         .from('calendar_auth_tokens')
         .insert({
           user_id: user.id,
+          ws_id: wsId,
           access_token: tokens.access_token,
           refresh_token: refreshToken,
           created_at: new Date().toISOString(),
@@ -99,7 +106,10 @@ export async function GET(request: Request) {
     }
 
     // Redirect to the calendar page without tokens in the URL
-    return NextResponse.redirect(new URL('/onboarding', request.url), 302);
+    return NextResponse.redirect(
+      new URL(`/${wsId}/calendar`, request.url),
+      302
+    );
   } catch (error) {
     console.error('Error during OAuth callback:', error);
     return NextResponse.json(
diff --git a/apps/web/src/app/api/v1/calendar/auth/fetch/route.ts b/apps/web/src/app/api/v1/calendar/auth/fetch/route.ts
new file mode 100644
index 0000000000..ee2b142ed1
--- /dev/null
+++ b/apps/web/src/app/api/v1/calendar/auth/fetch/route.ts
@@ -0,0 +1,113 @@
+import { createClient } from '@tuturuuu/supabase/next/server';
+import { OAuth2Client } from 'google-auth-library';
+import { google } from 'googleapis';
+import { NextResponse } from 'next/server';
+
+const getGoogleAuthClient = (tokens: {
+  access_token: string;
+  refresh_token?: string;
+}) => {
+  const oauth2Client = new OAuth2Client({
+    clientId: process.env.GOOGLE_CLIENT_ID,
+    clientSecret: process.env.GOOGLE_CLIENT_SECRET,
+    redirectUri: process.env.GOOGLE_REDIRECT_URI,
+  });
+
+  oauth2Client.setCredentials(tokens);
+  return oauth2Client;
+};
+
+export async function GET() {
+  const supabase = await createClient();
+  const {
+    data: { user },
+    error: userError,
+  } = await supabase.auth.getUser();
+
+  if (userError || !user) {
+    return NextResponse.json(
+      { error: 'User not authenticated' },
+      { status: 401 }
+    );
+  }
+
+  const { data: googleTokens, error: googleTokensError } = await supabase
+    .from('calendar_auth_tokens')
+    .select('*')
+    .eq('user_id', user.id)
+    .single();
+
+  if (googleTokensError || !googleTokens?.access_token) {
+    return NextResponse.json(
+      { error: 'Google Calendar not authenticated' },
+      { status: 401 }
+    );
+  }
+
+  try {
+    const auth = getGoogleAuthClient(googleTokens);
+    const calendar = google.calendar({ version: 'v3', auth });
+
+    // define the time range for the events
+    const timeMin = new Date();
+    timeMin.setFullYear(timeMin.getFullYear() - 1);
+    const timeMax = new Date();
+    timeMax.setFullYear(timeMax.getFullYear() + 1);
+
+    const response = await calendar.events.list({
+      calendarId: 'primary',
+      timeMin: timeMin.toISOString(),
+      timeMax: timeMax.toISOString(),
+      singleEvents: true, // separate recurring events
+      orderBy: 'startTime',
+    });
+
+    const events = response.data.items || [];
+
+    // format the events to match the expected structure
+    const formattedEvents = events.map((event) => ({
+      google_event_id: event.id,
+      title: event.summary || 'Untitled Event',
+      description: event.description || '',
+      start_at: event.start?.dateTime || event.start?.date || '',
+      end_at: event.end?.dateTime || event.end?.date || '',
+      location: event.location || '',
+      color: getColorFromGoogleColorId(event.colorId ?? undefined),
+      ws_id: '',
+      locked: false,
+    }));
+
+    return NextResponse.json({ events: formattedEvents }, { status: 200 });
+  } catch (error: any) {
+    console.error('Failed to fetch Google Calendar events:', error);
+    if (error.response?.data?.error === 'invalid_grant') {
+      return NextResponse.json(
+        {
+          error: 'Google token invalid, please re-authenticate.',
+          needsReAuth: true,
+        },
+        { status: 401 }
+      );
+    }
+    return NextResponse.json(
+      { error: 'Failed to fetch events from Google Calendar' },
+      { status: 500 }
+    );
+  }
+}
+
+const getColorFromGoogleColorId = (colorId?: string): string => {
+  const colorMap: Record<string, string> = {
+    '1': 'RED',
+    '2': 'GREEN',
+    '3': 'GRAY',
+    '4': 'PINK',
+    '5': 'YELLOW',
+    '6': 'ORANGE',
+    '8': 'CYAN',
+    '9': 'PURPLE',
+    '10': 'INDIGO',
+    '11': 'BLUE',
+  };
+  return colorId && colorMap[colorId] ? colorMap[colorId] : 'BLUE';
+};
\ No newline at end of file
diff --git a/apps/web/src/app/api/v1/calendar/auth/route.ts b/apps/web/src/app/api/v1/calendar/auth/route.ts
index 6c107b12df..330b1b905d 100644
--- a/apps/web/src/app/api/v1/calendar/auth/route.ts
+++ b/apps/web/src/app/api/v1/calendar/auth/route.ts
@@ -1,7 +1,13 @@
 import { OAuth2Client } from 'google-auth-library';
-import { NextResponse } from 'next/server';
+import { type NextRequest, NextResponse } from 'next/server';
+
+export async function GET(request: NextRequest) {
+  const wsId = request.nextUrl.searchParams.get('wsId');
+
+  if (!wsId) {
+    return NextResponse.json({ error: 'wsId is required' }, { status: 400 });
+  }
 
-export async function GET() {
   const auth = new OAuth2Client({
     clientId: process.env.GOOGLE_CLIENT_ID,
     clientSecret: process.env.GOOGLE_CLIENT_SECRET,
@@ -9,6 +15,7 @@ export async function GET() {
   });
 
   const authUrl = auth.generateAuthUrl({
+    state: wsId,
     scope: ['https://www.googleapis.com/auth/calendar.events'],
     prompt: 'consent',
     access_type: 'offline',
diff --git a/apps/web/src/app/api/v1/calendar/auth/sync/route.ts b/apps/web/src/app/api/v1/calendar/auth/sync/route.ts
index 613c0e9b89..e21886a3c5 100644
--- a/apps/web/src/app/api/v1/calendar/auth/sync/route.ts
+++ b/apps/web/src/app/api/v1/calendar/auth/sync/route.ts
@@ -1,8 +1,13 @@
+import { CalendarEvent as BaseCalendarEvent } from '@tuturuuu/ai/calendar/events';
 import { createClient } from '@tuturuuu/supabase/next/server';
 import { OAuth2Client } from 'google-auth-library';
 import { google } from 'googleapis';
 import { NextResponse } from 'next/server';
 
+interface CalendarEvent extends BaseCalendarEvent {
+  id?: string; // Add the optional 'id' property
+}
+
 const getGoogleAuthClient = (tokens: {
   access_token: string;
   refresh_token?: string;
@@ -35,7 +40,7 @@ const getGoogleColorId = (color?: string): string => {
 
 export async function POST(request: Request) {
   const body = await request.json();
-  const { event } = body;
+  const { event }: { event: CalendarEvent } = body;
 
   const supabase = await createClient();
 
@@ -64,6 +69,7 @@ export async function POST(request: Request) {
   }
 
   if (!googleTokens?.access_token) {
+    // T0D0: Don't return error immediately, maybe user hasn't linked (fix in the future)
     return NextResponse.json(
       { error: 'Google Calendar not authenticated' },
       { status: 401 }
@@ -75,8 +81,9 @@ export async function POST(request: Request) {
     const calendar = google.calendar({ version: 'v3', auth });
 
     const googleEvent = {
-      summary: event.title,
-      description: event.description,
+      summary: event.title || 'Untitled Event',
+      description: event.description || '',
+      location: event.location || '',
       start: {
         dateTime: event.start_at,
         timeZone: Intl.DateTimeFormat().resolvedOptions().timeZone,
@@ -93,14 +100,257 @@ export async function POST(request: Request) {
       requestBody: googleEvent,
     });
 
+    const googleEventId = response.data.id;
+
+    if (googleEventId && event.id && event.id !== 'new') {
+      // Ensure we have a local event ID
+      // *** Update Supabase with the Google Event ID ***
+      const { error: updateError } = await supabase
+        .from('workspace_calendar_events')
+        .update({ google_event_id: googleEventId })
+        .eq('id', event.id); // Use the local event ID passed from frontend
+
+      if (updateError) {
+        console.error(
+          'Failed to update Supabase with Google Event ID:',
+          updateError
+        );
+        // Don't fail the whole request, but log the issue
+      } else {
+        console.log(
+          `Stored Google Event ID ${googleEventId} for local event ${event.id}`
+        );
+      }
+    } else {
+      console.warn(
+        `Could not store Google Event ID. Google Event ID: ${googleEventId}, Local Event ID: ${event.id}`
+      );
+    }
+
+    return NextResponse.json({ googleEventId: googleEventId }, { status: 200 });
+  } catch (error: any) {
+    console.error('Failed to sync with Google Calendar (POST):', error);
+    if (error.response?.data?.error === 'invalid_grant') {
+      return NextResponse.json(
+        {
+          error: 'Google token invalid, please re-authenticate.',
+          needsReAuth: true,
+        },
+        { status: 401 }
+      );
+    }
+    return NextResponse.json(
+      { error: 'Failed to sync event with Google Calendar' },
+      { status: 500 }
+    );
+  }
+}
+
+export async function PUT(request: Request) {
+  const body = await request.json();
+  // Expecting local event ID, google event ID, and the updated data
+  const {
+    eventId,
+    googleCalendarEventId,
+    eventUpdates,
+  }: {
+    eventId: string;
+    googleCalendarEventId: string;
+    eventUpdates: Partial<CalendarEvent>;
+  } = body;
+
+  if (!googleCalendarEventId) {
+    return NextResponse.json(
+      { error: 'Missing Google Calendar Event ID' },
+      { status: 400 }
+    );
+  }
+  if (!eventUpdates) {
+    return NextResponse.json(
+      { error: 'Missing event update data' },
+      { status: 400 }
+    );
+  }
+
+  const supabase = await createClient();
+  const {
+    data: { user },
+  } = await supabase.auth.getUser();
+  if (!user) {
+    return NextResponse.json(
+      { error: 'User not authenticated' },
+      { status: 401 }
+    );
+  }
+  const { data: googleTokens, error: googleTokensError } = await supabase
+    .from('calendar_auth_tokens')
+    .select('*')
+    .eq('user_id', user.id)
+    .single();
+  if (googleTokensError || !googleTokens?.access_token) {
+    console.error('Google Tokens Error or Missing:', googleTokensError);
+    return NextResponse.json(
+      { error: 'Google Calendar not authenticated or tokens missing' },
+      { status: 401 }
+    );
+  }
+
+  try {
+    const auth = getGoogleAuthClient(googleTokens);
+    const calendar = google.calendar({ version: 'v3', auth });
+
+    // Prepare the updated Google Event data
+    const googleEventUpdate: any = {}; // Use 'any' for flexibility or define a specific type
+    if (eventUpdates.title !== undefined)
+      googleEventUpdate.summary = eventUpdates.title || 'Untitled Event';
+    if (eventUpdates.description !== undefined)
+      googleEventUpdate.description = eventUpdates.description || '';
+    if (eventUpdates.location !== undefined)
+      googleEventUpdate.location = eventUpdates.location || ''; // Update location
+    if (eventUpdates.start_at) {
+      googleEventUpdate.start = {
+        dateTime: eventUpdates.start_at,
+        timeZone: Intl.DateTimeFormat().resolvedOptions().timeZone,
+      };
+    }
+    if (eventUpdates.end_at) {
+      googleEventUpdate.end = {
+        dateTime: eventUpdates.end_at,
+        timeZone: Intl.DateTimeFormat().resolvedOptions().timeZone,
+      };
+    }
+    if (eventUpdates.color)
+      googleEventUpdate.colorId = getGoogleColorId(eventUpdates.color);
+
+    // Use patch for partial updates, update for full replacement
+    const response = await calendar.events.patch({
+      calendarId: 'primary',
+      eventId: googleCalendarEventId, // The ID of the event on Google Calendar
+      requestBody: googleEventUpdate,
+    });
+
+    console.log(
+      `Google Calendar event ${googleCalendarEventId} updated successfully.`
+    );
+
     return NextResponse.json(
       { googleEventId: response.data.id },
       { status: 200 }
     );
-  } catch (error) {
-    console.error('Failed to sync with Google Calendar:', error);
+  } catch (error: any) {
+    console.error(
+      `Failed to update Google Calendar event ${googleCalendarEventId}:`,
+      error
+    );
+    if (error.response?.data?.error === 'invalid_grant') {
+      return NextResponse.json(
+        {
+          error: 'Google token invalid, please re-authenticate.',
+          needsReAuth: true,
+        },
+        { status: 401 }
+      );
+    }
+    if (error.response?.status === 404 || error.response?.status === 410) {
+      // Event not found or gone - maybe it was deleted directly on Google Calendar
+      console.warn(
+        `Google Calendar event ${googleCalendarEventId} not found for update. Might have been deleted.`
+      );
+      // Optionally: remove the google_calendar_event_id from Supabase record
+      await supabase
+        .from('workspace_calendar_events')
+        .update({ google_event_id: null })
+        .eq('id', eventId);
+      return NextResponse.json(
+        { error: 'Event not found on Google Calendar', eventNotFound: true },
+        { status: 404 }
+      );
+    }
+    return NextResponse.json(
+      { error: 'Failed to update event on Google Calendar' },
+      { status: 500 }
+    );
+  }
+}
+
+export async function DELETE(request: Request) {
+  const { googleCalendarEventId } = await request.json();
+
+  if (!googleCalendarEventId) {
+    return NextResponse.json(
+      { error: 'Missing Google Calendar Event ID' },
+      { status: 400 }
+    );
+  }
+
+  const supabase = await createClient();
+  const {
+    data: { user },
+  } = await supabase.auth.getUser();
+  if (!user) {
+    return NextResponse.json(
+      { error: 'User not authenticated' },
+      { status: 401 }
+    );
+  }
+  const { data: googleTokens, error: googleTokensError } = await supabase
+    .from('calendar_auth_tokens')
+    .select('*')
+    .eq('user_id', user.id)
+    .single();
+  if (googleTokensError || !googleTokens?.access_token) {
+    console.error('Google Tokens Error or Missing:', googleTokensError);
+    return NextResponse.json(
+      { error: 'Google Calendar not authenticated or tokens missing' },
+      { status: 401 }
+    );
+  }
+
+  try {
+    const auth = getGoogleAuthClient(googleTokens);
+    const calendar = google.calendar({ version: 'v3', auth });
+
+    await calendar.events.delete({
+      calendarId: 'primary',
+      eventId: googleCalendarEventId,
+    });
+
+    console.log(
+      `Google Calendar event ${googleCalendarEventId} deleted successfully.`
+    );
+    return NextResponse.json(
+      { message: 'Event deleted successfully from Google Calendar' },
+      { status: 200 }
+    );
+  } catch (error: any) {
+    console.error(
+      `Failed to delete Google Calendar event ${googleCalendarEventId}:`,
+      error
+    );
+    if (error.response?.status === 404 || error.response?.status === 410) {
+      // Event already gone, consider it a success in terms of local state
+      console.warn(
+        `Google Calendar event ${googleCalendarEventId} not found for deletion. Assuming already deleted.`
+      );
+      return NextResponse.json(
+        {
+          message: 'Event already deleted or not found on Google Calendar',
+          eventNotFound: true,
+        },
+        { status: 200 }
+      ); // Return success as the goal is achieved
+    }
+    if (error.response?.data?.error === 'invalid_grant') {
+      return NextResponse.json(
+        {
+          error: 'Google token invalid, please re-authenticate.',
+          needsReAuth: true,
+        },
+        { status: 401 }
+      );
+    }
     return NextResponse.json(
-      { error: 'Failed to sync event' },
+      { error: 'Failed to delete event from Google Calendar' },
       { status: 500 }
     );
   }
diff --git a/packages/types/src/primitives/calendar-event.ts b/packages/types/src/primitives/calendar-event.ts
index d78729c09c..56c0b5e636 100644
--- a/packages/types/src/primitives/calendar-event.ts
+++ b/packages/types/src/primitives/calendar-event.ts
@@ -16,6 +16,7 @@ export interface CalendarEvent {
   scheduling_note?: string;
   priority?: EventPriority;
   locked?: boolean;
+  google_event_id?: string;
 
   // Properties for multi-day events
   _originalId?: string;
diff --git a/packages/types/src/supabase.ts b/packages/types/src/supabase.ts
index 6824b3a6c7..5772d0a072 100644
--- a/packages/types/src/supabase.ts
+++ b/packages/types/src/supabase.ts
@@ -4009,6 +4009,7 @@ export type Database = {
           created_at: string | null;
           description: string;
           end_at: string;
+          google_event_id: string | null;
           id: string;
           location: string | null;
           locked: boolean;
@@ -4021,6 +4022,7 @@ export type Database = {
           created_at?: string | null;
           description?: string;
           end_at: string;
+          google_event_id?: string | null;
           id?: string;
           location?: string | null;
           locked?: boolean;
@@ -4033,6 +4035,7 @@ export type Database = {
           created_at?: string | null;
           description?: string;
           end_at?: string;
+          google_event_id?: string | null;
           id?: string;
           location?: string | null;
           locked?: boolean;
diff --git a/packages/ui/src/components/ui/legacy/calendar/Calendar.tsx b/packages/ui/src/components/ui/legacy/calendar/Calendar.tsx
index 2b96f2e7ab..790361acc4 100644
--- a/packages/ui/src/components/ui/legacy/calendar/Calendar.tsx
+++ b/packages/ui/src/components/ui/legacy/calendar/Calendar.tsx
@@ -10,13 +10,14 @@ import WeekdayBar from './WeekdayBar';
 import { CalendarSettings } from './settings/CalendarSettingsContext';
 import { Workspace } from '@tuturuuu/types/primitives/Workspace';
 import { Button } from '@tuturuuu/ui/button';
+import { useToast } from '@tuturuuu/ui/hooks/use-toast';
 import {
   type CalendarView,
   useViewTransition,
 } from '@tuturuuu/ui/hooks/use-view-transition';
 import { Tooltip, TooltipContent, TooltipTrigger } from '@tuturuuu/ui/tooltip';
 import { cn } from '@tuturuuu/utils/format';
-import { PlusIcon, Settings } from 'lucide-react';
+import { Link, Loader2, PlusIcon, Settings } from 'lucide-react';
 import { useCallback, useEffect, useState } from 'react';
 
 // Floating action button for quick event creation
@@ -44,14 +45,37 @@ const CreateEventButton = () => {
 
 // Settings button component
 const SettingsButton = ({
+  showSyncButton = false,
   // initialSettings,
   onSaveSettings,
 }: {
+  showSyncButton?: boolean;
   initialSettings?: Partial<CalendarSettings>;
   onSaveSettings?: (settings: CalendarSettings) => Promise<void>;
 }) => {
+  const { toast } = useToast();
   const [open, setOpen] = useState(false);
-  const { updateSettings, settings } = useCalendar();
+  const { updateSettings, settings, syncAllFromGoogleCalendar } = useCalendar();
+  const [isSyncing, setIsSyncing] = useState(false);
+
+  const handleSyncGoogleCalendar = async () => {
+    setIsSyncing(true);
+    try {
+      await syncAllFromGoogleCalendar();
+      toast({
+        title: 'Success',
+        description: 'Google Calendar events synced successfully',
+      });
+    } catch (error) {
+      toast({
+        title: 'Error',
+        description: 'Failed to sync Google Calendar events',
+        variant: 'destructive',
+      });
+    } finally {
+      setIsSyncing(false);
+    }
+  };
 
   const handleSaveSettings = async (newSettings: CalendarSettings) => {
     console.log('Saving settings from dialog:', newSettings);
@@ -89,6 +113,27 @@ const SettingsButton = ({
         </TooltipTrigger>
         <TooltipContent>Calendar settings</TooltipContent>
       </Tooltip>
+      {showSyncButton && (
+        <Tooltip>
+          <TooltipTrigger asChild>
+            <Button
+              variant="outline"
+              size="icon"
+              className="h-12 w-12 rounded-full shadow-lg"
+              onClick={handleSyncGoogleCalendar}
+              disabled={isSyncing}
+            >
+              {isSyncing ? (
+                <Loader2 className="h-5 w-5 animate-spin" />
+              ) : (
+                <Link className="h-5 w-5" />
+              )}
+              <span className="sr-only">Sync Google Calendar</span>
+            </Button>
+          </TooltipTrigger>
+          <TooltipContent>Sync Google Calendar</TooltipContent>
+        </Tooltip>
+      )}
       <CalendarSettingsDialog
         open={open}
         onOpenChange={setOpen}
@@ -530,12 +575,24 @@ const CalendarContent = ({
 
       {disabled ? null : (
         <>
-          <UnifiedEventModal
-            experimentalGoogleCalendarLinked={experimentalGoogleCalendarLinked}
-            enableExperimentalGoogleCalendar={enableExperimentalGoogleCalendar}
-          />
+          {workspace && (
+            <UnifiedEventModal
+              wsId={workspace.id}
+              experimentalGoogleCalendarLinked={
+                experimentalGoogleCalendarLinked
+              }
+              enableExperimentalGoogleCalendar={
+                enableExperimentalGoogleCalendar
+              }
+            />
+          )}
           <CreateEventButton />
           <SettingsButton
+            showSyncButton={
+              !!workspace?.id &&
+              experimentalGoogleCalendarLinked &&
+              enableExperimentalGoogleCalendar
+            }
             initialSettings={initialSettings}
             onSaveSettings={onSaveSettings}
           />
diff --git a/packages/ui/src/components/ui/legacy/calendar/UnifiedEventModal.tsx b/packages/ui/src/components/ui/legacy/calendar/UnifiedEventModal.tsx
index c107301bbe..72977417ed 100644
--- a/packages/ui/src/components/ui/legacy/calendar/UnifiedEventModal.tsx
+++ b/packages/ui/src/components/ui/legacy/calendar/UnifiedEventModal.tsx
@@ -95,9 +95,11 @@ const AIFormSchema = z.object({
 });
 
 export function UnifiedEventModal({
+  wsId,
   experimentalGoogleCalendarLinked = false,
   enableExperimentalGoogleCalendar = false,
 }: {
+  wsId: string;
   experimentalGoogleCalendarLinked?: boolean;
   enableExperimentalGoogleCalendar?: boolean;
 }) {
@@ -120,7 +122,7 @@ export function UnifiedEventModal({
   const handleGoogleAuth = async () => {
     setIsGoogleAuthenticating(true);
     try {
-      const response = await fetch('/api/v1/calendar/auth', {
+      const response = await fetch(`/api/v1/calendar/auth?wsId=${wsId}`, {
         method: 'GET',
       });
 
diff --git a/packages/ui/src/hooks/use-calendar.tsx b/packages/ui/src/hooks/use-calendar.tsx
index 024961ad83..624de2bd60 100644
--- a/packages/ui/src/hooks/use-calendar.tsx
+++ b/packages/ui/src/hooks/use-calendar.tsx
@@ -57,7 +57,10 @@ const CalendarContext = createContext<{
   getModalStatus: (id: string) => boolean;
   getActiveEvent: () => CalendarEvent | undefined;
   isModalActive: () => boolean;
+  // google calendar API
   syncWithGoogleCalendar: (event: CalendarEvent) => Promise<void>;
+  syncAllFromGoogleCalendar: () => Promise<void>;
+
   settings: CalendarSettings;
   updateSettings: (settings: Partial<CalendarSettings>) => void;
 }>({
@@ -80,7 +83,10 @@ const CalendarContext = createContext<{
   getModalStatus: () => false,
   getActiveEvent: () => undefined,
   isModalActive: () => false,
+  // Google Calendar API
   syncWithGoogleCalendar: () => Promise.resolve(),
+  syncAllFromGoogleCalendar: () => Promise.resolve(),
+
   settings: defaultCalendarSettings,
   updateSettings: () => undefined,
 });
@@ -405,7 +411,7 @@ export const CalendarProvider = ({
 
   const addEmptyEvent = useCallback(
     (date: Date) => {
-      // TODO: Fix this weird workaround in the future
+      // TOD0: Fix this weird workaround in the future
       const selectedDate = dayjs(date);
       const correctDate = selectedDate.add(1, 'day');
 
@@ -599,6 +605,8 @@ export const CalendarProvider = ({
 
   const deleteEvent = useCallback(
     async (eventId: string) => {
+      console.log('Current events array:', events);
+      console.log('Deleting event with ID:', eventId);
       // If this is a pending new event that hasn't been saved yet
       if (pendingNewEvent && eventId === 'new') {
         // Just clear the pending event
@@ -609,6 +617,55 @@ export const CalendarProvider = ({
 
       if (!ws) throw new Error('No workspace selected');
 
+      // Find the event first to get the Google Calendar ID
+      const eventToDelete = events.find((e: CalendarEvent) => e.id === eventId);
+      const googleCalendarEventId = eventToDelete?.google_event_id;
+
+      // --- Google Calendar Sync (Delete) ---
+      if (googleCalendarEventId && enableExperimentalGoogleCalendar) {
+        // Check if ID exists and feature enabled
+        try {
+          const syncResponse = await fetch('/api/v1/calendar/auth/sync', {
+            method: 'DELETE',
+            headers: { 'Content-Type': 'application/json' },
+            body: JSON.stringify({ googleCalendarEventId }),
+          });
+
+          if (!syncResponse.ok) {
+            const errorData = await syncResponse.json();
+            if (errorData.eventNotFound) {
+              console.warn(
+                `Google event ${googleCalendarEventId} not found during delete sync. Proceeding with local delete.`
+              );
+              // Don't throw, just log. The event is gone from Google anyway.
+            } else if (errorData.needsReAuth) {
+              console.error(
+                'Google token needs refresh/re-auth during delete.'
+              );
+              // TOD0: Notify user to re-authenticate. Should we block local delete? Maybe not.
+            } else {
+              // Throw an error to potentially stop the local delete or notify user
+              throw new Error(
+                `Google Calendar sync (DELETE) failed: ${syncResponse.statusText} - ${JSON.stringify(errorData)}`
+              );
+            }
+          } else {
+            console.log(
+              `Google Calendar event ${googleCalendarEventId} deleted via sync.`
+            );
+          }
+        } catch (syncError) {
+          console.error(
+            `Failed to sync delete with Google Calendar for event ${eventId}:`,
+            syncError
+          );
+        }
+      } else if (enableExperimentalGoogleCalendar && !googleCalendarEventId) {
+        console.log(
+          `Event ${eventId} has no Google Calendar ID, skipping delete sync.`
+        );
+      }
+
       try {
         const supabase = createClient();
         const { error } = await supabase
@@ -620,7 +677,6 @@ export const CalendarProvider = ({
 
         // Refresh the query cache after deleting an event
         refresh();
-
         setActiveEventId(null);
       } catch (err) {
         console.error(`Failed to delete event ${eventId}:`, err);
@@ -630,29 +686,141 @@ export const CalendarProvider = ({
     [ws, refresh, pendingNewEvent]
   );
 
-  // Google Calendar sync moved to API Route
-  const syncWithGoogleCalendar = useCallback(async (event: CalendarEvent) => {
-    if (!enableExperimentalGoogleCalendar) {
+  const syncAllFromGoogleCalendar = useCallback(async () => {
+    if (!enableExperimentalGoogleCalendar || !ws?.id) {
+      console.log('Google Calendar sync disabled or no workspace selected');
       return;
     }
 
     try {
-      const response = await fetch('/api/v1/calendar/auth/sync', {
-        method: 'POST',
+      // get all events from Google Calendar
+      const response = await fetch('/api/v1/calendar/auth/fetch', {
+        method: 'GET',
         headers: { 'Content-Type': 'application/json' },
-        body: JSON.stringify({ event }),
       });
 
       if (!response.ok) {
-        throw new Error('Failed to sync with Google Calendar');
+        const errorData = await response.json();
+        throw new Error(
+          errorData.error || 'Failed to fetch Google Calendar events'
+        );
+      }
+
+      const { events: googleEvents } = await response.json();
+
+      // get existing events from Supabase
+      const supabase = createClient();
+      const { data: localEvents, error: localEventsError } = await supabase
+        .from('workspace_calendar_events')
+        .select('id, google_event_id')
+        .eq('ws_id', ws.id);
+
+      if (localEventsError) {
+        throw new Error('Failed to fetch local events');
       }
 
-      console.log('Event synced with Google Calendar');
+      // create a set of existing Google event IDs from Supabase
+      const localGoogleEventIds = new Set(
+        localEvents
+          .filter((e: any) => e.google_event_id)
+          .map((e: any) => e.google_event_id)
+      );
+
+      // filter out events that already exist in Supabase
+      const newEvents = googleEvents.filter(
+        (event: any) => !localGoogleEventIds.has(event.google_event_id)
+      );
+
+      // create a new event in Supabase for each new Google Calendar event
+      for (const event of newEvents) {
+        const { error: insertError } = await supabase
+          .from('workspace_calendar_events')
+          .insert({
+            title: event.title,
+            description: event.description,
+            start_at: event.start_at,
+            end_at: event.end_at,
+            color: event.color,
+            location: event.location,
+            ws_id: ws.id,
+            google_event_id: event.google_event_id,
+            locked: event.locked || false,
+          });
+
+        if (insertError) {
+          console.error('Failed to insert event:', insertError);
+          continue; // continue to the next event on error
+        }
+      }
+
+      console.log(`Synced ${newEvents.length} new events from Google Calendar`);
+      refresh();
     } catch (error) {
-      console.error('Failed to sync with Google Calendar:', error);
+      console.error('Failed to sync all events from Google Calendar:', error);
       throw error;
     }
-  }, []);
+  }, [enableExperimentalGoogleCalendar, ws?.id, refresh]);
+
+  // Google Calendar sync moved to API Route
+  const syncWithGoogleCalendar = useCallback(
+    async (event: CalendarEvent) => {
+      if (!enableExperimentalGoogleCalendar) {
+        return;
+      }
+
+      try {
+        let response;
+        if (event.google_event_id) {
+          // Update an existing event on Google Calendar
+          response = await fetch('/api/v1/calendar/auth/sync', {
+            method: 'PUT',
+            headers: { 'Content-Type': 'application/json' },
+            body: JSON.stringify({
+              eventId: event.id,
+              googleCalendarEventId: event.google_event_id,
+              eventUpdates: {
+                title: event.title,
+                description: event.description,
+                start_at: event.start_at,
+                end_at: event.end_at,
+                color: event.color,
+                location: event.location,
+              },
+            }),
+          });
+        } else {
+          // create a new event on Google Calendar
+          response = await fetch('/api/v1/calendar/auth/sync', {
+            method: 'POST',
+            headers: { 'Content-Type': 'application/json' },
+            body: JSON.stringify({ event }),
+          });
+        }
+
+        if (!response.ok) {
+          const errorData = await response.json();
+          if (errorData.needsReAuth) {
+            console.error('Google token needs refresh/re-auth.');
+            throw new Error('Google token invalid, please re-authenticate.');
+          } else if (errorData.eventNotFound) {
+            console.warn('Event not found on Google Calendar.');
+            // Handle the case where the event is not found
+          } else {
+            throw new Error(
+              errorData.error || 'Failed to sync with Google Calendar'
+            );
+          }
+        }
+
+        console.log('Event synced with Google Calendar');
+        refresh();
+      } catch (error) {
+        console.error('Failed to sync with Google Calendar:', error);
+        throw error;
+      }
+    },
+    [enableExperimentalGoogleCalendar, refresh]
+  );
 
   // Modal management
   const openModal = useCallback((eventId?: string) => {
@@ -749,6 +917,7 @@ export const CalendarProvider = ({
 
     // Google Calendar API
     syncWithGoogleCalendar,
+    syncAllFromGoogleCalendar,
 
     // Settings API
     settings,
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 72646a2423..0461aaf070 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -414,10 +414,10 @@ importers:
         version: 2.7.23
       '@vercel/analytics':
         specifier: ^1.5.0
-        version: 1.5.0(next@15.3.0(@babel/core@7.26.10)(@opentelemetry/api@1.9.0)(babel-plugin-react-compiler@19.0.0-beta-21e868a-20250216)(react-dom@19.1.0(react@19.1.0))(react@19.1.0))(react@19.1.0)
+        version: 1.5.0(next@15.3.0(@opentelemetry/api@1.9.0)(babel-plugin-react-compiler@19.0.0-beta-21e868a-20250216)(react-dom@19.1.0(react@19.1.0))(react@19.1.0))(react@19.1.0)
       '@vercel/speed-insights':
         specifier: ^1.2.0
-        version: 1.2.0(next@15.3.0(@babel/core@7.26.10)(@opentelemetry/api@1.9.0)(babel-plugin-react-compiler@19.0.0-beta-21e868a-20250216)(react-dom@19.1.0(react@19.1.0))(react@19.1.0))(react@19.1.0)
+        version: 1.2.0(next@15.3.0(@opentelemetry/api@1.9.0)(babel-plugin-react-compiler@19.0.0-beta-21e868a-20250216)(react-dom@19.1.0(react@19.1.0))(react@19.1.0))(react@19.1.0)
       babel-plugin-react-compiler:
         specifier: 19.0.0-beta-21e868a-20250216
         version: 19.0.0-beta-21e868a-20250216
@@ -465,10 +465,10 @@ importers:
         version: 1.0.0
       next:
         specifier: ^15.3.0
-        version: 15.3.0(@babel/core@7.26.10)(@opentelemetry/api@1.9.0)(babel-plugin-react-compiler@19.0.0-beta-21e868a-20250216)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
+        version: 15.3.0(@opentelemetry/api@1.9.0)(babel-plugin-react-compiler@19.0.0-beta-21e868a-20250216)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
       next-intl:
         specifier: ^3.26.5
-        version: 3.26.5(next@15.3.0(@babel/core@7.26.10)(@opentelemetry/api@1.9.0)(babel-plugin-react-compiler@19.0.0-beta-21e868a-20250216)(react-dom@19.1.0(react@19.1.0))(react@19.1.0))(react@19.1.0)
+        version: 3.26.5(next@15.3.0(@opentelemetry/api@1.9.0)(babel-plugin-react-compiler@19.0.0-beta-21e868a-20250216)(react-dom@19.1.0(react@19.1.0))(react@19.1.0))(react@19.1.0)
       next-themes:
         specifier: ^0.4.6
         version: 0.4.6(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
@@ -486,7 +486,7 @@ importers:
         version: 19.1.0(react@19.1.0)
       react-email:
         specifier: ^4.0.7
-        version: 4.0.7(@babel/core@7.26.10)(@opentelemetry/api@1.9.0)(babel-plugin-react-compiler@19.0.0-beta-21e868a-20250216)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
+        version: 4.0.7(@opentelemetry/api@1.9.0)(babel-plugin-react-compiler@19.0.0-beta-21e868a-20250216)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
       react-intersection-observer:
         specifier: ^9.16.0
         version: 9.16.0(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
@@ -1497,10 +1497,10 @@ importers:
         version: 2.7.23
       '@vercel/analytics':
         specifier: ^1.5.0
-        version: 1.5.0(next@15.3.0(@opentelemetry/api@1.9.0)(babel-plugin-react-compiler@19.0.0-beta-21e868a-20250216)(react-dom@19.1.0(react@19.1.0))(react@19.1.0))(react@19.1.0)
+        version: 1.5.0(next@15.3.0(@babel/core@7.26.10)(@opentelemetry/api@1.9.0)(babel-plugin-react-compiler@19.0.0-beta-21e868a-20250216)(react-dom@19.1.0(react@19.1.0))(react@19.1.0))(react@19.1.0)
       '@vercel/speed-insights':
         specifier: ^1.2.0
-        version: 1.2.0(next@15.3.0(@opentelemetry/api@1.9.0)(babel-plugin-react-compiler@19.0.0-beta-21e868a-20250216)(react-dom@19.1.0(react@19.1.0))(react@19.1.0))(react@19.1.0)
+        version: 1.2.0(next@15.3.0(@babel/core@7.26.10)(@opentelemetry/api@1.9.0)(babel-plugin-react-compiler@19.0.0-beta-21e868a-20250216)(react-dom@19.1.0(react@19.1.0))(react@19.1.0))(react@19.1.0)
       babel-plugin-react-compiler:
         specifier: 19.0.0-beta-21e868a-20250216
         version: 19.0.0-beta-21e868a-20250216
@@ -1563,10 +1563,10 @@ importers:
         version: 1.0.0
       next:
         specifier: ^15.3.0
-        version: 15.3.0(@opentelemetry/api@1.9.0)(babel-plugin-react-compiler@19.0.0-beta-21e868a-20250216)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
+        version: 15.3.0(@babel/core@7.26.10)(@opentelemetry/api@1.9.0)(babel-plugin-react-compiler@19.0.0-beta-21e868a-20250216)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
       next-intl:
         specifier: ^3.26.5
-        version: 3.26.5(next@15.3.0(@opentelemetry/api@1.9.0)(babel-plugin-react-compiler@19.0.0-beta-21e868a-20250216)(react-dom@19.1.0(react@19.1.0))(react@19.1.0))(react@19.1.0)
+        version: 3.26.5(next@15.3.0(@babel/core@7.26.10)(@opentelemetry/api@1.9.0)(babel-plugin-react-compiler@19.0.0-beta-21e868a-20250216)(react-dom@19.1.0(react@19.1.0))(react@19.1.0))(react@19.1.0)
       next-themes:
         specifier: ^0.4.6
         version: 0.4.6(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
@@ -1596,7 +1596,7 @@ importers:
         version: 19.1.0(react@19.1.0)
       react-email:
         specifier: ^4.0.7
-        version: 4.0.7(@opentelemetry/api@1.9.0)(babel-plugin-react-compiler@19.0.0-beta-21e868a-20250216)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
+        version: 4.0.7(@babel/core@7.26.10)(@opentelemetry/api@1.9.0)(babel-plugin-react-compiler@19.0.0-beta-21e868a-20250216)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
       react-intersection-observer:
         specifier: ^9.16.0
         version: 9.16.0(react-dom@19.1.0(react@19.1.0))(react@19.1.0)


Reviews:
user: login: coderabbitai[bot]
body: **Actionable comments posted: 0**

<details>
<summary>🧹 Nitpick comments (11)</summary><blockquote>

<details>
<summary>apps/web/src/app/api/v1/calendar/auth/fetch/route.ts (3)</summary><blockquote>

`34-45`: **Consider optional flow for missing tokens**  
Currently, if no Google tokens or an error occurs, the response immediately returns a 401. The TODO at line 72 (in another segment) suggests a future improvement. In future iterations, you might consider partial flows or separate UX prompts for linking Google accounts.

---

`47-65`: **Pagination or batch fetching**  
Fetching all events for a one-year range in both directions might be large for users with many events. Consider implementing pagination or batch requests to prevent potential rate limits or performance issues with high-volume calendars.

---

`99-113`: **Fallback color assumption**  
The function maps Google color IDs to named colors, defaulting to `'BLUE'` if none match. Confirm if `'BLUE'` is the desired universal fallback or consider using `'GRAY'` or another neutral color.

</blockquote></details>
<details>
<summary>apps/web/src/app/api/v1/calendar/auth/sync/route.ts (1)</summary><blockquote>

`43-76`: **Address the TODO comment at line 72**  
There's a TODO about not returning an error immediately if the user hasn't linked Google Calendar. To reduce linter noise and ensure clarity, consider opening an issue or converting it into a more detailed plan.

<details>
<summary>🧰 Tools</summary>

<details>
<summary>🪛 GitHub Check: CodeFactor</summary>

[notice] 72-72: apps/web/src/app/api/v1/calendar/auth/sync/route.ts#L72
Unresolved 'todo' comment. (eslint/no-warning-comments)

</details>

</details>

</blockquote></details>
<details>
<summary>packages/ui/src/hooks/use-calendar.tsx (6)</summary><blockquote>

`60-63`: **Document or test newly introduced synchronization methods.**

The new `syncWithGoogleCalendar` and `syncAllFromGoogleCalendar` entries in the context look good overall. However, consider adding or updating test coverage and documentation to ensure correct usage and to help other developers understand their purpose and constraints (e.g., requiring a valid Google token).

---

`86-89`: **Consider throwing an error or rejected promise in default no-op.**

Returning a resolved promise in these default methods could hide usage errors if someone calls them outside of a proper `CalendarProvider`. A short-term alternative is to log a warning or throw an error to alert developers that the method is not hooked up.

```diff
-  syncWithGoogleCalendar: () => Promise.resolve(),
-  syncAllFromGoogleCalendar: () => Promise.resolve(),
+  syncWithGoogleCalendar: () => Promise.reject(new Error("Not implemented")),
+  syncAllFromGoogleCalendar: () => Promise.reject(new Error("Not implemented")),
```

---

`608-609`: **Remove or refine debug logging.**

These console logs might be noisy in production and may reveal internal data structures. Consider switching them to a more purposeful logging mechanism, or remove them if they are no longer needed.

---

`624-668`: **Address the 'TODO' comment and re-auth flow.**

Line 645 has a "TODO: Notify user to re-authenticate." This is flagged by static analysis. Consider implementing a user-facing prompt or automated re-auth flow to handle expired tokens. Lack of re-auth could leave events out of sync.

Would you like help implementing a re-auth prompt or partial rollback logic if the Google delete fails?

<details>
<summary>🧰 Tools</summary>

<details>
<summary>🪛 GitHub Check: CodeFactor</summary>

[notice] 645-645: packages/ui/src/hooks/use-calendar.tsx#L645
Unresolved 'todo' comment. (eslint/no-warning-comments)

</details>

</details>

---

`689-762`: **Remove the unnecessary 'continue' statement for cleaner control flow.**

Static analysis flags line 752 as an unnecessary continue statement, given it's the last line within the loop's body. You can safely remove it without changing functionality.

```diff
         if (insertError) {
           console.error('Failed to insert event:', insertError);
-          continue; // continue to the next event on error
         }
```

<details>
<summary>🧰 Tools</summary>

<details>
<summary>🪛 Biome (1.9.4)</summary>

[error] 752-752: Unnecessary continue statement

Unsafe fix: Delete the unnecessary continue statement


(lint/correctness/noUnnecessaryContinue)

</details>

</details>

---

`764-823`: **Enhance user-facing messaging on sync errors.**

The expanded error handling is great, but consider displaying a more user-friendly message (e.g., toast notification) when re-auth is needed or if the event is not found on Google. This makes the synchronization process more transparent to end users.

</blockquote></details>
<details>
<summary>apps/db/supabase/migrations/20250411110924_update_ws_calendar_events.sql (1)</summary><blockquote>

`1-4`: **New Column Addition for Google Calendar Integration**

The migration correctly adds a new text column `google_event_id` to the `workspace_calendar_events` table. This change will support the storage and synchronization of Google event identifiers. If you expect frequent lookups by this column, consider adding an index to improve query performance in the future.

</blockquote></details>

</blockquote></details>

<details>
<summary>📜 Review details</summary>

**Configuration used: CodeRabbit UI**
**Review profile: CHILL**
**Plan: Pro**

<details>
<summary>📥 Commits</summary>

Reviewing files that changed from the base of the PR and between 337d560762e6cdd0f2b9a1ea9f2ea0fc38cbf54b and e93b1f3c3a00b5b1ea51b76bad7a5fadf81ad6f3.

</details>

<details>
<summary>⛔ Files ignored due to path filters (1)</summary>

* `pnpm-lock.yaml` is excluded by `!**/pnpm-lock.yaml`

</details>

<details>
<summary>📒 Files selected for processing (8)</summary>

* `apps/db/supabase/migrations/20250411110924_update_ws_calendar_events.sql` (1 hunks)
* `apps/web/src/app/api/v1/calendar/auth/callback/route.ts` (1 hunks)
* `apps/web/src/app/api/v1/calendar/auth/fetch/route.ts` (1 hunks)
* `apps/web/src/app/api/v1/calendar/auth/sync/route.ts` (5 hunks)
* `packages/types/src/primitives/calendar-event.ts` (1 hunks)
* `packages/types/src/supabase.ts` (3 hunks)
* `packages/ui/src/components/ui/legacy/calendar/Calendar.tsx` (4 hunks)
* `packages/ui/src/hooks/use-calendar.tsx` (6 hunks)

</details>

<details>
<summary>🧰 Additional context used</summary>

<details>
<summary>🧬 Code Graph Analysis (3)</summary>

<details>
<summary>apps/web/src/app/api/v1/calendar/auth/fetch/route.ts (1)</summary><blockquote>

<details>
<summary>packages/ui/src/components/ui/legacy/calendar/settings/ColorPicker.tsx (1)</summary>

* `colorMap` (8-72)

</details>

</blockquote></details>
<details>
<summary>apps/web/src/app/api/v1/calendar/auth/sync/route.ts (1)</summary><blockquote>

<details>
<summary>packages/types/src/primitives/calendar-event.ts (1)</summary>

* `CalendarEvent` (5-30)

</details>

</blockquote></details>
<details>
<summary>packages/ui/src/components/ui/legacy/calendar/Calendar.tsx (3)</summary><blockquote>

<details>
<summary>packages/ui/src/hooks/use-calendar.tsx (1)</summary>

* `useCalendar` (947-952)

</details>
<details>
<summary>packages/ui/src/components/ui/sonner.tsx (1)</summary>

* `toast` (29-29)

</details>
<details>
<summary>packages/ui/src/components/ui/tooltip.tsx (2)</summary>

* `TooltipTrigger` (60-60)
* `TooltipContent` (60-60)

</details>

</blockquote></details>

</details><details>
<summary>🪛 GitHub Check: CodeFactor</summary>

<details>
<summary>apps/web/src/app/api/v1/calendar/auth/sync/route.ts</summary>

[notice] 72-72: apps/web/src/app/api/v1/calendar/auth/sync/route.ts#L72
Unresolved 'todo' comment. (eslint/no-warning-comments)

</details>
<details>
<summary>packages/ui/src/hooks/use-calendar.tsx</summary>

[notice] 645-645: packages/ui/src/hooks/use-calendar.tsx#L645
Unresolved 'todo' comment. (eslint/no-warning-comments)

</details>

</details>
<details>
<summary>🪛 Biome (1.9.4)</summary>

<details>
<summary>packages/ui/src/hooks/use-calendar.tsx</summary>

[error] 752-752: Unnecessary continue statement

Unsafe fix: Delete the unnecessary continue statement


(lint/correctness/noUnnecessaryContinue)

</details>

</details>

</details>

<details>
<summary>⏰ Context from checks skipped due to timeout of 90000ms (2)</summary>

* GitHub Check: Analyze (javascript-typescript)
* GitHub Check: Deploy-Preview

</details>

<details>
<summary>🔇 Additional comments (17)</summary><blockquote>

<details>
<summary>packages/types/src/primitives/calendar-event.ts (1)</summary>

`19-19`: **Type definition update is consistent with DB schema.**

Adding the optional `google_event_id` property to the interface properly aligns with the database migration and ensures type safety for Google Calendar integration.

</details>
<details>
<summary>packages/types/src/supabase.ts (1)</summary>

`4012-4012`: **Good implementation of the Google Calendar integration field.**

The addition of the `google_event_id` field to the `workspace_calendar_events` type is well-implemented across all three contexts (Row, Insert, and Update). Making it nullable (string | null) is appropriate as not all calendar events will necessarily have a Google Calendar counterpart.



Also applies to: 4025-4025, 4038-4038

</details>
<details>
<summary>apps/web/src/app/api/v1/calendar/auth/fetch/route.ts (3)</summary>

`1-18`: **Well-structured helper function for creating OAuth client**  
This `getGoogleAuthClient` function is straightforward and correctly sets the credentials from the provided tokens.

---

`27-33`: **Clear user authentication check**  
Returning a 401 if the user is not authenticated is appropriate and maintains straightforward error handling.

---

`67-78`: **Check handling of `ws_id`**  
The `ws_id` is currently set to an empty string. Ensure downstream logic or references to `ws_id` can handle empty strings safely if your application expects a valid workspace identifier.

</details>
<details>
<summary>apps/web/src/app/api/v1/calendar/auth/sync/route.ts (5)</summary>

`7-9`: **Optional `id` property**  
Allowing `id?: string` in `CalendarEvent` is fine for scenarios where an event is newly created and not yet persisted.

---

`83-130`: **Graceful handling of local and Google event ID mismatch**  
The insertion logic correctly updates Supabase if the local event has an existing `id`. Logging an issue instead of failing the request is a sound choice for resiliency.

---

`149-174`: **Robust input validation for PUT**  
Checking for the presence of `googleCalendarEventId` and `eventUpdates` helps prevent partial or malformed updates, avoiding potential errors later.

---

`175-274`: **Partial updates with `patch`**  
Using `calendar.events.patch` is a good approach for updating only changed fields. This keeps upstream data intact and reduces the risk of overwriting user data.

---

`276-357`: **Reasonably safe delete operation**  
The `DELETE` function handles event-not-found cases by logging a warning and returning a success response, preserving local state correctness.

</details>
<details>
<summary>packages/ui/src/components/ui/legacy/calendar/Calendar.tsx (4)</summary>

`3-3`: **New icon and toast imports**  
Importing `useToast` and the icons (`Link`, `Loader2`, etc.) is straightforward and aligns with the newly introduced sync functionality.  



Also applies to: 20-20

---

`54-57`: **Integration of `toast` and `syncAllFromGoogleCalendar`**  
Using `toast` from the UI library and destructuring `syncAllFromGoogleCalendar` ensures seamless communication with your calendar context for real-time user feedback.

---

`59-76`: **Graceful sync handler**  
The `handleSyncGoogleCalendar` function sets a loading state, calls the sync method, and displays success or error toasts. This flow is clean and prevents multiple submissions by disabling the button.

---

`114-132`: **User-friendly sync button**  
The button toggles between a spinner and a link icon, giving immediate visual feedback about the sync process. Disabling the button while syncing helps prevent duplicate requests.

</details>
<details>
<summary>packages/ui/src/hooks/use-calendar.tsx (2)</summary>

`620-623`: **Gracefully handle event not found scenario.**

If the event is not found in the local state, the code proceeds with the Supabase delete query. Consider handling the case when `eventToDelete` is `undefined` to provide user feedback or skip the delete operation entirely.

---

`920-920`: **Good addition to the exported context.**

Exposing `syncAllFromGoogleCalendar` in the context is helpful. This should allow UI components to trigger a full synchronization easily.

</details>
<details>
<summary>apps/web/src/app/api/v1/calendar/auth/callback/route.ts (1)</summary>

`101-102`: **Updated OAuth Callback Redirect**

The redirect URL has been updated to `/onboarding/calendar`, which aligns with the new authentication flow for Google Calendar synchronization. Ensure that the client-side route for `/onboarding/calendar` is correctly configured to handle the transition. Additionally, double-check that no sensitive tokens are leaked in the URL.

</details>

</blockquote></details>

</details>

<!-- This is an auto-generated comment by CodeRabbit for review status -->
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-13T14:07:36Z
user: login: coderabbitai[bot]
body: **Actionable comments posted: 0**

<details>
<summary>🔭 Outside diff range comments (1)</summary><blockquote>

<details>
<summary>apps/web/src/app/api/v1/calendar/auth/sync/route.ts (1)</summary><blockquote>

`1-357`: _💡 Verification agent_

<details>
<summary>🧩 Analysis chain</summary>

**Add unit tests for new functionality**

The static analysis hints indicate that the added code is not covered by tests. Given the complexity of the Google Calendar integration and the potential for issues when interacting with external APIs, add comprehensive unit tests for the added functionality.

---

🏁 Script executed:

```shell
#!/bin/bash
# Check if tests exist for the calendar sync functionality

echo "Searching for test files related to calendar sync..."
fd "test.*calendar.*sync" --type f

echo "Checking for existing tests that might cover this functionality..."
rg -l "calendar.*sync" --glob "**/*test*.*"
```

Length of output: 340

---



</details>

**Action Required: Add Comprehensive Unit Tests for Calendar Sync Integration**

Based on our searches, it appears that the new calendar synchronization endpoints (POST, PUT, DELETE) lack corresponding unit tests. Given the complexity of the integration with Google Calendar and Supabase, it's essential to add tests covering both success and error scenarios. Specifically, please ensure that your tests:

- Validate the correct behavior for each endpoint (POST, PUT, DELETE).
- Handle cases where the user is not authenticated.
- Cover scenarios with missing or invalid Google tokens.
- Verify proper error handling when the Google API returns errors (e.g., invalid_grant, 404/410 responses).
- Mock external API calls (Google Calendar and Supabase) to reliably test endpoints without triggering actual external calls.

Please add comprehensive unit tests to ensure robust validation of the new functionality.

<details>
<summary>🧰 Tools</summary>

<details>
<summary>🪛 GitHub Check: codecov/patch</summary>

[warning] 43-43: apps/web/src/app/api/v1/calendar/auth/sync/route.ts#L43
Added line #L43 was not covered by tests

---

[warning] 84-86: apps/web/src/app/api/v1/calendar/auth/sync/route.ts#L84-L86
Added lines #L84 - L86 were not covered by tests

---

[warning] 103-103: apps/web/src/app/api/v1/calendar/auth/sync/route.ts#L103
Added line #L103 was not covered by tests

---

[warning] 105-105: apps/web/src/app/api/v1/calendar/auth/sync/route.ts#L105
Added line #L105 was not covered by tests

---

[warning] 108-111: apps/web/src/app/api/v1/calendar/auth/sync/route.ts#L108-L111
Added lines #L108 - L111 were not covered by tests

---

[warning] 113-117: apps/web/src/app/api/v1/calendar/auth/sync/route.ts#L113-L117
Added lines #L113 - L117 were not covered by tests

---

[warning] 119-128: apps/web/src/app/api/v1/calendar/auth/sync/route.ts#L119-L128
Added lines #L119 - L128 were not covered by tests

---

[warning] 130-147: apps/web/src/app/api/v1/calendar/auth/sync/route.ts#L130-L147
Added lines #L130 - L147 were not covered by tests

---

[warning] 149-150: apps/web/src/app/api/v1/calendar/auth/sync/route.ts#L149-L150
Added lines #L149 - L150 were not covered by tests

---

[warning] 152-156: apps/web/src/app/api/v1/calendar/auth/sync/route.ts#L152-L156
Added lines #L152 - L156 were not covered by tests

---

[warning] 160-160: apps/web/src/app/api/v1/calendar/auth/sync/route.ts#L160
Added line #L160 was not covered by tests

---

[warning] 162-173: apps/web/src/app/api/v1/calendar/auth/sync/route.ts#L162-L173
Added lines #L162 - L173 were not covered by tests

---

[warning] 175-196: apps/web/src/app/api/v1/calendar/auth/sync/route.ts#L175-L196
Added lines #L175 - L196 were not covered by tests

---

[warning] 198-200: apps/web/src/app/api/v1/calendar/auth/sync/route.ts#L198-L200
Added lines #L198 - L200 were not covered by tests

</details>

</details>

</blockquote></details>

</blockquote></details>

<details>
<summary>🧹 Nitpick comments (9)</summary><blockquote>

<details>
<summary>apps/web/src/app/api/v1/calendar/auth/sync/route.ts (3)</summary><blockquote>

`72-72`: **Convert TODO comment to a proper issue/ticket**

Instead of keeping this comment in the code, consider creating a proper issue in your tracking system to address this in the future. This will ensure it doesn't get forgotten.

```diff
-    // T0D0: Don't return error immediately, maybe user hasn't linked (fix in the future)
+    // Issue #XXXX: Improve handling when user hasn't linked their Google Calendar
```

---

`83-96`: **Add typings for Google Calendar API objects**

The `googleEvent` object should have explicit typing to ensure type safety when interacting with the Google Calendar API. This would help catch potential errors at compile time.

```diff
-    const googleEvent = {
+    const googleEvent: google.calendar_v3.Schema$Event = {
       summary: event.title || 'Untitled Event',
       description: event.description || '',
       location: event.location || '',
       start: {
         dateTime: event.start_at,
         timeZone: Intl.DateTimeFormat().resolvedOptions().timeZone,
       },
       end: {
         dateTime: event.end_at,
         timeZone: Intl.DateTimeFormat().resolvedOptions().timeZone,
       },
       colorId: getGoogleColorId(event.color),
     };
```

<details>
<summary>🧰 Tools</summary>

<details>
<summary>🪛 GitHub Check: codecov/patch</summary>

[warning] 84-86: apps/web/src/app/api/v1/calendar/auth/sync/route.ts#L84-L86
Added lines #L84 - L86 were not covered by tests

</details>

</details>

---

`203-224`: **Avoid using `any` type for better type safety**

Using `any` type reduces TypeScript's ability to catch errors. Consider creating a proper interface for the Google event update object.

```diff
-    const googleEventUpdate: any = {}; // Use 'any' for flexibility or define a specific type
+    const googleEventUpdate: Partial<google.calendar_v3.Schema$Event> = {}; // Use proper typing
```

</blockquote></details>
<details>
<summary>packages/ui/src/hooks/use-calendar.tsx (6)</summary><blockquote>

`414-414`: **Convert TODO comment to a proper issue/ticket**

Similar to the other file, it's better to create a tracked issue for this workaround rather than leaving it as a TODO in the code.

```diff
-      // TOD0: Fix this weird workaround in the future
+      // Issue #XXXX: Fix date handling workaround in addEmptyEvent
```

---

`608-610`: **Remove debug console.log statements**

These debug console.log statements should be removed before merging to production as they can clutter the console and potentially expose sensitive information.

```diff
-      console.log('Current events array:', events);
-      console.log('Deleting event with ID:', eventId);
```

---

`644-646`: **Convert TODO comment to a proper issue/ticket**

Another TODO that should be converted to a tracked issue for better follow-up.

```diff
-              // TOD0: Notify user to re-authenticate. Should we block local delete? Maybe not.
+              // Issue #XXXX: Implement user notification for re-authentication with Google Calendar
```

---

`733-754`: **Consider using batch operations for better performance**

When inserting multiple events into the database, consider using a batch operation rather than inserting them one by one. This would reduce the number of database calls and improve performance.

```diff
+      // Batch insert new events if there are any
+      if (newEvents.length > 0) {
+        const { error: batchInsertError } = await supabase
+          .from('workspace_calendar_events')
+          .insert(
+            newEvents.map(event => ({
+              title: event.title,
+              description: event.description,
+              start_at: event.start_at,
+              end_at: event.end_at,
+              color: event.color,
+              location: event.location,
+              ws_id: ws.id,
+              google_event_id: event.google_event_id,
+              locked: event.locked || false,
+            }))
+          );
+
+        if (batchInsertError) {
+          console.error('Failed to batch insert events:', batchInsertError);
+        }
+      }
```

<details>
<summary>🧰 Tools</summary>

<details>
<summary>🪛 Biome (1.9.4)</summary>

[error] 752-752: Unnecessary continue statement

Unsafe fix: Delete the unnecessary continue statement


(lint/correctness/noUnnecessaryContinue)

</details>

</details>

---

`751-753`: **Remove unnecessary continue statement**

The continue statement at the end of the loop iteration is unnecessary and can be removed.

```diff
         if (insertError) {
           console.error('Failed to insert event:', insertError);
-          continue; // continue to the next event on error
         }
```

<details>
<summary>🧰 Tools</summary>

<details>
<summary>🪛 Biome (1.9.4)</summary>

[error] 752-752: Unnecessary continue statement

Unsafe fix: Delete the unnecessary continue statement


(lint/correctness/noUnnecessaryContinue)

</details>

</details>

---

`792-827`: **Use variable for API endpoint for better maintainability**

The endpoint URL is repeated in multiple places. Consider extracting it to a constant for better maintainability.

```diff
+      const GOOGLE_CALENDAR_SYNC_ENDPOINT = '/api/v1/calendar/auth/sync';
       if (event.google_event_id) {
         // Update an existing event on Google Calendar
-        response = await fetch('/api/v1/calendar/auth/sync', {
+        response = await fetch(GOOGLE_CALENDAR_SYNC_ENDPOINT, {
           method: 'PUT',
           headers: { 'Content-Type': 'application/json' },
           body: JSON.stringify({
             eventId: event.id,
             googleCalendarEventId: event.google_event_id,
             eventUpdates: {
               title: event.title,
               description: event.description,
               start_at: event.start_at,
               end_at: event.end_at,
               color: event.color,
               location: event.location,
             },
           }),
         });
       } else {
         // create a new event on Google Calendar
-        response = await fetch('/api/v1/calendar/auth/sync', {
+        response = await fetch(GOOGLE_CALENDAR_SYNC_ENDPOINT, {
           method: 'POST',
           headers: { 'Content-Type': 'application/json' },
           body: JSON.stringify({ event }),
         });
```

</blockquote></details>

</blockquote></details>

<details>
<summary>📜 Review details</summary>

**Configuration used: CodeRabbit UI**
**Review profile: CHILL**
**Plan: Pro**

<details>
<summary>📥 Commits</summary>

Reviewing files that changed from the base of the PR and between e93b1f3c3a00b5b1ea51b76bad7a5fadf81ad6f3 and 709504abb0bdfbee311cbfec35e34bbaee69bf17.

</details>

<details>
<summary>📒 Files selected for processing (2)</summary>

* `apps/web/src/app/api/v1/calendar/auth/sync/route.ts` (5 hunks)
* `packages/ui/src/hooks/use-calendar.tsx` (7 hunks)

</details>

<details>
<summary>🧰 Additional context used</summary>

<details>
<summary>🧬 Code Graph Analysis (1)</summary>

<details>
<summary>apps/web/src/app/api/v1/calendar/auth/sync/route.ts (1)</summary><blockquote>

<details>
<summary>packages/types/src/primitives/calendar-event.ts (1)</summary>

* `CalendarEvent` (5-30)

</details>

</blockquote></details>

</details><details>
<summary>🪛 Biome (1.9.4)</summary>

<details>
<summary>packages/ui/src/hooks/use-calendar.tsx</summary>

[error] 752-752: Unnecessary continue statement

Unsafe fix: Delete the unnecessary continue statement


(lint/correctness/noUnnecessaryContinue)

</details>

</details>
<details>
<summary>🪛 GitHub Check: codecov/patch</summary>

<details>
<summary>apps/web/src/app/api/v1/calendar/auth/sync/route.ts</summary>

[warning] 43-43: apps/web/src/app/api/v1/calendar/auth/sync/route.ts#L43
Added line #L43 was not covered by tests

---

[warning] 84-86: apps/web/src/app/api/v1/calendar/auth/sync/route.ts#L84-L86
Added lines #L84 - L86 were not covered by tests

---

[warning] 103-103: apps/web/src/app/api/v1/calendar/auth/sync/route.ts#L103
Added line #L103 was not covered by tests

---

[warning] 105-105: apps/web/src/app/api/v1/calendar/auth/sync/route.ts#L105
Added line #L105 was not covered by tests

---

[warning] 108-111: apps/web/src/app/api/v1/calendar/auth/sync/route.ts#L108-L111
Added lines #L108 - L111 were not covered by tests

---

[warning] 113-117: apps/web/src/app/api/v1/calendar/auth/sync/route.ts#L113-L117
Added lines #L113 - L117 were not covered by tests

---

[warning] 119-128: apps/web/src/app/api/v1/calendar/auth/sync/route.ts#L119-L128
Added lines #L119 - L128 were not covered by tests

---

[warning] 130-147: apps/web/src/app/api/v1/calendar/auth/sync/route.ts#L130-L147
Added lines #L130 - L147 were not covered by tests

---

[warning] 149-150: apps/web/src/app/api/v1/calendar/auth/sync/route.ts#L149-L150
Added lines #L149 - L150 were not covered by tests

---

[warning] 152-156: apps/web/src/app/api/v1/calendar/auth/sync/route.ts#L152-L156
Added lines #L152 - L156 were not covered by tests

---

[warning] 160-160: apps/web/src/app/api/v1/calendar/auth/sync/route.ts#L160
Added line #L160 was not covered by tests

---

[warning] 162-173: apps/web/src/app/api/v1/calendar/auth/sync/route.ts#L162-L173
Added lines #L162 - L173 were not covered by tests

---

[warning] 175-196: apps/web/src/app/api/v1/calendar/auth/sync/route.ts#L175-L196
Added lines #L175 - L196 were not covered by tests

---

[warning] 198-200: apps/web/src/app/api/v1/calendar/auth/sync/route.ts#L198-L200
Added lines #L198 - L200 were not covered by tests

</details>

</details>

</details>

<details>
<summary>⏰ Context from checks skipped due to timeout of 90000ms (3)</summary>

* GitHub Check: Analyze (javascript-typescript)
* GitHub Check: Deploy-Preview
* GitHub Check: Deploy-Preview

</details>

<details>
<summary>🔇 Additional comments (13)</summary><blockquote>

<details>
<summary>apps/web/src/app/api/v1/calendar/auth/sync/route.ts (8)</summary>

`1-9`: **Interface structure looks good, just ensure consistency with the imported type**

The new `CalendarEvent` interface properly extends `BaseCalendarEvent` with an optional `id` property. This is consistent with the relevant code snippets from the types file.

---

`103-128`: **Good implementation for storing Google Event ID**

The code correctly handles storing the Google Event ID in the local database after creating an event in Google Calendar. It also has proper error handling and logging.

<details>
<summary>🧰 Tools</summary>

<details>
<summary>🪛 GitHub Check: codecov/patch</summary>

[warning] 103-103: apps/web/src/app/api/v1/calendar/auth/sync/route.ts#L103
Added line #L103 was not covered by tests

---

[warning] 105-105: apps/web/src/app/api/v1/calendar/auth/sync/route.ts#L105
Added line #L105 was not covered by tests

---

[warning] 108-111: apps/web/src/app/api/v1/calendar/auth/sync/route.ts#L108-L111
Added lines #L108 - L111 were not covered by tests

---

[warning] 113-117: apps/web/src/app/api/v1/calendar/auth/sync/route.ts#L113-L117
Added lines #L113 - L117 were not covered by tests

---

[warning] 119-128: apps/web/src/app/api/v1/calendar/auth/sync/route.ts#L119-L128
Added lines #L119 - L128 were not covered by tests

</details>

</details>

---

`131-146`: **Improved error handling for Google API interactions**

The error handling has been enhanced to specifically detect and handle invalid token errors, which is important for OAuth integrations. This will make it easier for users to recover from authentication issues.

---

`149-173`: **Solid input validation in the PUT endpoint**

The input validation checks for required parameters before proceeding, which is a good security practice. This helps prevent errors from propagating through the system.

<details>
<summary>🧰 Tools</summary>

<details>
<summary>🪛 GitHub Check: codecov/patch</summary>

[warning] 149-150: apps/web/src/app/api/v1/calendar/auth/sync/route.ts#L149-L150
Added lines #L149 - L150 were not covered by tests

---

[warning] 152-156: apps/web/src/app/api/v1/calendar/auth/sync/route.ts#L152-L156
Added lines #L152 - L156 were not covered by tests

---

[warning] 160-160: apps/web/src/app/api/v1/calendar/auth/sync/route.ts#L160
Added line #L160 was not covered by tests

---

[warning] 162-173: apps/web/src/app/api/v1/calendar/auth/sync/route.ts#L162-L173
Added lines #L162 - L173 were not covered by tests

</details>

</details>

---

`225-239`: **Proper usage of the patch method for partial updates**

Using the patch method is the correct approach for partial updates to Google Calendar events. This is more efficient than sending the entire event object for updates.

---

`254-268`: **Great error handling for deleted Google Calendar events**

The code properly handles the case where events might have been deleted directly in Google Calendar. It updates the local database to reflect this and returns an appropriate response to the client.

---

`276-324`: **Well-implemented DELETE endpoint**

The DELETE endpoint follows the same pattern as the other endpoints with proper validation, authentication, and error handling. The implementation is clean and consistent.

---

`330-342`: **Appropriate handling of already deleted events**

Treating an already deleted event as a success is the right approach since the end goal (event not existing in Google Calendar) is achieved. This prevents unnecessary errors from surfacing to the user.

</details>
<details>
<summary>packages/ui/src/hooks/use-calendar.tsx (5)</summary>

`60-63`: **Good addition of syncAllFromGoogleCalendar to CalendarContext**

Adding the `syncAllFromGoogleCalendar` method to the context is a clean way to expose this functionality to components. The typing is correct and consistent with the existing context methods.

---

`620-628`: **Good implementation for Google Calendar event deletion**

The code correctly handles the deletion of events from Google Calendar by first checking if there's a Google event ID associated with the local event. This prevents unnecessary API calls.

---

`634-667`: **Robust error handling for Google Calendar deletion**

The error handling is comprehensive, covering cases where events might be missing or authentication has expired. The approach of continuing with the local deletion even when Google Calendar sync fails is sensible.

---

`689-763`: **Well-implemented syncAllFromGoogleCalendar function**

The function correctly fetches events from Google Calendar, checks for existing events in the local database, and creates new events for those that don't already exist. The error handling is thorough.

<details>
<summary>🧰 Tools</summary>

<details>
<summary>🪛 Biome (1.9.4)</summary>

[error] 752-752: Unnecessary continue statement

Unsafe fix: Delete the unnecessary continue statement


(lint/correctness/noUnnecessaryContinue)

</details>

</details>

---

`764-823`: **Well-implemented syncWithGoogleCalendar function**

The function correctly handles both creating and updating events based on the presence of a Google event ID. The error handling is comprehensive and provides appropriate feedback.

</details>

</blockquote></details>

</details>

<!-- This is an auto-generated comment by CodeRabbit for review status -->
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-13T14:12:40Z
user: login: vhpx
body: LGTM! Thanks @coder1807.
state: DISMISSED
author_association: MEMBER
submitted_at: 2025-04-13T21:30:41Z
user: login: coderabbitai[bot]
body: **Actionable comments posted: 1**

<details>
<summary>📜 Review details</summary>

**Configuration used: CodeRabbit UI**
**Review profile: CHILL**
**Plan: Pro**

<details>
<summary>📥 Commits</summary>

Reviewing files that changed from the base of the PR and between 709504abb0bdfbee311cbfec35e34bbaee69bf17 and 4eb8c8810db521817ada1db5cde9da9799b6f227.

</details>

<details>
<summary>📒 Files selected for processing (2)</summary>

* `apps/db/supabase/migrations/20250413110924_update_ws_calendar_events.sql` (1 hunks)
* `apps/web/src/app/[locale]/(dashboard)/[wsId]/calendar/client.tsx` (1 hunks)

</details>

<details>
<summary>🧰 Additional context used</summary>

<details>
<summary>🪛 GitHub Check: codecov/patch</summary>

<details>
<summary>apps/web/src/app/[locale]/(dashboard)/[wsId]/calendar/client.tsx</summary>

[warning] 26-26: apps/web/src/app/[locale]/(dashboard)/[wsId]/calendar/client.tsx#L26
Added line #L26 was not covered by tests

</details>

</details>

</details>

<details>
<summary>⏰ Context from checks skipped due to timeout of 90000ms (5)</summary>

* GitHub Check: Analyze (javascript-typescript)
* GitHub Check: Deploy-Preview
* GitHub Check: Deploy-Preview
* GitHub Check: Deploy-Preview
* GitHub Check: Deploy-Preview

</details>

<details>
<summary>🔇 Additional comments (1)</summary><blockquote>

<details>
<summary>apps/db/supabase/migrations/20250413110924_update_ws_calendar_events.sql (1)</summary>

`1-1`: **Schema change looks good for Google Calendar integration.**

The addition of the `google_event_id` column to the `workspace_calendar_events` table is appropriately designed as a nullable text field, which will allow for storing Google Calendar event identifiers. This is essential for maintaining the relationship between local calendar events and their Google Calendar counterparts.

</details>

</blockquote></details>

</details>

<!-- This is an auto-generated comment by CodeRabbit for review status -->
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-13T21:33:31Z
user: login: coderabbitai[bot]
body: **Actionable comments posted: 1**

<details>
<summary>🔭 Outside diff range comments (1)</summary><blockquote>

<details>
<summary>apps/web/src/app/api/v1/calendar/auth/callback/route.ts (1)</summary><blockquote>

`8-112`: _💡 Verification agent_

<details>
<summary>🧩 Analysis chain</summary>

**Missing test coverage for OAuth callback changes**

The static analysis indicates that the changes to the OAuth callback are not covered by tests.

---

🏁 Script executed:

```shell
#!/bin/bash
# Check for existing tests for the callback route
rg -A 5 -B 5 "calendar/auth/callback" --glob "**/*.test.ts" --glob "**/*.spec.ts"

# Check if we need to add tests for the wsId validation
echo "Consider adding tests for wsId validation in the OAuth callback"
```

Length of output: 218

---



</details>

**Missing Test Coverage for OAuth Callback Endpoint**

Our analysis indicates that the OAuth callback route (apps/web/src/app/api/v1/calendar/auth/callback/route.ts, lines 8-112) currently lacks test cases. In particular, there are no tests covering the wsId validation and the handling of missing parameters (e.g., no code provided) or token errors. Please add comprehensive tests to ensure that:

- The wsId parameter is validated correctly.
- Proper error responses are returned when required parameters (wsId, code, tokens) are missing.
- The logic for token update/insertion is exercised under both success and failure conditions.

<details>
<summary>🧰 Tools</summary>

<details>
<summary>🪛 GitHub Check: codecov/patch</summary>

[warning] 8-8: apps/web/src/app/api/v1/calendar/auth/callback/route.ts#L8
Added line #L8 was not covered by tests

---

[warning] 10-12: apps/web/src/app/api/v1/calendar/auth/callback/route.ts#L10-L12
Added lines #L10 - L12 were not covered by tests

---

[warning] 57-57: apps/web/src/app/api/v1/calendar/auth/callback/route.ts#L57
Added line #L57 was not covered by tests

---

[warning] 93-93: apps/web/src/app/api/v1/calendar/auth/callback/route.ts#L93
Added line #L93 was not covered by tests

---

[warning] 109-112: apps/web/src/app/api/v1/calendar/auth/callback/route.ts#L109-L112
Added lines #L109 - L112 were not covered by tests

</details>

</details>

</blockquote></details>

</blockquote></details>

<details>
<summary>📜 Review details</summary>

**Configuration used: CodeRabbit UI**
**Review profile: CHILL**
**Plan: Pro**

<details>
<summary>📥 Commits</summary>

Reviewing files that changed from the base of the PR and between 4eb8c8810db521817ada1db5cde9da9799b6f227 and 2806c807e05552fd5a74033aa6457eead2c4fb66.

</details>

<details>
<summary>📒 Files selected for processing (4)</summary>

* `apps/web/src/app/api/v1/calendar/auth/callback/route.ts` (4 hunks)
* `apps/web/src/app/api/v1/calendar/auth/route.ts` (1 hunks)
* `packages/ui/src/components/ui/legacy/calendar/Calendar.tsx` (4 hunks)
* `packages/ui/src/components/ui/legacy/calendar/UnifiedEventModal.tsx` (2 hunks)

</details>

<details>
<summary>🚧 Files skipped from review as they are similar to previous changes (1)</summary>

* packages/ui/src/components/ui/legacy/calendar/Calendar.tsx

</details>

<details>
<summary>🧰 Additional context used</summary>

<details>
<summary>🧬 Code Graph Analysis (1)</summary>

<details>
<summary>apps/web/src/app/api/v1/calendar/auth/route.ts (1)</summary><blockquote>

<details>
<summary>apps/web/src/app/api/v1/calendar/auth/callback/route.ts (1)</summary>

* `GET` (5-120)

</details>

</blockquote></details>

</details><details>
<summary>🪛 GitHub Check: codecov/patch</summary>

<details>
<summary>apps/web/src/app/api/v1/calendar/auth/route.ts</summary>

[warning] 2-2: apps/web/src/app/api/v1/calendar/auth/route.ts#L2
Added line #L2 was not covered by tests

---

[warning] 4-5: apps/web/src/app/api/v1/calendar/auth/route.ts#L4-L5
Added lines #L4 - L5 were not covered by tests

---

[warning] 7-9: apps/web/src/app/api/v1/calendar/auth/route.ts#L7-L9
Added lines #L7 - L9 were not covered by tests

---

[warning] 18-18: apps/web/src/app/api/v1/calendar/auth/route.ts#L18
Added line #L18 was not covered by tests

</details>
<details>
<summary>apps/web/src/app/api/v1/calendar/auth/callback/route.ts</summary>

[warning] 8-8: apps/web/src/app/api/v1/calendar/auth/callback/route.ts#L8
Added line #L8 was not covered by tests

---

[warning] 10-12: apps/web/src/app/api/v1/calendar/auth/callback/route.ts#L10-L12
Added lines #L10 - L12 were not covered by tests

---

[warning] 57-57: apps/web/src/app/api/v1/calendar/auth/callback/route.ts#L57
Added line #L57 was not covered by tests

---

[warning] 93-93: apps/web/src/app/api/v1/calendar/auth/callback/route.ts#L93
Added line #L93 was not covered by tests

---

[warning] 109-112: apps/web/src/app/api/v1/calendar/auth/callback/route.ts#L109-L112
Added lines #L109 - L112 were not covered by tests

</details>

</details>

</details>

<details>
<summary>⏰ Context from checks skipped due to timeout of 90000ms (4)</summary>

* GitHub Check: Analyze (javascript-typescript)
* GitHub Check: Deploy-Preview
* GitHub Check: Deploy-Preview
* GitHub Check: Deploy-Preview

</details>

<details>
<summary>🔇 Additional comments (10)</summary><blockquote>

<details>
<summary>apps/web/src/app/api/v1/calendar/auth/callback/route.ts (4)</summary>

`57-57`: **Adding workspace filter to token retrieval**

Good approach filtering tokens by both user_id and ws_id, which supports proper multi-workspace functionality.

<details>
<summary>🧰 Tools</summary>

<details>
<summary>🪛 GitHub Check: codecov/patch</summary>

[warning] 57-57: apps/web/src/app/api/v1/calendar/auth/callback/route.ts#L57
Added line #L57 was not covered by tests

</details>

</details>

---

`93-93`: **Storing workspace context with auth tokens**

Correctly saving the workspace ID when inserting a new token ensures proper workspace-specific authentication.

<details>
<summary>🧰 Tools</summary>

<details>
<summary>🪛 GitHub Check: codecov/patch</summary>

[warning] 93-93: apps/web/src/app/api/v1/calendar/auth/callback/route.ts#L93
Added line #L93 was not covered by tests

</details>

</details>

---

`109-112`: **Updated redirect to workspace-specific calendar page**

The redirect now properly directs users to their workspace-specific calendar page after authentication.

<details>
<summary>🧰 Tools</summary>

<details>
<summary>🪛 GitHub Check: codecov/patch</summary>

[warning] 109-112: apps/web/src/app/api/v1/calendar/auth/callback/route.ts#L109-L112
Added lines #L109 - L112 were not covered by tests

</details>

</details>

---

`8-12`: <details>
<summary>❓ Verification inconclusive</summary>

**Add wsId validation to ensure workspace context in OAuth flow**

The implementation correctly extracts the workspace ID from the OAuth state parameter and adds validation to ensure it's present. This enhances the security and context of the OAuth flow.


---

🏁 Script executed:

```shell
#!/bin/bash
# Verify if any existing code is using the callback route without providing a state parameter
rg "window\.location\.href\s*=.*\/api\/v1\/calendar\/auth\/callback" --no-heading
```

Length of output: 241

---

**OAuth Callback: wsId Validation Reviewed**

The implementation in `apps/web/src/app/api/v1/calendar/auth/callback/route.ts` (lines 8–12) properly extracts and validates the workspace ID from the OAuth state parameter. This check is essential to ensure the correct workspace context and enhance security in the OAuth flow.

Please note that the original verification command encountered an escape sequence error. The above script uses a corrected regex pattern. If necessary, please manually verify that no client-side code is invoking this callback route without the `state` (wsId) parameter.



---

🏁 Script executed:

```shell

#!/bin/bash
# Verify if any existing code is using the callback route without providing a state parameter,
# using a corrected regex pattern to avoid escape sequence issues.
rg 'window\.location\.href\s*=.*/api/v1/calendar/auth/callback' --no-heading
```

Length of output: 200

---



</details>

**OAuth Callback: Confirm wsId Validation and Client Usage**

The code in `apps/web/src/app/api/v1/calendar/auth/callback/route.ts` (lines 8–12) correctly extracts the workspace ID from the `state` parameter and returns a 400 error when absent, which is appropriate to enforce the OAuth flow's workspace context.

A verification attempt to locate client-side invocations of the callback route (ensuring they provide a valid `wsId`) produced no output—likely due to the command formatting issue and/or the absence of such invocations in the scanned code. Since automated checking did not yield definitive results, please manually verify that all calls to `/api/v1/calendar/auth/callback` include the necessary `state` parameter.

<details>
<summary>🧰 Tools</summary>

<details>
<summary>🪛 GitHub Check: codecov/patch</summary>

[warning] 8-8: apps/web/src/app/api/v1/calendar/auth/callback/route.ts#L8
Added line #L8 was not covered by tests

---

[warning] 10-12: apps/web/src/app/api/v1/calendar/auth/callback/route.ts#L10-L12
Added lines #L10 - L12 were not covered by tests

</details>

</details>

</details>
<details>
<summary>apps/web/src/app/api/v1/calendar/auth/route.ts (3)</summary>

`2-5`: **Updated imports and function signature for NextRequest handling**

The changes properly update the function signature to handle the request parameter and extract the wsId.

<details>
<summary>🧰 Tools</summary>

<details>
<summary>🪛 GitHub Check: codecov/patch</summary>

[warning] 2-2: apps/web/src/app/api/v1/calendar/auth/route.ts#L2
Added line #L2 was not covered by tests

---

[warning] 4-5: apps/web/src/app/api/v1/calendar/auth/route.ts#L4-L5
Added lines #L4 - L5 were not covered by tests

</details>

</details>

---

`7-9`: **Added wsId validation for OAuth initialization**

Good implementation of validation to ensure the workspace ID is present before initiating the OAuth flow.

<details>
<summary>🧰 Tools</summary>

<details>
<summary>🪛 GitHub Check: codecov/patch</summary>

[warning] 7-9: apps/web/src/app/api/v1/calendar/auth/route.ts#L7-L9
Added lines #L7 - L9 were not covered by tests

</details>

</details>

---

`18-18`: **Using wsId as OAuth state parameter**

Using the workspace ID as the state parameter is an effective way to maintain context through the OAuth flow.

<details>
<summary>🧰 Tools</summary>

<details>
<summary>🪛 GitHub Check: codecov/patch</summary>

[warning] 18-18: apps/web/src/app/api/v1/calendar/auth/route.ts#L18
Added line #L18 was not covered by tests

</details>

</details>

</details>
<details>
<summary>packages/ui/src/components/ui/legacy/calendar/UnifiedEventModal.tsx (3)</summary>

`98-103`: **Updated component props to require workspace ID**

The addition of the required wsId parameter ensures the component has the necessary context for workspace-specific operations.

---

`125-125`: **Including workspace context in Google auth request**

The implementation correctly passes the workspace ID to the auth endpoint, maintaining consistent workspace context.

---

`98-125`: <details>
<summary>✅ Verification successful</summary>

**Ensure all component usages provide the new required wsId parameter**

Since wsId is now a required parameter, all usage sites of UnifiedEventModal need to be updated.

---

🏁 Script executed:

```shell
#!/bin/bash
# Find all usages of UnifiedEventModal to ensure they provide the wsId parameter
rg -A 3 "UnifiedEventModal" --glob "**/*.tsx" --glob "**/*.jsx" | grep -v "packages/ui/src/components/ui/legacy/calendar/UnifiedEventModal.tsx"
```

Length of output: 1015

---



</details>

**Component Usage Verified: All usages correctly pass the required `wsId` parameter.**  
I verified that in the usage found in `packages/ui/src/components/ui/legacy/calendar/Calendar.tsx`, the `UnifiedEventModal` is invoked with `wsId={workspace.id}`. No other usages appear to be missing this parameter.

</details>

</blockquote></details>

</details>

<!-- This is an auto-generated comment by CodeRabbit for review status -->
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-13T21:50:16Z

Review comments:
diff_hunk: @@ -24,7 +23,7 @@
       useQuery={useQuery}
       useQueryClient={useQueryClient}
       experimentalGoogleCalendarLinked={experimentalGoogleCalendarLinked}
-      enableExperimentalGoogleCalendar={DEV_MODE}
+      enableExperimentalGoogleCalendar={true}
path: apps/web/src/app/[locale]/(dashboard)/[wsId]/calendar/client.tsx
user: login: coderabbitai[bot]
body: _💡 Verification agent_

<details>
<summary>❓ Verification inconclusive</summary>

**Google Calendar integration now enabled by default.**

Changing from a conditional value based on `DEV_MODE` to a hardcoded `true` means this feature is now enabled for all users, not just in development environments. This aligns with the PR objective to fully sync with Google Calendar.


Consider adding test coverage for this change as it enables a significant feature. The static analysis indicates this line isn't covered by tests.

---

🏁 Script executed:

```shell
#!/bin/bash
# Check for existing tests that might need updating
fd -t f -e test.tsx -e spec.tsx "calendar" apps/web/src
```

Length of output: 55

---

🏁 Script executed:

```shell
#!/bin/bash
# Searching test files for any usage of "enableExperimentalGoogleCalendar"
rg "enableExperimentalGoogleCalendar" -t tsx apps/web/src
```

Length of output: 85

---



</details>

**Google Calendar integration enabled by default – additional test coverage required**

The change in `apps/web/src/app/[locale]/(dashboard)/[wsId]/calendar/client.tsx` now hardcodes the `enableExperimentalGoogleCalendar` flag to `true`, which aligns with the PR’s objective to fully sync with Google Calendar. However, our preliminary searches indicate that automated tests covering this change are missing. Please add and/or update tests to ensure that this significant feature is properly verified.

- **Location:** `apps/web/src/app/[locale]/(dashboard)/[wsId]/calendar/client.tsx`, around line 26
- **Action:** Add tests covering the Google Calendar integration scenario

<details>
<summary>🧰 Tools</summary>

<details>
<summary>🪛 GitHub Check: codecov/patch</summary>

[warning] 26-26: apps/web/src/app/[locale]/(dashboard)/[wsId]/calendar/client.tsx#L26
Added line #L26 was not covered by tests

</details>

</details>

<!-- This is an auto-generated comment by CodeRabbit -->
created_at: 2025-04-13T21:33:30Z
author_association: CONTRIBUTOR
diff_hunk: @@ -1,14 +1,21 @@
 import { OAuth2Client } from 'google-auth-library';
-import { NextResponse } from 'next/server';
+import { type NextRequest, NextResponse } from 'next/server';
+
+export async function GET(request: NextRequest) {
+  const wsId = request.nextUrl.searchParams.get('wsId');
+
+  if (!wsId) {
+    return NextResponse.json({ error: 'wsId is required' }, { status: 400 });
+  }
 
-export async function GET() {
   const auth = new OAuth2Client({
     clientId: process.env.GOOGLE_CLIENT_ID,
     clientSecret: process.env.GOOGLE_CLIENT_SECRET,
     redirectUri: process.env.GOOGLE_REDIRECT_URI,
   });
 
   const authUrl = auth.generateAuthUrl({
+    state: wsId,
path: apps/web/src/app/api/v1/calendar/auth/route.ts
user: login: coderabbitai[bot]
body: _💡 Verification agent_

<details>
<summary>🧩 Analysis chain</summary>

**Missing test coverage for OAuth initialization changes**

Static analysis indicates that none of the changes to the OAuth initialization are covered by tests.

---

🏁 Script executed:

```shell
#!/bin/bash
# Check for existing tests for the auth route
rg -A 5 -B 5 "calendar/auth" --glob "**/*.test.ts" --glob "**/*.spec.ts"

# Check if we need to add tests for the wsId validation
echo "Consider adding tests for wsId validation in the OAuth initialization"
```

Length of output: 221

---



</details>

**Address missing test coverage for OAuth initialization**

It appears that there are no tests covering the OAuth2Client initialization changes, including the wsId validation logic. Please add tests to verify:
- The 400 error response when wsId is missing.
- That the OAuth2Client is correctly initialized and `generateAuthUrl` is properly invoked with the provided wsId value.

<details>
<summary>🧰 Tools</summary>

<details>
<summary>🪛 GitHub Check: codecov/patch</summary>

[warning] 2-2: apps/web/src/app/api/v1/calendar/auth/route.ts#L2
Added line #L2 was not covered by tests

---

[warning] 4-5: apps/web/src/app/api/v1/calendar/auth/route.ts#L4-L5
Added lines #L4 - L5 were not covered by tests

---

[warning] 7-9: apps/web/src/app/api/v1/calendar/auth/route.ts#L7-L9
Added lines #L7 - L9 were not covered by tests

---

[warning] 18-18: apps/web/src/app/api/v1/calendar/auth/route.ts#L18
Added line #L18 was not covered by tests

</details>

</details>

<!-- This is an auto-generated comment by CodeRabbit -->
created_at: 2025-04-13T21:50:15Z
author_association: CONTRIBUTOR

PR comments:
user: login: coderabbitai[bot]
created_at: 2025-04-13T14:02:06Z
body: <!-- This is an auto-generated comment: summarize by coderabbit.ai -->
<!-- walkthrough_start -->

## Walkthrough

This update enhances the Google Calendar integration across various components. It introduces a new parameter `wsId` for OAuth callback redirection, adds a new API endpoint for fetching calendar events, and extends event synchronization functionality with new POST, PUT, and DELETE operations. The calendar event types and database schemas now include an optional Google event identifier. Additionally, the UI features a sync button with loading states and toast notifications, while the hook managing calendar operations has been updated for full synchronization with Google Calendar.

## Changes

| File(s) | Change Summary |
|---------|----------------|
| `apps/web/src/app/api/v1/calendar/auth/callback/route.ts` | Introduced `wsId` parameter, updated OAuth callback redirection URL to include `wsId`. |
| `apps/web/src/app/api/v1/calendar/auth/fetch/route.ts` | Added a new GET endpoint to fetch Google Calendar events, including user authentication and error handling. |
| `apps/web/src/app/api/v1/calendar/auth/sync/route.ts` | Introduced `CalendarEvent` interface, modified POST endpoint, and added PUT and DELETE endpoints for event updates and deletions with enhanced error handling. |
| `packages/types/src/primitives/calendar-event.ts`, `packages/types/src/supabase.ts` | Added an optional property `google_event_id` to the `CalendarEvent` interface and the `workspace_calendar_events` type (Row, Insert, Update). |
| `packages/ui/src/components/ui/legacy/calendar/Calendar.tsx`, `packages/ui/src/hooks/use-calendar.tsx` | Implemented Google Calendar sync functionality: added sync methods, a syncing state in the UI, and a new sync button with toast notifications. |
| `apps/db/supabase/migrations/20250413110924_update_ws_calendar_events.sql` | Added a `google_event_id` column to the `workspace_calendar_events` table in the database schema. |
| `apps/web/src/app/[locale]/(dashboard)/[wsId]/calendar/client.tsx` | Changed `enableExperimentalGoogleCalendar` prop to a hardcoded `true` value, removing the dependency on `DEV_MODE`. |
| `apps/web/src/app/api/v1/calendar/auth/route.ts` | Updated `GET` function to accept `NextRequest`, introduced `wsId` parameter for error handling and URL generation. |
| `packages/ui/src/components/ui/legacy/calendar/UnifiedEventModal.tsx` | Updated `UnifiedEventModal` to require `wsId` parameter for API requests. |

## Sequence Diagram(s)

```mermaid
sequenceDiagram
    participant U as User
    participant WC as WebClient
    participant API as Calendar API
    participant GC as Google Calendar API
    participant DB as Supabase/Database

    U->>WC: Click "Sync Google Calendar"
    WC->>API: Request to sync calendar events
    API->>GC: Fetch events (past & future)
    GC-->>API: Return list of events
    API->>DB: Check and sync new events in DB
    DB-->>API: Confirmation/result
    API-->>WC: Return success/failure response
    WC->>U: Display toast notification
```

## Poem

> I'm a rabbit, hopping with delight,  
> New calendar syncs make my day so bright.  
> With tokens, events, and API flow,  
> I nibble through data as they dance and go.  
> Cheers to changes, swift and true,  
> In my leafy code, the sync is new!  
> 🐇🌸 Happy hops to me and you!

<!-- walkthrough_end -->
<!-- This is an auto-generated comment: all tool run failures by coderabbit.ai -->

> [!WARNING]
> There were issues while running some tools. Please review the errors and either fix the tool's configuration or disable the tool if it's a critical failure.
> 
> <details>
> <summary>🔧 ESLint</summary>
> 
> > If the error stems from missing dependencies, add them to the package.json file. For unrecoverable errors (e.g., due to private dependencies), disable the tool in the CodeRabbit configuration.
> 
> <details>
> <summary>apps/web/src/app/api/v1/calendar/auth/route.ts</summary>
> 
> 
> Oops! Something went wrong! :(
> 
> ESLint: 9.24.0
> 
> Error [ERR_MODULE_NOT_FOUND]: Cannot find package '@typescript-eslint/parser' imported from /eslint.config.mjs
>     at packageResolve (node:internal/modules/esm/resolve:839:9)
>     at moduleResolve (node:internal/modules/esm/resolve:908:18)
>     at defaultResolve (node:internal/modules/esm/resolve:1038:11)
>     at ModuleLoader.defaultResolve (node:internal/modules/esm/loader:557:12)
>     at ModuleLoader.resolve (node:internal/modules/esm/loader:525:25)
>     at ModuleLoader.getModuleJob (node:internal/modules/esm/loader:246:38)
>     at ModuleJob._link (node:internal/modules/esm/module_job:126:49)
> 
> </details>
> 
> <details>
> <summary>apps/web/src/app/api/v1/calendar/auth/callback/route.ts</summary>
> 
> 
> Oops! Something went wrong! :(
> 
> ESLint: 9.24.0
> 
> Error [ERR_MODULE_NOT_FOUND]: Cannot find package '@typescript-eslint/parser' imported from /eslint.config.mjs
>     at packageResolve (node:internal/modules/esm/resolve:839:9)
>     at moduleResolve (node:internal/modules/esm/resolve:908:18)
>     at defaultResolve (node:internal/modules/esm/resolve:1038:11)
>     at ModuleLoader.defaultResolve (node:internal/modules/esm/loader:557:12)
>     at ModuleLoader.resolve (node:internal/modules/esm/loader:525:25)
>     at ModuleLoader.getModuleJob (node:internal/modules/esm/loader:246:38)
>     at ModuleJob._link (node:internal/modules/esm/module_job:126:49)
> 
> </details>
> 
> <details>
> <summary>packages/ui/src/components/ui/legacy/calendar/Calendar.tsx</summary>
> 
> 
> Oops! Something went wrong! :(
> 
> ESLint: 9.24.0
> 
> Error [ERR_MODULE_NOT_FOUND]: Cannot find package '@typescript-eslint/parser' imported from /eslint.config.mjs
>     at packageResolve (node:internal/modules/esm/resolve:839:9)
>     at moduleResolve (node:internal/modules/esm/resolve:908:18)
>     at defaultResolve (node:internal/modules/esm/resolve:1038:11)
>     at ModuleLoader.defaultResolve (node:internal/modules/esm/loader:557:12)
>     at ModuleLoader.resolve (node:internal/modules/esm/loader:525:25)
>     at ModuleLoader.getModuleJob (node:internal/modules/esm/loader:246:38)
>     at ModuleJob._link (node:internal/modules/esm/module_job:126:49)
> 
> </details>
> 
> + 1 others
> 
> </details>

<!-- end of auto-generated comment: all tool run failures by coderabbit.ai -->

<!-- announcements_start -->

> [!TIP]
> <details>
> <summary>⚡💬 Agentic Chat (Pro Plan, General Availability)</summary>
> 
> - We're introducing multi-step agentic chat in review comments and issue comments, within and outside of PR's. This feature enhances review and issue discussions with the CodeRabbit agentic chat by enabling advanced interactions, including the ability to create pull requests directly from comments and add commits to existing pull requests.
> 
> </details>

<!-- announcements_end -->
<!-- internal state start -->


<!-- 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= -->

<!-- internal state end -->
<!-- finishing_touch_checkbox_start -->

<details>
<summary>✨ Finishing Touches</summary>

- [ ] <!-- {"checkboxId": "7962f53c-55bc-4827-bfbf-6a18da830691"} --> 📝 Generate Docstrings

</details>

<!-- finishing_touch_checkbox_end -->
<!-- tips_start -->

---



<details>
<summary>🪧 Tips</summary>

### Chat

There are 3 ways to chat with [CodeRabbit](https://coderabbit.ai?utm_source=oss&utm_medium=github&utm_campaign=tutur3u/platform&utm_content=2339):

- Review comments: Directly reply to a review comment made by CodeRabbit. Example:
  - `I pushed a fix in commit <commit_id>, please review it.`
  - `Generate unit testing code for this file.`
  - `Open a follow-up GitHub issue for this discussion.`
- Files and specific lines of code (under the "Files changed" tab): Tag `@coderabbitai` in a new review comment at the desired location with your query. Examples:
  - `@coderabbitai generate unit testing code for this file.`
  -	`@coderabbitai modularize this function.`
- PR comments: Tag `@coderabbitai` in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
  - `@coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.`
  - `@coderabbitai read src/utils.ts and generate unit testing code.`
  - `@coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.`
  - `@coderabbitai help me debug CodeRabbit configuration file.`

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

### CodeRabbit Commands (Invoked using PR comments)

- `@coderabbitai pause` to pause the reviews on a PR.
- `@coderabbitai resume` to resume the paused reviews.
- `@coderabbitai review` to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
- `@coderabbitai full review` to do a full review from scratch and review all the files again.
- `@coderabbitai summary` to regenerate the summary of the PR.
- `@coderabbitai generate docstrings` to [generate docstrings](https://docs.coderabbit.ai/finishing-touches/docstrings) for this PR.
- `@coderabbitai resolve` resolve all the CodeRabbit review comments.
- `@coderabbitai plan` to trigger planning for file edits and PR creation.
- `@coderabbitai configuration` to show the current CodeRabbit configuration for the repository.
- `@coderabbitai help` to get help.

### Other keywords and placeholders

- Add `@coderabbitai ignore` anywhere in the PR description to prevent this PR from being reviewed.
- Add `@coderabbitai summary` to generate the high-level summary at a specific location in the PR description.
- Add `@coderabbitai` anywhere in the PR title to generate the title automatically.

### CodeRabbit Configuration File (`.coderabbit.yaml`)

- You can programmatically configure CodeRabbit by adding a `.coderabbit.yaml` file to the root of your repository.
- Please see the [configuration documentation](https://docs.coderabbit.ai/guides/configure-coderabbit) for more information.
- If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: `# yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json`

### Documentation and Community

- Visit our [Documentation](https://docs.coderabbit.ai) for detailed information on how to use CodeRabbit.
- Join our [Discord Community](http://discord.gg/coderabbit) to get help, request features, and share feedback.
- Follow us on [X/Twitter](https://twitter.com/coderabbitai) for updates and announcements.

</details>

<!-- tips_end -->
author_association: CONTRIBUTOR
user: login: graphite-app[bot]
created_at: 2025-04-13T14:02:09Z
body: ## How to use the Graphite Merge Queue

Add either label to this PR to merge it via the merge queue:
- <kbd>merge-queue</kbd> - adds this PR to the back of the merge queue
- <kbd>hotfix</kbd> - for urgent hot fixes, skip the queue and merge this PR next

You must have a Graphite account in order to use the merge queue. Sign up using [this link](https://app.graphite.dev/invite/github/tutur3u?ref=merge-queue-instructions-comment&prId=6094680987).

<h2></h2>
<sub>

An organization admin has enabled the [Graphite Merge Queue](https://graphite.dev/docs/graphite-merge-queue) in this repository.

</sub>

<sub>

Please do not merge from GitHub as this will restart CI on PRs being processed by the merge queue.

</sub>
author_association: NONE
user: login: codecov[bot]
created_at: 2025-04-13T14:07:43Z
body: ## [Codecov](https://app.codecov.io/gh/tutur3u/platform/pull/2339?dropdown=coverage&src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=tutur3u) Report
Attention: Patch coverage is `0.18692%` with `534 lines` in your changes missing coverage. Please review.
> Project coverage is 1.05%. Comparing base [(`337d560`)](https://app.codecov.io/gh/tutur3u/platform/commit/337d560762e6cdd0f2b9a1ea9f2ea0fc38cbf54b?dropdown=coverage&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=tutur3u) to head [(`2806c80`)](https://app.codecov.io/gh/tutur3u/platform/commit/2806c807e05552fd5a74033aa6457eead2c4fb66?dropdown=coverage&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=tutur3u).
> Report is 13 commits behind head on main.

| [Files with missing lines](https://app.codecov.io/gh/tutur3u/platform/pull/2339?dropdown=coverage&src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=tutur3u) | Patch % | Lines |
|---|---|---|
| [...pps/web/src/app/api/v1/calendar/auth/sync/route.ts](https://app.codecov.io/gh/tutur3u/platform/pull/2339?src=pr&el=tree&filepath=apps%2Fweb%2Fsrc%2Fapp%2Fapi%2Fv1%2Fcalendar%2Fauth%2Fsync%2Froute.ts&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=tutur3u#diff-YXBwcy93ZWIvc3JjL2FwcC9hcGkvdjEvY2FsZW5kYXIvYXV0aC9zeW5jL3JvdXRlLnRz) | 0.45% | [221 Missing :warning: ](https://app.codecov.io/gh/tutur3u/platform/pull/2339?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=tutur3u) |
| [packages/ui/src/hooks/use-calendar.tsx](https://app.codecov.io/gh/tutur3u/platform/pull/2339?src=pr&el=tree&filepath=packages%2Fui%2Fsrc%2Fhooks%2Fuse-calendar.tsx&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=tutur3u#diff-cGFja2FnZXMvdWkvc3JjL2hvb2tzL3VzZS1jYWxlbmRhci50c3g=) | 0.00% | [146 Missing :warning: ](https://app.codecov.io/gh/tutur3u/platform/pull/2339?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=tutur3u) |
| [...ps/web/src/app/api/v1/calendar/auth/fetch/route.ts](https://app.codecov.io/gh/tutur3u/platform/pull/2339?src=pr&el=tree&filepath=apps%2Fweb%2Fsrc%2Fapp%2Fapi%2Fv1%2Fcalendar%2Fauth%2Ffetch%2Froute.ts&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=tutur3u#diff-YXBwcy93ZWIvc3JjL2FwcC9hcGkvdjEvY2FsZW5kYXIvYXV0aC9mZXRjaC9yb3V0ZS50cw==) | 0.00% | [95 Missing and 1 partial :warning: ](https://app.codecov.io/gh/tutur3u/platform/pull/2339?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=tutur3u) |
| [.../ui/src/components/ui/legacy/calendar/Calendar.tsx](https://app.codecov.io/gh/tutur3u/platform/pull/2339?src=pr&el=tree&filepath=packages%2Fui%2Fsrc%2Fcomponents%2Fui%2Flegacy%2Fcalendar%2FCalendar.tsx&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=tutur3u#diff-cGFja2FnZXMvdWkvc3JjL2NvbXBvbmVudHMvdWkvbGVnYWN5L2NhbGVuZGFyL0NhbGVuZGFyLnRzeA==) | 0.00% | [51 Missing :warning: ](https://app.codecov.io/gh/tutur3u/platform/pull/2339?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=tutur3u) |
| [...web/src/app/api/v1/calendar/auth/callback/route.ts](https://app.codecov.io/gh/tutur3u/platform/pull/2339?src=pr&el=tree&filepath=apps%2Fweb%2Fsrc%2Fapp%2Fapi%2Fv1%2Fcalendar%2Fauth%2Fcallback%2Froute.ts&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=tutur3u#diff-YXBwcy93ZWIvc3JjL2FwcC9hcGkvdjEvY2FsZW5kYXIvYXV0aC9jYWxsYmFjay9yb3V0ZS50cw==) | 0.00% | [10 Missing :warning: ](https://app.codecov.io/gh/tutur3u/platform/pull/2339?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=tutur3u) |
| [apps/web/src/app/api/v1/calendar/auth/route.ts](https://app.codecov.io/gh/tutur3u/platform/pull/2339?src=pr&el=tree&filepath=apps%2Fweb%2Fsrc%2Fapp%2Fapi%2Fv1%2Fcalendar%2Fauth%2Froute.ts&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=tutur3u#diff-YXBwcy93ZWIvc3JjL2FwcC9hcGkvdjEvY2FsZW5kYXIvYXV0aC9yb3V0ZS50cw==) | 0.00% | [7 Missing :warning: ](https://app.codecov.io/gh/tutur3u/platform/pull/2339?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=tutur3u) |
| [...omponents/ui/legacy/calendar/UnifiedEventModal.tsx](https://app.codecov.io/gh/tutur3u/platform/pull/2339?src=pr&el=tree&filepath=packages%2Fui%2Fsrc%2Fcomponents%2Fui%2Flegacy%2Fcalendar%2FUnifiedEventModal.tsx&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=tutur3u#diff-cGFja2FnZXMvdWkvc3JjL2NvbXBvbmVudHMvdWkvbGVnYWN5L2NhbGVuZGFyL1VuaWZpZWRFdmVudE1vZGFsLnRzeA==) | 0.00% | [2 Missing :warning: ](https://app.codecov.io/gh/tutur3u/platform/pull/2339?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=tutur3u) |
| [...pp/[locale]/(dashboard)/[wsId]/calendar/client.tsx](https://app.codecov.io/gh/tutur3u/platform/pull/2339?src=pr&el=tree&filepath=apps%2Fweb%2Fsrc%2Fapp%2F%5Blocale%5D%2F%28dashboard%29%2F%5BwsId%5D%2Fcalendar%2Fclient.tsx&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=tutur3u#diff-YXBwcy93ZWIvc3JjL2FwcC9bbG9jYWxlXS8oZGFzaGJvYXJkKS9bd3NJZF0vY2FsZW5kYXIvY2xpZW50LnRzeA==) | 0.00% | [1 Missing :warning: ](https://app.codecov.io/gh/tutur3u/platform/pull/2339?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=tutur3u) |

<details><summary>Additional details and impacted files</summary>


```diff
@@            Coverage Diff             @@
##             main    #2339      +/-   ##
==========================================
- Coverage    1.05%    1.05%   -0.01%     
==========================================
  Files        1864     1865       +1     
  Lines      186155   186663     +508     
  Branches     2274     2275       +1     
==========================================
+ Hits         1966     1967       +1     
- Misses     182659   183166     +507     
  Partials     1530     1530              
```

</details>

[:umbrella: View full report in Codecov by Sentry](https://app.codecov.io/gh/tutur3u/platform/pull/2339?dropdown=coverage&src=pr&el=continue&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=tutur3u).   
:loudspeaker: Have feedback on the report? [Share it here](https://about.codecov.io/codecov-pr-comment-feedback/?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=tutur3u).

<details><summary> :rocket: New features to boost your workflow: </summary>

- :snowflake: [Test Analytics](https://docs.codecov.com/docs/test-analytics): Detect flaky tests, report on failures, and find test suite problems.
- :package: [JS Bundle Analysis](https://docs.codecov.com/docs/javascript-bundle-analysis): Save yourself from yourself by tracking and limiting bundle sizes in JS merges.
</details>
author_association: NONE