(frontend) introduce dedicated recording download page

Create initial version of dedicated page for recording downloads, linked
directly from email notifications sent to users. Implementation is basic
but functional, serving as temporary solution until files can be stored in
drive storage. Enables recipients to access recordings through direct links.
This commit is contained in:
lebaudantoine
2025-04-17 11:58:32 +02:00
committed by aleb_the_flash
parent 06462a55b0
commit e5af74685e
7 changed files with 155 additions and 1 deletions

View File

@@ -13,3 +13,6 @@ export { RecordingMode } from './types'
export { RecordingStateToast } from './components/RecordingStateToast'
export { TranscriptSidePanel } from './components/TranscriptSidePanel'
export { ScreenRecordingSidePanel } from './components/ScreenRecordingSidePanel'
// routes
export { RecordingDownload as RecordingDownloadRoute } from './routes/RecordingDownload'

View File

@@ -0,0 +1,81 @@
import { useParams } from 'wouter'
import { useQuery } from '@tanstack/react-query'
import { Center, VStack } from '@/styled-system/jsx'
import { css } from '@/styled-system/css'
import { useTranslation } from 'react-i18next'
import fourthSlide from '@/assets/intro-slider/4_record.png'
import { mediaUrl } from '@/api/mediaUrl'
import { UserAware, useUser } from '@/features/auth'
import { Screen } from '@/layout/Screen'
import { H, LinkButton, Text } from '@/primitives'
import { formatDate } from '@/utils/formatDate'
import { ErrorScreen } from '@/components/ErrorScreen'
import { LoadingScreen } from '@/components/LoadingScreen'
import { fetchRecording } from '../api/fetchRecording'
export const RecordingDownload = () => {
const { t } = useTranslation('recording')
const { recordingId } = useParams()
const { isLoggedIn } = useUser()
const { data, isLoading, isError } = useQuery({
queryKey: ['recording', recordingId],
queryFn: () => fetchRecording({ recordingId }),
retry: false,
enabled: !!recordingId,
})
if (isLoading || !data) {
return <LoadingScreen />
}
if (!isLoggedIn) {
return (
<ErrorScreen
title={t('authentication.title')}
body={t('authentication.body')}
/>
)
}
if (isError) {
return <ErrorScreen title={t('error.title')} body={t('error.body')} />
}
return (
<UserAware>
<Screen layout="centered" footer={false}>
<Center>
<VStack>
<img
src={fourthSlide}
alt={''}
className={css({
maxHeight: '309px',
})}
/>
<H lvl={1} centered>
{t('success.title')}
</H>
<Text centered margin="md" wrap={'balance'}>
<span
dangerouslySetInnerHTML={{
__html: t('success.body', {
room: data.room.name,
created_at: formatDate(data.created_at, 'YYYY-MM-DD HH:mm'),
}),
}}
/>
</Text>
<LinkButton
href={mediaUrl(data.key)}
download={`${data.room.name}-${formatDate(data.created_at)}`}
>
{t('success.button')}
</LinkButton>
</VStack>
</Center>
</Screen>
</UserAware>
)
}

View File

@@ -0,0 +1,15 @@
{
"error": {
"title": "",
"body": ""
},
"authentication": {
"title": "",
"body": ""
},
"success": {
"title": "",
"body": "",
"button": ""
}
}

View File

@@ -0,0 +1,15 @@
{
"error": {
"title": "Recording unavailable",
"body": "This recording could not be found or was deleted after its 7-day validity period."
},
"authentication": {
"title": "Authentication required",
"body": "Please log in to access this recording."
},
"success": {
"title": "Your recording is ready!",
"body": "Recording of the meeting <b>{{room}}</b> from {{created_at}}.",
"button": "Download"
}
}

View File

@@ -0,0 +1,15 @@
{
"error": {
"title": "Enregistrement indisponible",
"body": "Cet enregistrement est introuvable ou a été supprimé après sa période de validité de 7 jours."
},
"authentication": {
"title": "Authentification requise",
"body": "Veuillez vous connecter pour accéder à cet enregistrement."
},
"success": {
"title": "Votre enregistrement est prêt !",
"body": "Enregistrement de la réunion <b>{{room}}</b> du {{created_at}}.",
"button": "Télécharger"
}
}

View File

@@ -0,0 +1,15 @@
{
"error": {
"title": "Opname niet beschikbaar",
"body": "Deze opname is niet gevonden of is verwijderd na de geldigheidsperiode van 7 dagen."
},
"authentication": {
"title": "Authenticatie vereist",
"body": "Log in om toegang te krijgen tot deze opname."
},
"success": {
"title": "Je opname is klaar!",
"body": "Opname van de vergadering <b>{{room}}</b> op {{created_at}}.",
"button": "Downloaden"
}
}

View File

@@ -5,6 +5,7 @@ import { AccessibilityRoute } from '@/features/legalsTerms/Accessibility'
import { TermsOfServiceRoute } from '@/features/legalsTerms/TermsOfService'
import { CreatePopup } from '@/features/sdk/routes/CreatePopup'
import { CreateMeetingButton } from '@/features/sdk/routes/CreateMeetingButton'
import { RecordingDownloadRoute } from '@/features/recording'
export const routes: Record<
| 'home'
@@ -14,7 +15,8 @@ export const routes: Record<
| 'accessibility'
| 'termsOfService'
| 'sdkCreatePopup'
| 'sdkCreateButton',
| 'sdkCreateButton'
| 'recordingDownload',
{
name: RouteName
path: RegExp | string
@@ -64,6 +66,14 @@ export const routes: Record<
path: '/sdk/create-button',
Component: CreateMeetingButton,
},
recordingDownload: {
name: 'recordingDownload',
path: new RegExp(
`^[/]recording[/](?<recordingId>[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12})$`
),
to: (recordingId: string) => `/recording/${recordingId.trim()}`,
Component: RecordingDownloadRoute,
},
}
export type RouteName = keyof typeof routes