Skip to content

feat: implement iCloud sync functionality with CRUD operations#7

Merged
panteLx merged 4 commits intomainfrom
feat/icloud-sync
Dec 2, 2025
Merged

feat: implement iCloud sync functionality with CRUD operations#7
panteLx merged 4 commits intomainfrom
feat/icloud-sync

Conversation

@panteLx
Copy link
Owner

@panteLx panteLx commented Dec 2, 2025

  • Added API routes for managing iCloud syncs, including GET, POST, PATCH, and DELETE methods.
  • Implemented synchronization logic to fetch and update shifts from iCloud calendars.
  • Created UI components for managing iCloud syncs and displaying shifts in a dialog.
  • Updated database schema to include new fields for iCloud syncs and associated shifts.
  • Integrated error handling and user feedback for sync operations.

Summary by CodeRabbit

  • New Features

    • Per-calendar iCloud syncs: add, edit, delete, manual per-sync import with sync summary (created/updated/deleted)
    • Imported shifts are tracked to their source syncs; calendar colors propagate to imported shifts
  • UI Updates

    • ICloud Sync management dialog and Cloud action in calendar selector
    • Day Shifts dialog to view/delete all shifts for a selected day
    • Interactive "+N" shift indicator and color-coded sync entries
  • Localization

    • Added iCloud sync UI translations (EN/DE)

✏️ Tip: You can customize this high-level summary in your review settings.

- Added API routes for managing iCloud syncs, including GET, POST, PATCH, and DELETE methods.
- Implemented synchronization logic to fetch and update shifts from iCloud calendars.
- Created UI components for managing iCloud syncs and displaying shifts in a dialog.
- Updated database schema to include new fields for iCloud syncs and associated shifts.
- Integrated error handling and user feedback for sync operations.
Copilot AI review requested due to automatic review settings December 2, 2025 21:38
@coderabbitai
Copy link

coderabbitai bot commented Dec 2, 2025

Caution

Review failed

The pull request is closed.

Note

Other AI code review bot(s) detected

CodeRabbit has detected other AI code review bot(s) in this pull request and will avoid duplicating their findings in the review comments. This may lead to a less comprehensive review.

Walkthrough

Adds multi-calendar iCloud sync: DB migration and types, API endpoints for listing/creating/reading/updating/deleting and on-demand syncing (fetch/parse ICS with ical.js and transactional shift sync), new UI dialogs/components and integrations, i18n strings, shared preset constants, and utility helpers.

Changes

