✨(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 { useTranslation } from 'react-i18next'
|
||||||
import { DialogTrigger } from 'react-aria-components'
|
import { DialogTrigger } from 'react-aria-components'
|
||||||
import { Button, Text } from '@/primitives'
|
import { Button, Menu, Text } from '@/primitives'
|
||||||
import { HStack } from '@/styled-system/jsx'
|
import { HStack } from '@/styled-system/jsx'
|
||||||
import { navigateTo } from '@/navigation/navigateTo'
|
import { navigateTo } from '@/navigation/navigateTo'
|
||||||
import { Screen } from '@/layout/Screen'
|
import { Screen } from '@/layout/Screen'
|
||||||
@@ -10,6 +10,11 @@ import { authUrl, useUser, UserAware } from '@/features/auth'
|
|||||||
import { JoinMeetingDialog } from '../components/JoinMeetingDialog'
|
import { JoinMeetingDialog } from '../components/JoinMeetingDialog'
|
||||||
import { useCreateRoom } from '@/features/rooms'
|
import { useCreateRoom } from '@/features/rooms'
|
||||||
import { usePersistentUserChoices } from '@livekit/components-react'
|
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 = () => {
|
export const Home = () => {
|
||||||
const { t } = useTranslation('home')
|
const { t } = useTranslation('home')
|
||||||
@@ -19,13 +24,10 @@ export const Home = () => {
|
|||||||
userChoices: { username },
|
userChoices: { username },
|
||||||
} = usePersistentUserChoices()
|
} = usePersistentUserChoices()
|
||||||
|
|
||||||
const { mutateAsync: createRoom } = useCreateRoom({
|
const { mutateAsync: createRoom } = useCreateRoom()
|
||||||
onSuccess: (data) => {
|
const [laterRoomId, setLaterRoomId] = useState<null | string>(null)
|
||||||
navigateTo('room', data.slug, {
|
|
||||||
state: { create: true, initialRoomData: data },
|
console.log(laterRoomId)
|
||||||
})
|
|
||||||
},
|
|
||||||
})
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<UserAware>
|
<UserAware>
|
||||||
@@ -43,21 +45,43 @@ export const Home = () => {
|
|||||||
</Text>
|
</Text>
|
||||||
)}
|
)}
|
||||||
<HStack gap="gutter">
|
<HStack gap="gutter">
|
||||||
<Button
|
{isLoggedIn ? (
|
||||||
variant="primary"
|
<Menu>
|
||||||
onPress={
|
<Button variant="primary">{t('createMeeting')}</Button>
|
||||||
isLoggedIn
|
<RACMenu>
|
||||||
? async () => {
|
<MenuItem
|
||||||
|
className={menuItemRecipe({ icon: true })}
|
||||||
|
onAction={async () => {
|
||||||
const slug = generateRoomId()
|
const slug = generateRoomId()
|
||||||
await createRoom({ slug, username })
|
createRoom({ slug, username }).then((data) =>
|
||||||
}
|
navigateTo('room', data.slug, {
|
||||||
: undefined
|
state: { create: true, initialRoomData: data },
|
||||||
}
|
})
|
||||||
href={isLoggedIn ? undefined : authUrl()}
|
)
|
||||||
>
|
}}
|
||||||
{isLoggedIn ? t('createMeeting') : t('login', { ns: 'global' })}
|
>
|
||||||
</Button>
|
<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>
|
<DialogTrigger>
|
||||||
<Button variant="primary" outline>
|
<Button variant="primary" outline>
|
||||||
{t('joinMeeting')}
|
{t('joinMeeting')}
|
||||||
@@ -66,6 +90,10 @@ export const Home = () => {
|
|||||||
</DialogTrigger>
|
</DialogTrigger>
|
||||||
</HStack>
|
</HStack>
|
||||||
</Centered>
|
</Centered>
|
||||||
|
<LaterMeetingDialog
|
||||||
|
roomId={laterRoomId || ''}
|
||||||
|
onOpenChange={() => setLaterRoomId(null)}
|
||||||
|
/>
|
||||||
</Screen>
|
</Screen>
|
||||||
</UserAware>
|
</UserAware>
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -9,5 +9,16 @@
|
|||||||
"joinMeeting": "",
|
"joinMeeting": "",
|
||||||
"joinMeetingTipContent": "",
|
"joinMeetingTipContent": "",
|
||||||
"joinMeetingTipHeading": "",
|
"joinMeetingTipHeading": "",
|
||||||
"loginToCreateMeeting": ""
|
"loginToCreateMeeting": "",
|
||||||
|
"createMenu": {
|
||||||
|
"laterOption": "",
|
||||||
|
"instantOption": ""
|
||||||
|
},
|
||||||
|
"laterMeetingDialog": {
|
||||||
|
"heading": "",
|
||||||
|
"description": "",
|
||||||
|
"copied": "",
|
||||||
|
"copy": "",
|
||||||
|
"inputLabel": ""
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,5 +9,16 @@
|
|||||||
"joinMeeting": "Join a meeting",
|
"joinMeeting": "Join a meeting",
|
||||||
"joinMeetingTipContent": "You can join a meeting by pasting its full link in the browser's address bar.",
|
"joinMeetingTipContent": "You can join a meeting by pasting its full link in the browser's address bar.",
|
||||||
"joinMeetingTipHeading": "Did you know?",
|
"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",
|
"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.",
|
"joinMeetingTipContent": "Vous pouvez rejoindre une réunion en copiant directement son lien complet dans la barre d'adresse du navigateur.",
|
||||||
"joinMeetingTipHeading": "Astuce",
|
"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