Permanent room urls

This commit is contained in:
2024-08-16 22:26:00 +02:00
parent ad84e4626c
commit 55697e670d
20 changed files with 1001 additions and 31 deletions

View File

@@ -0,0 +1,33 @@
"use client";
import "@whereby.com/browser-sdk/embed";
import { useCallback, useEffect, useRef } from "react";
import useRoomMeeting from "../../rooms/useRoomMeeting";
export type RoomDetails = {
params: {
roomName: string;
};
};
export default function Room(details: RoomDetails) {
const wherebyRef = useRef<HTMLElement>(null);
const roomName = details.params.roomName;
const meeting = useRoomMeeting(roomName);
const roomUrl = meeting?.response?.host_room_url
? meeting?.response?.host_room_url
: meeting?.response?.room_url;
return (
<>
{roomUrl && (
<whereby-embed
ref={wherebyRef}
room={roomUrl}
style={{ width: "100%", height: "98%" }}
/>
)}
</>
);
}

View File

@@ -0,0 +1,171 @@
"use client";
import {
Box,
Button,
Card,
CardBody,
Flex,
FormControl,
FormHelperText,
FormLabel,
Grid,
Heading,
Input,
Link,
Modal,
ModalBody,
ModalCloseButton,
ModalContent,
ModalFooter,
ModalHeader,
ModalOverlay,
Spacer,
Spinner,
useDisclosure,
VStack,
Text,
Menu,
MenuButton,
MenuList,
MenuItem,
AlertDialog,
IconButton,
} from "@chakra-ui/react";
import NextLink from "next";
import React, { ReactNode, useState } from "react";
import { Container } from "@chakra-ui/react";
import { PlusSquareIcon } from "@chakra-ui/icons";
import useApi from "../../lib/useApi";
import useRoomList from "./useRoomList";
import { FaEllipsisVertical, FaTrash } from "react-icons/fa6";
import next from "next";
export default function RoomsList() {
const { isOpen, onOpen, onClose } = useDisclosure();
const [roomName, setRoomName] = useState("");
const api = useApi();
const [page, setPage] = useState<number>(1);
const { loading, response, refetch } = useRoomList(page);
const handleAddRoom = async () => {
try {
const response = await api?.v1RoomsCreate({
requestBody: { name: roomName },
});
setRoomName("");
refetch();
} catch (err) {}
onClose();
};
const handleDeleteRoom = async (roomId: string) => {
try {
const response = await api?.v1RoomsDelete({
roomId,
});
refetch();
} catch (err) {}
};
const handleRoomNameChange = (e) => {
setRoomName(e.target.value);
};
if (loading && !response)
return (
<Flex flexDir="column" align="center" justify="center" h="100%">
<Spinner size="xl" />
</Flex>
);
return (
<>
<Container maxW={"container.lg"}>
<Flex
flexDir="row"
justify="flex-end"
align="center"
flexWrap={"wrap-reverse"}
mb={2}
>
<Heading>Rooms</Heading>
<Spacer />
<Button colorScheme="blue" onClick={onOpen}>
Add Room
</Button>
<Modal isOpen={isOpen} onClose={onClose}>
<ModalOverlay />
<ModalContent>
<ModalHeader>Add Room</ModalHeader>
<ModalCloseButton />
<ModalBody>
<FormControl>
<FormLabel>Room name</FormLabel>
<Input
placeholder="room-name"
value={roomName}
onChange={handleRoomNameChange}
/>
<FormHelperText>Please enter room name</FormHelperText>
</FormControl>
</ModalBody>
<ModalFooter>
<Button variant="ghost" mr={3} onClick={onClose}>
Cancel
</Button>
<Button colorScheme="blue" onClick={handleAddRoom}>
Add
</Button>
</ModalFooter>
</ModalContent>
</Modal>
</Flex>
<VStack>
{response?.items && response.items.length > 0 ? (
response.items.map((room) => (
<Card w={"full"}>
<CardBody>
<Flex align={"center"}>
<Heading size="md">
<Link
// as={NextLink}
href={`/rooms/${room.name}`}
noOfLines={2}
>
{room.name}
</Link>
</Heading>
<Spacer />
<Menu closeOnSelect={true}>
<MenuButton
as={IconButton}
icon={<FaEllipsisVertical />}
aria-label="actions"
/>
<MenuList>
<MenuItem
onClick={() => handleDeleteRoom(room.id)}
icon={<FaTrash color={"red.500"} />}
>
Delete
</MenuItem>
</MenuList>
</Menu>
</Flex>
</CardBody>
</Card>
))
) : (
<Flex flexDir="column" align="center" justify="center" h="100%">
<Text>No rooms found</Text>
</Flex>
)}
</VStack>
</Container>
</>
);
}

View File

@@ -0,0 +1,47 @@
import { useEffect, useState } from "react";
import { useError } from "../../(errors)/errorContext";
import useApi from "../../lib/useApi";
import { Page_Room_ } from "../../api";
type RoomList = {
response: Page_Room_ | null;
loading: boolean;
error: Error | null;
refetch: () => void;
};
//always protected
const useRoomList = (page: number): RoomList => {
const [response, setResponse] = useState<Page_Room_ | null>(null);
const [loading, setLoading] = useState<boolean>(true);
const [error, setErrorState] = useState<Error | null>(null);
const { setError } = useError();
const api = useApi();
const [refetchCount, setRefetchCount] = useState(0);
const refetch = () => {
setLoading(true);
setRefetchCount(refetchCount + 1);
};
useEffect(() => {
if (!api) return;
setLoading(true);
api
.v1RoomsList({ page })
.then((response) => {
setResponse(response);
setLoading(false);
})
.catch((err) => {
setResponse(null);
setLoading(false);
setError(err);
setErrorState(err);
});
}, [!api, page, refetchCount]);
return { response, loading, error, refetch };
};
export default useRoomList;

View File

@@ -0,0 +1,70 @@
import { useEffect, useState } from "react";
import { useError } from "../../(errors)/errorContext";
import { GetMeeting } from "../../api";
import { shouldShowError } from "../../lib/errorUtils";
import useApi from "../../lib/useApi";
type ErrorMeeting = {
error: Error;
loading: false;
response: null;
reload: () => void;
};
type LoadingMeeting = {
response: null;
loading: true;
error: false;
reload: () => void;
};
type SuccessMeeting = {
response: GetMeeting;
loading: false;
error: null;
reload: () => void;
};
const useRoomMeeting = (
roomName: string | null | undefined,
): ErrorMeeting | LoadingMeeting | SuccessMeeting => {
const [response, setResponse] = useState<GetMeeting | null>(null);
const [loading, setLoading] = useState<boolean>(true);
const [error, setErrorState] = useState<Error | null>(null);
const [reload, setReload] = useState(0);
const { setError } = useError();
const api = useApi();
const reloadHandler = () => setReload((prev) => prev + 1);
useEffect(() => {
if (!roomName || !api) return;
if (!response) {
setLoading(true);
}
api
.v1RoomsCreateMeeting({ roomName })
.then((result) => {
setResponse(result);
setLoading(false);
console.debug("Meeting Loaded:", result);
})
.catch((error) => {
const shouldShowHuman = shouldShowError(error);
if (shouldShowHuman) {
setError(error, "There was an error loading the meeting");
} else {
setError(error);
}
setErrorState(error);
});
}, [roomName, !api, reload]);
return { response, loading, error, reload: reloadHandler } as
| ErrorMeeting
| LoadingMeeting
| SuccessMeeting;
};
export default useRoomMeeting;