Cohort / File(s) Summary
Database schema & migrations
drizzle/0004_modern_swarm.sql, drizzle/meta/0004_snapshot.json, drizzle/meta/_journal.json, lib/db/schema.ts
Add icloud_syncs table; add icloud_event_id, icloud_sync_id, synced_from_icloud to shifts; snapshot and journal updated; new ICloudSync/NewICloudSync types.
API – iCloud sync list & create
app/api/icloud-syncs/route.ts
GET: list syncs by calendarId; POST: create sync with URL validation, default color, timestamps, and UUID id.
API – iCloud sync CRUD
app/api/icloud-syncs/[id]/route.ts
GET/PATCH/DELETE single sync; PATCH validates iCloud URL, updates fields and updatedAt, when color changes updates related shifts' color and emits calendar-change events.
API – iCloud sync execution
app/api/icloud-syncs/[id]/sync/route.ts
POST: normalize webcal→https, fetch ICS, parse with ical.js, expand recurring events, split multi-day events, create/update/delete shifts transactionally, update lastSyncedAt, return counts.
API – Shifts projection
app/api/shifts/route.ts
GET response projection now includes icloudSyncId in returned shift objects.
UI – iCloud management dialog
components/icloud-sync-manage-dialog.tsx
New client component to list/create/edit/delete syncs, trigger per-item manual syncs, per-item loading states, validation, toasts; accepts calendarId and optional onSyncComplete.
UI – Day shifts dialog
components/day-shifts-dialog.tsx
New client modal to view shifts for a date with localized formatting and optional per-shift delete callback; delete hidden for icloud-synced shifts.
UI – Integration & header
app/page.tsx, components/app-header.tsx, components/calendar-selector.tsx, components/calendar-grid.tsx, components/shift-card.tsx
Page wired to show ICloudSyncManageDialog and DayShiftsDialog; AppHeader/CalendarSelector gain onICloudSync prop; CalendarGrid gains onShowAllShifts and makes “+N” clickable; ShiftCard hides delete for icloud-synced shifts.
Presets & shared constants
lib/constants.ts, components/calendar-dialog.tsx, components/preset-edit-dialog.tsx, components/shift-form-fields.tsx
Extract PRESET_COLORS into shared lib/constants.ts; components updated to use .value; PresetFormData and ShiftFormFields APIs expanded.
Utilities
lib/icloud-utils.ts
New iCloud helper functions: isValidICloudUrl, expandRecurringEvents, splitMultiDayEvent.
Types & exports
lib/types.ts
Export ICloudSync; add `icloudSyncId?: string
Internationalization
messages/en.json, messages/de.json
Add icloud translation block with strings for management UI, validation, status messages, and instructions.
Dependencies
package.json
Added runtime dependency ical.js (^2.2.1).
Documentation
README.md
Expanded Key Features to mention iCloud Sync (import from multiple iCloud calendars with per-sync management).

Sequence Diagram(s)

sequenceDiagram
    actor User
    participant UI as Browser / UI
    participant API as Next.js API
    participant DB as Database
    participant iCloud as iCloud Calendar

    User->>UI: Click "Sync Now" for a sync entry
    UI->>API: POST /api/icloud-syncs/{id}/sync
    API->>DB: Load icloud_syncs by id
    DB-->>API: icloud_sync record
    API->>API: Normalize URL (webcal→https) & validate
    API->>iCloud: Fetch ICS
    alt fetch failed
        API-->>UI: 500 error
    else fetch success
        API->>API: Parse ICS (ical.js)
        alt parse failed
            API-->>UI: 500 error
        else parse success
            API->>DB: Load existing shifts for sync
            DB-->>API: existing shifts
            API->>API: Expand recurring events & split multi-day events
            loop each occurrence
                alt match existing icloud_event_id
                    API->>DB: update shift
                else
                    API->>DB: insert shift
                end
            end
            API->>DB: delete shifts not in ICS
            API->>DB: update icloud_syncs.last_synced_at
            DB-->>API: transaction commit
            API-->>UI: { created, updated, deleted, total }
        end
    end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

  • Files needing extra attention:
    • app/api/icloud-syncs/[id]/sync/route.ts — ICS parsing, recurrence expansion, timezone/all-day handling, transactional DB operations.
    • components/icloud-sync-manage-dialog.tsx — client state flows, concurrency, per-item operations and UX edge cases.
    • app/api/icloud-syncs/[id]/route.ts — cascading updates to shifts when color changes and event emission.
    • DB migration and schema files (drizzle/0004_modern_swarm.sql, lib/db/schema.ts) — FK constraints and cascade behavior.

Possibly related PRs

Poem

🐰 A nibble on calendars, a hop through time,

I fetch, split, and stitch each meeting and rhyme.
Tiny paws tapping, syncing colors and days,
I hum with the ical rhythm in soft, busy ways.
Hooray — multiple clouds, organized by my maze. ✨

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 66.67% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'feat: implement iCloud sync functionality with CRUD operations' clearly and specifically describes the main change: adding iCloud sync capabilities with full CRUD operations. It directly aligns with the PR's core objective and the extensive changes across API routes, UI components, and database schema.

📜 Recent review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between aa0c4c5 and 06b74a5.

📒 Files selected for processing (3)
  • components/icloud-sync-manage-dialog.tsx (1 hunks)
  • messages/de.json (1 hunks)
  • messages/en.json (1 hunks)

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🧹 Nitpick comments (8)
README.md (1)

39-39: Use Apple’s standard “iCloud” casing in feature bullet

For consistency with Apple branding and the rest of the codebase, consider changing “ICloud Sync” to “iCloud Sync”.

lib/db/schema.ts (1)

19-36: Schema design for iCloud syncs is consistent with existing patterns

icloud_syncs and the new shift fields use cascading FKs and timestamp columns in line with the rest of the schema, and the exported ICloudSync/NewICloudSync types follow the project’s type conventions. This should integrate smoothly with the new API routes and UI.

If you expect large numbers of synced events and frequent lookups by icloudEventId or icloudSyncId, consider adding an index (or composite index) on those columns in a follow-up migration for better query performance. Based on learnings, schema changes are coupled with migrations.

Also applies to: 58-64, 116-117

app/api/icloud-syncs/[id]/route.ts (1)

3-3: Remove unused import.

The shifts import is not used in this file and should be removed to maintain code cleanliness.

Apply this diff:

-import { icloudSyncs, shifts } from "@/lib/db/schema";
+import { icloudSyncs } from "@/lib/db/schema";
app/api/icloud-syncs/[id]/sync/route.ts (2)

34-38: Add timeout to external fetch to prevent hanging requests.

The fetch call to the iCloud URL has no timeout, which could cause the request to hang indefinitely if the iCloud server is slow or unresponsive.

-      const response = await fetch(fetchUrl);
+      const controller = new AbortController();
+      const timeoutId = setTimeout(() => controller.abort(), 30000); // 30s timeout
+      const response = await fetch(fetchUrl, { signal: controller.signal });
+      clearTimeout(timeoutId);

140-169: Consider batching database operations for better performance.

Individual insert, update, and delete calls within loops create N+1 database round-trips. For calendars with many events, consider batching these operations.

For example, batch inserts:

// Collect all new shifts, then insert in one call
if (newShiftsData.length > 0) {
  await db.insert(shifts).values(newShiftsData);
}
components/icloud-sync-manage-dialog.tsx (1)

197-198: Consider using a custom confirmation dialog.

Using native confirm() works but is inconsistent with the app's styled dialog patterns. Consider using a custom confirmation dialog for better UX consistency.

drizzle/0004_modern_swarm.sql (2)

1-11: Consider adding a unique constraint on (calendar_id, icloud_url).

The UI prevents duplicate URLs per calendar client-side, but a database-level unique constraint would ensure data integrity and prevent race conditions.

 CREATE TABLE `icloud_syncs` (
 	`id` text PRIMARY KEY NOT NULL,
 	`calendar_id` text NOT NULL,
 	`name` text NOT NULL,
 	`icloud_url` text NOT NULL,
 	`color` text DEFAULT '#3b82f6' NOT NULL,
 	`last_synced_at` integer,
 	`created_at` integer DEFAULT CURRENT_TIMESTAMP NOT NULL,
 	`updated_at` integer DEFAULT CURRENT_TIMESTAMP NOT NULL,
-	FOREIGN KEY (`calendar_id`) REFERENCES `calendars`(`id`) ON UPDATE no action ON DELETE cascade
+	FOREIGN KEY (`calendar_id`) REFERENCES `calendars`(`id`) ON UPDATE no action ON DELETE cascade,
+	UNIQUE(`calendar_id`, `icloud_url`)
 );

13-15: Consider adding an index on icloud_sync_id for query performance.

The sync route queries shifts by icloud_sync_id (line 66 in sync/route.ts). Adding an index would improve performance for calendars with many shifts.

CREATE INDEX idx_shifts_icloud_sync_id ON shifts(icloud_sync_id);
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between d33cd60 and 8ee564b.

⛔ Files ignored due to path filters (1)
  • package-lock.json is excluded by !**/package-lock.json
📒 Files selected for processing (20)
  • README.md (1 hunks)
  • app/api/icloud-syncs/[id]/route.ts (1 hunks)
  • app/api/icloud-syncs/[id]/sync/route.ts (1 hunks)
  • app/api/icloud-syncs/route.ts (1 hunks)
  • app/api/shifts/route.ts (1 hunks)
  • app/page.tsx (8 hunks)
  • components/app-header.tsx (4 hunks)
  • components/calendar-grid.tsx (3 hunks)
  • components/calendar-selector.tsx (4 hunks)
  • components/day-shifts-dialog.tsx (1 hunks)
  • components/icloud-sync-manage-dialog.tsx (1 hunks)
  • components/shift-card.tsx (1 hunks)
  • drizzle/0004_modern_swarm.sql (1 hunks)
  • drizzle/meta/0004_snapshot.json (1 hunks)
  • drizzle/meta/_journal.json (1 hunks)
  • lib/db/schema.ts (3 hunks)
  • lib/types.ts (2 hunks)
  • messages/de.json (1 hunks)
  • messages/en.json (1 hunks)
  • package.json (1 hunks)
🧰 Additional context used
📓 Path-based instructions (10)
app/api/**/*.ts

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

app/api/**/*.ts: Always await dynamic route params in API routes and server components (Next.js 16 breaking change)
Create API route handlers for new database tables at app/api/tablename/route.ts and app/api/tablename/[id]/route.ts
Log all API errors with console.error() for debugging

Files:

  • app/api/shifts/route.ts
  • app/api/icloud-syncs/route.ts
  • app/api/icloud-syncs/[id]/sync/route.ts
  • app/api/icloud-syncs/[id]/route.ts
app/api/**/route.ts

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

API GET routes must validate required query parameters and return 400 status for missing params

Files:

  • app/api/shifts/route.ts
  • app/api/icloud-syncs/route.ts
  • app/api/icloud-syncs/[id]/sync/route.ts
  • app/api/icloud-syncs/[id]/route.ts
app/**/*.{ts,tsx}

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

Use useTranslations() hook for all user-facing text and t("key") to access translation keys from messages/{de,en}.json

Files:

  • app/api/shifts/route.ts
  • app/api/icloud-syncs/route.ts
  • app/api/icloud-syncs/[id]/sync/route.ts
  • app/api/icloud-syncs/[id]/route.ts
  • app/page.tsx
**/*.{ts,tsx}

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

Store colors as hex values (e.g., #3b82f6) and apply 20% opacity for backgrounds using format ${color}20

Files:

  • app/api/shifts/route.ts
  • components/shift-card.tsx
  • app/api/icloud-syncs/route.ts
  • components/calendar-grid.tsx
  • components/day-shifts-dialog.tsx
  • lib/db/schema.ts
  • app/api/icloud-syncs/[id]/sync/route.ts
  • app/api/icloud-syncs/[id]/route.ts
  • components/icloud-sync-manage-dialog.tsx
  • app/page.tsx
  • components/calendar-selector.tsx
  • lib/types.ts
  • components/app-header.tsx
components/**/*.tsx

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

Use PRESET_COLORS constant array for all color picker options

Files:

  • components/shift-card.tsx
  • components/calendar-grid.tsx
  • components/day-shifts-dialog.tsx
  • components/icloud-sync-manage-dialog.tsx
  • components/calendar-selector.tsx
  • components/app-header.tsx
components/**/calendar*.tsx

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

Calendar interaction: left-click toggles shift (requires preset selection), right-click opens note dialog with e.preventDefault()

Files:

  • components/calendar-grid.tsx
  • components/calendar-selector.tsx
components/**/*-dialog.tsx

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

components/**/*-dialog.tsx: Dialog components must control open state via props, reset internal state when open changes to false, and call parent callback on form submission
Form submission in dialogs: prevent default, validate, call parent callback, close dialog

Files:

  • components/day-shifts-dialog.tsx
  • components/icloud-sync-manage-dialog.tsx
lib/db/schema.ts

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

lib/db/schema.ts: Database schema changes in lib/db/schema.ts require running npm run db:generate followed by npm run db:migrate
Use cascade delete relationships in database schema (onDelete: "cascade") for dependent tables
Drizzle ORM timestamp fields must use { mode: "timestamp" } and are stored as integers, auto-converted to Date objects
Export type definitions from database schema as TableName = typeof tableName.$inferSelect for use in components and API routes

Files:

  • lib/db/schema.ts
app/page.tsx

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

app/page.tsx: Password verification for calendar access must use pendingAction state pattern: set pending action, show PasswordDialog, retry after password verification via /api/calendars/[id]/verify-password
Main page (app/page.tsx) uses URL sync via useRouter().replace() for selected calendar
Refresh data after mutations by incrementing statsRefreshTrigger counter

Files:

  • app/page.tsx
app/**/*.tsx

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

app/**/*.tsx: Use formatDateToLocal() helper for consistent YYYY-MM-DD date formatting in UI
Shift toggle logic must check if matching shift exists: delete if present, create if not
Use locale-specific date formatters in components: locale === "de" ? de : enUS
Frontend uses Next.js 16 App Router (app/ directory), React 19, and client-side state management via useState/useEffect

Files:

  • app/page.tsx
🧠 Learnings (14)
📚 Learning: 2025-11-29T20:11:49.992Z
Learnt from: CR
Repo: panteLx/BetterShift PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-11-29T20:11:49.992Z
Learning: Applies to app/**/*.tsx : Shift toggle logic must check if matching shift exists: delete if present, create if not

Applied to files:

  • components/shift-card.tsx
  • components/calendar-grid.tsx
  • components/day-shifts-dialog.tsx
  • app/page.tsx
📚 Learning: 2025-11-29T20:11:49.992Z
Learnt from: CR
Repo: panteLx/BetterShift PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-11-29T20:11:49.992Z
Learning: Applies to components/**/calendar*.tsx : Calendar interaction: left-click toggles shift (requires preset selection), right-click opens note dialog with e.preventDefault()

Applied to files:

  • components/shift-card.tsx
  • components/calendar-grid.tsx
  • components/day-shifts-dialog.tsx
  • lib/db/schema.ts
  • components/icloud-sync-manage-dialog.tsx
  • app/page.tsx
  • components/calendar-selector.tsx
  • components/app-header.tsx
📚 Learning: 2025-11-29T20:11:49.992Z
Learnt from: CR
Repo: panteLx/BetterShift PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-11-29T20:11:49.992Z
Learning: Applies to app/api/**/*.ts : Create API route handlers for new database tables at app/api/tablename/route.ts and app/api/tablename/[id]/route.ts

Applied to files:

  • app/api/icloud-syncs/route.ts
  • app/api/icloud-syncs/[id]/route.ts
📚 Learning: 2025-11-29T20:11:49.992Z
Learnt from: CR
Repo: panteLx/BetterShift PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-11-29T20:11:49.992Z
Learning: Applies to app/page.tsx : Main page (app/page.tsx) uses URL sync via useRouter().replace() for selected calendar

Applied to files:

  • app/api/icloud-syncs/route.ts
  • app/page.tsx
  • components/calendar-selector.tsx
  • components/app-header.tsx
📚 Learning: 2025-11-29T20:11:49.992Z
Learnt from: CR
Repo: panteLx/BetterShift PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-11-29T20:11:49.992Z
Learning: Applies to components/**/*-dialog.tsx : Dialog components must control open state via props, reset internal state when open changes to false, and call parent callback on form submission

Applied to files:

  • components/day-shifts-dialog.tsx
  • components/icloud-sync-manage-dialog.tsx
  • app/page.tsx
📚 Learning: 2025-11-29T20:11:49.992Z
Learnt from: CR
Repo: panteLx/BetterShift PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-11-29T20:11:49.992Z
Learning: Applies to app/**/*.tsx : Use locale-specific date formatters in components: locale === "de" ? de : enUS

Applied to files:

  • components/day-shifts-dialog.tsx
  • app/page.tsx
📚 Learning: 2025-11-29T20:11:49.992Z
Learnt from: CR
Repo: panteLx/BetterShift PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-11-29T20:11:49.992Z
Learning: Applies to components/**/*-dialog.tsx : Form submission in dialogs: prevent default, validate, call parent callback, close dialog

Applied to files:

  • components/day-shifts-dialog.tsx
  • components/icloud-sync-manage-dialog.tsx
  • app/page.tsx
📚 Learning: 2025-11-29T20:11:49.992Z
Learnt from: CR
Repo: panteLx/BetterShift PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-11-29T20:11:49.992Z
Learning: Applies to app/**/*.tsx : Use formatDateToLocal() helper for consistent YYYY-MM-DD date formatting in UI

Applied to files:

  • components/day-shifts-dialog.tsx
  • app/page.tsx
📚 Learning: 2025-11-29T20:11:49.992Z
Learnt from: CR
Repo: panteLx/BetterShift PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-11-29T20:11:49.992Z
Learning: Migrations are committed to git; schema changes require both updating lib/db/schema.ts AND running migrations

Applied to files:

  • lib/db/schema.ts
📚 Learning: 2025-11-29T20:11:49.992Z
Learnt from: CR
Repo: panteLx/BetterShift PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-11-29T20:11:49.992Z
Learning: Applies to lib/db/schema.ts : Database schema changes in lib/db/schema.ts require running npm run db:generate followed by npm run db:migrate

Applied to files:

  • lib/db/schema.ts
📚 Learning: 2025-11-29T20:11:49.992Z
Learnt from: CR
Repo: panteLx/BetterShift PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-11-29T20:11:49.992Z
Learning: Applies to lib/db/schema.ts : Export type definitions from database schema as TableName = typeof tableName.$inferSelect for use in components and API routes

Applied to files:

  • lib/db/schema.ts
📚 Learning: 2025-11-29T20:11:49.992Z
Learnt from: CR
Repo: panteLx/BetterShift PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-11-29T20:11:49.992Z
Learning: Applies to lib/db/schema.ts : Drizzle ORM timestamp fields must use { mode: "timestamp" } and are stored as integers, auto-converted to Date objects

Applied to files:

  • lib/db/schema.ts
📚 Learning: 2025-11-29T20:11:49.992Z
Learnt from: CR
Repo: panteLx/BetterShift PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-11-29T20:11:49.992Z
Learning: Applies to app/**/*.tsx : Frontend uses Next.js 16 App Router (app/ directory), React 19, and client-side state management via useState/useEffect

Applied to files:

  • package.json
📚 Learning: 2025-11-29T20:11:49.992Z
Learnt from: CR
Repo: panteLx/BetterShift PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-11-29T20:11:49.992Z
Learning: Applies to app/page.tsx : Password verification for calendar access must use pendingAction state pattern: set pending action, show PasswordDialog, retry after password verification via /api/calendars/[id]/verify-password

Applied to files:

  • app/page.tsx
  • components/calendar-selector.tsx
  • components/app-header.tsx
🧬 Code graph analysis (7)
app/api/shifts/route.ts (1)
lib/db/schema.ts (1)
  • shifts (38-71)
components/shift-card.tsx (1)
components/ui/button.tsx (1)
  • Button (60-60)
components/calendar-grid.tsx (2)
lib/db/schema.ts (1)
  • shifts (38-71)
lib/types.ts (1)
  • ShiftWithCalendar (14-32)
components/day-shifts-dialog.tsx (1)
lib/types.ts (1)
  • ShiftWithCalendar (14-32)
lib/db/schema.ts (1)
lib/types.ts (1)
  • ICloudSync (2-2)
app/api/icloud-syncs/[id]/route.ts (3)
app/api/icloud-syncs/route.ts (1)
  • GET (7-33)
lib/db/index.ts (1)
  • db (19-19)
lib/db/schema.ts (1)
  • icloudSyncs (19-36)
components/icloud-sync-manage-dialog.tsx (7)
lib/db/schema.ts (1)
  • ICloudSync (116-116)
lib/types.ts (1)
  • ICloudSync (2-2)
components/ui/dialog.tsx (5)
  • Dialog (139-139)
  • DialogContent (141-141)
  • DialogHeader (144-144)
  • DialogTitle (147-147)
  • DialogDescription (142-142)
components/ui/button.tsx (1)
  • Button (60-60)
components/ui/label.tsx (1)
  • Label (24-24)
components/ui/input.tsx (1)
  • Input (21-21)
components/ui/color-picker.tsx (1)
  • ColorPicker (24-127)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
  • GitHub Check: Agent
  • GitHub Check: build-and-push
🔇 Additional comments (26)
components/app-header.tsx (1)

17-35: New onICloudSync wiring in header looks consistent across desktop and mobile

The new onICloudSync prop is threaded cleanly through AppHeader into both desktop and mobile CalendarSelector usages, and the mobile dialog correctly closes before triggering the callback. No issues from a typing or behavioral perspective.

Also applies to: 37-55, 120-128, 242-261

messages/de.json (1)

158-193: German icloud translations are structurally sound and aligned with English keys

The new icloud block mirrors the English keys and fits existing i18n structure; strings look clear and appropriate for the described flows.

messages/en.json (1)

158-193: English icloud translations cleanly cover the new sync UX

The added icloud block provides all needed labels, help text, and status messages, and aligns with the German translations and new UI flows.

drizzle/meta/_journal.json (1)

32-39: Migration journal correctly tracks the new iCloud schema change

The additional journal entry (idx: 4, tag 0004_modern_swarm) and version bump ensure Drizzle’s migration history is consistent with the new schema.

app/api/shifts/route.ts (1)

21-35: Including icloudSyncId in GET shifts response is consistent with the new schema

Exposing icloudSyncId alongside the existing shift fields lines up with the schema and enables the UI to associate shifts with specific iCloud syncs. Just ensure any consumer types (e.g., ShiftWithCalendar) are updated to include this optional field, which looks already planned in related types.

components/shift-card.tsx (1)

52-61: LGTM! Proper protection for synced shifts.

The conditional rendering prevents deletion of iCloud-synced shifts, which is the correct behavior to avoid conflicts with the sync source.

lib/types.ts (2)

2-2: LGTM! Type export extends public API surface.

Adding ICloudSync to the exported types properly supports the new iCloud sync functionality.


29-29: LGTM! Optional field correctly typed.

The icloudSyncId field is appropriately optional and nullable, allowing shifts to exist both with and without iCloud sync associations.

components/calendar-selector.tsx (1)

13-13: LGTM! Cloud sync button follows established patterns.

The new iCloud sync button implementation is consistent with existing action buttons (password management, deletion) and properly uses conditional rendering and internationalized titles.

Also applies to: 22-22, 32-32, 67-77

components/calendar-grid.tsx (1)

206-214: LGTM! Clickable overflow indicator properly implemented.

The "+N" indicator correctly uses stopPropagation to prevent the day click handler from firing, and the optional callback pattern allows for graceful degradation when the handler isn't provided.

app/api/icloud-syncs/route.ts (2)

7-33: LGTM! GET handler follows API guidelines.

The handler correctly validates the required calendarId query parameter and returns a 400 status for missing params, as specified in the coding guidelines.


36-69: LGTM! POST handler properly validates required fields.

The handler validates all required fields (calendarId, name, icloudUrl) and includes appropriate error handling with console logging as per guidelines.

components/day-shifts-dialog.tsx (2)

26-42: LGTM! Dialog follows component guidelines.

The dialog properly controls open state via props and uses locale-specific date formatting with date-fns, consistent with coding guidelines.


86-95: LGTM! Consistent deletion logic for synced shifts.

The conditional rendering of the delete button matches the pattern in shift-card.tsx, correctly preventing deletion of iCloud-synced shifts.

drizzle/meta/0004_snapshot.json (1)

1-498: Database snapshot looks structurally correct.

This is a generated Drizzle migration snapshot. The schema includes the new icloud_syncs table with proper foreign key relationships and cascade behavior. No manual review needed for generated files.

app/api/icloud-syncs/[id]/route.ts (1)

7-35: LGTM! Dynamic params properly awaited.

All route handlers correctly await the dynamic params as required by Next.js 15, and include proper error handling with console logging per the coding guidelines.

Also applies to: 38-76, 79-96

app/api/icloud-syncs/[id]/sync/route.ts (2)

92-110: Potential timezone issue with time extraction.

Using toJSDate() followed by getHours()/getMinutes() extracts time in the server's local timezone, which may differ from the event's original timezone. For timed events, consider using the ICAL.Time hour/minute properties directly to preserve the intended time.

       if (!isAllDay) {
-        const startHours = startJsDate.getHours().toString().padStart(2, "0");
-        const startMinutes = startJsDate
-          .getMinutes()
-          .toString()
-          .padStart(2, "0");
+        const startHours = startDate.hour.toString().padStart(2, "0");
+        const startMinutes = startDate.minute.toString().padStart(2, "0");
         startTime = `${startHours}:${startMinutes}`;

-        const endHours = endJsDate.getHours().toString().padStart(2, "0");
-        const endMinutes = endJsDate.getMinutes().toString().padStart(2, "0");
+        const endHours = endDate.hour.toString().padStart(2, "0");
+        const endMinutes = endDate.minute.toString().padStart(2, "0");
         endTime = `${endHours}:${endMinutes}`;
       }

7-12: LGTM!

Correctly awaits the dynamic route params per Next.js 16 breaking change requirements. The async params pattern is properly implemented.

app/page.tsx (4)

103-108: LGTM!

New state variables for iCloud sync dialog and day shifts dialog are properly initialized.


163-211: LGTM!

The handleICloudSyncClick correctly implements the pendingAction state pattern for password-protected calendars, verifying stored passwords via /api/calendars/[id]/verify-password before opening the iCloud sync dialog. Based on learnings, this follows the established pattern.


446-457: LGTM!

The handleShowAllShifts and handleDeleteShiftFromDayDialog functions properly manage dialog state and refresh data after mutations by calling refetchShifts().


853-874: LGTM!

Dialog components are properly wired with controlled open state via props. The onSyncComplete callback correctly refreshes shifts, calendars, and increments statsRefreshTrigger to update the UI after sync operations.

components/icloud-sync-manage-dialog.tsx (3)

79-126: LGTM!

The handleAddSync function properly validates inputs, checks for duplicate URLs, and auto-triggers a sync after creation. Error handling uses toast notifications appropriately.


128-160: LGTM!

The update logic correctly handles the case where URL editing is disabled. The formUrl.trim() || undefined ensures the original URL is preserved when editing.


245-256: LGTM!

Dialog component correctly implements controlled open state via props. The gradient styling is consistent with other dialogs in the codebase.

drizzle/0004_modern_swarm.sql (1)

14-14: LGTM!

The CASCADE delete on icloud_sync_id ensures that deleting an iCloud sync configuration automatically removes all associated shifts, maintaining referential integrity.

Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This pull request implements comprehensive iCloud calendar synchronization functionality, enabling users to import and manage shifts from multiple iCloud calendars. The feature includes full CRUD operations for sync configurations, automated event synchronization with conflict resolution, and seamless UI integration with the existing calendar management system.

Key Changes

  • Database Schema Extension: Added icloud_syncs table with cascade delete relationships and three new fields to the shifts table (icloud_event_id, icloud_sync_id, synced_from_icloud) to track synchronized events
  • API Routes: Implemented complete REST API for iCloud sync management with endpoints for listing, creating, updating, deleting sync configurations, and triggering synchronization operations
  • UI Components: Created ICloudSyncManageDialog for managing multiple sync configurations and DayShiftsDialog for viewing all shifts on days with more than 2 events, both with full i18n support

Reviewed changes

Copilot reviewed 20 out of 21 changed files in this pull request and generated 10 comments.

Show a summary per file
File Description
package.json & package-lock.json Added ical.js for parsing iCalendar data and node-fetch (unused) as new dependencies
messages/en.json & messages/de.json Added 33 new translation keys for iCloud sync UI elements in both English and German
lib/types.ts Exported ICloudSync type and added icloudSyncId field to ShiftWithCalendar interface
lib/db/schema.ts Added icloudSyncs table schema and extended shifts table with sync tracking fields
drizzle/meta/_journal.json & drizzle/meta/0004_snapshot.json Migration metadata for database schema changes
drizzle/0004_modern_swarm.sql SQL migration creating icloud_syncs table and adding iCloud-related fields to shifts table
components/shift-card.tsx Conditionally hides delete button for synced shifts to prevent manual deletion
components/icloud-sync-manage-dialog.tsx New component for managing iCloud sync configurations with add/edit/delete/sync operations
components/day-shifts-dialog.tsx New component displaying all shifts for a selected day with conditional delete functionality
components/calendar-selector.tsx Added iCloud sync button to calendar selector UI
components/calendar-grid.tsx Made "+N more" indicator clickable to show all shifts in a dialog
components/app-header.tsx Integrated iCloud sync button in both desktop and mobile header layouts
app/page.tsx Added iCloud sync dialog integration with password protection flow and shift dialog management
app/api/shifts/route.ts Added icloudSyncId to the shift query response
app/api/icloud-syncs/route.ts New API route handling GET (list syncs) and POST (create sync) operations
app/api/icloud-syncs/[id]/sync/route.ts New API route implementing the core synchronization logic with event parsing and CRUD operations
app/api/icloud-syncs/[id]/route.ts New API route handling GET (fetch sync), PATCH (update sync), and DELETE (remove sync) operations
README.md Updated features list to include iCloud sync functionality

package.json Outdated
"motion": "^12.23.24",
"next": "16.0.3",
"next-intl": "^4.5.5",
"node-fetch": "^3.3.2",
Copy link

Copilot AI Dec 2, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The node-fetch package is added as a dependency but is never imported or used anywhere in the codebase. Next.js provides a built-in fetch API that works in both server and client contexts, making this dependency unnecessary. Consider removing it from the dependencies.

Suggested change
"node-fetch": "^3.3.2",

Copilot uses AI. Check for mistakes.
import { NextResponse } from "next/server";
import { db } from "@/lib/db";
import { icloudSyncs, shifts } from "@/lib/db/schema";
import { eq, and } from "drizzle-orm";
Copy link

Copilot AI Dec 2, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The and import from drizzle-orm is imported but never used in this file. Remove unused imports to keep the code clean.

Suggested change
import { eq, and } from "drizzle-orm";
import { eq } from "drizzle-orm";

Copilot uses AI. Check for mistakes.
Comment on lines 54 to 58
useEffect(() => {
if (open && calendarId) {
fetchSyncs();
}
}, [open, calendarId]);
Copy link

Copilot AI Dec 2, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The fetchSyncs function is called in the useEffect but is not included in the dependency array. This violates the React hooks exhaustive-deps rule and could lead to stale closures. Either include fetchSyncs in the dependency array or wrap it with useCallback.

Copilot uses AI. Check for mistakes.
Comment on lines 78 to 169
for (const vevent of vevents) {
const event = new ICAL.Event(vevent);
const eventId = event.uid;
processedEventIds.add(eventId);

const startDate = event.startDate;
const endDate = event.endDate;

if (!startDate || !endDate) {
continue; // Skip events without dates
}

const isAllDay = event.startDate.isDate;

// Convert ICAL.Time to JavaScript Date
const startJsDate = startDate.toJSDate();
const endJsDate = endDate.toJSDate();

let startTime = "00:00";
let endTime = "23:59";

if (!isAllDay) {
const startHours = startJsDate.getHours().toString().padStart(2, "0");
const startMinutes = startJsDate
.getMinutes()
.toString()
.padStart(2, "0");
startTime = `${startHours}:${startMinutes}`;

const endHours = endJsDate.getHours().toString().padStart(2, "0");
const endMinutes = endJsDate.getMinutes().toString().padStart(2, "0");
endTime = `${endHours}:${endMinutes}`;
}

// Normalize date to midnight for storage
const normalizedDate = new Date(
startJsDate.getFullYear(),
startJsDate.getMonth(),
startJsDate.getDate()
);

const shiftData = {
calendarId: icloudSync.calendarId,
date: normalizedDate,
startTime,
endTime,
title: event.summary || "Untitled Event",
color: icloudSync.color,
notes: event.description || null,
isAllDay,
isSecondary: false,
icloudEventId: eventId,
icloudSyncId: syncId,
syncedFromIcloud: true,
presetId: null,
};

// Check if this event already exists
const existingShift = existingShifts.find(
(s) => s.icloudEventId === eventId
);

if (existingShift) {
// Update existing shift
await db
.update(shifts)
.set({
...shiftData,
updatedAt: new Date(),
})
.where(eq(shifts.id, existingShift.id));
updatedShifts.push(eventId);
} else {
// Create new shift
await db.insert(shifts).values({
id: crypto.randomUUID(),
...shiftData,
createdAt: new Date(),
updatedAt: new Date(),
});
newShifts.push(eventId);
}
}

// Delete shifts that are no longer in the iCloud calendar
const shiftsToDelete = existingShifts.filter(
(s) => s.icloudEventId && !processedEventIds.has(s.icloudEventId)
);

for (const shift of shiftsToDelete) {
await db.delete(shifts).where(eq(shifts.id, shift.id));
}
Copy link

Copilot AI Dec 2, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The sync logic performs individual database operations (INSERT/UPDATE/DELETE) in a loop, which can result in poor performance when syncing calendars with many events. Consider using batch operations or a transaction to improve performance. For example, collect all inserts/updates/deletes and execute them in batches using db.insert().values([...]) and db.delete().where(inArray(shifts.id, [...])).

Copilot uses AI. Check for mistakes.
Comment on lines 17 to 24
import {
CloudUpload,
Loader2,
Trash2,
RefreshCw,
Plus,
Edit2,
} from "lucide-react";
Copy link

Copilot AI Dec 2, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unused import CloudUpload.

Copilot uses AI. Check for mistakes.
Adds strict iCloud URL validation to prevent SSRF attacks.
Optimizes sync logic using batch database operations for inserts, updates, and deletes.
Ensures associated shift colors update with sync changes and emits events for client updates.
Cleans up unused dependencies.
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

♻️ Duplicate comments (1)
app/api/icloud-syncs/[id]/sync/route.ts (1)

100-112: Timezone handling may cause incorrect times on servers in different timezones.

The time extraction uses getHours() and getMinutes() on JavaScript Date objects, which returns hours/minutes in the server's local timezone. If an event is stored as "9:00 AM Eastern" and the server runs in UTC, the stored time will be "14:00" instead of "09:00".

Consider using UTC methods or the ical.js native time properties for consistent behavior across different server environments.

🧹 Nitpick comments (5)
app/api/icloud-syncs/[id]/sync/route.ts (1)

31-45: Consider adding a fetch timeout.

The external fetch to the iCloud calendar has no timeout configured. If the iCloud server is slow or unresponsive, this could cause the request to hang indefinitely, potentially exhausting server resources.

Apply this diff to add a timeout:

     let icsData: string;
     try {
-      const response = await fetch(fetchUrl);
+      const controller = new AbortController();
+      const timeoutId = setTimeout(() => controller.abort(), 30000);
+      
+      const response = await fetch(fetchUrl, {
+        signal: controller.signal,
+      });
+      clearTimeout(timeoutId);
+      
       if (!response.ok) {
         throw new Error(`Failed to fetch calendar: ${response.statusText}`);
       }
       icsData = await response.text();
     } catch (error) {
       console.error("Error fetching iCloud calendar:", error);
       return NextResponse.json(
-        { error: "Failed to fetch iCloud calendar. Please check the URL." },
+        { error: error instanceof Error && error.name === 'AbortError' 
+            ? "Request timed out. The calendar server took too long to respond."
+            : "Failed to fetch iCloud calendar. Please check the URL." },
         { status: 500 }
       );
     }
app/api/icloud-syncs/[id]/route.ts (2)

7-32: Consider extracting isValidICloudUrl to a shared module.

This validation function is duplicated from app/api/icloud-syncs/route.ts. Extracting it to a shared utility (e.g., lib/validators.ts) would improve maintainability and ensure consistent validation logic.


136-154: Consider emitting a calendar-change event on DELETE.

When an iCloud sync is deleted, the cascade delete removes associated shifts, but no event is emitted to notify connected clients. This could cause stale data to remain visible until the page is manually refreshed.

Apply this diff to emit an event:

 export async function DELETE(
   request: Request,
   { params }: { params: Promise<{ id: string }> }
 ) {
   try {
     const { id } = await params;

+    // Get the sync first to access calendarId for event emission
+    const [syncToDelete] = await db
+      .select()
+      .from(icloudSyncs)
+      .where(eq(icloudSyncs.id, id))
+      .limit(1);
+
     await db.delete(icloudSyncs).where(eq(icloudSyncs.id, id));

+    if (syncToDelete) {
+      eventEmitter.emit("calendar-change", {
+        type: "shift",
+        action: "delete",
+        calendarId: syncToDelete.calendarId,
+        data: { icloudSyncId: id, deleted: true },
+      });
+    }
+
     return NextResponse.json({ success: true });
components/icloud-sync-manage-dialog.tsx (2)

20-29: Consider importing PRESET_COLORS from a shared location.

Per coding guidelines, "Use PRESET_COLORS constant array for all color picker options." Defining colors locally may lead to inconsistency if other components use different color sets. Consider moving this to @/lib/constants.ts and importing it here.

+import { PRESET_COLORS } from "@/lib/constants";
-const PRESET_COLORS = [
-  { name: "Blue", value: "#3b82f6" },
-  { name: "Red", value: "#ef4444" },
-  { name: "Green", value: "#10b981" },
-  { name: "Amber", value: "#f59e0b" },
-  { name: "Violet", value: "#8b5cf6" },
-  { name: "Pink", value: "#ec4899" },
-  { name: "Cyan", value: "#06b6d4" },
-  { name: "Orange", value: "#f97316" },
-];

And in lib/constants.ts:

export const PRESET_COLORS = [
  { name: "Blue", value: "#3b82f6" },
  { name: "Red", value: "#ef4444" },
  { name: "Green", value: "#10b981" },
  { name: "Amber", value: "#f59e0b" },
  { name: "Violet", value: "#8b5cf6" },
  { name: "Pink", value: "#ec4899" },
  { name: "Cyan", value: "#06b6d4" },
  { name: "Orange", value: "#f97316" },
];

213-215: Consider replacing native confirm() with an accessible dialog.

The native window.confirm() has accessibility limitations (no keyboard focus management, inconsistent styling, blocks the main thread). Consider using a custom confirmation dialog component for better UX and accessibility.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 8ee564b and eede5d2.

⛔ Files ignored due to path filters (1)
  • package-lock.json is excluded by !**/package-lock.json
📒 Files selected for processing (5)
  • app/api/icloud-syncs/[id]/route.ts (1 hunks)
  • app/api/icloud-syncs/[id]/sync/route.ts (1 hunks)
  • app/api/icloud-syncs/route.ts (1 hunks)
  • components/icloud-sync-manage-dialog.tsx (1 hunks)
  • package.json (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (2)
  • package.json
  • app/api/icloud-syncs/route.ts
🧰 Additional context used
📓 Path-based instructions (6)
app/api/**/*.ts

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

app/api/**/*.ts: Always await dynamic route params in API routes and server components (Next.js 16 breaking change)
Create API route handlers for new database tables at app/api/tablename/route.ts and app/api/tablename/[id]/route.ts
Log all API errors with console.error() for debugging

Files:

  • app/api/icloud-syncs/[id]/sync/route.ts
  • app/api/icloud-syncs/[id]/route.ts
app/api/**/route.ts

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

API GET routes must validate required query parameters and return 400 status for missing params

Files:

  • app/api/icloud-syncs/[id]/sync/route.ts
  • app/api/icloud-syncs/[id]/route.ts
app/**/*.{ts,tsx}

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

Use useTranslations() hook for all user-facing text and t("key") to access translation keys from messages/{de,en}.json

Files:

  • app/api/icloud-syncs/[id]/sync/route.ts
  • app/api/icloud-syncs/[id]/route.ts
**/*.{ts,tsx}

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

Store colors as hex values (e.g., #3b82f6) and apply 20% opacity for backgrounds using format ${color}20

Files:

  • app/api/icloud-syncs/[id]/sync/route.ts
  • app/api/icloud-syncs/[id]/route.ts
  • components/icloud-sync-manage-dialog.tsx
components/**/*-dialog.tsx

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

components/**/*-dialog.tsx: Dialog components must control open state via props, reset internal state when open changes to false, and call parent callback on form submission
Form submission in dialogs: prevent default, validate, call parent callback, close dialog

Files:

  • components/icloud-sync-manage-dialog.tsx
components/**/*.tsx

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

Use PRESET_COLORS constant array for all color picker options

Files:

  • components/icloud-sync-manage-dialog.tsx
🧠 Learnings (10)
📚 Learning: 2025-11-29T20:11:49.992Z
Learnt from: CR
Repo: panteLx/BetterShift PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-11-29T20:11:49.992Z
Learning: Applies to lib/db/schema.ts : Drizzle ORM timestamp fields must use { mode: "timestamp" } and are stored as integers, auto-converted to Date objects

Applied to files:

  • app/api/icloud-syncs/[id]/sync/route.ts
📚 Learning: 2025-11-29T20:11:49.992Z
Learnt from: CR
Repo: panteLx/BetterShift PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-11-29T20:11:49.992Z
Learning: Applies to app/**/*.tsx : Use formatDateToLocal() helper for consistent YYYY-MM-DD date formatting in UI

Applied to files:

  • app/api/icloud-syncs/[id]/sync/route.ts
📚 Learning: 2025-11-29T20:11:49.992Z
Learnt from: CR
Repo: panteLx/BetterShift PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-11-29T20:11:49.992Z
Learning: Applies to lib/db/schema.ts : Use cascade delete relationships in database schema (onDelete: "cascade") for dependent tables

Applied to files:

  • app/api/icloud-syncs/[id]/route.ts
📚 Learning: 2025-11-29T20:11:49.992Z
Learnt from: CR
Repo: panteLx/BetterShift PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-11-29T20:11:49.992Z
Learning: Applies to app/**/*.tsx : Shift toggle logic must check if matching shift exists: delete if present, create if not

Applied to files:

  • app/api/icloud-syncs/[id]/route.ts
📚 Learning: 2025-11-29T20:11:49.992Z
Learnt from: CR
Repo: panteLx/BetterShift PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-11-29T20:11:49.992Z
Learning: Applies to components/**/*-dialog.tsx : Dialog components must control open state via props, reset internal state when open changes to false, and call parent callback on form submission

Applied to files:

  • components/icloud-sync-manage-dialog.tsx
📚 Learning: 2025-11-29T20:11:49.992Z
Learnt from: CR
Repo: panteLx/BetterShift PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-11-29T20:11:49.992Z
Learning: Applies to components/**/calendar*.tsx : Calendar interaction: left-click toggles shift (requires preset selection), right-click opens note dialog with e.preventDefault()

Applied to files:

  • components/icloud-sync-manage-dialog.tsx
📚 Learning: 2025-11-29T20:11:49.992Z
Learnt from: CR
Repo: panteLx/BetterShift PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-11-29T20:11:49.992Z
Learning: Applies to app/page.tsx : Password verification for calendar access must use pendingAction state pattern: set pending action, show PasswordDialog, retry after password verification via /api/calendars/[id]/verify-password

Applied to files:

  • components/icloud-sync-manage-dialog.tsx
📚 Learning: 2025-11-29T20:11:49.992Z
Learnt from: CR
Repo: panteLx/BetterShift PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-11-29T20:11:49.992Z
Learning: Applies to components/**/*-dialog.tsx : Form submission in dialogs: prevent default, validate, call parent callback, close dialog

Applied to files:

  • components/icloud-sync-manage-dialog.tsx
📚 Learning: 2025-11-29T20:11:49.992Z
Learnt from: CR
Repo: panteLx/BetterShift PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-11-29T20:11:49.992Z
Learning: Applies to components/**/*.tsx : Use PRESET_COLORS constant array for all color picker options

Applied to files:

  • components/icloud-sync-manage-dialog.tsx
📚 Learning: 2025-11-29T20:11:49.992Z
Learnt from: CR
Repo: panteLx/BetterShift PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-11-29T20:11:49.992Z
Learning: Applies to **/*.{ts,tsx} : Store colors as hex values (e.g., #3b82f6) and apply 20% opacity for backgrounds using format ${color}20

Applied to files:

  • components/icloud-sync-manage-dialog.tsx
🧬 Code graph analysis (2)
app/api/icloud-syncs/[id]/sync/route.ts (4)
app/api/icloud-syncs/route.ts (1)
  • POST (63-107)
app/api/shifts/route.ts (1)
  • POST (72-133)
lib/db/index.ts (1)
  • db (19-19)
lib/db/schema.ts (2)
  • icloudSyncs (19-36)
  • shifts (38-71)
components/icloud-sync-manage-dialog.tsx (7)
lib/types.ts (1)
  • ICloudSync (2-2)
lib/db/schema.ts (1)
  • ICloudSync (116-116)
components/ui/dialog.tsx (5)
  • Dialog (139-139)
  • DialogContent (141-141)
  • DialogHeader (144-144)
  • DialogTitle (147-147)
  • DialogDescription (142-142)
components/ui/button.tsx (1)
  • Button (60-60)
components/ui/label.tsx (1)
  • Label (24-24)
components/ui/input.tsx (1)
  • Input (21-21)
components/ui/color-picker.tsx (1)
  • ColorPicker (24-127)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: build-and-push
🔇 Additional comments (11)
app/api/icloud-syncs/[id]/sync/route.ts (4)

1-5: Imports are correctly used.

The eq and inArray imports are both utilized in the code (eq at lines 18, 66, 177, 192 and inArray at line 183). The past review comment about unused and import appears to have been addressed.


7-12: Params correctly awaited.

Properly awaits the dynamic route params as required by Next.js 16.


166-194: Transaction pattern is appropriate for synchronous driver.

The code correctly uses the synchronous transaction pattern with .run() calls, which is the expected approach for better-sqlite3 with Drizzle ORM. The transaction ensures atomicity for batch inserts, updates, and deletes.


196-211: Response and error handling are well-implemented.

The endpoint returns useful statistics (created, updated, deleted counts) and properly logs errors with console.error() per coding guidelines.

app/api/icloud-syncs/[id]/route.ts (3)

1-5: Imports are correctly used.

The shifts import is used at lines 109-115 for updating shift colors when the sync color changes. The past review comment about unused imports is no longer applicable.


35-63: GET handler is well-implemented.

Correctly awaits params, handles the 404 case, and logs errors per guidelines.


107-124: Color propagation to shifts is properly handled.

The PATCH handler correctly updates all associated shifts when the sync color changes and emits a calendar-change event to notify clients. This addresses the previous review comment.

components/icloud-sync-manage-dialog.tsx (4)

57-74: fetchSyncs correctly wrapped in useCallback.

The function is properly memoized with calendarId as a dependency, addressing the previous review comment about missing dependencies in the useEffect.


76-92: State reset on dialog close is properly implemented.

The useEffect now resets all internal state when the dialog closes, addressing the previous review comment. The dependency array correctly includes fetchSyncs.


376-386: ColorPicker correctly receives presetColors prop.

The presetColors={PRESET_COLORS} prop is now passed to the ColorPicker component, addressing the previous review comment about following coding guidelines.


359-374: URL field disabled during edit is a reasonable security measure.

Disabling the URL field when editing prevents users from changing the iCloud URL to a potentially malicious endpoint after initial validation. If URL changes are needed, users can delete and recreate the sync.

Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 20 out of 21 changed files in this pull request and generated 8 comments.

Moves iCloud URL validation and recurring event handling into shared utilities
for improved maintainability and accuracy. Centralizes color preset definitions
to prevent duplication and ensure consistency across dialogs and forms.
Enhances sync to handle recurring and multi-day events more robustly, tracks
stats for inserted, updated, and deleted shifts.
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

♻️ Duplicate comments (3)
app/api/icloud-syncs/[id]/sync/route.ts (2)

109-114: Timezone handling may cause incorrect times.

The toJSDate() conversion followed by getHours()/getMinutes() in splitMultiDayEvent operates in the server's local timezone. This could cause incorrect times if iCloud events contain timezone information different from the server's timezone.


35-46: Add timeout and response size limits to protect against resource exhaustion.

The fetch call lacks safeguards against malicious or slow-responding URLs. While isValidICloudUrl validates the domain, an attacker with access could still cause resource exhaustion via large responses or slow connections.

+    const controller = new AbortController();
+    const timeoutId = setTimeout(() => controller.abort(), 30000); // 30s timeout
+
     try {
-      const response = await fetch(fetchUrl);
+      const response = await fetch(fetchUrl, { signal: controller.signal });
+      clearTimeout(timeoutId);
+
       if (!response.ok) {
         throw new Error(`Failed to fetch calendar: ${response.statusText}`);
       }
+
+      const contentLength = response.headers.get("content-length");
+      if (contentLength && parseInt(contentLength) > 10 * 1024 * 1024) {
+        throw new Error("Calendar file too large");
+      }
+
       icsData = await response.text();
     } catch (error) {
+      clearTimeout(timeoutId);
       console.error("Error fetching iCloud calendar:", error);
lib/icloud-utils.ts (1)

164-171: Timezone-sensitive time extraction.

Using getHours() and getMinutes() on JavaScript Date objects operates in the server's local timezone. If iCloud events contain timezone information different from the server, extracted times may be incorrect. Consider using iCal.js timezone-aware properties or normalizing to UTC.

🧹 Nitpick comments (3)
components/preset-edit-dialog.tsx (1)

55-77: Consider resetting state when dialog closes.

Per coding guidelines, dialog components should reset internal state when open changes to false. Currently, the useEffect only handles state initialization when open is true. While this works because state is reset on next open, explicitly clearing state on close is more defensive.

   useEffect(() => {
     if (open && preset && !isCreating) {
       setFormData({
         title: preset.title,
         startTime: preset.startTime,
         endTime: preset.endTime,
         color: preset.color,
         notes: preset.notes || "",
         isSecondary: preset.isSecondary || false,
         isAllDay: preset.isAllDay || false,
       });
     } else if (open && isCreating) {
       setFormData({
         title: "",
         startTime: "09:00",
         endTime: "17:00",
         color: PRESET_COLORS[0].value,
         notes: "",
         isSecondary: false,
         isAllDay: false,
       });
+    } else if (!open) {
+      // Reset state when dialog closes
+      setFormData({
+        title: "",
+        startTime: "09:00",
+        endTime: "17:00",
+        color: PRESET_COLORS[0].value,
+        notes: "",
+        isSecondary: false,
+        isAllDay: false,
+      });
     }
   }, [open, preset, isCreating]);
app/api/icloud-syncs/route.ts (1)

60-71: Consider adding duplicate URL check on the backend.

The POST endpoint doesn't prevent creating duplicate iCloud syncs with the same URL for the same calendar. While client-side validation may exist, the backend should enforce uniqueness to prevent race conditions or direct API calls bypassing the UI.

+    // Check for duplicate URL within the same calendar
+    const existingSync = await db
+      .select()
+      .from(icloudSyncs)
+      .where(eq(icloudSyncs.calendarId, calendarId))
+      .where(eq(icloudSyncs.icloudUrl, icloudUrl))
+      .limit(1);
+
+    if (existingSync.length > 0) {
+      return NextResponse.json(
+        { error: "An iCloud sync with this URL already exists for this calendar" },
+        { status: 409 }
+      );
+    }
+
     const [icloudSync] = await db
       .insert(icloudSyncs)
       .values({

Alternatively, add a unique constraint on (calendar_id, icloud_url) in the database schema.

components/icloud-sync-manage-dialog.tsx (1)

378-394: Simplify cancel button logic.

The cancel button's else branch (lines 385-388) manually resets form state, but this logic is already encapsulated in the cancelEdit() function. Consider consolidating this for better maintainability.

Apply this diff to simplify:

-                <Button
-                  variant="outline"
-                  onClick={() => {
-                    if (editingSync) {
-                      cancelEdit();
-                    } else {
-                      setShowAddForm(false);
-                      setFormName("");
-                      setFormUrl("");
-                      setFormColor("#3b82f6");
-                    }
-                  }}
-                  disabled={isLoading}
-                >
+                <Button
+                  variant="outline"
+                  onClick={() => {
+                    if (editingSync) {
+                      cancelEdit();
+                    } else {
+                      setShowAddForm(false);
+                      cancelEdit(); // Reuse existing reset logic
+                    }
+                  }}
+                  disabled={isLoading}
+                >

Or create a dedicated cancelAdd() helper function similar to cancelEdit() for symmetry.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between eede5d2 and aa0c4c5.

📒 Files selected for processing (9)
  • app/api/icloud-syncs/[id]/route.ts (1 hunks)
  • app/api/icloud-syncs/[id]/sync/route.ts (1 hunks)
  • app/api/icloud-syncs/route.ts (1 hunks)
  • components/calendar-dialog.tsx (3 hunks)
  • components/icloud-sync-manage-dialog.tsx (1 hunks)
  • components/preset-edit-dialog.tsx (1 hunks)
  • components/shift-form-fields.tsx (1 hunks)
  • lib/constants.ts (1 hunks)
  • lib/icloud-utils.ts (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • app/api/icloud-syncs/[id]/route.ts
🧰 Additional context used
📓 Path-based instructions (7)
**/*.{ts,tsx}

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

Store colors as hex values (e.g., #3b82f6) and apply 20% opacity for backgrounds using format ${color}20

Files:

  • lib/constants.ts
  • lib/icloud-utils.ts
  • app/api/icloud-syncs/route.ts
  • app/api/icloud-syncs/[id]/sync/route.ts
  • components/shift-form-fields.tsx
  • components/preset-edit-dialog.tsx
  • components/calendar-dialog.tsx
  • components/icloud-sync-manage-dialog.tsx
app/api/**/*.ts

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

app/api/**/*.ts: Always await dynamic route params in API routes and server components (Next.js 16 breaking change)
Create API route handlers for new database tables at app/api/tablename/route.ts and app/api/tablename/[id]/route.ts
Log all API errors with console.error() for debugging

Files:

  • app/api/icloud-syncs/route.ts
  • app/api/icloud-syncs/[id]/sync/route.ts
app/api/**/route.ts

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

API GET routes must validate required query parameters and return 400 status for missing params

Files:

  • app/api/icloud-syncs/route.ts
  • app/api/icloud-syncs/[id]/sync/route.ts
app/**/*.{ts,tsx}

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

Use useTranslations() hook for all user-facing text and t("key") to access translation keys from messages/{de,en}.json

Files:

  • app/api/icloud-syncs/route.ts
  • app/api/icloud-syncs/[id]/sync/route.ts
components/**/*.tsx

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

Use PRESET_COLORS constant array for all color picker options

Files:

  • components/shift-form-fields.tsx
  • components/preset-edit-dialog.tsx
  • components/calendar-dialog.tsx
  • components/icloud-sync-manage-dialog.tsx
components/**/*-dialog.tsx

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

components/**/*-dialog.tsx: Dialog components must control open state via props, reset internal state when open changes to false, and call parent callback on form submission
Form submission in dialogs: prevent default, validate, call parent callback, close dialog

Files:

  • components/preset-edit-dialog.tsx
  • components/calendar-dialog.tsx
  • components/icloud-sync-manage-dialog.tsx
components/**/calendar*.tsx

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

Calendar interaction: left-click toggles shift (requires preset selection), right-click opens note dialog with e.preventDefault()

Files:

  • components/calendar-dialog.tsx
🧠 Learnings (11)
📚 Learning: 2025-11-29T20:11:49.992Z
Learnt from: CR
Repo: panteLx/BetterShift PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-11-29T20:11:49.992Z
Learning: Applies to components/**/*.tsx : Use PRESET_COLORS constant array for all color picker options

Applied to files:

  • lib/constants.ts
  • components/shift-form-fields.tsx
  • components/preset-edit-dialog.tsx
  • components/calendar-dialog.tsx
  • components/icloud-sync-manage-dialog.tsx
📚 Learning: 2025-11-29T20:11:49.992Z
Learnt from: CR
Repo: panteLx/BetterShift PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-11-29T20:11:49.992Z
Learning: Applies to **/*.{ts,tsx} : Store colors as hex values (e.g., #3b82f6) and apply 20% opacity for backgrounds using format ${color}20

Applied to files:

  • lib/constants.ts
  • components/calendar-dialog.tsx
  • components/icloud-sync-manage-dialog.tsx
📚 Learning: 2025-11-29T20:11:49.992Z
Learnt from: CR
Repo: panteLx/BetterShift PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-11-29T20:11:49.992Z
Learning: Applies to components/**/calendar*.tsx : Calendar interaction: left-click toggles shift (requires preset selection), right-click opens note dialog with e.preventDefault()

Applied to files:

  • lib/constants.ts
  • components/shift-form-fields.tsx
  • components/preset-edit-dialog.tsx
  • components/calendar-dialog.tsx
  • components/icloud-sync-manage-dialog.tsx
📚 Learning: 2025-11-29T20:11:49.992Z
Learnt from: CR
Repo: panteLx/BetterShift PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-11-29T20:11:49.992Z
Learning: Applies to app/page.tsx : Main page (app/page.tsx) uses URL sync via useRouter().replace() for selected calendar

Applied to files:

  • app/api/icloud-syncs/route.ts
📚 Learning: 2025-11-29T20:11:49.992Z
Learnt from: CR
Repo: panteLx/BetterShift PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-11-29T20:11:49.992Z
Learning: Applies to app/api/**/*.ts : Create API route handlers for new database tables at app/api/tablename/route.ts and app/api/tablename/[id]/route.ts

Applied to files:

  • app/api/icloud-syncs/route.ts
  • app/api/icloud-syncs/[id]/sync/route.ts
📚 Learning: 2025-11-29T20:11:49.992Z
Learnt from: CR
Repo: panteLx/BetterShift PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-11-29T20:11:49.992Z
Learning: Applies to app/**/*.tsx : Frontend uses Next.js 16 App Router (app/ directory), React 19, and client-side state management via useState/useEffect

Applied to files:

  • app/api/icloud-syncs/route.ts
📚 Learning: 2025-11-29T20:11:49.992Z
Learnt from: CR
Repo: panteLx/BetterShift PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-11-29T20:11:49.992Z
Learning: Applies to lib/db/schema.ts : Drizzle ORM timestamp fields must use { mode: "timestamp" } and are stored as integers, auto-converted to Date objects

Applied to files:

  • app/api/icloud-syncs/[id]/sync/route.ts
📚 Learning: 2025-11-29T20:11:49.992Z
Learnt from: CR
Repo: panteLx/BetterShift PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-11-29T20:11:49.992Z
Learning: Applies to app/**/*.tsx : Use formatDateToLocal() helper for consistent YYYY-MM-DD date formatting in UI

Applied to files:

  • app/api/icloud-syncs/[id]/sync/route.ts
  • components/preset-edit-dialog.tsx
  • components/calendar-dialog.tsx
📚 Learning: 2025-11-29T20:11:49.992Z
Learnt from: CR
Repo: panteLx/BetterShift PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-11-29T20:11:49.992Z
Learning: Applies to components/**/*-dialog.tsx : Dialog components must control open state via props, reset internal state when open changes to false, and call parent callback on form submission

Applied to files:

  • components/calendar-dialog.tsx
  • components/icloud-sync-manage-dialog.tsx
📚 Learning: 2025-11-29T20:11:49.992Z
Learnt from: CR
Repo: panteLx/BetterShift PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-11-29T20:11:49.992Z
Learning: Applies to components/**/*-dialog.tsx : Form submission in dialogs: prevent default, validate, call parent callback, close dialog

Applied to files:

  • components/calendar-dialog.tsx
  • components/icloud-sync-manage-dialog.tsx
📚 Learning: 2025-11-29T20:11:49.992Z
Learnt from: CR
Repo: panteLx/BetterShift PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-11-29T20:11:49.992Z
Learning: Applies to app/page.tsx : Password verification for calendar access must use pendingAction state pattern: set pending action, show PasswordDialog, retry after password verification via /api/calendars/[id]/verify-password

Applied to files:

  • components/calendar-dialog.tsx
  • components/icloud-sync-manage-dialog.tsx
🧬 Code graph analysis (2)
app/api/icloud-syncs/[id]/sync/route.ts (4)
app/api/icloud-syncs/route.ts (1)
  • POST (37-81)
lib/db/index.ts (1)
  • db (19-19)
lib/db/schema.ts (2)
  • icloudSyncs (19-36)
  • shifts (38-71)
lib/icloud-utils.ts (2)
  • expandRecurringEvents (41-116)
  • splitMultiDayEvent (125-219)
components/calendar-dialog.tsx (1)
lib/constants.ts (1)
  • PRESET_COLORS (8-17)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: build-and-push
🔇 Additional comments (21)
components/preset-edit-dialog.tsx (1)

17-17: Good: Centralized PRESET_COLORS import.

The import from @/lib/constants aligns with the coding guidelines to use a shared PRESET_COLORS constant array for all color picker options.

components/calendar-dialog.tsx (1)

17-17: LGTM! PRESET_COLORS refactored correctly.

The changes properly:

  • Import PRESET_COLORS from the centralized constants module
  • Initialize and reset selectedColor using PRESET_COLORS[0].value
  • Iterate with colorObj accessing .value and .name properties
  • Apply hex colors with opacity suffixes (e.g., ${colorObj.value}40) per coding guidelines

Also applies to: 32-32, 45-45, 89-124

app/api/icloud-syncs/[id]/sync/route.ts (1)

172-209: Transaction handling looks correct.

The transaction result is properly captured and returned. The synchronous transaction with better-sqlite3 is correctly structured with batch inserts, individual updates (SQLite limitation), batch deletes, and metadata update.

lib/icloud-utils.ts (2)

12-32: Good SSRF mitigation for iCloud URLs.

The validation correctly restricts URLs to webcal:// or https:// protocols and ensures the hostname is from the iCloud domain using endsWith(".icloud.com") which prevents subdomain spoofing attacks.


41-116: Well-implemented recurring event expansion.

The function correctly:

  • Uses iCal.js iterator API to expand recurring events
  • Filters occurrences within the specified time window
  • Calculates end times using event duration
  • Returns recurrenceId for unique identification of each occurrence
app/api/icloud-syncs/route.ts (2)

5-5: Good: Centralized URL validation utility.

Using isValidICloudUrl from @/lib/icloud-utils addresses the previous DRY concern about duplicated validation logic.


8-34: GET handler follows API route guidelines.

Properly validates the required calendarId query parameter and returns 400 status for missing params. Errors are logged with console.error.

lib/constants.ts (1)

1-17: LGTM! Good centralization of color presets.

This new constants module properly exports PRESET_COLORS with hex values, enabling consistent color options across all color picker components. Aligns with coding guidelines to use a shared constant array.

components/shift-form-fields.tsx (2)

9-9: Good: Uses centralized PRESET_COLORS.

Correctly imports the color presets from @/lib/constants per coding guidelines.


145-150: ColorPicker integration looks correct.

The PRESET_COLORS is properly passed to the ColorPicker component, and the default color fallback (#3b82f6) matches the first preset color.

components/icloud-sync-manage-dialog.tsx (11)

1-26: LGTM! Imports and interface are well-structured.

All necessary dependencies are imported, including PRESET_COLORS from the shared constants file. The interface properly defines the dialog props following the component guidelines.


28-46: LGTM! State management is well-organized.

The component uses separate loading states for different operations, which enables proper per-item loading indicators and prevents race conditions.


47-64: LGTM! Proper use of useCallback.

The function is correctly memoized with calendarId in the dependency array, which was properly addressed from previous feedback.


67-82: LGTM! Dialog state management follows guidelines.

The effect properly loads syncs when the dialog opens and comprehensively resets all internal state when it closes, which addresses the previous review feedback. The fetchSyncs callback is correctly included in the dependency array.


133-166: LGTM! Update logic is correct.

The function properly updates the sync and calls onSyncComplete to refresh shifts when the color is updated, with a helpful comment explaining the reason.


168-201: LGTM! Sync operation with good user feedback.

The manual sync function provides excellent user feedback by displaying detailed statistics (created, updated, deleted) in the success toast, which helps users understand what changed.


203-226: LGTM! Delete operation works correctly.

The function properly confirms deletion and updates the UI. The native browser confirm() is functional, though a custom dialog component would provide a more polished UX if you want to enhance this in the future.


228-249: LGTM! Helper functions for form state management.

These functions cleanly encapsulate the logic for transitioning between different form modes (add, edit, view).


251-328: LGTM! Dialog structure and sync list UI are well-implemented.

The dialog properly controls open state via props and displays syncs with color-coded indicators. The per-item action buttons have appropriate loading states and disabled conditions.


366-376: LGTM! ColorPicker correctly uses PRESET_COLORS.

The ColorPicker component properly receives the PRESET_COLORS constant via the presetColors prop, which addresses previous review feedback. As per coding guidelines.


411-436: LGTM! Add button and instructional guidance are user-friendly.

The conditional add button and instruction panel provide clear guidance for users on how to set up iCloud syncs.

Prevents invalid iCloud URLs from being added by validating
the format before submission. Improves user feedback with
localized error messages for incorrect URL input.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant