+export const Card = ({ children, className }) => (
+
{children}
+
+ );
+};
+
+WaitingParticipantRow.propTypes = {
+ participant: PropTypes.object,
+};
+
+export default WaitingParticipantRow;
diff --git a/dailyjs/shared/components/WaitingRoom/WaitingRoomModal.js b/dailyjs/shared/components/WaitingRoom/WaitingRoomModal.js
new file mode 100644
index 0000000..ce65263
--- /dev/null
+++ b/dailyjs/shared/components/WaitingRoom/WaitingRoomModal.js
@@ -0,0 +1,45 @@
+import React from 'react';
+import Modal from '@dailyjs/shared/components/Modal';
+import { useWaitingRoom } from '@dailyjs/shared/contexts/WaitingRoomProvider';
+import PropTypes from 'prop-types';
+import { Button } from '../Button';
+import { WaitingParticipantRow } from './WaitingParticipantRow';
+
+export const WaitingRoomModal = ({ onClose }) => {
+ const { denyAccess, grantAccess, waitingParticipants } = useWaitingRoom();
+
+ const handleAllowAllClick = (close) => {
+ grantAccess('all');
+ close();
+ };
+ const handleDenyAllClick = (close) => {
+ denyAccess('all');
+ close();
+ };
+
+ return (
+
onClose()}
+ actions={[
+ ,
+ ,
+ ]}
+ >
+ {waitingParticipants.map((p) => (
+
+ ))}
+
+ );
+};
+
+WaitingRoomModal.propTypes = {
+ onClose: PropTypes.func,
+};
+
+export default WaitingRoomModal;
diff --git a/dailyjs/shared/components/WaitingRoom/WaitingRoomNotification.js b/dailyjs/shared/components/WaitingRoom/WaitingRoomNotification.js
new file mode 100644
index 0000000..aeb9df3
--- /dev/null
+++ b/dailyjs/shared/components/WaitingRoom/WaitingRoomNotification.js
@@ -0,0 +1,101 @@
+import React, { useEffect, useState } from 'react';
+
+import { useCallState } from '../../contexts/CallProvider';
+import { useWaitingRoom } from '../../contexts/WaitingRoomProvider';
+import { Button } from '../Button';
+import { Card, CardBody, CardFooter } from '../Card';
+
+export const WaitingRoomNotification = () => {
+ const { callObject } = useCallState();
+ const {
+ denyAccess,
+ grantAccess,
+ showModal,
+ setShowModal,
+ waitingParticipants,
+ } = useWaitingRoom();
+ const [showNotification, setShowNotification] = useState(false);
+
+ /**
+ * Show notification when waiting participants change.
+ */
+ useEffect(() => {
+ if (showModal) return false;
+
+ const handleWaitingParticipantAdded = () => {
+ setShowNotification(
+ Object.keys(callObject.waitingParticipants()).length > 0
+ );
+ };
+
+ callObject.on('waiting-participant-added', handleWaitingParticipantAdded);
+ return () => {
+ callObject.off(
+ 'waiting-participant-added',
+ handleWaitingParticipantAdded
+ );
+ };
+ }, [callObject, showModal]);
+
+ /**
+ * Hide notification when people panel is opened.
+ */
+ useEffect(() => {
+ if (showModal) setShowNotification(false);
+ }, [showModal]);
+
+ if (!showNotification || waitingParticipants.length === 0) return null;
+
+ const hasMultiplePeopleWaiting = waitingParticipants.length > 1;
+
+ const handleViewAllClick = () => {
+ setShowModal(true);
+ setShowNotification(false);
+ };
+ const handleAllowClick = () => {
+ grantAccess(waitingParticipants[0].id);
+ };
+ const handleDenyClick = () => {
+ denyAccess(hasMultiplePeopleWaiting ? 'all' : waitingParticipants[0].id);
+ };
+ // const handleClose = () => setShowNotification(false);
+
+ return (
+
+
+ {hasMultiplePeopleWaiting
+ ? `${waitingParticipants.length} people would like to join the call`
+ : `${waitingParticipants[0].name} would like to join the call`}
+
+
+ {hasMultiplePeopleWaiting ? (
+
+ ) : (
+
+ )}
+
+
+
+
+ );
+};
+
+export default WaitingRoomNotification;
diff --git a/dailyjs/shared/components/WaitingRoom/index.js b/dailyjs/shared/components/WaitingRoom/index.js
new file mode 100644
index 0000000..4041549
--- /dev/null
+++ b/dailyjs/shared/components/WaitingRoom/index.js
@@ -0,0 +1,3 @@
+export { WaitingRoomModal } from './WaitingRoomModal';
+export { WaitingRoomNotification } from './WaitingRoomNotification';
+export { WaitingParticipantRow } from './WaitingParticipantRow';
diff --git a/dailyjs/shared/contexts/WaitingRoomProvider.js b/dailyjs/shared/contexts/WaitingRoomProvider.js
new file mode 100644
index 0000000..978036a
--- /dev/null
+++ b/dailyjs/shared/contexts/WaitingRoomProvider.js
@@ -0,0 +1,111 @@
+import React, {
+ createContext,
+ useCallback,
+ useContext,
+ useEffect,
+ useState,
+} from 'react';
+import PropTypes from 'prop-types';
+import { useCallState } from './CallProvider';
+
+const WaitingRoomContext = createContext(null);
+
+export const WaitingRoomProvider = ({ children }) => {
+ const { callObject } = useCallState();
+ const [waitingParticipants, setWaitingParticipants] = useState([]);
+ const [showModal, setShowModal] = useState(false);
+
+ const handleWaitingParticipantEvent = useCallback(() => {
+ if (!callObject) return;
+
+ const waiting = Object.entries(callObject.waitingParticipants());
+
+ console.log(`🚪 ${waiting.length} participant(s) waiting for access`);
+
+ setWaitingParticipants((wp) =>
+ waiting.map(([pid, p]) => {
+ const prevWP = wp.find(({ id }) => id === pid);
+ return {
+ ...p,
+ joinDate: prevWP?.joinDate ?? new Date(),
+ };
+ })
+ );
+ }, [callObject]);
+
+ useEffect(() => {
+ if (waitingParticipants.length === 0) {
+ setShowModal(false);
+ }
+ }, [waitingParticipants]);
+
+ useEffect(() => {
+ if (!callObject) return false;
+
+ console.log('🚪 Waiting room provider listening for requests');
+
+ const events = [
+ 'waiting-participant-added',
+ 'waiting-participant-updated',
+ 'waiting-participant-removed',
+ ];
+
+ events.forEach((e) => callObject.on(e, handleWaitingParticipantEvent));
+
+ return () =>
+ events.forEach((e) => callObject.off(e, handleWaitingParticipantEvent));
+ }, [callObject, handleWaitingParticipantEvent]);
+
+ const updateWaitingParticipant = (id, grantRequestedAccess) => {
+ if (!waitingParticipants.some((p) => p.id === id)) return;
+ callObject.updateWaitingParticipant(id, {
+ grantRequestedAccess,
+ });
+ setWaitingParticipants((wp) => wp.filter((p) => p.id !== id));
+ };
+
+ const updateAllWaitingParticipants = (grantRequestedAccess) => {
+ if (!waitingParticipants.length) return;
+ callObject.updateWaitingParticipants({
+ '*': {
+ grantRequestedAccess,
+ },
+ });
+ setWaitingParticipants([]);
+ };
+
+ const grantAccess = (id = 'all') => {
+ if (id === 'all') {
+ updateAllWaitingParticipants(true);
+ return;
+ }
+ updateWaitingParticipant(id, true);
+ };
+ const denyAccess = (id = 'all') => {
+ if (id === 'all') {
+ updateAllWaitingParticipants(false);
+ return;
+ }
+ updateWaitingParticipant(id, false);
+ };
+
+ return (
+
+ {children}
+
+ );
+};
+
+WaitingRoomProvider.propTypes = {
+ children: PropTypes.node,
+};
+
+export const useWaitingRoom = () => useContext(WaitingRoomContext);