diff --git a/www/app/(app)/rooms/_components/ICSSettings.tsx b/www/app/(app)/rooms/_components/ICSSettings.tsx
index a39780d2..beb4d9fa 100644
--- a/www/app/(app)/rooms/_components/ICSSettings.tsx
+++ b/www/app/(app)/rooms/_components/ICSSettings.tsx
@@ -201,20 +201,32 @@ export default function ICSSettings({
bg="gray.100"
_hover={{ bg: "gray.200" }}
_focus={{ bg: "gray.200" }}
- pr="48px"
+ pr="90px"
width="100%"
/>
-
- {justCopied ? : }
-
+
+
+ {syncStatus === "syncing" ? (
+
+ ) : (
+
+ )}
+
+
+ {justCopied ? : }
+
+
diff --git a/www/app/(app)/rooms/_components/RoomTable.tsx b/www/app/(app)/rooms/_components/RoomTable.tsx
index 8b5e1ae1..9ad64880 100644
--- a/www/app/(app)/rooms/_components/RoomTable.tsx
+++ b/www/app/(app)/rooms/_components/RoomTable.tsx
@@ -1,4 +1,4 @@
-import React from "react";
+import React, { useState } from "react";
import {
Box,
Table,
@@ -9,12 +9,15 @@ import {
Spinner,
Badge,
VStack,
+ Icon,
} from "@chakra-ui/react";
-import { LuLink } from "react-icons/lu";
+import { LuLink, LuRefreshCw } from "react-icons/lu";
+import { FaCalendarAlt } from "react-icons/fa";
import type { components } from "../../../reflector-api";
import {
useRoomActiveMeetings,
useRoomUpcomingMeetings,
+ useRoomIcsSync,
} from "../../../lib/apiHooks";
type Room = components["schemas"]["Room"];
@@ -23,6 +26,34 @@ type CalendarEventResponse = components["schemas"]["CalendarEventResponse"];
import { RoomActionsMenu } from "./RoomActionsMenu";
import { MEETING_DEFAULT_TIME_MINUTES } from "../../../[roomName]/[meetingId]/constants";
+// Custom icon component that combines calendar and refresh icons
+const CalendarSyncIcon = () => (
+
+
+
+
+
+
+);
+
interface RoomTableProps {
rooms: Room[];
linkCopied: string;
@@ -148,6 +179,28 @@ export function RoomTable({
onDelete,
loading,
}: RoomTableProps) {
+ const [syncingRooms, setSyncingRooms] = useState>(new Set());
+ const syncMutation = useRoomIcsSync();
+
+ const handleForceSync = async (roomName: string) => {
+ setSyncingRooms((prev) => new Set(prev).add(roomName));
+ try {
+ await syncMutation.mutateAsync({
+ params: {
+ path: { room_name: roomName },
+ },
+ });
+ } catch (err) {
+ console.error("Failed to sync calendar:", err);
+ } finally {
+ setSyncingRooms((prev) => {
+ const next = new Set(prev);
+ next.delete(roomName);
+ return next;
+ });
+ }
+ };
+
return (
{loading && (
@@ -217,6 +270,21 @@ export function RoomTable({
+ {room.ics_enabled && (
+ handleForceSync(room.name)}
+ size="sm"
+ variant="ghost"
+ disabled={syncingRooms.has(room.name)}
+ >
+ {syncingRooms.has(room.name) ? (
+
+ ) : (
+
+ )}
+
+ )}
{linkCopied === room.name ? (
Copied!