(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:
lebaudantoine
2024-08-21 23:01:50 +02:00
committed by aleb_the_flash
parent 0e2c805b41
commit 8d77332a0a
5 changed files with 149 additions and 25 deletions

View File

@@ -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>
)
}

View File

@@ -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>
)

View File

@@ -9,5 +9,16 @@
"joinMeeting": "",
"joinMeetingTipContent": "",
"joinMeetingTipHeading": "",
"loginToCreateMeeting": ""
"loginToCreateMeeting": "",
"createMenu": {
"laterOption": "",
"instantOption": ""
},
"laterMeetingDialog": {
"heading": "",
"description": "",
"copied": "",
"copy": "",
"inputLabel": ""
}
}

View File

@@ -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"
}
}

View File

@@ -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"
}
}