mirror of
https://github.com/suitenumerique/meet.git
synced 2026-08-06 17:07:46 +00:00
✨(sdk) add a room configuration popup from CreateMeetingButton
Introduce a room configuration popup opened from the SDK's CreateMeetingButton, laid out like the Google Meet "call options" dialog: logo header, grey section bands, and a footer bar with the close action. Like CreatePopup, it runs in a dedicated popup window so it can access session cookies, which would be blocked in an iframe. If the user is not authenticated, they are redirected to login and come back to this popup afterwards. Permissions are enforced server-side. The room is fetched with the user's session, and settings are only shown when the room is administrable by this user. Since #1482 removed the is_administrable flag from the room serializer (roles now live in the LiveKit participant attributes, only available in-meeting), administrability is detected here through the presence of the `accesses` field, which the backend only serializes for administrators and owners. The PATCH endpoint enforces the same permissions server-side regardless. The settings mirror the in-room Admin panel. Unlike the Admin panel, there is no LiveKit connection here, so changes are only persisted in the room configuration (and applied when a session starts): participants of an ongoing session are not live-synced or notified.
This commit is contained in:
committed by
aleb_the_flash
parent
f49c61d9bf
commit
0536896373
@@ -19,6 +19,7 @@ and this project adheres to
|
||||
- ✨(frontend) expose media state to external gateways
|
||||
- ✨(frontend) add connection test feature
|
||||
- ✨(sdk) allow passing a background color to the calendar iframe
|
||||
- ✨(sdk) add a room configuration popup from CreateMeetingButton
|
||||
|
||||
### Changed
|
||||
|
||||
|
||||
@@ -18,6 +18,14 @@ export type RoomConfiguration = {
|
||||
everyone_can_mute?: boolean | null
|
||||
}
|
||||
|
||||
export type ParticipantRole = 'member' | 'administrator' | 'owner'
|
||||
export type AssignableParticipantRole = Exclude<ParticipantRole, 'owner'>
|
||||
|
||||
export type ApiResourceAccess = {
|
||||
id: string
|
||||
role: ParticipantRole
|
||||
}
|
||||
|
||||
export type ApiRoom = {
|
||||
id: string
|
||||
name: string
|
||||
@@ -27,7 +35,12 @@ export type ApiRoom = {
|
||||
access_level: ApiAccessLevel
|
||||
livekit?: ApiLiveKit
|
||||
configuration?: RoomConfiguration
|
||||
/**
|
||||
* Only present in the API response when the requesting user is an
|
||||
* administrator or owner of the room (see RoomSerializer.to_representation
|
||||
* in the backend). Its presence can therefore be used to detect
|
||||
* administrability outside of a LiveKit session, where the room_role
|
||||
* participant attribute is not available.
|
||||
*/
|
||||
accesses?: ApiResourceAccess[]
|
||||
}
|
||||
|
||||
export type ParticipantRole = 'member' | 'administrator' | 'owner'
|
||||
export type AssignableParticipantRole = Exclude<ParticipantRole, 'owner'>
|
||||
|
||||
@@ -4,7 +4,7 @@ import { Link } from 'react-aria-components'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { HStack, VStack } from '@/styled-system/jsx'
|
||||
import { css } from '@/styled-system/css'
|
||||
import { RiCloseLine, RiFileCopyLine } from '@remixicon/react'
|
||||
import { RiCloseLine, RiFileCopyLine, RiSettings3Line } from '@remixicon/react'
|
||||
import { Text } from '@/primitives'
|
||||
import { Spinner } from '@/primitives/Spinner'
|
||||
import { buttonRecipe } from '@/primitives/buttonRecipe'
|
||||
@@ -37,6 +37,11 @@ const CreateMeetingButton = () => {
|
||||
initialRoom
|
||||
)
|
||||
|
||||
const [isRoomCreatedInSession, setIsRoomCreatedInSession] = useState(false)
|
||||
|
||||
const showSettingsButton =
|
||||
isRoomCreatedInSession || searchParams.get('settings') === 'true'
|
||||
|
||||
const { data } = useRoomCreationCallback({ callbackId })
|
||||
|
||||
const roomUrl = useMemo(() => {
|
||||
@@ -74,6 +79,7 @@ const CreateMeetingButton = () => {
|
||||
useEffect(() => {
|
||||
if (!data?.room?.slug) return
|
||||
setRoom(data.room)
|
||||
setIsRoomCreatedInSession(true)
|
||||
setCallbackId(undefined)
|
||||
setIsPending(false)
|
||||
popupManager.sendRoomData({
|
||||
@@ -89,6 +95,7 @@ const CreateMeetingButton = () => {
|
||||
(id) => setCallbackId(id),
|
||||
(data) => {
|
||||
setRoom(data)
|
||||
setIsRoomCreatedInSession(true)
|
||||
setIsPending(false)
|
||||
}
|
||||
)
|
||||
@@ -98,6 +105,7 @@ const CreateMeetingButton = () => {
|
||||
|
||||
const resetState = () => {
|
||||
setRoom(undefined)
|
||||
setIsRoomCreatedInSession(false)
|
||||
setCallbackId(undefined)
|
||||
setIsPending(false)
|
||||
popupManager.clearState()
|
||||
@@ -158,14 +166,25 @@ const CreateMeetingButton = () => {
|
||||
{t('joinButton')}
|
||||
</Link>
|
||||
<HStack gap={0}>
|
||||
{showSettingsButton && (
|
||||
<Button
|
||||
variant="quaternaryText"
|
||||
square
|
||||
icon={<RiSettings3Line />}
|
||||
aria-label={t('settingsTooltip')}
|
||||
onPress={() => {
|
||||
popupManager.createSettingsPopupWindow(room.slug, () => {})
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
<Button
|
||||
variant="quaternaryText"
|
||||
square
|
||||
icon={<RiFileCopyLine />}
|
||||
tooltip={t('copyLinkTooltip')}
|
||||
onPress={() => {
|
||||
navigator.clipboard.writeText(roomUrl)
|
||||
}}
|
||||
aria-label={t('copyLinkTooltip')}
|
||||
/>
|
||||
{searchParams.get('readOnly') === 'false' && (
|
||||
<Button
|
||||
|
||||
@@ -0,0 +1,383 @@
|
||||
import { useEffect, useMemo, type ReactNode } from 'react'
|
||||
import { useSearchParams } from 'wouter'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { useQuery } from '@tanstack/react-query'
|
||||
import { Track } from 'livekit-client'
|
||||
import { css } from '@/styled-system/css'
|
||||
import { Button, Field, H, Text } from '@/primitives'
|
||||
import { Spinner } from '@/primitives/Spinner'
|
||||
import { keys } from '@/api/queryKeys'
|
||||
import { queryClient } from '@/api/queryClient'
|
||||
import { useConfig } from '@/api/useConfig'
|
||||
import { useUser } from '@/features/auth/api/useUser'
|
||||
import { authUrl } from '@/features/auth/utils/authUrl'
|
||||
import { fetchRoom } from '@/features/rooms/api/fetchRoom'
|
||||
import { usePatchRoom } from '@/features/rooms/api/patchRoom'
|
||||
import { ApiAccessLevel } from '@/features/rooms/api/ApiRoom'
|
||||
import { updatePublishSources } from '@/features/rooms/livekit/hooks/usePublishSourcesManager'
|
||||
import { isSubsetOf } from '@/features/rooms/utils/isSubsetOf'
|
||||
|
||||
type Source = Track.Source
|
||||
|
||||
const SectionHeader = ({ children }: { children: ReactNode }) => (
|
||||
<div
|
||||
className={css({
|
||||
backgroundColor: 'greyscale.50',
|
||||
borderTopWidth: '1px',
|
||||
borderTopStyle: 'solid',
|
||||
borderTopColor: 'greyscale.250',
|
||||
borderBottomWidth: '1px',
|
||||
borderBottomStyle: 'solid',
|
||||
borderBottomColor: 'greyscale.250',
|
||||
padding: '0.75rem 1.5rem',
|
||||
})}
|
||||
>
|
||||
<H
|
||||
lvl={2}
|
||||
margin={false}
|
||||
className={css({
|
||||
fontWeight: 500,
|
||||
fontSize: '1.125rem',
|
||||
})}
|
||||
>
|
||||
{children}
|
||||
</H>
|
||||
</div>
|
||||
)
|
||||
|
||||
const SectionBody = ({ children }: { children: ReactNode }) => (
|
||||
<div
|
||||
className={css({
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
padding: '1rem 1.5rem 1.5rem',
|
||||
})}
|
||||
>
|
||||
{children}
|
||||
</div>
|
||||
)
|
||||
|
||||
const SettingsPopup = () => {
|
||||
const { t } = useTranslation('sdk', { keyPrefix: 'roomSettings' })
|
||||
const { t: tRooms } = useTranslation('rooms', { keyPrefix: 'admin' })
|
||||
|
||||
const [searchParams] = useSearchParams()
|
||||
const roomSlug = searchParams.get('slug')?.trim()
|
||||
|
||||
const { isLoggedIn } = useUser({ fetchUserOptions: { attemptSilent: false } })
|
||||
|
||||
useEffect(() => {
|
||||
if (isLoggedIn === false) {
|
||||
// returnTo defaults to the current URL, so the user comes back to this
|
||||
// popup (with the slug preserved) once authentication completes.
|
||||
window.location.href = authUrl({})
|
||||
}
|
||||
}, [isLoggedIn])
|
||||
|
||||
const {
|
||||
data: room,
|
||||
isLoading,
|
||||
isError,
|
||||
} = useQuery({
|
||||
queryKey: [keys.room, roomSlug],
|
||||
queryFn: () => fetchRoom({ roomId: roomSlug as string }),
|
||||
enabled: !!isLoggedIn && !!roomSlug,
|
||||
retry: false,
|
||||
})
|
||||
|
||||
const { mutateAsync: patchRoom } = usePatchRoom()
|
||||
const { data: configData } = useConfig()
|
||||
|
||||
const configuration = room?.configuration
|
||||
|
||||
const currentSources = useMemo(() => {
|
||||
const defaultSources = configData?.livekit?.default_sources ?? []
|
||||
|
||||
if (
|
||||
configuration?.can_publish_sources == undefined ||
|
||||
!Array.isArray(configuration?.can_publish_sources)
|
||||
) {
|
||||
return defaultSources
|
||||
}
|
||||
return configuration.can_publish_sources
|
||||
}, [configData, configuration?.can_publish_sources])
|
||||
|
||||
const patchConfiguration = (
|
||||
newConfiguration: NonNullable<typeof configuration>
|
||||
) => {
|
||||
if (!roomSlug) return
|
||||
patchRoom({
|
||||
roomId: roomSlug,
|
||||
room: { configuration: newConfiguration },
|
||||
})
|
||||
.then((updatedRoom) => {
|
||||
queryClient.setQueryData([keys.room, roomSlug], updatedRoom)
|
||||
})
|
||||
.catch((e) => console.error(e))
|
||||
}
|
||||
|
||||
const updateSource = (sources: Source[], enabled: boolean) => {
|
||||
patchConfiguration({
|
||||
...configuration,
|
||||
can_publish_sources: updatePublishSources(
|
||||
currentSources,
|
||||
sources,
|
||||
enabled
|
||||
),
|
||||
})
|
||||
}
|
||||
|
||||
const toggleMicrophone = (enabled: boolean) =>
|
||||
updateSource([Track.Source.Microphone], enabled)
|
||||
const toggleCamera = (enabled: boolean) =>
|
||||
updateSource([Track.Source.Camera], enabled)
|
||||
const toggleScreenShare = (enabled: boolean) =>
|
||||
updateSource(
|
||||
[Track.Source.ScreenShare, Track.Source.ScreenShareAudio],
|
||||
enabled
|
||||
)
|
||||
const toggleMuting = (enabled: boolean) =>
|
||||
patchConfiguration({
|
||||
...configuration,
|
||||
everyone_can_mute: enabled,
|
||||
})
|
||||
|
||||
const isMicrophoneEnabled = isSubsetOf(
|
||||
[Track.Source.Microphone],
|
||||
currentSources
|
||||
)
|
||||
const isCameraEnabled = isSubsetOf([Track.Source.Camera], currentSources)
|
||||
const isScreenShareEnabled = isSubsetOf(
|
||||
[Track.Source.ScreenShare, Track.Source.ScreenShareAudio],
|
||||
currentSources
|
||||
)
|
||||
const isMutingEnabled = configuration?.everyone_can_mute ?? true
|
||||
|
||||
const renderCentered = (children: ReactNode) => (
|
||||
<div
|
||||
className={css({
|
||||
display: 'flex',
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center',
|
||||
height: '100%',
|
||||
width: '100%',
|
||||
padding: '1.5rem',
|
||||
})}
|
||||
>
|
||||
{children}
|
||||
</div>
|
||||
)
|
||||
|
||||
if (!roomSlug || isError) {
|
||||
return renderCentered(
|
||||
<Text variant="note" margin={false}>
|
||||
{t('error')}
|
||||
</Text>
|
||||
)
|
||||
}
|
||||
|
||||
if (isLoggedIn === undefined || isLoggedIn === false || isLoading || !room) {
|
||||
return renderCentered(<Spinner />)
|
||||
}
|
||||
|
||||
const isAdministrable = room.accesses !== undefined
|
||||
|
||||
if (!isAdministrable) {
|
||||
return renderCentered(
|
||||
<Text variant="note" margin={false}>
|
||||
{t('notAllowed')}
|
||||
</Text>
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<div
|
||||
className={css({
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
height: '100%',
|
||||
width: '100%',
|
||||
minHeight: 0,
|
||||
})}
|
||||
>
|
||||
<header
|
||||
className={css({
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
alignItems: 'flex-start',
|
||||
gap: '0.5rem',
|
||||
padding: '1.5rem',
|
||||
borderBottomWidth: '1px',
|
||||
borderBottomStyle: 'solid',
|
||||
borderBottomColor: 'greyscale.250',
|
||||
})}
|
||||
>
|
||||
<img
|
||||
src="/assets/logo.svg"
|
||||
alt=""
|
||||
className={css({
|
||||
maxHeight: '40px',
|
||||
flexShrink: 0,
|
||||
})}
|
||||
/>
|
||||
<div className={css({ display: 'flex', flexDirection: 'column' })}>
|
||||
<H
|
||||
lvl={1}
|
||||
margin={false}
|
||||
className={css({
|
||||
fontWeight: 500,
|
||||
})}
|
||||
>
|
||||
{t('title')}
|
||||
</H>
|
||||
<Text variant="smNote" margin={false}>
|
||||
{roomSlug}
|
||||
</Text>
|
||||
</div>
|
||||
</header>
|
||||
<div
|
||||
className={css({
|
||||
flexGrow: 1,
|
||||
overflowY: 'auto',
|
||||
minHeight: 0,
|
||||
})}
|
||||
>
|
||||
<SectionHeader>{tRooms('moderation.title')}</SectionHeader>
|
||||
<SectionBody>
|
||||
<Text
|
||||
variant="note"
|
||||
wrap="balance"
|
||||
className={css({
|
||||
textStyle: 'sm',
|
||||
})}
|
||||
margin={'md'}
|
||||
>
|
||||
{tRooms('moderation.description')}
|
||||
</Text>
|
||||
<div
|
||||
className={css({
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
gap: '0.75rem',
|
||||
})}
|
||||
>
|
||||
<Field
|
||||
type="switch"
|
||||
label={tRooms('moderation.microphone.label')}
|
||||
description={tRooms('moderation.microphone.description')}
|
||||
isSelected={isMicrophoneEnabled}
|
||||
onChange={toggleMicrophone}
|
||||
wrapperProps={{
|
||||
noMargin: true,
|
||||
fullWidth: true,
|
||||
}}
|
||||
/>
|
||||
<Field
|
||||
type="switch"
|
||||
label={tRooms('moderation.camera.label')}
|
||||
description={tRooms('moderation.camera.description')}
|
||||
isSelected={isCameraEnabled}
|
||||
onChange={toggleCamera}
|
||||
wrapperProps={{
|
||||
noMargin: true,
|
||||
fullWidth: true,
|
||||
}}
|
||||
/>
|
||||
<Field
|
||||
type="switch"
|
||||
label={tRooms('moderation.screenshare.label')}
|
||||
description={tRooms('moderation.screenshare.description')}
|
||||
isSelected={isScreenShareEnabled}
|
||||
onChange={toggleScreenShare}
|
||||
wrapperProps={{
|
||||
noMargin: true,
|
||||
fullWidth: true,
|
||||
}}
|
||||
/>
|
||||
<Field
|
||||
type="switch"
|
||||
label={tRooms('moderation.mute.label')}
|
||||
description={tRooms('moderation.mute.description')}
|
||||
isSelected={isMutingEnabled}
|
||||
onChange={toggleMuting}
|
||||
wrapperProps={{
|
||||
noMargin: true,
|
||||
fullWidth: true,
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</SectionBody>
|
||||
<SectionHeader>{tRooms('access.title')}</SectionHeader>
|
||||
<SectionBody>
|
||||
<Text
|
||||
variant="note"
|
||||
wrap="balance"
|
||||
className={css({
|
||||
textStyle: 'sm',
|
||||
})}
|
||||
margin={'md'}
|
||||
>
|
||||
{tRooms('access.description')}
|
||||
</Text>
|
||||
<Field
|
||||
type="radioGroup"
|
||||
label={tRooms('access.type')}
|
||||
aria-label={tRooms('access.type')}
|
||||
labelProps={{
|
||||
className: css({
|
||||
fontSize: '1rem',
|
||||
paddingBottom: '1rem',
|
||||
}),
|
||||
}}
|
||||
value={room.access_level}
|
||||
onChange={(value) =>
|
||||
patchRoom({
|
||||
roomId: roomSlug,
|
||||
room: { access_level: value as ApiAccessLevel },
|
||||
})
|
||||
.then((updatedRoom) => {
|
||||
queryClient.setQueryData([keys.room, roomSlug], updatedRoom)
|
||||
})
|
||||
.catch((e) => console.error(e))
|
||||
}
|
||||
items={[
|
||||
{
|
||||
value: ApiAccessLevel.PUBLIC,
|
||||
label: tRooms('access.levels.public.label'),
|
||||
description: tRooms('access.levels.public.description'),
|
||||
},
|
||||
{
|
||||
value: ApiAccessLevel.TRUSTED,
|
||||
label: tRooms('access.levels.trusted.label'),
|
||||
description: tRooms('access.levels.trusted.description'),
|
||||
},
|
||||
{
|
||||
value: ApiAccessLevel.RESTRICTED,
|
||||
label: tRooms('access.levels.restricted.label'),
|
||||
description: tRooms('access.levels.restricted.description'),
|
||||
},
|
||||
]}
|
||||
/>
|
||||
</SectionBody>
|
||||
</div>
|
||||
<footer
|
||||
className={css({
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'space-between',
|
||||
gap: '1rem',
|
||||
padding: '1rem 1.5rem',
|
||||
borderTopWidth: '1px',
|
||||
borderTopStyle: 'solid',
|
||||
borderTopColor: 'greyscale.250',
|
||||
})}
|
||||
>
|
||||
<Button size="sm" onPress={() => window.close()}>
|
||||
{t('closeButton')}
|
||||
</Button>
|
||||
</footer>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default SettingsPopup
|
||||
@@ -24,6 +24,20 @@ export class PopupManager {
|
||||
}
|
||||
}
|
||||
|
||||
public createSettingsPopupWindow(roomSlug: string, onFailure: () => void) {
|
||||
const popupWindow = window.open(
|
||||
`${window.location.origin}/sdk/settings-popup?slug=${encodeURIComponent(roomSlug)}`,
|
||||
'SettingsPopupWindow',
|
||||
`status=no,location=no,toolbar=no,menubar=no,width=600,height=800,left=100,top=100, resizable=yes,scrollbars=yes`
|
||||
)
|
||||
|
||||
if (popupWindow) {
|
||||
popupWindow.focus()
|
||||
} else {
|
||||
onFailure()
|
||||
}
|
||||
}
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
private messageParent(type: ClientMessageType, data: any) {
|
||||
window?.parent.postMessage(
|
||||
|
||||
@@ -5,6 +5,14 @@
|
||||
"copyLinkTooltip": "Link kopieren",
|
||||
"resetLabel": "Zurücksetzen",
|
||||
"participantLimit": "Bis zu 150 Teilnehmende.",
|
||||
"popupBlocked": "Popup wurde blockiert. Bitte erlaube Popups für diese Website."
|
||||
"popupBlocked": "Popup wurde blockiert. Bitte erlaube Popups für diese Website.",
|
||||
"settingsTooltip": "Besprechungseinstellungen"
|
||||
},
|
||||
"roomSettings": {
|
||||
"title": "Besprechungseinstellungen",
|
||||
"description": "Konfiguriere die Besprechung {{roomSlug}}.",
|
||||
"notAllowed": "Du hast keine Berechtigung, die Einstellungen dieser Besprechung zu ändern.",
|
||||
"error": "Die Besprechungseinstellungen konnten nicht geladen werden.",
|
||||
"closeButton": "Schließen"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,6 +5,14 @@
|
||||
"copyLinkTooltip": "Copy link",
|
||||
"resetLabel": "Reset",
|
||||
"participantLimit": "Up to 150 participants.",
|
||||
"popupBlocked": "Popup was blocked. Please allow popups for this site."
|
||||
"popupBlocked": "Popup was blocked. Please allow popups for this site.",
|
||||
"settingsTooltip": "Meeting settings"
|
||||
},
|
||||
"roomSettings": {
|
||||
"title": "Meeting settings",
|
||||
"description": "Configure the meeting {{roomSlug}}.",
|
||||
"notAllowed": "You don't have permission to modify this meeting's settings.",
|
||||
"error": "Unable to load meeting settings.",
|
||||
"closeButton": "Close"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,6 +5,14 @@
|
||||
"copyLinkTooltip": "Copier le lien",
|
||||
"resetLabel": "Réinitialiser",
|
||||
"participantLimit": "Jusqu'à 150 participants.",
|
||||
"popupBlocked": "La fenêtre pop-up a été bloquée. Veuillez autoriser les pop-ups pour ce site."
|
||||
"popupBlocked": "La fenêtre pop-up a été bloquée. Veuillez autoriser les pop-ups pour ce site.",
|
||||
"settingsTooltip": "Paramètres de la réunion"
|
||||
},
|
||||
"roomSettings": {
|
||||
"title": "Paramètres de la réunion",
|
||||
"description": "Configurez la réunion {{roomSlug}}.",
|
||||
"notAllowed": "Vous n'avez pas les droits pour modifier les paramètres de cette réunion.",
|
||||
"error": "Impossible de charger les paramètres de la réunion.",
|
||||
"closeButton": "Fermer"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,6 +5,14 @@
|
||||
"copyLinkTooltip": "Link kopiëren",
|
||||
"resetLabel": "Resetten",
|
||||
"participantLimit": "Tot 150 deelnemers.",
|
||||
"popupBlocked": "Pop-up werd geblokkeerd. Sta pop-ups toe voor deze site."
|
||||
"popupBlocked": "Pop-up werd geblokkeerd. Sta pop-ups toe voor deze site.",
|
||||
"settingsTooltip": "Vergaderinstellingen"
|
||||
},
|
||||
"roomSettings": {
|
||||
"title": "Vergaderinstellingen",
|
||||
"description": "Configureer de vergadering {{roomSlug}}.",
|
||||
"notAllowed": "Je hebt geen toestemming om de instellingen van deze vergadering te wijzigen.",
|
||||
"error": "De vergaderinstellingen konden niet worden geladen.",
|
||||
"closeButton": "Sluiten"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,6 +9,7 @@ const CreatePopup = lazy(() => import('@/features/sdk/routes/CreatePopup'))
|
||||
const CreateMeetingButton = lazy(
|
||||
() => import('@/features/sdk/routes/CreateMeetingButton')
|
||||
)
|
||||
const SettingsPopup = lazy(() => import('@/features/sdk/routes/SettingsPopup'))
|
||||
const LegalTermsRoute = lazy(
|
||||
() => import('@/features/legalsTerms/LegalTermsRoute')
|
||||
)
|
||||
@@ -36,6 +37,7 @@ export const routes: Record<
|
||||
| 'termsOfService'
|
||||
| 'sdkCreatePopup'
|
||||
| 'sdkCreateButton'
|
||||
| 'sdkSettingsPopup'
|
||||
| 'recordingDownload',
|
||||
{
|
||||
name: RouteName
|
||||
@@ -91,6 +93,11 @@ export const routes: Record<
|
||||
path: '/sdk/create-button',
|
||||
Component: CreateMeetingButton,
|
||||
},
|
||||
sdkSettingsPopup: {
|
||||
name: 'sdkSettingsPopup',
|
||||
path: '/sdk/settings-popup',
|
||||
Component: SettingsPopup,
|
||||
},
|
||||
recordingDownload: {
|
||||
name: 'recordingDownload',
|
||||
path: /^\/recording\/(?<recordingId>[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12})$/,
|
||||
|
||||
Reference in New Issue
Block a user