mirror of
https://github.com/Monadical-SAS/reflector.git
synced 2025-12-21 12:49:06 +00:00
fix: creation of meeting
This commit is contained in:
@@ -113,6 +113,10 @@ class UpdateRoom(BaseModel):
|
|||||||
ics_enabled: Optional[bool] = None
|
ics_enabled: Optional[bool] = None
|
||||||
|
|
||||||
|
|
||||||
|
class CreateRoomMeeting(BaseModel):
|
||||||
|
allow_duplicated: Optional[bool] = False
|
||||||
|
|
||||||
|
|
||||||
class DeletionStatus(BaseModel):
|
class DeletionStatus(BaseModel):
|
||||||
status: str
|
status: str
|
||||||
|
|
||||||
@@ -235,6 +239,7 @@ async def rooms_delete(
|
|||||||
@router.post("/rooms/{room_name}/meeting", response_model=Meeting)
|
@router.post("/rooms/{room_name}/meeting", response_model=Meeting)
|
||||||
async def rooms_create_meeting(
|
async def rooms_create_meeting(
|
||||||
room_name: str,
|
room_name: str,
|
||||||
|
info: CreateRoomMeeting,
|
||||||
user: Annotated[Optional[auth.UserInfo], Depends(auth.current_user_optional)],
|
user: Annotated[Optional[auth.UserInfo], Depends(auth.current_user_optional)],
|
||||||
):
|
):
|
||||||
user_id = user["sub"] if user else None
|
user_id = user["sub"] if user else None
|
||||||
@@ -243,7 +248,12 @@ async def rooms_create_meeting(
|
|||||||
raise HTTPException(status_code=404, detail="Room not found")
|
raise HTTPException(status_code=404, detail="Room not found")
|
||||||
|
|
||||||
current_time = datetime.now(timezone.utc)
|
current_time = datetime.now(timezone.utc)
|
||||||
meeting = await meetings_controller.get_active(room=room, current_time=current_time)
|
|
||||||
|
meeting = None
|
||||||
|
if not info.allow_duplicated:
|
||||||
|
meeting = await meetings_controller.get_active(
|
||||||
|
room=room, current_time=current_time
|
||||||
|
)
|
||||||
|
|
||||||
if meeting is None:
|
if meeting is None:
|
||||||
end_date = current_time + timedelta(hours=8)
|
end_date = current_time + timedelta(hours=8)
|
||||||
|
|||||||
@@ -191,6 +191,7 @@ async def process_meetings():
|
|||||||
|
|
||||||
# This API call could be slow, extend lock if needed
|
# This API call could be slow, extend lock if needed
|
||||||
response = await get_room_sessions(meeting.room_name)
|
response = await get_room_sessions(meeting.room_name)
|
||||||
|
print(response)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
# Extend lock after slow operation to ensure we still hold it
|
# Extend lock after slow operation to ensure we still hold it
|
||||||
|
|||||||
@@ -25,7 +25,6 @@ import {
|
|||||||
import { useRouter } from "next/navigation";
|
import { useRouter } from "next/navigation";
|
||||||
import { formatDateTime, formatStartedAgo } from "../lib/timeUtils";
|
import { formatDateTime, formatStartedAgo } from "../lib/timeUtils";
|
||||||
import MeetingMinimalHeader from "../components/MeetingMinimalHeader";
|
import MeetingMinimalHeader from "../components/MeetingMinimalHeader";
|
||||||
import { MEETING_DEFAULT_TIME_MINUTES } from "./[meetingId]/constants";
|
|
||||||
|
|
||||||
type Meeting = components["schemas"]["Meeting"];
|
type Meeting = components["schemas"]["Meeting"];
|
||||||
|
|
||||||
@@ -36,6 +35,7 @@ interface MeetingSelectionProps {
|
|||||||
authLoading: boolean;
|
authLoading: boolean;
|
||||||
onMeetingSelect: (meeting: Meeting) => void;
|
onMeetingSelect: (meeting: Meeting) => void;
|
||||||
onCreateUnscheduled: () => void;
|
onCreateUnscheduled: () => void;
|
||||||
|
isCreatingMeeting?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function MeetingSelection({
|
export default function MeetingSelection({
|
||||||
@@ -44,6 +44,7 @@ export default function MeetingSelection({
|
|||||||
isSharedRoom,
|
isSharedRoom,
|
||||||
onMeetingSelect,
|
onMeetingSelect,
|
||||||
onCreateUnscheduled,
|
onCreateUnscheduled,
|
||||||
|
isCreatingMeeting = false,
|
||||||
}: MeetingSelectionProps) {
|
}: MeetingSelectionProps) {
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
|
|
||||||
@@ -56,19 +57,22 @@ export default function MeetingSelection({
|
|||||||
const allMeetings = activeMeetingsQuery.data || [];
|
const allMeetings = activeMeetingsQuery.data || [];
|
||||||
|
|
||||||
const now = new Date();
|
const now = new Date();
|
||||||
const [currentMeetings, upcomingMeetings] = partition(
|
const [currentMeetings, nonCurrentMeetings] = partition(
|
||||||
allMeetings,
|
allMeetings,
|
||||||
(meeting) => {
|
(meeting) => {
|
||||||
const startTime = new Date(meeting.start_date);
|
const startTime = new Date(meeting.start_date);
|
||||||
// Meeting is ongoing if it started and participants have joined or it's been running for a while
|
const endTime = new Date(meeting.end_date);
|
||||||
return (
|
// Meeting is ongoing if current time is between start and end
|
||||||
meeting.num_clients > 0 ||
|
return now >= startTime && now <= endTime;
|
||||||
now.getTime() - startTime.getTime() >
|
|
||||||
MEETING_DEFAULT_TIME_MINUTES * 1000
|
|
||||||
);
|
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const upcomingMeetings = nonCurrentMeetings.filter((meeting) => {
|
||||||
|
const startTime = new Date(meeting.start_date);
|
||||||
|
// Meeting is upcoming if it hasn't started yet
|
||||||
|
return now < startTime;
|
||||||
|
});
|
||||||
|
|
||||||
const loading = roomQuery.isLoading || activeMeetingsQuery.isLoading;
|
const loading = roomQuery.isLoading || activeMeetingsQuery.isLoading;
|
||||||
const error = roomQuery.error || activeMeetingsQuery.error;
|
const error = roomQuery.error || activeMeetingsQuery.error;
|
||||||
|
|
||||||
@@ -139,7 +143,30 @@ export default function MeetingSelection({
|
|||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Flex flexDir="column" minH="100vh">
|
<Flex flexDir="column" minH="100vh" position="relative">
|
||||||
|
{/* Loading overlay */}
|
||||||
|
{isCreatingMeeting && (
|
||||||
|
<Box
|
||||||
|
position="fixed"
|
||||||
|
top={0}
|
||||||
|
left={0}
|
||||||
|
right={0}
|
||||||
|
bottom={0}
|
||||||
|
bg="blackAlpha.600"
|
||||||
|
zIndex={9999}
|
||||||
|
display="flex"
|
||||||
|
alignItems="center"
|
||||||
|
justifyContent="center"
|
||||||
|
>
|
||||||
|
<VStack gap={4} p={8} bg="white" borderRadius="lg" boxShadow="xl">
|
||||||
|
<Spinner size="lg" color="blue.500" />
|
||||||
|
<Text fontSize="lg" fontWeight="medium">
|
||||||
|
Creating meeting...
|
||||||
|
</Text>
|
||||||
|
</VStack>
|
||||||
|
</Box>
|
||||||
|
)}
|
||||||
|
|
||||||
<MeetingMinimalHeader
|
<MeetingMinimalHeader
|
||||||
roomName={roomName}
|
roomName={roomName}
|
||||||
displayName={room?.name}
|
displayName={room?.name}
|
||||||
@@ -147,6 +174,7 @@ export default function MeetingSelection({
|
|||||||
onLeave={handleLeaveMeeting}
|
onLeave={handleLeaveMeeting}
|
||||||
showCreateButton={isOwner || isSharedRoom}
|
showCreateButton={isOwner || isSharedRoom}
|
||||||
onCreateMeeting={onCreateUnscheduled}
|
onCreateMeeting={onCreateUnscheduled}
|
||||||
|
isCreatingMeeting={isCreatingMeeting}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<Flex
|
<Flex
|
||||||
@@ -160,11 +188,8 @@ export default function MeetingSelection({
|
|||||||
gap={6}
|
gap={6}
|
||||||
>
|
>
|
||||||
{/* Current Ongoing Meetings - BIG DISPLAY */}
|
{/* Current Ongoing Meetings - BIG DISPLAY */}
|
||||||
{currentMeetings.length > 0 && (
|
{currentMeetings.length > 0 ? (
|
||||||
<VStack align="stretch" gap={6} mb={8}>
|
<VStack align="stretch" gap={6} mb={8}>
|
||||||
<Text fontSize="xl" fontWeight="bold" color="gray.800">
|
|
||||||
Live Meeting{currentMeetings.length > 1 ? "s" : ""}
|
|
||||||
</Text>
|
|
||||||
{currentMeetings.map((meeting) => (
|
{currentMeetings.map((meeting) => (
|
||||||
<Box
|
<Box
|
||||||
key={meeting.id}
|
key={meeting.id}
|
||||||
@@ -273,10 +298,130 @@ export default function MeetingSelection({
|
|||||||
</Box>
|
</Box>
|
||||||
))}
|
))}
|
||||||
</VStack>
|
</VStack>
|
||||||
)}
|
) : upcomingMeetings.length > 0 ? (
|
||||||
|
/* Upcoming Meetings - BIG DISPLAY when no ongoing meetings */
|
||||||
|
<VStack align="stretch" gap={6} mb={8}>
|
||||||
|
<Text fontSize="xl" fontWeight="bold" color="gray.800">
|
||||||
|
Upcoming Meeting{upcomingMeetings.length > 1 ? "s" : ""}
|
||||||
|
</Text>
|
||||||
|
{upcomingMeetings.map((meeting) => {
|
||||||
|
const now = new Date();
|
||||||
|
const startTime = new Date(meeting.start_date);
|
||||||
|
const minutesUntilStart = Math.floor(
|
||||||
|
(startTime.getTime() - now.getTime()) / (1000 * 60),
|
||||||
|
);
|
||||||
|
|
||||||
{/* Upcoming Meetings - SMALLER ASIDE DISPLAY */}
|
return (
|
||||||
{upcomingMeetings.length > 0 && (
|
<Box
|
||||||
|
key={meeting.id}
|
||||||
|
width="100%"
|
||||||
|
bg="orange.50"
|
||||||
|
borderRadius="xl"
|
||||||
|
p={8}
|
||||||
|
border="2px solid"
|
||||||
|
borderColor="orange.200"
|
||||||
|
>
|
||||||
|
<HStack justify="space-between" align="start">
|
||||||
|
<VStack align="start" gap={4} flex={1}>
|
||||||
|
<HStack>
|
||||||
|
<Icon
|
||||||
|
as={FaCalendarAlt}
|
||||||
|
color="orange.600"
|
||||||
|
boxSize="24px"
|
||||||
|
/>
|
||||||
|
<Text
|
||||||
|
fontSize="2xl"
|
||||||
|
fontWeight="bold"
|
||||||
|
color="orange.800"
|
||||||
|
>
|
||||||
|
{(meeting.calendar_metadata as any)?.title ||
|
||||||
|
"Upcoming Meeting"}
|
||||||
|
</Text>
|
||||||
|
</HStack>
|
||||||
|
|
||||||
|
{isOwner &&
|
||||||
|
(meeting.calendar_metadata as any)?.description && (
|
||||||
|
<Text fontSize="lg" color="gray.700">
|
||||||
|
{(meeting.calendar_metadata as any).description}
|
||||||
|
</Text>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<HStack gap={8} fontSize="md" color="gray.600">
|
||||||
|
<Badge colorScheme="orange" fontSize="md" px={3} py={1}>
|
||||||
|
Starts in {minutesUntilStart} minute
|
||||||
|
{minutesUntilStart !== 1 ? "s" : ""}
|
||||||
|
</Badge>
|
||||||
|
<Text>{formatDateTime(meeting.start_date)}</Text>
|
||||||
|
</HStack>
|
||||||
|
|
||||||
|
{isOwner &&
|
||||||
|
(meeting.calendar_metadata as any)?.attendees && (
|
||||||
|
<HStack gap={3} flexWrap="wrap">
|
||||||
|
{(meeting.calendar_metadata as any).attendees
|
||||||
|
.slice(0, 4)
|
||||||
|
.map((attendee: any, idx: number) => (
|
||||||
|
<Badge
|
||||||
|
key={idx}
|
||||||
|
colorScheme="orange"
|
||||||
|
fontSize="sm"
|
||||||
|
px={3}
|
||||||
|
py={1}
|
||||||
|
>
|
||||||
|
{attendee.name || attendee.email}
|
||||||
|
</Badge>
|
||||||
|
))}
|
||||||
|
{(meeting.calendar_metadata as any).attendees
|
||||||
|
.length > 4 && (
|
||||||
|
<Badge
|
||||||
|
colorScheme="gray"
|
||||||
|
fontSize="sm"
|
||||||
|
px={3}
|
||||||
|
py={1}
|
||||||
|
>
|
||||||
|
+
|
||||||
|
{(meeting.calendar_metadata as any).attendees
|
||||||
|
.length - 4}{" "}
|
||||||
|
more
|
||||||
|
</Badge>
|
||||||
|
)}
|
||||||
|
</HStack>
|
||||||
|
)}
|
||||||
|
</VStack>
|
||||||
|
|
||||||
|
<VStack gap={3}>
|
||||||
|
<Button
|
||||||
|
colorScheme="orange"
|
||||||
|
size="xl"
|
||||||
|
fontSize="lg"
|
||||||
|
px={8}
|
||||||
|
py={6}
|
||||||
|
onClick={() => handleJoinUpcoming(meeting)}
|
||||||
|
>
|
||||||
|
<Icon as={FaClock} boxSize="20px" me={2} />
|
||||||
|
Join Early
|
||||||
|
</Button>
|
||||||
|
{isOwner && (
|
||||||
|
<Button
|
||||||
|
variant="outline"
|
||||||
|
colorScheme="red"
|
||||||
|
size="md"
|
||||||
|
onClick={() => handleEndMeeting(meeting.id)}
|
||||||
|
loading={deactivateMeetingMutation.isPending}
|
||||||
|
>
|
||||||
|
<Icon as={LuX} me={2} />
|
||||||
|
Cancel Meeting
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
|
</VStack>
|
||||||
|
</HStack>
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</VStack>
|
||||||
|
) : null}
|
||||||
|
|
||||||
|
{/* Upcoming Meetings - SMALLER ASIDE DISPLAY when there are ongoing meetings */}
|
||||||
|
{currentMeetings.length > 0 && upcomingMeetings.length > 0 && (
|
||||||
<VStack align="stretch" gap={4} mb={6}>
|
<VStack align="stretch" gap={4} mb={6}>
|
||||||
<Text fontSize="lg" fontWeight="semibold" color="gray.700">
|
<Text fontSize="lg" fontWeight="semibold" color="gray.700">
|
||||||
Starting Soon
|
Starting Soon
|
||||||
|
|||||||
@@ -49,6 +49,9 @@ const useRoomMeeting = (
|
|||||||
room_name: roomName,
|
room_name: roomName,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
body: {
|
||||||
|
allow_duplicated: false,
|
||||||
|
},
|
||||||
});
|
});
|
||||||
setResponse(result);
|
setResponse(result);
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ interface MeetingMinimalHeaderProps {
|
|||||||
onLeave?: () => void;
|
onLeave?: () => void;
|
||||||
showCreateButton?: boolean;
|
showCreateButton?: boolean;
|
||||||
onCreateMeeting?: () => void;
|
onCreateMeeting?: () => void;
|
||||||
|
isCreatingMeeting?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function MeetingMinimalHeader({
|
export default function MeetingMinimalHeader({
|
||||||
@@ -21,6 +22,7 @@ export default function MeetingMinimalHeader({
|
|||||||
onLeave,
|
onLeave,
|
||||||
showCreateButton = false,
|
showCreateButton = false,
|
||||||
onCreateMeeting,
|
onCreateMeeting,
|
||||||
|
isCreatingMeeting = false,
|
||||||
}: MeetingMinimalHeaderProps) {
|
}: MeetingMinimalHeaderProps) {
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
|
|
||||||
@@ -70,7 +72,13 @@ export default function MeetingMinimalHeader({
|
|||||||
{/* Action Buttons */}
|
{/* Action Buttons */}
|
||||||
<HStack gap={2}>
|
<HStack gap={2}>
|
||||||
{showCreateButton && onCreateMeeting && (
|
{showCreateButton && onCreateMeeting && (
|
||||||
<Button colorScheme="green" size="sm" onClick={onCreateMeeting}>
|
<Button
|
||||||
|
colorScheme="green"
|
||||||
|
size="sm"
|
||||||
|
onClick={onCreateMeeting}
|
||||||
|
loading={isCreatingMeeting}
|
||||||
|
disabled={isCreatingMeeting}
|
||||||
|
>
|
||||||
Create Meeting
|
Create Meeting
|
||||||
</Button>
|
</Button>
|
||||||
)}
|
)}
|
||||||
@@ -80,6 +88,7 @@ export default function MeetingMinimalHeader({
|
|||||||
colorScheme="gray"
|
colorScheme="gray"
|
||||||
size="sm"
|
size="sm"
|
||||||
onClick={handleLeaveMeeting}
|
onClick={handleLeaveMeeting}
|
||||||
|
disabled={isCreatingMeeting}
|
||||||
>
|
>
|
||||||
Leave Room
|
Leave Room
|
||||||
</Button>
|
</Button>
|
||||||
|
|||||||
29
www/app/reflector-api.d.ts
vendored
29
www/app/reflector-api.d.ts
vendored
@@ -54,10 +54,7 @@ export interface paths {
|
|||||||
delete?: never;
|
delete?: never;
|
||||||
options?: never;
|
options?: never;
|
||||||
head?: never;
|
head?: never;
|
||||||
/**
|
/** Meeting Deactivate */
|
||||||
* Meeting Deactivate
|
|
||||||
* @description Deactivate a meeting (owner only)
|
|
||||||
*/
|
|
||||||
patch: operations["v1_meeting_deactivate"];
|
patch: operations["v1_meeting_deactivate"];
|
||||||
trace?: never;
|
trace?: never;
|
||||||
};
|
};
|
||||||
@@ -227,10 +224,7 @@ export interface paths {
|
|||||||
path?: never;
|
path?: never;
|
||||||
cookie?: never;
|
cookie?: never;
|
||||||
};
|
};
|
||||||
/**
|
/** Rooms List Active Meetings */
|
||||||
* Rooms List Active Meetings
|
|
||||||
* @description List all active meetings for a room (supports multiple active meetings)
|
|
||||||
*/
|
|
||||||
get: operations["v1_rooms_list_active_meetings"];
|
get: operations["v1_rooms_list_active_meetings"];
|
||||||
put?: never;
|
put?: never;
|
||||||
post?: never;
|
post?: never;
|
||||||
@@ -249,10 +243,7 @@ export interface paths {
|
|||||||
};
|
};
|
||||||
get?: never;
|
get?: never;
|
||||||
put?: never;
|
put?: never;
|
||||||
/**
|
/** Rooms Join Meeting */
|
||||||
* Rooms Join Meeting
|
|
||||||
* @description Join a specific meeting by ID
|
|
||||||
*/
|
|
||||||
post: operations["v1_rooms_join_meeting"];
|
post: operations["v1_rooms_join_meeting"];
|
||||||
delete?: never;
|
delete?: never;
|
||||||
options?: never;
|
options?: never;
|
||||||
@@ -740,6 +731,14 @@ export interface components {
|
|||||||
*/
|
*/
|
||||||
ics_enabled: boolean;
|
ics_enabled: boolean;
|
||||||
};
|
};
|
||||||
|
/** CreateRoomMeeting */
|
||||||
|
CreateRoomMeeting: {
|
||||||
|
/**
|
||||||
|
* Allow Duplicated
|
||||||
|
* @default false
|
||||||
|
*/
|
||||||
|
allow_duplicated: boolean | null;
|
||||||
|
};
|
||||||
/** CreateTranscript */
|
/** CreateTranscript */
|
||||||
CreateTranscript: {
|
CreateTranscript: {
|
||||||
/** Name */
|
/** Name */
|
||||||
@@ -1780,7 +1779,11 @@ export interface operations {
|
|||||||
};
|
};
|
||||||
cookie?: never;
|
cookie?: never;
|
||||||
};
|
};
|
||||||
requestBody?: never;
|
requestBody: {
|
||||||
|
content: {
|
||||||
|
"application/json": components["schemas"]["CreateRoomMeeting"];
|
||||||
|
};
|
||||||
|
};
|
||||||
responses: {
|
responses: {
|
||||||
/** @description Successful Response */
|
/** @description Successful Response */
|
||||||
200: {
|
200: {
|
||||||
|
|||||||
Reference in New Issue
Block a user