✨(rooms) show an invite dialog when creating a room
for now the dialog appears as a regular dialog with an overlay and all, would be better as less intrusive. but good as is as a first step
This commit is contained in:
@@ -1,22 +1,25 @@
|
||||
import { useMemo } from "react";
|
||||
import { useMemo, useState } from 'react'
|
||||
import { useQuery } from '@tanstack/react-query'
|
||||
import {
|
||||
LiveKitRoom,
|
||||
VideoConference,
|
||||
type LocalUserChoices,
|
||||
} from '@livekit/components-react'
|
||||
import { Room, RoomOptions } from "livekit-client";
|
||||
import { Room, RoomOptions } from 'livekit-client'
|
||||
import { keys } from '@/api/queryKeys'
|
||||
import { navigateTo } from '@/navigation/navigateTo'
|
||||
import { QueryAware } from '@/layout/QueryAware'
|
||||
import { fetchRoom } from '../api/fetchRoom'
|
||||
import { InviteDialog } from './InviteDialog'
|
||||
|
||||
export const Conference = ({
|
||||
roomId,
|
||||
userConfig,
|
||||
mode = 'join',
|
||||
}: {
|
||||
roomId: string
|
||||
userConfig: LocalUserChoices
|
||||
mode?: 'join' | 'create'
|
||||
}) => {
|
||||
const { status, data } = useQuery({
|
||||
queryKey: [keys.room, roomId, userConfig.username],
|
||||
@@ -39,7 +42,9 @@ export const Conference = ({
|
||||
// do not rely on the userConfig object directly as its reference may change on every render
|
||||
}, [userConfig.videoDeviceId, userConfig.audioDeviceId])
|
||||
|
||||
const room = useMemo(() => new Room(roomOptions), [roomOptions]);
|
||||
const room = useMemo(() => new Room(roomOptions), [roomOptions])
|
||||
|
||||
const [showInviteDialog, setShowInviteDialog] = useState(mode === 'create')
|
||||
|
||||
return (
|
||||
<QueryAware status={status}>
|
||||
@@ -55,6 +60,14 @@ export const Conference = ({
|
||||
}}
|
||||
>
|
||||
<VideoConference />
|
||||
{showInviteDialog && (
|
||||
<InviteDialog
|
||||
isOpen={showInviteDialog}
|
||||
onOpenChange={setShowInviteDialog}
|
||||
roomId={roomId}
|
||||
onClose={() => setShowInviteDialog(false)}
|
||||
/>
|
||||
)}
|
||||
</LiveKitRoom>
|
||||
</QueryAware>
|
||||
)
|
||||
|
||||
58
src/frontend/src/features/rooms/components/InviteDialog.tsx
Normal file
58
src/frontend/src/features/rooms/components/InviteDialog.tsx
Normal file
@@ -0,0 +1,58 @@
|
||||
import { useEffect, useState } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { getRouteUrl } from '@/navigation/getRouteUrl'
|
||||
import { Div, Button, Dialog, Input, type DialogProps } from '@/primitives'
|
||||
import { HStack } from '@/styled-system/jsx'
|
||||
|
||||
export const InviteDialog = ({
|
||||
roomId,
|
||||
...dialogProps
|
||||
}: { roomId: string } & Omit<DialogProps, 'title'>) => {
|
||||
const { t } = useTranslation('rooms')
|
||||
const roomUrl = getRouteUrl('room', roomId)
|
||||
|
||||
const copyLabel = t('shareDialog.copy')
|
||||
const copiedLabel = t('shareDialog.copied')
|
||||
const [copyLinkLabel, setCopyLinkLabel] = useState(copyLabel)
|
||||
useEffect(() => {
|
||||
if (copyLinkLabel == copiedLabel) {
|
||||
const timeout = setTimeout(() => {
|
||||
setCopyLinkLabel(copyLabel)
|
||||
}, 5000)
|
||||
return () => {
|
||||
clearTimeout(timeout)
|
||||
}
|
||||
}
|
||||
}, [copyLinkLabel, copyLabel, copiedLabel])
|
||||
|
||||
return (
|
||||
<Dialog {...dialogProps} title={t('shareDialog.heading')}>
|
||||
<HStack alignItems="stretch" gap="gutter">
|
||||
<Div flex="1">
|
||||
<Input
|
||||
type="text"
|
||||
aria-label={t('shareDialog.inputLabel')}
|
||||
value={roomUrl}
|
||||
readOnly
|
||||
onClick={(e) => {
|
||||
e.currentTarget.select()
|
||||
}}
|
||||
/>
|
||||
</Div>
|
||||
<Div minWidth="8rem">
|
||||
<Button
|
||||
variant="primary"
|
||||
size="sm"
|
||||
fullWidth
|
||||
onPress={() => {
|
||||
navigator.clipboard.writeText(roomUrl)
|
||||
setCopyLinkLabel(copiedLabel)
|
||||
}}
|
||||
>
|
||||
{copyLinkLabel}
|
||||
</Button>
|
||||
</Div>
|
||||
</HStack>
|
||||
</Dialog>
|
||||
)
|
||||
}
|
||||
@@ -35,6 +35,7 @@ export const Room = () => {
|
||||
<Screen>
|
||||
<Conference
|
||||
roomId={roomId}
|
||||
mode={mode}
|
||||
userConfig={{
|
||||
...existingUserChoices,
|
||||
...(skipJoinScreen ? { username: user?.email as string } : {}),
|
||||
|
||||
@@ -6,5 +6,10 @@
|
||||
"micLabel": "",
|
||||
"userLabel": ""
|
||||
},
|
||||
"leaveRoomPrompt": ""
|
||||
"leaveRoomPrompt": "",
|
||||
"shareDialog": {
|
||||
"copy": "",
|
||||
"heading": "",
|
||||
"inputLabel": ""
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,5 +6,11 @@
|
||||
"micLabel": "Microphone",
|
||||
"userLabel": "Your name"
|
||||
},
|
||||
"leaveRoomPrompt": "This will make you leave the meeting."
|
||||
"leaveRoomPrompt": "This will make you leave the meeting.",
|
||||
"shareDialog": {
|
||||
"copy": "Copy",
|
||||
"copied": "Copied",
|
||||
"heading": "Share the meeting link",
|
||||
"inputLabel": "Meeting link"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,5 +6,11 @@
|
||||
"micLabel": "Micro",
|
||||
"userLabel": "Votre nom"
|
||||
},
|
||||
"leaveRoomPrompt": "Revenir à l'accueil vous fera quitter la réunion."
|
||||
"leaveRoomPrompt": "Revenir à l'accueil vous fera quitter la réunion.",
|
||||
"shareDialog": {
|
||||
"copy": "Copier le lien",
|
||||
"copied": "Lien copié",
|
||||
"heading": "Partager le lien vers la réunion",
|
||||
"inputLabel": "Lien vers la réunion"
|
||||
}
|
||||
}
|
||||
|
||||
19
src/frontend/src/navigation/getRoutePath.ts
Normal file
19
src/frontend/src/navigation/getRoutePath.ts
Normal file
@@ -0,0 +1,19 @@
|
||||
import { RouteName } from '@/routes'
|
||||
import { getRouteByName } from './getRouteByName'
|
||||
|
||||
export const getRoutePath = (
|
||||
routeName: RouteName,
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
params?: any
|
||||
) => {
|
||||
const route = getRouteByName(routeName)
|
||||
const to = route.to
|
||||
? route.to(params)
|
||||
: typeof route.path === 'string'
|
||||
? route.path
|
||||
: null
|
||||
if (!to) {
|
||||
throw new Error(`Can't find path to navigate to for ${routeName}`)
|
||||
}
|
||||
return to
|
||||
}
|
||||
11
src/frontend/src/navigation/getRouteUrl.ts
Normal file
11
src/frontend/src/navigation/getRouteUrl.ts
Normal file
@@ -0,0 +1,11 @@
|
||||
import { RouteName } from '@/routes'
|
||||
import { getRoutePath } from './getRoutePath'
|
||||
|
||||
export const getRouteUrl = (
|
||||
routeName: RouteName,
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
params?: any
|
||||
) => {
|
||||
const to = getRoutePath(routeName, params)
|
||||
return `${window.location.origin}${to}`
|
||||
}
|
||||
@@ -97,6 +97,11 @@ const button = cva({
|
||||
},
|
||||
},
|
||||
},
|
||||
fullWidth: {
|
||||
true: {
|
||||
width: 'full',
|
||||
},
|
||||
},
|
||||
},
|
||||
defaultVariants: {
|
||||
size: 'default',
|
||||
|
||||
@@ -5,7 +5,7 @@ import {
|
||||
Dialog as RACDialog,
|
||||
ModalOverlay,
|
||||
Modal,
|
||||
type DialogProps,
|
||||
type DialogProps as RACDialogProps,
|
||||
Heading,
|
||||
} from 'react-aria-components'
|
||||
import { Div, Button, Box, VerticallyOffCenter } from '@/primitives'
|
||||
@@ -46,16 +46,44 @@ const StyledRACDialog = styled(RACDialog, {
|
||||
pointerEvents: 'none',
|
||||
},
|
||||
})
|
||||
|
||||
export type DialogProps = RACDialogProps & {
|
||||
title: string
|
||||
onClose?: () => void
|
||||
/**
|
||||
* use the Dialog as a controlled component
|
||||
*/
|
||||
isOpen?: boolean
|
||||
/**
|
||||
* use the Dialog as a controlled component:
|
||||
* this is called when isOpen should be updated
|
||||
* after user interaction
|
||||
*/
|
||||
onOpenChange?: (isOpen: boolean) => void
|
||||
}
|
||||
|
||||
export const Dialog = ({
|
||||
title,
|
||||
children,
|
||||
onClose,
|
||||
isOpen,
|
||||
onOpenChange,
|
||||
...dialogProps
|
||||
}: DialogProps & { title: string }) => {
|
||||
}: DialogProps) => {
|
||||
const isAlert = dialogProps['role'] === 'alertdialog'
|
||||
return (
|
||||
<StyledModalOverlay
|
||||
isKeyboardDismissDisabled={isAlert}
|
||||
isDismissable={!isAlert}
|
||||
isOpen={isOpen}
|
||||
onOpenChange={(isOpen) => {
|
||||
if (onOpenChange) {
|
||||
onOpenChange(isOpen)
|
||||
}
|
||||
if (!isOpen && onClose) {
|
||||
onClose()
|
||||
}
|
||||
}}
|
||||
>
|
||||
<StyledModal>
|
||||
<StyledRACDialog {...dialogProps}>
|
||||
|
||||
@@ -9,8 +9,8 @@ import { Input as RACInput } from 'react-aria-components'
|
||||
export const Input = styled(RACInput, {
|
||||
base: {
|
||||
width: 'full',
|
||||
paddingY: 0.125,
|
||||
paddingX: 0.25,
|
||||
paddingY: 0.25,
|
||||
paddingX: 0.5,
|
||||
border: '1px solid',
|
||||
borderColor: 'control.border',
|
||||
color: 'control.text',
|
||||
|
||||
@@ -4,13 +4,14 @@ export { Bold } from './Bold'
|
||||
export { Box } from './Box'
|
||||
export { Button } from './Button'
|
||||
export { useCloseDialog } from './useCloseDialog'
|
||||
export { Dialog } from './Dialog'
|
||||
export { Dialog, type DialogProps } from './Dialog'
|
||||
export { Div } from './Div'
|
||||
export { Field } from './Field'
|
||||
export { Form } from './Form'
|
||||
export { H } from './H'
|
||||
export { Hr } from './Hr'
|
||||
export { Italic } from './Italic'
|
||||
export { Input } from './Input'
|
||||
export { Link } from './Link'
|
||||
export { P } from './P'
|
||||
export { Popover } from './Popover'
|
||||
|
||||
Reference in New Issue
Block a user