From 8d1f01645a04d52f034e5e775795ad87cf1eff30 Mon Sep 17 00:00:00 2001 From: lebaudantoine Date: Mon, 26 May 2025 23:53:56 +0200 Subject: [PATCH] =?UTF-8?q?=F0=9F=9A=B8(frontend)=20add=20notification=20w?= =?UTF-8?q?ith=20recording=20handling=20details?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Display notification clarifying recording is processing and show which email will receive completion notification. Reduces user mental load per @sampaccoud's feedback. --- .../notifications/NotificationDuration.ts | 1 + .../notifications/NotificationType.ts | 1 + .../components/ToastProvider.tsx | 2 + .../components/ToastRecordingSaving.tsx | 62 +++++++++++++++++++ .../notifications/components/ToastRegion.tsx | 6 ++ .../src/features/notifications/index.ts | 1 + .../src/features/notifications/utils.ts | 15 +++++ .../components/RecordingStateToast.tsx | 4 -- .../components/ScreenRecordingSidePanel.tsx | 5 ++ .../components/TranscriptSidePanel.tsx | 5 ++ .../src/locales/de/notifications.json | 10 +++ .../src/locales/en/notifications.json | 10 +++ .../src/locales/fr/notifications.json | 10 +++ .../src/locales/nl/notifications.json | 10 +++ 14 files changed, 138 insertions(+), 4 deletions(-) create mode 100644 src/frontend/src/features/notifications/components/ToastRecordingSaving.tsx diff --git a/src/frontend/src/features/notifications/NotificationDuration.ts b/src/frontend/src/features/notifications/NotificationDuration.ts index b5433dfd..23886db0 100644 --- a/src/frontend/src/features/notifications/NotificationDuration.ts +++ b/src/frontend/src/features/notifications/NotificationDuration.ts @@ -11,5 +11,6 @@ export const NotificationDuration = { PARTICIPANT_JOINED: ToastDuration.LONG, HAND_RAISED: ToastDuration.LONG, LOWER_HAND: ToastDuration.EXTRA_LONG, + RECORDING_SAVING: ToastDuration.EXTRA_LONG, REACTION_RECEIVED: ToastDuration.SHORT, } as const diff --git a/src/frontend/src/features/notifications/NotificationType.ts b/src/frontend/src/features/notifications/NotificationType.ts index 5b0a75fa..7f022822 100644 --- a/src/frontend/src/features/notifications/NotificationType.ts +++ b/src/frontend/src/features/notifications/NotificationType.ts @@ -10,4 +10,5 @@ export enum NotificationType { TranscriptionStopped = 'transcriptionStopped', ScreenRecordingStarted = 'screenRecordingStarted', ScreenRecordingStopped = 'screenRecordingStopped', + RecordingSaving = 'recordingSaving', } diff --git a/src/frontend/src/features/notifications/components/ToastProvider.tsx b/src/frontend/src/features/notifications/components/ToastProvider.tsx index 2ddf22e9..86c0e82e 100644 --- a/src/frontend/src/features/notifications/components/ToastProvider.tsx +++ b/src/frontend/src/features/notifications/components/ToastProvider.tsx @@ -8,6 +8,8 @@ export interface ToastData { participant: Participant type: NotificationType message?: string + // eslint-disable-next-line @typescript-eslint/no-explicit-any + [key: string]: any } // Using a global queue for toasts allows for centralized management and queuing of notifications diff --git a/src/frontend/src/features/notifications/components/ToastRecordingSaving.tsx b/src/frontend/src/features/notifications/components/ToastRecordingSaving.tsx new file mode 100644 index 00000000..b031d92f --- /dev/null +++ b/src/frontend/src/features/notifications/components/ToastRecordingSaving.tsx @@ -0,0 +1,62 @@ +import { useToast } from '@react-aria/toast' +import { useMemo, useRef } from 'react' +import { Text } from '@/primitives' + +import { StyledToastContainer, ToastProps } from './Toast' +import { HStack } from '@/styled-system/jsx' +import { useTranslation } from 'react-i18next' +import { useUser } from '@/features/auth' +import { css } from '@/styled-system/css' +import { RecordingMode } from '@/features/recording' + +export function ToastRecordingSaving({ state, ...props }: ToastProps) { + const { t } = useTranslation('notifications', { keyPrefix: 'recordingSave' }) + const ref = useRef(null) + const { toastProps, contentProps } = useToast(props, state, ref) + + const { user } = useUser() + + const modeLabel = useMemo(() => { + const mode = props.toast.content.mode as RecordingMode + switch (mode) { + case RecordingMode.Transcript: + return 'transcript' + case RecordingMode.ScreenRecording: + return 'screenRecording' + } + }, [props.toast.content]) + + return ( + + + + {user?.email ? ( + + ) : ( + t(`${modeLabel}.default`) + )} + + + + ) +} diff --git a/src/frontend/src/features/notifications/components/ToastRegion.tsx b/src/frontend/src/features/notifications/components/ToastRegion.tsx index cf0e4870..06b1d007 100644 --- a/src/frontend/src/features/notifications/components/ToastRegion.tsx +++ b/src/frontend/src/features/notifications/components/ToastRegion.tsx @@ -10,6 +10,7 @@ import { ToastMuted } from './ToastMuted' import { ToastMessageReceived } from './ToastMessageReceived' import { ToastLowerHand } from './ToastLowerHand' import { ToastAnyRecording } from './ToastAnyRecording' +import { ToastRecordingSaving } from './ToastRecordingSaving' interface ToastRegionProps extends AriaToastRegionProps { state: ToastState @@ -43,6 +44,11 @@ const renderToast = ( case NotificationType.ScreenRecordingStopped: return + case NotificationType.RecordingSaving: + return ( + + ) + default: return } diff --git a/src/frontend/src/features/notifications/index.ts b/src/frontend/src/features/notifications/index.ts index ffe15d4b..b94220b7 100644 --- a/src/frontend/src/features/notifications/index.ts +++ b/src/frontend/src/features/notifications/index.ts @@ -1,2 +1,3 @@ export { useNotifyParticipants } from './hooks/useNotifyParticipants' export { NotificationType } from './NotificationType' +export { notifyRecordingSaveInProgress } from './utils' diff --git a/src/frontend/src/features/notifications/utils.ts b/src/frontend/src/features/notifications/utils.ts index a99d6840..24ef5422 100644 --- a/src/frontend/src/features/notifications/utils.ts +++ b/src/frontend/src/features/notifications/utils.ts @@ -3,6 +3,7 @@ import { NotificationType } from './NotificationType' import { NotificationDuration } from './NotificationDuration' import { Participant } from 'livekit-client' import { NotificationPayload } from './NotificationPayload' +import { RecordingMode } from '@/features/recording' export const showLowerHandToast = ( participant: Participant, @@ -49,3 +50,17 @@ export const decodeNotificationDataReceived = ( return } } + +export const notifyRecordingSaveInProgress = ( + mode: RecordingMode, + participant: Participant +) => { + toastQueue.add( + { + participant, + mode, + type: NotificationType.RecordingSaving, + }, + { timeout: NotificationDuration.RECORDING_SAVING } + ) +} diff --git a/src/frontend/src/features/recording/components/RecordingStateToast.tsx b/src/frontend/src/features/recording/components/RecordingStateToast.tsx index f39c8f2e..6803936c 100644 --- a/src/frontend/src/features/recording/components/RecordingStateToast.tsx +++ b/src/frontend/src/features/recording/components/RecordingStateToast.tsx @@ -106,14 +106,10 @@ export const RecordingStateToast = () => { switch (recordingSnap.status) { case RecordingStatus.TRANSCRIPT_STARTED: return 'transcript.started' - case RecordingStatus.TRANSCRIPT_STOPPING: - return 'transcript.stopping' case RecordingStatus.TRANSCRIPT_STARTING: return 'transcript.starting' case RecordingStatus.SCREEN_RECORDING_STARTED: return 'screenRecording.started' - case RecordingStatus.SCREEN_RECORDING_STOPPING: - return 'screenRecording.stopping' case RecordingStatus.SCREEN_RECORDING_STARTING: return 'screenRecording.starting' case RecordingStatus.ANY_STARTED: diff --git a/src/frontend/src/features/recording/components/ScreenRecordingSidePanel.tsx b/src/frontend/src/features/recording/components/ScreenRecordingSidePanel.tsx index d847d52c..79e4f396 100644 --- a/src/frontend/src/features/recording/components/ScreenRecordingSidePanel.tsx +++ b/src/frontend/src/features/recording/components/ScreenRecordingSidePanel.tsx @@ -18,6 +18,7 @@ import { CRISP_HELP_ARTICLE_RECORDING } from '@/utils/constants' import { NotificationType, + notifyRecordingSaveInProgress, useNotifyParticipants, } from '@/features/notifications' import posthog from 'posthog-js' @@ -84,6 +85,10 @@ export const ScreenRecordingSidePanel = () => { await notifyParticipants({ type: NotificationType.ScreenRecordingStopped, }) + notifyRecordingSaveInProgress( + RecordingMode.ScreenRecording, + room.localParticipant + ) } else { await startRecordingRoom({ id: roomId, diff --git a/src/frontend/src/features/recording/components/TranscriptSidePanel.tsx b/src/frontend/src/features/recording/components/TranscriptSidePanel.tsx index ce6a5f2b..92bfc77e 100644 --- a/src/frontend/src/features/recording/components/TranscriptSidePanel.tsx +++ b/src/frontend/src/features/recording/components/TranscriptSidePanel.tsx @@ -24,6 +24,7 @@ import { FeatureFlags } from '@/features/analytics/enums' import { NotificationType, useNotifyParticipants, + notifyRecordingSaveInProgress, } from '@/features/notifications' import posthog from 'posthog-js' import { useSnapshot } from 'valtio/index' @@ -99,6 +100,10 @@ export const TranscriptSidePanel = () => { await notifyParticipants({ type: NotificationType.TranscriptionStopped, }) + notifyRecordingSaveInProgress( + RecordingMode.Transcript, + room.localParticipant + ) } else { await startRecordingRoom({ id: roomId, mode: RecordingMode.Transcript }) recordingStore.status = RecordingStatus.TRANSCRIPT_STARTING diff --git a/src/frontend/src/locales/de/notifications.json b/src/frontend/src/locales/de/notifications.json index 0f5bfa8c..fb8d37c7 100644 --- a/src/frontend/src/locales/de/notifications.json +++ b/src/frontend/src/locales/de/notifications.json @@ -29,5 +29,15 @@ "screenRecording": { "started": "{{name}} hat die Aufzeichnung des Treffens gestartet.", "stopped": "{{name}} hat die Aufzeichnung des Treffens gestoppt." + }, + "recordingSave": { + "transcript": { + "message": "Wir finalisieren Ihre Aufnahme! Sie erhalten eine E-Mail an {{email}}, sobald die Transkription fertig ist.", + "default": "Wir finalisieren Ihre Aufnahme! Sie erhalten eine E-Mail, sobald die Transkription fertig ist." + }, + "screenRecording": { + "message": "Wir finalisieren Ihre Aufnahme! Sie erhalten eine E-Mail an {{email}}, sobald sie fertig ist.", + "default": "Wir finalisieren Ihre Aufnahme! Sie erhalten eine E-Mail, sobald sie fertig ist." + } } } diff --git a/src/frontend/src/locales/en/notifications.json b/src/frontend/src/locales/en/notifications.json index f8d4f7aa..c38c446b 100644 --- a/src/frontend/src/locales/en/notifications.json +++ b/src/frontend/src/locales/en/notifications.json @@ -29,5 +29,15 @@ "screenRecording": { "started": "{{name}} started the meeting recording.", "stopped": "{{name}} stopped the meeting recording." + }, + "recordingSave": { + "transcript": { + "message": "Your recording is being saved! We’ll send the transcript to {{email}} as soon as it’s ready.", + "default": "Your recording is being saved! We’ll send the transcript to your email as soon as it’s ready." + }, + "screenRecording": { + "message": "Your recording is being saved! We’ll send a notification to {{email}} as soon as it’s ready.", + "default": "Your recording is being saved! We’ll send a notification to your email as soon as it’s ready." + } } } diff --git a/src/frontend/src/locales/fr/notifications.json b/src/frontend/src/locales/fr/notifications.json index 90b065eb..2339500f 100644 --- a/src/frontend/src/locales/fr/notifications.json +++ b/src/frontend/src/locales/fr/notifications.json @@ -29,5 +29,15 @@ "screenRecording": { "started": "{{name}} a démarré l'enregistrement de la réunion.", "stopped": "{{name}} a arrêté l'enregistrement de la réunion." + }, + "recordingSave": { + "transcript": { + "message": "Nous finalisons votre enregistrement ! Vous recevrez un e-mail à {{email}} dès que la transcription sera prête.", + "default": "Nous finalisons votre enregistrement ! Vous recevrez un e-mail dès que la transcription sera prête." + }, + "screenRecording": { + "message": "Nous finalisons votre enregistrement ! Vous recevrez un e-mail à {{email}} dès qu’il sera prêt.", + "default": "Nous finalisons votre enregistrement ! Vous recevrez un e-mail dès qu’il sera prêt." + } } } diff --git a/src/frontend/src/locales/nl/notifications.json b/src/frontend/src/locales/nl/notifications.json index 2e653876..ebaaebbc 100644 --- a/src/frontend/src/locales/nl/notifications.json +++ b/src/frontend/src/locales/nl/notifications.json @@ -29,5 +29,15 @@ "screenRecording": { "started": "{{name}} is begonnen met het opnemen van de vergadering.", "stopped": "{{name}} is gestopt met het opnemen van de vergadering." + }, + "recordingSave": { + "transcript": { + "message": "We zijn uw opname aan het voltooien! U ontvangt een e-mail op {{email}} zodra de transcriptie klaar is.", + "default": "We zijn uw opname aan het voltooien! U ontvangt een e-mail zodra de transcriptie klaar is." + }, + "screenRecording": { + "message": "We zijn uw opname aan het voltooien! U ontvangt een e-mail op {{email}} zodra deze klaar is.", + "default": "We zijn uw opname aan het voltooien! U ontvangt een e-mail zodra deze klaar is." + } } }