diff --git a/src/frontend/src/features/recording/index.ts b/src/frontend/src/features/recording/index.ts
index b2c29a0e..386819cd 100644
--- a/src/frontend/src/features/recording/index.ts
+++ b/src/frontend/src/features/recording/index.ts
@@ -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'
diff --git a/src/frontend/src/features/recording/routes/RecordingDownload.tsx b/src/frontend/src/features/recording/routes/RecordingDownload.tsx
new file mode 100644
index 00000000..f79d2eec
--- /dev/null
+++ b/src/frontend/src/features/recording/routes/RecordingDownload.tsx
@@ -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
+ }
+
+ if (!isLoggedIn) {
+ return (
+
+ )
+ }
+
+ if (isError) {
+ return
+ }
+
+ return (
+
+
+
+
+
+
+ {t('success.title')}
+
+
+
+
+
+ {t('success.button')}
+
+
+
+
+
+ )
+}
diff --git a/src/frontend/src/locales/de/recording.json b/src/frontend/src/locales/de/recording.json
new file mode 100644
index 00000000..ef8502a1
--- /dev/null
+++ b/src/frontend/src/locales/de/recording.json
@@ -0,0 +1,15 @@
+{
+ "error": {
+ "title": "",
+ "body": ""
+ },
+ "authentication": {
+ "title": "",
+ "body": ""
+ },
+ "success": {
+ "title": "",
+ "body": "",
+ "button": ""
+ }
+}
diff --git a/src/frontend/src/locales/en/recording.json b/src/frontend/src/locales/en/recording.json
new file mode 100644
index 00000000..fff09fb0
--- /dev/null
+++ b/src/frontend/src/locales/en/recording.json
@@ -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 {{room}} from {{created_at}}.",
+ "button": "Download"
+ }
+}
diff --git a/src/frontend/src/locales/fr/recording.json b/src/frontend/src/locales/fr/recording.json
new file mode 100644
index 00000000..5818e570
--- /dev/null
+++ b/src/frontend/src/locales/fr/recording.json
@@ -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 {{room}} du {{created_at}}.",
+ "button": "Télécharger"
+ }
+}
diff --git a/src/frontend/src/locales/nl/recording.json b/src/frontend/src/locales/nl/recording.json
new file mode 100644
index 00000000..de3a9965
--- /dev/null
+++ b/src/frontend/src/locales/nl/recording.json
@@ -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 {{room}} op {{created_at}}.",
+ "button": "Downloaden"
+ }
+}
diff --git a/src/frontend/src/routes.ts b/src/frontend/src/routes.ts
index 0783a15d..24e3f866 100644
--- a/src/frontend/src/routes.ts
+++ b/src/frontend/src/routes.ts
@@ -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[/](?[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