Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allow custom room names #315

Open
wants to merge 4 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
134 changes: 134 additions & 0 deletions src/frontend/src/features/home/components/PersonalizeMeetingDialog.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,134 @@
import { FormEvent, useState } from 'react'
import { useTranslation } from 'react-i18next'
import { Button, Dialog, type DialogProps, Text } from '@/primitives'
import { HStack } from '@/styled-system/jsx'
import { RiSpam2Fill } from '@remixicon/react'
import { navigateTo } from '@/navigation/navigateTo.ts'
import { useCreateRoom } from '@/features/rooms'
import {
FieldError,
Form as RACForm,
Input,
Label,
TextField,
} from 'react-aria-components'
import { css } from '@/styled-system/css'

export const PersonalizeMeetingDialog = ({
isOpen,
...dialogProps
}: Omit<DialogProps, 'title'>) => {
const { t } = useTranslation('home', {
keyPrefix: 'personalizeMeetingDialog',
})
const { mutateAsync: createRoom } = useCreateRoom()
const [roomSlug, setRoomSlug] = useState('')
const [serverErrors, setServerErrors] = useState<Array<string>>([])

const onSubmit = (e: FormEvent<HTMLFormElement>) => {
e.preventDefault()
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I may have overlooked something in the React Aria documentation, but this is the approach I found to prevent a HTML form from being submitted and navigating to a URL suffixed with a question mark.

createRoom({ slug: roomSlug })
.then((data) =>
navigateTo('room', data.slug, {
state: { create: true, initialRoomData: data },
})
)
.catch((e) => {
const msg =
e.statusCode === 400
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Related to my comment about error names: what if there are multiple possible errors with the same http code?

Not to fix now ofc

? t('errors.server.taken')
: t('errors.server.unknown')
setServerErrors([msg])
})
}

const validationErrors = []
if (roomSlug.length < 5) {
validationErrors.push(t('errors.validation.length'))
}
if (!roomSlug.match(/^[a-z0-9]+$/)) {
validationErrors.push(t('errors.validation.spaceOrSpecialCharacter'))
}
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I like how simple this approach is were most of devs wants to use libraries to validate their forms :)


const errors = [...validationErrors, ...serverErrors]

