mirror of
https://github.com/Monadical-SAS/reflector.git
synced 2025-12-20 12:19:06 +00:00
## Frontend Implementation
### Meeting Selection & Management
- Created MeetingSelection component for choosing between multiple active meetings
- Shows both active meetings and upcoming calendar events (30 min ahead)
- Displays meeting metadata with privacy controls (owner-only details)
- Supports creation of unscheduled meetings alongside calendar meetings
### Waiting Room
- Added waiting page for users joining before scheduled start time
- Shows countdown timer until meeting begins
- Auto-transitions to meeting when calendar event becomes active
- Handles early joining with proper routing
### Meeting Info Panel
- Created collapsible info panel showing meeting details
- Displays calendar metadata (title, description, attendees)
- Shows participant count and duration
- Privacy-aware: sensitive info only visible to room owners
### ICS Configuration UI
- Integrated ICS settings into room configuration dialog
- Test connection functionality with immediate feedback
- Manual sync trigger with detailed results
- Shows last sync time and ETag for monitoring
- Configurable sync intervals (1 min to 1 hour)
### Routing & Navigation
- New /room/{roomName} route for meeting selection
- Waiting room at /room/{roomName}/wait?eventId={id}
- Classic room page at /{roomName} with meeting info
- Uses sessionStorage to pass selected meeting between pages
### API Integration
- Added new endpoints for active/upcoming meetings
- Regenerated TypeScript client with latest OpenAPI spec
- Proper error handling and loading states
- Auto-refresh every 30 seconds for live updates
### UI/UX Improvements
- Color-coded badges for meeting status
- Attendee status indicators (accepted/declined/tentative)
- Responsive design with Chakra UI components
- Clear visual hierarchy between active and upcoming meetings
- Smart truncation for long attendee lists
This completes the frontend implementation for calendar integration,
enabling users to seamlessly join scheduled meetings from their
calendar applications.
148 lines
4.0 KiB
TypeScript
148 lines
4.0 KiB
TypeScript
"use client";
|
|
|
|
import { useEffect, useState } from "react";
|
|
import { Box, Spinner, VStack, Text } from "@chakra-ui/react";
|
|
import { useRouter } from "next/navigation";
|
|
import useApi from "../../lib/useApi";
|
|
import useSessionStatus from "../../lib/useSessionStatus";
|
|
import MeetingSelection from "../../[roomName]/MeetingSelection";
|
|
import { Meeting, Room } from "../../api";
|
|
|
|
interface RoomPageProps {
|
|
params: {
|
|
roomName: string;
|
|
};
|
|
}
|
|
|
|
export default function RoomPage({ params }: RoomPageProps) {
|
|
const { roomName } = params;
|
|
const router = useRouter();
|
|
const api = useApi();
|
|
const { data: session } = useSessionStatus();
|
|
|
|
const [room, setRoom] = useState<Room | null>(null);
|
|
const [loading, setLoading] = useState(true);
|
|
const [checkingMeetings, setCheckingMeetings] = useState(false);
|
|
|
|
const isOwner = session?.user?.id === room?.user_id;
|
|
|
|
useEffect(() => {
|
|
if (!api) return;
|
|
|
|
const fetchRoom = async () => {
|
|
try {
|
|
// Get room details
|
|
const roomData = await api.v1RoomsRetrieve({ roomName });
|
|
setRoom(roomData);
|
|
|
|
// Check if we should show meeting selection
|
|
if (roomData.ics_enabled) {
|
|
setCheckingMeetings(true);
|
|
|
|
// Check for active meetings
|
|
const activeMeetings = await api.v1RoomsListActiveMeetings({
|
|
roomName,
|
|
});
|
|
|
|
// Check for upcoming meetings
|
|
const upcomingEvents = await api.v1RoomsListUpcomingMeetings({
|
|
roomName,
|
|
minutesAhead: 30,
|
|
});
|
|
|
|
// If there's only one active meeting and no upcoming, auto-join
|
|
if (activeMeetings.length === 1 && upcomingEvents.length === 0) {
|
|
handleMeetingSelect(activeMeetings[0]);
|
|
} else if (
|
|
activeMeetings.length === 0 &&
|
|
upcomingEvents.length === 0
|
|
) {
|
|
// No meetings, create unscheduled
|
|
handleCreateUnscheduled();
|
|
}
|
|
// Otherwise, show selection UI (handled by render)
|
|
} else {
|
|
// ICS not enabled, use traditional flow
|
|
handleCreateUnscheduled();
|
|
}
|
|
} catch (err) {
|
|
console.error("Failed to fetch room:", err);
|
|
// Room not found or error
|
|
router.push("/rooms");
|
|
} finally {
|
|
setLoading(false);
|
|
setCheckingMeetings(false);
|
|
}
|
|
};
|
|
|
|
fetchRoom();
|
|
}, [api, roomName]);
|
|
|
|
const handleMeetingSelect = (meeting: Meeting) => {
|
|
// Navigate to the classic room page with the meeting
|
|
// Store meeting in session storage for the classic page to use
|
|
sessionStorage.setItem(`meeting_${roomName}`, JSON.stringify(meeting));
|
|
router.push(`/${roomName}`);
|
|
};
|
|
|
|
const handleCreateUnscheduled = async () => {
|
|
if (!api) return;
|
|
|
|
try {
|
|
// Create a new unscheduled meeting
|
|
const meeting = await api.v1RoomsCreateMeeting({ roomName });
|
|
handleMeetingSelect(meeting);
|
|
} catch (err) {
|
|
console.error("Failed to create meeting:", err);
|
|
}
|
|
};
|
|
|
|
if (loading || checkingMeetings) {
|
|
return (
|
|
<Box
|
|
minH="100vh"
|
|
display="flex"
|
|
alignItems="center"
|
|
justifyContent="center"
|
|
bg="gray.50"
|
|
>
|
|
<VStack spacing={4}>
|
|
<Spinner size="xl" color="blue.500" />
|
|
<Text>{loading ? "Loading room..." : "Checking meetings..."}</Text>
|
|
</VStack>
|
|
</Box>
|
|
);
|
|
}
|
|
|
|
if (!room) {
|
|
return (
|
|
<Box
|
|
minH="100vh"
|
|
display="flex"
|
|
alignItems="center"
|
|
justifyContent="center"
|
|
bg="gray.50"
|
|
>
|
|
<Text fontSize="lg">Room not found</Text>
|
|
</Box>
|
|
);
|
|
}
|
|
|
|
// Show meeting selection if ICS is enabled and we have multiple options
|
|
if (room.ics_enabled) {
|
|
return (
|
|
<Box minH="100vh" bg="gray.50">
|
|
<MeetingSelection
|
|
roomName={roomName}
|
|
isOwner={isOwner}
|
|
onMeetingSelect={handleMeetingSelect}
|
|
onCreateUnscheduled={handleCreateUnscheduled}
|
|
/>
|
|
</Box>
|
|
);
|
|
}
|
|
|
|
// Should not reach here - redirected above
|
|
return null;
|
|
}
|