(frontend) allow local participant to update her name

The user name is initially set by the backend when generating the LiveKit access
token. By default, if the frontend has not provided a username, the backend uses
the user's email address. This approach isn't ideal, as some users prefer using
their first name.

This update allows local participant to change their username in real-time
during a session. However, these changes are not yet persisted for
future meetings. This persistence feature will be added in upcoming commits.
This commit is contained in:
lebaudantoine
2024-08-07 11:36:07 +02:00
committed by aleb_the_flash
parent fe8ed43aae
commit 37eea16a50
5 changed files with 91 additions and 7 deletions

View File

@@ -3,6 +3,7 @@ import {
RiFeedbackLine, RiFeedbackLine,
RiQuestionLine, RiQuestionLine,
RiSettings3Line, RiSettings3Line,
RiUser5Line,
} from '@remixicon/react' } from '@remixicon/react'
import { useState } from 'react' import { useState } from 'react'
import { styled } from '@/styled-system/jsx' import { styled } from '@/styled-system/jsx'
@@ -10,8 +11,10 @@ import {
Menu as RACMenu, Menu as RACMenu,
MenuItem as RACMenuItem, MenuItem as RACMenuItem,
Popover as RACPopover, Popover as RACPopover,
Separator as RACSeparator,
} from 'react-aria-components' } from 'react-aria-components'
import { SettingsDialog } from '@/features/settings' import { SettingsDialog } from '@/features/settings'
import { UsernameDialog } from '../../dialogs/UsernameDialog'
// Styled components to be refactored // Styled components to be refactored
const StyledMenu = styled(RACMenu, { const StyledMenu = styled(RACMenu, {
@@ -60,13 +63,28 @@ const StyledPopover = styled(RACPopover, {
}, },
}) })
const StyledSeparator = styled(RACSeparator, {
base: {
height: '1px',
background: '#9ca3af',
margin: '2px 4px',
},
})
type DialogState = 'username' | 'settings' | null
export const OptionsMenu = () => { export const OptionsMenu = () => {
const { t } = useTranslation('rooms') const { t } = useTranslation('rooms')
const [isSettingsDialogOpen, setIsSettingsDialogOpen] = useState(false) const [dialogOpen, setDialogOpen] = useState<DialogState>(null)
return ( return (
<> <>
<StyledPopover> <StyledPopover>
<StyledMenu> <StyledMenu>
<StyledMenuItem onAction={() => setDialogOpen('username')}>
<RiUser5Line size={18} />
{t('options.items.username')}
</StyledMenuItem>
<StyledSeparator />
<StyledMenuItem <StyledMenuItem
href="https://tchap.gouv.fr/#/room/!aGImQayAgBLjSBycpm:agent.dinum.tchap.gouv.fr?via=agent.dinum.tchap.gouv.fr" href="https://tchap.gouv.fr/#/room/!aGImQayAgBLjSBycpm:agent.dinum.tchap.gouv.fr?via=agent.dinum.tchap.gouv.fr"
target="_blank" target="_blank"
@@ -81,15 +99,19 @@ export const OptionsMenu = () => {
<RiFeedbackLine size={18} /> <RiFeedbackLine size={18} />
{t('options.items.feedbacks')} {t('options.items.feedbacks')}
</StyledMenuItem> </StyledMenuItem>
<StyledMenuItem onAction={() => setIsSettingsDialogOpen(true)}> <StyledMenuItem onAction={() => setDialogOpen('settings')}>
<RiSettings3Line size={18} /> <RiSettings3Line size={18} />
{t('options.items.settings')} {t('options.items.settings')}
</StyledMenuItem> </StyledMenuItem>
</StyledMenu> </StyledMenu>
</StyledPopover> </StyledPopover>
<UsernameDialog
isOpen={dialogOpen === 'username'}
onOpenChange={(v) => !v && setDialogOpen(null)}
/>
<SettingsDialog <SettingsDialog
isOpen={isSettingsDialogOpen} isOpen={dialogOpen === 'settings'}
onOpenChange={(v) => setIsSettingsDialogOpen(v)} onOpenChange={(v) => !v && setDialogOpen(null)}
/> />
</> </>
) )

View File

@@ -0,0 +1,38 @@
import { useTranslation } from 'react-i18next'
import { Field, Form, Dialog, DialogProps } from '@/primitives'
import { useRoomContext } from '@livekit/components-react'
export type UsernameDialogProps = Pick<DialogProps, 'isOpen' | 'onOpenChange'>
export const UsernameDialog = (props: UsernameDialogProps) => {
const { t } = useTranslation('rooms')
const ctx = useRoomContext()
return (
<Dialog title={t('options.username.heading')} {...props}>
<Form
onSubmit={(data) => {
ctx.localParticipant.setName(data.username as string)
const { onOpenChange } = props
if (onOpenChange) {
onOpenChange(false)
}
}}
submitLabel={t('options.username.submitLabel')}
>
<Field
type="text"
name="username"
label={t('options.username.label')}
description={t('options.username.description')}
defaultValue={ctx.localParticipant.name}
validate={(value) => {
return !value ? (
<p>{t('options.username.validationError')}</p>
) : null
}}
/>
</Form>
</Dialog>
)
}

View File

@@ -36,7 +36,15 @@
"items": { "items": {
"feedbacks": "", "feedbacks": "",
"support": "", "support": "",
"settings": "" "settings": "",
"username": ""
},
"username": {
"heading": "",
"label": "",
"description": "",
"validationError": "",
"submitLabel": ""
} }
} }
} }

View File

@@ -36,7 +36,15 @@
"items": { "items": {
"feedbacks": "Give us feedbacks", "feedbacks": "Give us feedbacks",
"support": "Get Help on Tchap", "support": "Get Help on Tchap",
"settings": "Settings" "settings": "Settings",
"username": "Update Your Name"
},
"username": {
"heading": "Update Your Name",
"label": "Your Name",
"description": "All other participants will see this name.",
"validationError": "Name cannot be empty.",
"submitLabel": "Save"
} }
} }
} }

View File

@@ -36,7 +36,15 @@
"items": { "items": {
"feedbacks": "Partager votre avis", "feedbacks": "Partager votre avis",
"support": "Obtenir de l'aide sur Tchap", "support": "Obtenir de l'aide sur Tchap",
"settings": "Paramètres" "settings": "Paramètres",
"username": "Choisir votre nom"
},
"username": {
"heading": "Choisir votre nom",
"label": "Votre Nom",
"description": "Tous les autres participants verront ce nom.",
"validationError": "Le nom ne peut pas être vide.",
"submitLabel": "Enregistrer"
} }
} }
} }