return (
<Dialog isOpen={!!isOpen} {...dialogProps} title={t('heading')}>
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Broader subject (out of scope): on mobile I tend to think having a full screen modal with its content being aligned on top is more UX. Mobile user are more used to panes-like navigation.

Capture d’écran 2025-01-27 à 17 38 52

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agree, we have a poor mobile UX.

<RACForm onSubmit={onSubmit}>
<TextField
isRequired
isInvalid={errors.length > 0}
value={roomSlug}
onChange={(e) => {
setServerErrors([])
setRoomSlug(e.toLowerCase())
}}
className={css({
display: 'flex',
flexDirection: 'column',
})}
>
<Label>{t('label')}</Label>
<div
className={css({
display: 'flex',
justifyContent: 'space-between',
})}
>
<Input
className={css({
height: '46px',
border: '1px solid black',
padding: '4px 8px',
width: '80%',
borderRadius: '0.25rem',
})}
placeholder={t('placeholder')}
/>
<Button type="submit">{t('submit')}</Button>
</div>
<div
className={css({
minHeight: '72px',
})}
>
<FieldError>
<ul
className={css({
listStyle: 'square inside',
color: 'red',
marginLeft: '10px',
marginTop: '10px',
})}
>
{errors.map((error, i) => (
<li key={i}>
<Text as="span" variant={'sm'}>
{error}
</Text>
</li>
))}
</ul>
</FieldError>
</div>
</TextField>
</RACForm>

<HStack>
<div
style={{
backgroundColor: '#d9e5ff',
borderRadius: '50%',
padding: '4px',
marginTop: '1rem',
}}
>
<RiSpam2Fill size={22} style={{ fill: '#4c84fc' }} />
</div>
<Text variant="sm" style={{ marginTop: '1rem', textWrap: 'balance' }}>
{t('warning')}
</Text>
</HStack>
</Dialog>
)
}
20 changes: 19 additions & 1 deletion src/frontend/src/features/home/routes/Home.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,9 @@ import { JoinMeetingDialog } from '../components/JoinMeetingDialog'
import { ProConnectButton } from '@/components/ProConnectButton'
import { useCreateRoom } from '@/features/rooms'
import { usePersistentUserChoices } from '@livekit/components-react'
import { RiAddLine, RiLink } from '@remixicon/react'
import { RiAddLine, RiLink, RiUserAddLine } from '@remixicon/react'
import { LaterMeetingDialog } from '@/features/home/components/LaterMeetingDialog'
import { PersonalizeMeetingDialog } from '@/features/home/components/PersonalizeMeetingDialog'
import { IntroSlider } from '@/features/home/components/IntroSlider'
import { MoreLink } from '@/features/home/components/MoreLink'
import { ReactNode, useState } from 'react'
Expand Down Expand Up @@ -155,6 +156,7 @@ export const Home = () => {

const { mutateAsync: createRoom } = useCreateRoom()
const [laterRoomId, setLaterRoomId] = useState<null | string>(null)
const [isPersonalizeModalOpen, setIsPersonalizeModalOpen] = useState(false)

return (
<UserAware>
Expand Down Expand Up @@ -209,6 +211,18 @@ export const Home = () => {
<RiLink size={18} />
{t('createMenu.laterOption')}
</MenuItem>
<MenuItem
className={
menuRecipe({ icon: true, variant: 'light' }).item
}
onAction={() => {
setIsPersonalizeModalOpen(true)
}}
data-attr="create-option-personalize"
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What is it used for ?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's used by Posthog to scrap event data!

>
<RiUserAddLine size={18} />
{t('createMenu.personalizeOption')}
</MenuItem>
</RACMenu>
</Menu>
) : (
Expand Down Expand Up @@ -250,6 +264,10 @@ export const Home = () => {
roomId={laterRoomId || ''}
onOpenChange={() => setLaterRoomId(null)}
/>
<PersonalizeMeetingDialog
isOpen={isPersonalizeModalOpen}
onOpenChange={() => setIsPersonalizeModalOpen(false)}
/>
</Screen>
</UserAware>
)
Expand Down
20 changes: 19 additions & 1 deletion src/frontend/src/locales/de/home.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,8 @@
"moreAbout": "",
"createMenu": {
"laterOption": "",
"instantOption": ""
"instantOption": "",
"personalizeOption": ""
},
"laterMeetingDialog": {
"heading": "",
Expand All @@ -24,6 +25,23 @@
"copied": "",
"permissions": ""
},
"personalizeMeetingDialog": {
"heading": "",
"label": "",
"submit": "",
"placeholder": "",
"errors": {
"validation": {
"length": "",
"spaceOrSpecialCharacter": ""
},
"server": {
"taken": "",
"unknown": ""
}
},
"warning": ""
},
"introSlider": {
"previous": {
"label": "",
Expand Down
20 changes: 19 additions & 1 deletion src/frontend/src/locales/en/home.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,8 @@
"moreAbout": "about Visio",
"createMenu": {
"laterOption": "Create a meeting for a later date",
"instantOption": "Start an instant meeting"
"instantOption": "Start an instant meeting",
"personalizeOption": "Create a custom link"
},
"laterMeetingDialog": {
"heading": "Your connection details",
Expand All @@ -24,6 +25,23 @@
"copied": "Link copied to clipboard",
"permissions": "People with this link do not need your permission to join this meeting."
},
"personalizeMeetingDialog": {
"heading": "Your custom link",
"label": "Your custom link",
"submit": "Create",
"placeholder": "standupbizdev",
"errors": {
"validation": {
"length": "Must contain at least 5 characters.",
"spaceOrSpecialCharacter": "Cannot contain spaces or special characters"
},
"server": {
"taken": "Already in use, please try something else",
"unknown": "An unknown error occurred, please try again"
}
},
"warning": "This link provides direct access to the meeting. Choose a long and complex name to limit unauthorized access."
},
"introSlider": {
"previous": {
"label": "previous",
Expand Down
20 changes: 19 additions & 1 deletion src/frontend/src/locales/fr/home.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,8 @@
"moreAbout": "sur Visio",
"createMenu": {
"laterOption": "Créer une réunion pour une date ultérieure",
"instantOption": "Démarrer une réunion instantanée"
"instantOption": "Démarrer une réunion instantanée",
"personalizeOption": "Créer un lien personnalisé"
},
"laterMeetingDialog": {
"heading": "Vos informations de connexion",
Expand All @@ -24,6 +25,23 @@
"copied": "Lien copié dans le presse-papiers",
"permissions": "Les personnes disposant de ce lien n'ont pas besoin de votre autorisation pour rejoindre cette réunion."
},
"personalizeMeetingDialog": {
"heading": "Votre lien personnalisé",
"label": "Votre lien personnalisé",
"submit": "Créer",
"placeholder": "standupbizdev",
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

WDYT about French placeholder?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I felt bizdev was kinda french, but would you prefer, "equipecom" or smth else?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe like "Réunion Hebdo" or smth?

"errors": {
"validation": {
"length": "Doit contenir au moins 5 caractères.",
"spaceOrSpecialCharacter": "Ne peut pas contenir d'espaces ou de caractères spéciaux"
},
"server": {
"taken": "Déjà utilisé, veuillez essayer autre chose",
"unknown": "Une erreur inconnue s'est produite, veuillez réessayer"
}
},
"warning": "Ce lien permet un accès direct à la réunion. Choisissez un nom long et complexe pour limiter les accès non autorisés."
},
"introSlider": {
"previous": {
"label": "précédent",
Expand Down