From 8587574fcdbd2eccaa82341bb9861612ab0b2497 Mon Sep 17 00:00:00 2001 From: Emmanuel Pelletier Date: Fri, 26 Jul 2024 11:02:32 +0200 Subject: [PATCH] =?UTF-8?q?=E2=9C=A8(rooms)=20show=20an=20invite=20dialog?= =?UTF-8?q?=20when=20creating=20a=20room?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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 --- .../features/rooms/components/Conference.tsx | 19 +++++- .../rooms/components/InviteDialog.tsx | 58 +++++++++++++++++++ .../src/features/rooms/routes/Room.tsx | 1 + src/frontend/src/locales/de/rooms.json | 7 ++- src/frontend/src/locales/en/rooms.json | 8 ++- src/frontend/src/locales/fr/rooms.json | 8 ++- src/frontend/src/navigation/getRoutePath.ts | 19 ++++++ src/frontend/src/navigation/getRouteUrl.ts | 11 ++++ src/frontend/src/primitives/Button.tsx | 5 ++ src/frontend/src/primitives/Dialog.tsx | 32 +++++++++- src/frontend/src/primitives/Input.tsx | 4 +- src/frontend/src/primitives/index.ts | 3 +- 12 files changed, 164 insertions(+), 11 deletions(-) create mode 100644 src/frontend/src/features/rooms/components/InviteDialog.tsx create mode 100644 src/frontend/src/navigation/getRoutePath.ts create mode 100644 src/frontend/src/navigation/getRouteUrl.ts 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'