diff --git a/src/frontend/src/features/rooms/components/Conference.tsx b/src/frontend/src/features/rooms/components/Conference.tsx index c841a735..86e0b6ff 100644 --- a/src/frontend/src/features/rooms/components/Conference.tsx +++ b/src/frontend/src/features/rooms/components/Conference.tsx @@ -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 ( @@ -55,6 +60,14 @@ export const Conference = ({ }} > + {showInviteDialog && ( + setShowInviteDialog(false)} + /> + )} ) diff --git a/src/frontend/src/features/rooms/components/InviteDialog.tsx b/src/frontend/src/features/rooms/components/InviteDialog.tsx new file mode 100644 index 00000000..a4a6c708 --- /dev/null +++ b/src/frontend/src/features/rooms/components/InviteDialog.tsx @@ -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) => { + 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 ( + + +
+ { + e.currentTarget.select() + }} + /> +
+
+ +
+
+
+ ) +} diff --git a/src/frontend/src/features/rooms/routes/Room.tsx b/src/frontend/src/features/rooms/routes/Room.tsx index e7f3199d..5dab908e 100644 --- a/src/frontend/src/features/rooms/routes/Room.tsx +++ b/src/frontend/src/features/rooms/routes/Room.tsx @@ -35,6 +35,7 @@ export const Room = () => { { + 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 +} diff --git a/src/frontend/src/navigation/getRouteUrl.ts b/src/frontend/src/navigation/getRouteUrl.ts new file mode 100644 index 00000000..77fd8e77 --- /dev/null +++ b/src/frontend/src/navigation/getRouteUrl.ts @@ -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}` +} diff --git a/src/frontend/src/primitives/Button.tsx b/src/frontend/src/primitives/Button.tsx index 346713a5..23c66e43 100644 --- a/src/frontend/src/primitives/Button.tsx +++ b/src/frontend/src/primitives/Button.tsx @@ -97,6 +97,11 @@ const button = cva({ }, }, }, + fullWidth: { + true: { + width: 'full', + }, + }, }, defaultVariants: { size: 'default', diff --git a/src/frontend/src/primitives/Dialog.tsx b/src/frontend/src/primitives/Dialog.tsx index 19c1dfca..facd1877 100644 --- a/src/frontend/src/primitives/Dialog.tsx +++ b/src/frontend/src/primitives/Dialog.tsx @@ -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 ( { + if (onOpenChange) { + onOpenChange(isOpen) + } + if (!isOpen && onClose) { + onClose() + } + }} > diff --git a/src/frontend/src/primitives/Input.tsx b/src/frontend/src/primitives/Input.tsx index 013effea..ac2798ab 100644 --- a/src/frontend/src/primitives/Input.tsx +++ b/src/frontend/src/primitives/Input.tsx @@ -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', diff --git a/src/frontend/src/primitives/index.ts b/src/frontend/src/primitives/index.ts index 1d13b304..bbdb59ea 100644 --- a/src/frontend/src/primitives/index.ts +++ b/src/frontend/src/primitives/index.ts @@ -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'