♻️(frontend) refactor recording side panels to reduce code duplication
A lot of duplication existed, so I started factorizing components now that a proper user experience is clearer. Without over-abstracting, the first step introduces a reusable “no access” view with configurable message and image. This is just the beginning: props passing is still not ideal, but it’s sufficient to merge and significantly reduce duplication.
This commit is contained in:
committed by
aleb_the_flash
parent
9ebf2f277b
commit
236245740f
@@ -0,0 +1,45 @@
|
|||||||
|
import { H, Text } from '@/primitives'
|
||||||
|
import { css } from '@/styled-system/css'
|
||||||
|
import { LoginButton } from '@/components/LoginButton'
|
||||||
|
|
||||||
|
interface LoginPromptProps {
|
||||||
|
heading: string
|
||||||
|
body: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export const LoginPrompt = ({ heading, body }: LoginPromptProps) => {
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
className={css({
|
||||||
|
backgroundColor: 'primary.50',
|
||||||
|
borderRadius: '5px',
|
||||||
|
paddingY: '1rem',
|
||||||
|
paddingX: '1rem',
|
||||||
|
marginTop: '1rem',
|
||||||
|
display: 'flex',
|
||||||
|
flexDirection: 'column',
|
||||||
|
})}
|
||||||
|
>
|
||||||
|
<H
|
||||||
|
lvl={3}
|
||||||
|
className={css({
|
||||||
|
display: 'flex',
|
||||||
|
alignItems: 'center',
|
||||||
|
marginBottom: '0.35rem',
|
||||||
|
})}
|
||||||
|
>
|
||||||
|
{heading}
|
||||||
|
</H>
|
||||||
|
<Text variant="smNote" wrap="pretty">
|
||||||
|
{body}
|
||||||
|
</Text>
|
||||||
|
<div
|
||||||
|
className={css({
|
||||||
|
marginTop: '1rem',
|
||||||
|
})}
|
||||||
|
>
|
||||||
|
<LoginButton proConnectHint={false} />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -0,0 +1,82 @@
|
|||||||
|
import { A, Div, Text } from '@/primitives'
|
||||||
|
import { css } from '@/styled-system/css'
|
||||||
|
import { useTranslation } from 'react-i18next'
|
||||||
|
import { LoginPrompt } from './LoginPrompt'
|
||||||
|
import { useUser } from '@/features/auth'
|
||||||
|
|
||||||
|
interface NoAccessViewProps {
|
||||||
|
i18nKeyPrefix: string
|
||||||
|
i18nKey: string
|
||||||
|
helpArticle?: string
|
||||||
|
imagePath: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export const NoAccessView = ({
|
||||||
|
i18nKeyPrefix,
|
||||||
|
i18nKey,
|
||||||
|
helpArticle,
|
||||||
|
imagePath,
|
||||||
|
}: NoAccessViewProps) => {
|
||||||
|
const { isLoggedIn } = useUser()
|
||||||
|
const { t } = useTranslation('rooms', { keyPrefix: i18nKeyPrefix })
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Div
|
||||||
|
display="flex"
|
||||||
|
overflowY="scroll"
|
||||||
|
padding="0 1.5rem"
|
||||||
|
flexGrow={1}
|
||||||
|
flexDirection="column"
|
||||||
|
alignItems="center"
|
||||||
|
>
|
||||||
|
<img
|
||||||
|
src={imagePath}
|
||||||
|
alt=""
|
||||||
|
className={css({
|
||||||
|
minHeight: '309px',
|
||||||
|
height: '309px',
|
||||||
|
marginBottom: '1rem',
|
||||||
|
'@media (max-height: 700px)': {
|
||||||
|
height: 'auto',
|
||||||
|
minHeight: 'auto',
|
||||||
|
maxHeight: '45%',
|
||||||
|
marginBottom: '0.3rem',
|
||||||
|
},
|
||||||
|
'@media (max-height: 530px)': {
|
||||||
|
height: 'auto',
|
||||||
|
minHeight: 'auto',
|
||||||
|
maxHeight: '40%',
|
||||||
|
marginBottom: '0.1rem',
|
||||||
|
},
|
||||||
|
})}
|
||||||
|
/>
|
||||||
|
<Text>{t(`${i18nKey}.heading`)}</Text>
|
||||||
|
<Text
|
||||||
|
variant="note"
|
||||||
|
centered
|
||||||
|
className={css({
|
||||||
|
textStyle: 'sm',
|
||||||
|
marginBottom: '2.5rem',
|
||||||
|
marginTop: '0.25rem',
|
||||||
|
'@media (max-height: 700px)': {
|
||||||
|
marginBottom: '1rem',
|
||||||
|
},
|
||||||
|
})}
|
||||||
|
>
|
||||||
|
{t(`${i18nKey}.body`)}
|
||||||
|
<br />
|
||||||
|
{helpArticle && (
|
||||||
|
<A href={helpArticle} target="_blank">
|
||||||
|
{t(`${i18nKey}.linkMore`)}
|
||||||
|
</A>
|
||||||
|
)}
|
||||||
|
</Text>
|
||||||
|
{!isLoggedIn && (
|
||||||
|
<LoginPrompt
|
||||||
|
heading={t(`${i18nKey}.login.heading`)}
|
||||||
|
body={t(`${i18nKey}.login.body`)}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</Div>
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -27,8 +27,7 @@ import { useConfig } from '@/api/useConfig'
|
|||||||
import humanizeDuration from 'humanize-duration'
|
import humanizeDuration from 'humanize-duration'
|
||||||
import i18n from 'i18next'
|
import i18n from 'i18next'
|
||||||
import { FeatureFlags } from '@/features/analytics/enums'
|
import { FeatureFlags } from '@/features/analytics/enums'
|
||||||
import { LoginButton } from '@/components/LoginButton'
|
import { NoAccessView } from './NoAccessView'
|
||||||
import { useUser } from '@/features/auth'
|
|
||||||
|
|
||||||
export const ScreenRecordingSidePanel = () => {
|
export const ScreenRecordingSidePanel = () => {
|
||||||
const { data } = useConfig()
|
const { data } = useConfig()
|
||||||
@@ -43,8 +42,6 @@ export const ScreenRecordingSidePanel = () => {
|
|||||||
FeatureFlags.ScreenRecording
|
FeatureFlags.ScreenRecording
|
||||||
)
|
)
|
||||||
|
|
||||||
const { isLoggedIn } = useUser()
|
|
||||||
|
|
||||||
const { notifyParticipants } = useNotifyParticipants()
|
const { notifyParticipants } = useNotifyParticipants()
|
||||||
|
|
||||||
const roomId = useRoomId()
|
const roomId = useRoomId()
|
||||||
@@ -114,7 +111,7 @@ export const ScreenRecordingSidePanel = () => {
|
|||||||
posthog.capture('screen-recording-started', {})
|
posthog.capture('screen-recording-started', {})
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Failed to handle transcript:', error)
|
console.error('Failed to handle recording:', error)
|
||||||
setIsLoading(false)
|
setIsLoading(false)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -130,92 +127,12 @@ export const ScreenRecordingSidePanel = () => {
|
|||||||
|
|
||||||
if (hasFeatureWithoutAdminRights) {
|
if (hasFeatureWithoutAdminRights) {
|
||||||
return (
|
return (
|
||||||
<Div
|
<NoAccessView
|
||||||
display="flex"
|
i18nKeyPrefix="screenRecording"
|
||||||
overflowY="scroll"
|
i18nKey="notAdminOrOwner"
|
||||||
padding="0 1.5rem"
|
helpArticle={data?.support?.help_article_recording}
|
||||||
flexGrow={1}
|
imagePath="/assets/intro-slider/4.png"
|
||||||
flexDirection="column"
|
/>
|
||||||
alignItems="center"
|
|
||||||
>
|
|
||||||
<img
|
|
||||||
src="/assets/intro-slider/4.png"
|
|
||||||
alt={''}
|
|
||||||
className={css({
|
|
||||||
minHeight: '309px',
|
|
||||||
height: '309px',
|
|
||||||
marginBottom: '1rem',
|
|
||||||
'@media (max-height: 700px)': {
|
|
||||||
height: 'auto',
|
|
||||||
minHeight: 'auto',
|
|
||||||
maxHeight: '45%',
|
|
||||||
marginBottom: '0.3rem',
|
|
||||||
},
|
|
||||||
'@media (max-height: 530px)': {
|
|
||||||
height: 'auto',
|
|
||||||
minHeight: 'auto',
|
|
||||||
maxHeight: '40%',
|
|
||||||
marginBottom: '0.1rem',
|
|
||||||
},
|
|
||||||
})}
|
|
||||||
/>
|
|
||||||
<Text>{t('notAdminOrOwner.heading')}</Text>
|
|
||||||
<Text
|
|
||||||
variant="note"
|
|
||||||
wrap="balance"
|
|
||||||
centered
|
|
||||||
className={css({
|
|
||||||
textStyle: 'sm',
|
|
||||||
marginBottom: '2.5rem',
|
|
||||||
marginTop: '0.25rem',
|
|
||||||
'@media (max-height: 700px)': {
|
|
||||||
marginBottom: '1rem',
|
|
||||||
},
|
|
||||||
})}
|
|
||||||
>
|
|
||||||
{t('notAdminOrOwner.body')}
|
|
||||||
<br />
|
|
||||||
{data?.support?.help_article_recording && (
|
|
||||||
<A href={data.support.help_article_recording} target="_blank">
|
|
||||||
{t('notAdminOrOwner.linkMore')}
|
|
||||||
</A>
|
|
||||||
)}
|
|
||||||
</Text>
|
|
||||||
{!isLoggedIn && (
|
|
||||||
<div
|
|
||||||
className={css({
|
|
||||||
backgroundColor: 'primary.50',
|
|
||||||
borderRadius: '5px',
|
|
||||||
paddingY: '1rem',
|
|
||||||
paddingX: '1rem',
|
|
||||||
marginTop: '1rem',
|
|
||||||
display: 'flex',
|
|
||||||
flexDirection: 'column',
|
|
||||||
})}
|
|
||||||
>
|
|
||||||
<H
|
|
||||||
lvl={3}
|
|
||||||
className={css({
|
|
||||||
display: 'flex',
|
|
||||||
alignItems: 'center',
|
|
||||||
marginBottom: '0.35rem',
|
|
||||||
})}
|
|
||||||
>
|
|
||||||
{t('notAdminOrOwner.login.heading')}
|
|
||||||
</H>
|
|
||||||
<Text variant="smNote" wrap="balance">
|
|
||||||
{t('notAdminOrOwner.login.body')}
|
|
||||||
</Text>
|
|
||||||
<div
|
|
||||||
className={css({
|
|
||||||
marginTop: '1rem',
|
|
||||||
})}
|
|
||||||
>
|
|
||||||
<LoginButton proConnectHint={false} />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</Div>
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -30,8 +30,6 @@ import { Spinner } from '@/primitives/Spinner'
|
|||||||
import { useConfig } from '@/api/useConfig'
|
import { useConfig } from '@/api/useConfig'
|
||||||
import humanizeDuration from 'humanize-duration'
|
import humanizeDuration from 'humanize-duration'
|
||||||
import i18n from 'i18next'
|
import i18n from 'i18next'
|
||||||
import { useUser } from '@/features/auth'
|
|
||||||
import { LoginButton } from '@/components/LoginButton'
|
|
||||||
import { HStack, VStack } from '@/styled-system/jsx'
|
import { HStack, VStack } from '@/styled-system/jsx'
|
||||||
import { Checkbox } from '@/primitives/Checkbox.tsx'
|
import { Checkbox } from '@/primitives/Checkbox.tsx'
|
||||||
|
|
||||||
@@ -40,12 +38,11 @@ import {
|
|||||||
SettingsDialogExtendedKey,
|
SettingsDialogExtendedKey,
|
||||||
useTranscriptionLanguageOptions,
|
useTranscriptionLanguageOptions,
|
||||||
} from '@/features/settings'
|
} from '@/features/settings'
|
||||||
|
import { NoAccessView } from './NoAccessView'
|
||||||
|
|
||||||
export const TranscriptSidePanel = () => {
|
export const TranscriptSidePanel = () => {
|
||||||
const { data } = useConfig()
|
const { data } = useConfig()
|
||||||
|
|
||||||
const { isLoggedIn } = useUser()
|
|
||||||
|
|
||||||
const [isLoading, setIsLoading] = useState(false)
|
const [isLoading, setIsLoading] = useState(false)
|
||||||
const { t } = useTranslation('rooms', { keyPrefix: 'transcript' })
|
const { t } = useTranslation('rooms', { keyPrefix: 'transcript' })
|
||||||
|
|
||||||
@@ -153,179 +150,23 @@ export const TranscriptSidePanel = () => {
|
|||||||
|
|
||||||
if (hasFeatureWithoutAdminRights) {
|
if (hasFeatureWithoutAdminRights) {
|
||||||
return (
|
return (
|
||||||
<Div
|
<NoAccessView
|
||||||
display="flex"
|
i18nKeyPrefix="transcript"
|
||||||
overflowY="scroll"
|
i18nKey="notAdminOrOwner"
|
||||||
padding="0 1.5rem"
|
helpArticle={data?.support?.help_article_transcript}
|
||||||
flexGrow={1}
|
imagePath="/assets/intro-slider/3.png"
|
||||||
flexDirection="column"
|
/>
|
||||||
alignItems="center"
|
|
||||||
>
|
|
||||||
<img
|
|
||||||
src="/assets/intro-slider/3.png"
|
|
||||||
alt={''}
|
|
||||||
className={css({
|
|
||||||
minHeight: '309px',
|
|
||||||
height: '309px',
|
|
||||||
marginBottom: '1rem',
|
|
||||||
'@media (max-height: 700px)': {
|
|
||||||
height: 'auto',
|
|
||||||
minHeight: 'auto',
|
|
||||||
maxHeight: '45%',
|
|
||||||
marginBottom: '0.3rem',
|
|
||||||
},
|
|
||||||
'@media (max-height: 530px)': {
|
|
||||||
height: 'auto',
|
|
||||||
minHeight: 'auto',
|
|
||||||
maxHeight: '40%',
|
|
||||||
marginBottom: '0.1rem',
|
|
||||||
},
|
|
||||||
})}
|
|
||||||
/>
|
|
||||||
<Text>{t('notAdminOrOwner.heading')}</Text>
|
|
||||||
<Text
|
|
||||||
variant="note"
|
|
||||||
wrap="balance"
|
|
||||||
centered
|
|
||||||
className={css({
|
|
||||||
textStyle: 'sm',
|
|
||||||
marginBottom: '2.5rem',
|
|
||||||
marginTop: '0.25rem',
|
|
||||||
'@media (max-height: 700px)': {
|
|
||||||
marginBottom: '1rem',
|
|
||||||
},
|
|
||||||
})}
|
|
||||||
>
|
|
||||||
{t('notAdminOrOwner.body')}
|
|
||||||
<br />
|
|
||||||
{data?.support?.help_article_transcript && (
|
|
||||||
<A href={data.support.help_article_transcript} target="_blank">
|
|
||||||
{t('notAdminOrOwner.linkMore')}
|
|
||||||
</A>
|
|
||||||
)}
|
|
||||||
</Text>
|
|
||||||
{!isLoggedIn && (
|
|
||||||
<div
|
|
||||||
className={css({
|
|
||||||
backgroundColor: 'primary.50',
|
|
||||||
borderRadius: '5px',
|
|
||||||
paddingY: '1rem',
|
|
||||||
paddingX: '1rem',
|
|
||||||
marginTop: '1rem',
|
|
||||||
display: 'flex',
|
|
||||||
flexDirection: 'column',
|
|
||||||
})}
|
|
||||||
>
|
|
||||||
<H
|
|
||||||
lvl={3}
|
|
||||||
className={css({
|
|
||||||
display: 'flex',
|
|
||||||
alignItems: 'center',
|
|
||||||
marginBottom: '0.35rem',
|
|
||||||
})}
|
|
||||||
>
|
|
||||||
{t('notAdminOrOwner.login.heading')}
|
|
||||||
</H>
|
|
||||||
<Text variant="smNote" wrap="balance">
|
|
||||||
{t('notAdminOrOwner.login.body')}
|
|
||||||
</Text>
|
|
||||||
<div
|
|
||||||
className={css({
|
|
||||||
marginTop: '1rem',
|
|
||||||
})}
|
|
||||||
>
|
|
||||||
<LoginButton proConnectHint={false} />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</Div>
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!hasTranscriptAccess) {
|
if (!hasTranscriptAccess) {
|
||||||
return (
|
return (
|
||||||
<Div
|
<NoAccessView
|
||||||
display="flex"
|
i18nKeyPrefix="transcript"
|
||||||
overflowY="scroll"
|
i18nKey="premium"
|
||||||
padding="0 1.5rem"
|
helpArticle={data?.support?.help_article_transcript}
|
||||||
flexGrow={1}
|
imagePath="/assets/intro-slider/3.png"
|
||||||
flexDirection="column"
|
/>
|
||||||
alignItems="center"
|
|
||||||
>
|
|
||||||
<img
|
|
||||||
src="/assets/intro-slider/3.png"
|
|
||||||
alt={''}
|
|
||||||
className={css({
|
|
||||||
minHeight: '309px',
|
|
||||||
height: '309px',
|
|
||||||
marginBottom: '1rem',
|
|
||||||
'@media (max-height: 700px)': {
|
|
||||||
height: 'auto',
|
|
||||||
minHeight: 'auto',
|
|
||||||
maxHeight: '45%',
|
|
||||||
marginBottom: '0.3rem',
|
|
||||||
},
|
|
||||||
'@media (max-height: 530px)': {
|
|
||||||
height: 'auto',
|
|
||||||
minHeight: 'auto',
|
|
||||||
maxHeight: '40%',
|
|
||||||
marginBottom: '0.1rem',
|
|
||||||
},
|
|
||||||
})}
|
|
||||||
/>
|
|
||||||
<Text>{t('premium.heading')}</Text>
|
|
||||||
<Text
|
|
||||||
variant="note"
|
|
||||||
centered
|
|
||||||
className={css({
|
|
||||||
textStyle: 'sm',
|
|
||||||
marginBottom: '2.5rem',
|
|
||||||
marginTop: '0.25rem',
|
|
||||||
'@media (max-height: 700px)': {
|
|
||||||
marginBottom: '1rem',
|
|
||||||
},
|
|
||||||
})}
|
|
||||||
>
|
|
||||||
{t('premium.body')}{' '}
|
|
||||||
{data?.support?.help_article_transcript && (
|
|
||||||
<A href={data.support.help_article_transcript} target="_blank">
|
|
||||||
{t('linkMore')}
|
|
||||||
</A>
|
|
||||||
)}
|
|
||||||
</Text>
|
|
||||||
{!isLoggedIn && (
|
|
||||||
<div
|
|
||||||
className={css({
|
|
||||||
backgroundColor: 'primary.50',
|
|
||||||
borderRadius: '5px',
|
|
||||||
paddingY: '1rem',
|
|
||||||
paddingX: '1rem',
|
|
||||||
marginTop: '1rem',
|
|
||||||
display: 'flex',
|
|
||||||
flexDirection: 'column',
|
|
||||||
})}
|
|
||||||
>
|
|
||||||
<H
|
|
||||||
lvl={3}
|
|
||||||
className={css({
|
|
||||||
display: 'flex',
|
|
||||||
alignItems: 'center',
|
|
||||||
marginBottom: '0.35rem',
|
|
||||||
})}
|
|
||||||
>
|
|
||||||
{t('premium.login.heading')}
|
|
||||||
</H>
|
|
||||||
<Text variant="smNote">{t('premium.login.body')}</Text>
|
|
||||||
<div
|
|
||||||
className={css({
|
|
||||||
marginTop: '1rem',
|
|
||||||
})}
|
|
||||||
>
|
|
||||||
<LoginButton proConnectHint={false} />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</Div>
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -338,6 +338,7 @@
|
|||||||
"premium": {
|
"premium": {
|
||||||
"heading": "Premium-Funktion",
|
"heading": "Premium-Funktion",
|
||||||
"body": "Diese Funktion ist öffentlichen Bediensteten vorbehalten. Wenn Ihre E-Mail-Adresse nicht autorisiert ist, kontaktieren Sie bitte den Support, um Zugriff zu erhalten.",
|
"body": "Diese Funktion ist öffentlichen Bediensteten vorbehalten. Wenn Ihre E-Mail-Adresse nicht autorisiert ist, kontaktieren Sie bitte den Support, um Zugriff zu erhalten.",
|
||||||
|
"linkMore": "Mehr erfahren",
|
||||||
"login": {
|
"login": {
|
||||||
"heading": "Anmeldung erforderlich",
|
"heading": "Anmeldung erforderlich",
|
||||||
"body": "Nur der Ersteller des Meetings oder ein Administrator kann die Transkription starten. Melden Sie sich an, um Ihre Berechtigungen zu überprüfen."
|
"body": "Nur der Ersteller des Meetings oder ein Administrator kann die Transkription starten. Melden Sie sich an, um Ihre Berechtigungen zu überprüfen."
|
||||||
|
|||||||
@@ -338,6 +338,7 @@
|
|||||||
"premium": {
|
"premium": {
|
||||||
"heading": "Premium feature",
|
"heading": "Premium feature",
|
||||||
"body": "This feature is reserved for public agents. If your email address is not authorized, please contact support to get access.",
|
"body": "This feature is reserved for public agents. If your email address is not authorized, please contact support to get access.",
|
||||||
|
"linkMore": "Learn more",
|
||||||
"login": {
|
"login": {
|
||||||
"heading": "You are not logged in!",
|
"heading": "You are not logged in!",
|
||||||
"body": "You must be logged in to use this feature. Please log in, then try again."
|
"body": "You must be logged in to use this feature. Please log in, then try again."
|
||||||
|
|||||||
@@ -338,6 +338,7 @@
|
|||||||
"premium": {
|
"premium": {
|
||||||
"heading": "Fonctionnalité premium",
|
"heading": "Fonctionnalité premium",
|
||||||
"body": "Cette fonctionnalité est réservée aux agents publics. Si votre adresse email n’est pas autorisée, contactez le support pour obtenir l'accès.",
|
"body": "Cette fonctionnalité est réservée aux agents publics. Si votre adresse email n’est pas autorisée, contactez le support pour obtenir l'accès.",
|
||||||
|
"linkMore": "En savoir plus",
|
||||||
"login": {
|
"login": {
|
||||||
"heading": "Vous n'êtes pas connecté !",
|
"heading": "Vous n'êtes pas connecté !",
|
||||||
"body": "Vous devez être connecté pour utiliser cette fonctionnalité. Connectez-vous, puis réessayez."
|
"body": "Vous devez être connecté pour utiliser cette fonctionnalité. Connectez-vous, puis réessayez."
|
||||||
|
|||||||
@@ -338,6 +338,7 @@
|
|||||||
"premium": {
|
"premium": {
|
||||||
"heading": "Premiumfunctie",
|
"heading": "Premiumfunctie",
|
||||||
"body": "Deze functie is voorbehouden aan openbare medewerkers. Als uw e-mailadres niet is toegestaan, neem dan contact op met de support om toegang te krijgen.",
|
"body": "Deze functie is voorbehouden aan openbare medewerkers. Als uw e-mailadres niet is toegestaan, neem dan contact op met de support om toegang te krijgen.",
|
||||||
|
"linkMore": "Meer informatie",
|
||||||
"login": {
|
"login": {
|
||||||
"heading": "Inloggen vereist",
|
"heading": "Inloggen vereist",
|
||||||
"body": "Alleen de maker van de vergadering of een beheerder kan de transcriptie starten. Log in om uw machtigingen te controleren."
|
"body": "Alleen de maker van de vergadering of een beheerder kan de transcriptie starten. Log in om uw machtigingen te controleren."
|
||||||
|
|||||||
Reference in New Issue
Block a user