✨(frontend) create meeting for later
Inspired by the Google Meet user experience. A simple UX for scheduling meetings was requested by some users. This is a quick and basic poc to validate the UX in a production environment. If it gains traction, it will be refined and consolidated.
This commit is contained in:
committed by
aleb_the_flash
parent
0e2c805b41
commit
8d77332a0a
@@ -0,0 +1,63 @@
|
||||
import { useEffect, useState } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { getRouteUrl } from '@/navigation/getRouteUrl'
|
||||
import { Div, Button, Dialog, Input, type DialogProps, P } from '@/primitives'
|
||||
import { HStack } from '@/styled-system/jsx'
|
||||
|
||||
// fixme - duplication with the InviteDialog
|
||||
export const LaterMeetingDialog = ({
|
||||
roomId,
|
||||
...dialogProps
|
||||
}: { roomId: string } & Omit<DialogProps, 'title'>) => {
|
||||
const { t } = useTranslation('home')
|
||||
const roomUrl = getRouteUrl('room', roomId)
|
||||
const copyLabel = t('laterMeetingDialog.copy')
|
||||
const copiedLabel = t('laterMeetingDialog.copied')
|
||||
const [copyLinkLabel, setCopyLinkLabel] = useState(copyLabel)
|
||||
useEffect(() => {
|
||||
if (copyLinkLabel == copiedLabel) {
|
||||
const timeout = setTimeout(() => {
|
||||
setCopyLinkLabel(copyLabel)
|
||||
}, 5000)
|
||||
return () => {
|
||||
clearTimeout(timeout)
|
||||
}
|
||||
}
|
||||
}, [copyLinkLabel, copyLabel, copiedLabel])
|
||||
|
||||
return (
|
||||
<Dialog
|
||||
isOpen={!!roomId}
|
||||
{...dialogProps}
|
||||
title={t('laterMeetingDialog.heading')}
|
||||
>
|
||||
<P>{t('laterMeetingDialog.description')}</P>
|
||||
<HStack alignItems="stretch" gap="gutter">
|
||||
<Div flex="1">
|
||||
<Input
|
||||
type="text"
|
||||
aria-label={t('laterMeetingDialog.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>
|
||||
)
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { DialogTrigger } from 'react-aria-components'
|
||||
import { Button, Text } from '@/primitives'
|
||||
import { Button, Menu, Text } from '@/primitives'
|
||||
import { HStack } from '@/styled-system/jsx'
|
||||
import { navigateTo } from '@/navigation/navigateTo'
|
||||
import { Screen } from '@/layout/Screen'
|
||||
@@ -10,6 +10,11 @@ import { authUrl, useUser, UserAware } from '@/features/auth'
|
||||
import { JoinMeetingDialog } from '../components/JoinMeetingDialog'
|
||||
import { useCreateRoom } from '@/features/rooms'
|
||||
import { usePersistentUserChoices } from '@livekit/components-react'
|
||||
import { menuItemRecipe } from '@/primitives/menuItemRecipe'
|
||||
import { RiAddLine, RiLink } from '@remixicon/react'
|
||||
import { MenuItem, Menu as RACMenu } from 'react-aria-components'
|
||||
import { LaterMeetingDialog } from '@/features/home/components/LaterMeetingDialog'
|
||||
import { useState } from 'react'
|
||||
|
||||
export const Home = () => {
|
||||
const { t } = useTranslation('home')
|
||||
@@ -19,13 +24,10 @@ export const Home = () => {
|
||||
userChoices: { username },
|
||||
} = usePersistentUserChoices()
|
||||
|
||||
const { mutateAsync: createRoom } = useCreateRoom({
|
||||
onSuccess: (data) => {
|
||||
navigateTo('room', data.slug, {
|
||||
state: { create: true, initialRoomData: data },
|
||||
})
|
||||
},
|
||||
})
|
||||
const { mutateAsync: createRoom } = useCreateRoom()
|
||||
const [laterRoomId, setLaterRoomId] = useState<null | string>(null)
|
||||
|
||||
console.log(laterRoomId)
|
||||
|
||||
return (
|
||||
<UserAware>
|
||||
@@ -43,21 +45,43 @@ export const Home = () => {
|
||||
</Text>
|
||||
)}
|
||||
<HStack gap="gutter">
|
||||
<Button
|
||||
variant="primary"
|
||||
onPress={
|
||||
isLoggedIn
|
||||
? async () => {
|
||||
{isLoggedIn ? (
|
||||
<Menu>
|
||||
<Button variant="primary">{t('createMeeting')}</Button>
|
||||
<RACMenu>
|
||||
<MenuItem
|
||||
className={menuItemRecipe({ icon: true })}
|
||||
onAction={async () => {
|
||||
const slug = generateRoomId()
|
||||
await createRoom({ slug, username })
|
||||
}
|
||||
: undefined
|
||||
}
|
||||
href={isLoggedIn ? undefined : authUrl()}
|
||||
>
|
||||
{isLoggedIn ? t('createMeeting') : t('login', { ns: 'global' })}
|
||||
</Button>
|
||||
|
||||
createRoom({ slug, username }).then((data) =>
|
||||
navigateTo('room', data.slug, {
|
||||
state: { create: true, initialRoomData: data },
|
||||
})
|
||||
)
|
||||
}}
|
||||
>
|
||||
<RiAddLine size={18} />
|
||||
{t('createMenu.instantOption')}
|
||||
</MenuItem>
|
||||
<MenuItem
|
||||
className={menuItemRecipe({ icon: true })}
|
||||
onAction={() => {
|
||||
const slug = generateRoomId()
|
||||
createRoom({ slug, username }).then((data) =>
|
||||
setLaterRoomId(data.slug)
|
||||
)
|
||||
}}
|
||||
>
|
||||
<RiLink size={18} />
|
||||
{t('createMenu.laterOption')}
|
||||
</MenuItem>
|
||||
</RACMenu>
|
||||
</Menu>
|
||||
) : (
|
||||
<Button variant="primary" href={authUrl()}>
|
||||
{t('login', { ns: 'global' })}
|
||||
</Button>
|
||||
)}
|
||||
<DialogTrigger>
|
||||
<Button variant="primary" outline>
|
||||
{t('joinMeeting')}
|
||||
@@ -66,6 +90,10 @@ export const Home = () => {
|
||||
</DialogTrigger>
|
||||
</HStack>
|
||||
</Centered>
|
||||
<LaterMeetingDialog
|
||||
roomId={laterRoomId || ''}
|
||||
onOpenChange={() => setLaterRoomId(null)}
|
||||
/>
|
||||
</Screen>
|
||||
</UserAware>
|
||||
)
|
||||
|
||||
@@ -9,5 +9,16 @@
|
||||
"joinMeeting": "",
|
||||
"joinMeetingTipContent": "",
|
||||
"joinMeetingTipHeading": "",
|
||||
"loginToCreateMeeting": ""
|
||||
"loginToCreateMeeting": "",
|
||||
"createMenu": {
|
||||
"laterOption": "",
|
||||
"instantOption": ""
|
||||
},
|
||||
"laterMeetingDialog": {
|
||||
"heading": "",
|
||||
"description": "",
|
||||
"copied": "",
|
||||
"copy": "",
|
||||
"inputLabel": ""
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,5 +9,16 @@
|
||||
"joinMeeting": "Join a meeting",
|
||||
"joinMeetingTipContent": "You can join a meeting by pasting its full link in the browser's address bar.",
|
||||
"joinMeetingTipHeading": "Did you know?",
|
||||
"loginToCreateMeeting": "Login to create a meeting"
|
||||
"loginToCreateMeeting": "Login to create a meeting",
|
||||
"createMenu": {
|
||||
"laterOption": "Create a meeting for a later date",
|
||||
"instantOption": "Start an instant meeting"
|
||||
},
|
||||
"laterMeetingDialog": {
|
||||
"heading": "Your connection details",
|
||||
"description": "Send this link to the people you want to invite to the meeting. They will be able to join without Agent Connect.",
|
||||
"copied": "Copied",
|
||||
"copy": "Copy",
|
||||
"inputLabel": "Meeting link"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,5 +9,16 @@
|
||||
"joinMeeting": "Rejoindre une réunion",
|
||||
"joinMeetingTipContent": "Vous pouvez rejoindre une réunion en copiant directement son lien complet dans la barre d'adresse du navigateur.",
|
||||
"joinMeetingTipHeading": "Astuce",
|
||||
"loginToCreateMeeting": "Connectez-vous pour créer une réunion"
|
||||
"loginToCreateMeeting": "Connectez-vous pour créer une réunion",
|
||||
"createMenu": {
|
||||
"laterOption": "Créer une réunion pour une date ultérieure",
|
||||
"instantOption": "Démarrer une réunion instantanée"
|
||||
},
|
||||
"laterMeetingDialog": {
|
||||
"heading": "Vos informations de connexion",
|
||||
"description": "Envoyez ce lien aux personnes que vous souhaitez inviter à la réunion. Ils pourront la rejoindre sans Agent Connect.",
|
||||
"copied": "Lien copié",
|
||||
"copy": "Copier le lien",
|
||||
"inputLabel": "Lien vers la réunion"
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user