From f42b57da8298c348597988681c20a3cd4a43c1fb Mon Sep 17 00:00:00 2001 From: martincupela Date: Thu, 7 May 2026 16:53:08 +0200 Subject: [PATCH] feat: add snackbar notification upon channels query pagination failure --- .../__tests__/ChannelList.test.tsx | 84 +++++++++++++++++++ .../ChannelList/hooks/usePaginatedChannels.ts | 25 +++++- src/i18n/de.json | 4 +- src/i18n/en.json | 4 +- src/i18n/es.json | 4 +- src/i18n/fr.json | 4 +- src/i18n/hi.json | 4 +- src/i18n/it.json | 4 +- src/i18n/ja.json | 4 +- src/i18n/ko.json | 4 +- src/i18n/nl.json | 4 +- src/i18n/pt.json | 4 +- src/i18n/ru.json | 4 +- src/i18n/tr.json | 4 +- 14 files changed, 142 insertions(+), 15 deletions(-) diff --git a/src/components/ChannelList/__tests__/ChannelList.test.tsx b/src/components/ChannelList/__tests__/ChannelList.test.tsx index e22d5d7cf4..f2ff7579dc 100644 --- a/src/components/ChannelList/__tests__/ChannelList.test.tsx +++ b/src/components/ChannelList/__tests__/ChannelList.test.tsx @@ -314,6 +314,42 @@ describe('ChannelList', () => { expect(results).toHaveNoViolations(); }); + it('should add a notification when the first page of channels fails to load', async () => { + useMockedApis(chatClient, [erroredPostApi()]); + const addNotificationSpy = vi.spyOn(chatClient.notifications, 'add'); + vi.spyOn(console, 'warn').mockImplementation(() => null); + + render( + + + + + , + ); + + await waitFor(() => { + expect(addNotificationSpy).toHaveBeenCalledWith( + expect.objectContaining({ + message: 'Failed to load channels', + options: expect.objectContaining({ + severity: 'error', + tags: expect.arrayContaining(['target:channel-list']), + type: 'api:channel-list:load:failed', + }), + origin: { emitter: 'ChannelList' }, + }), + ); + }); + }); + it('provides the error object to LoadingErrorIndicator', async () => { useMockedApis(chatClient, [erroredPostApi()]); vi.spyOn(console, 'warn').mockImplementationOnce(() => null); @@ -341,6 +377,54 @@ describe('ChannelList', () => { expect(screen.getByText('StreamChat error HTTP code: 500')).toBeInTheDocument(); }); + it('should keep loaded channels visible and add a notification when loading more fails', async () => { + useMockedApis(chatClient, [queryChannelsApi([testChannel1, testChannel2])]); + const addNotificationSpy = vi.spyOn(chatClient.notifications, 'add'); + vi.spyOn(console, 'warn').mockImplementation(() => null); + + render( + + + + + , + ); + + await waitFor(() => { + expect(screen.getByTestId(testChannel1.channel.id)).toBeInTheDocument(); + expect(screen.getByTestId(testChannel2.channel.id)).toBeInTheDocument(); + }); + + useMockedApis(chatClient, [erroredPostApi()]); + + await act(() => { + fireEvent.click(screen.getByTestId('load-more-button')); + }); + + await waitFor(() => { + expect(addNotificationSpy).toHaveBeenCalledWith( + expect.objectContaining({ + message: 'Failed to load more channels', + options: expect.objectContaining({ + severity: 'error', + tags: expect.arrayContaining(['target:channel-list']), + type: 'api:channel-list:load-more:failed', + }), + origin: { emitter: 'ChannelList' }, + }), + ); + }); + + expect(screen.getByTestId(testChannel1.channel.id)).toBeInTheDocument(); + expect(screen.getByTestId(testChannel2.channel.id)).toBeInTheDocument(); + expect(screen.queryByTestId('error-indicator')).not.toBeInTheDocument(); + }); + it('should render loading indicator before the first channel list load and on reload', async () => { const channelsQueryStatesHistory = []; const channelListMessengerLoadingHistory = []; diff --git a/src/components/ChannelList/hooks/usePaginatedChannels.ts b/src/components/ChannelList/hooks/usePaginatedChannels.ts index 3dfa592c56..86073e7c25 100644 --- a/src/components/ChannelList/hooks/usePaginatedChannels.ts +++ b/src/components/ChannelList/hooks/usePaginatedChannels.ts @@ -12,6 +12,8 @@ import type { } from 'stream-chat'; import { useChatContext } from '../../../context/ChatContext'; +import { useTranslationContext } from '../../../context/TranslationContext'; +import { useNotificationApi } from '../../Notifications'; import type { ChannelsQueryState } from '../../Chat/hooks/useChannelsQueryState'; @@ -44,9 +46,11 @@ export const usePaginatedChannels = ( recoveryThrottleIntervalMs: number = RECOVER_LOADED_CHANNELS_THROTTLE_INTERVAL_IN_MS, customQueryChannels?: CustomQueryChannelsFn, ) => { + const { addNotification } = useNotificationApi(); const { channelsQueryState: { error, setError, setQueryInProgress }, } = useChatContext('usePaginatedChannels'); + const { t } = useTranslationContext(); const [channels, setChannels] = useState>([]); const [hasNextPage, setHasNextPage] = useState(true); const lastRecoveryTimestamp = useRef(undefined); @@ -62,6 +66,8 @@ export const usePaginatedChannels = ( // eslint-disable-next-line react-hooks/exhaustive-deps const queryChannels = async (queryType = 'load-more') => { setError(null); + const offset = queryType === 'reload' ? 0 : channels.length; + const isFirstPage = offset === 0; if (queryType === 'reload') { setChannels([]); @@ -77,8 +83,6 @@ export const usePaginatedChannels = ( setHasNextPage, }); } else { - const offset = queryType === 'reload' ? 0 : channels.length; - const newOptions = { offset, ...options, @@ -105,7 +109,22 @@ export const usePaginatedChannels = ( } } catch (error) { console.warn(error); - setError(error as ErrorFromResponse); + addNotification({ + emitter: 'ChannelList', + error: error instanceof Error ? error : undefined, + message: isFirstPage + ? t('Failed to load channels') + : t('Failed to load more channels'), + severity: 'error', + targetPanels: ['channel-list'], + type: isFirstPage + ? 'api:channel-list:load:failed' + : 'api:channel-list:load-more:failed', + }); + + if (isFirstPage) { + setError(error as ErrorFromResponse); + } } setQueryInProgress(null); diff --git a/src/i18n/de.json b/src/i18n/de.json index 2103fe44fc..b7fd83e70a 100644 --- a/src/i18n/de.json +++ b/src/i18n/de.json @@ -96,10 +96,10 @@ "aria/Mark Message Unread": "Als ungelesen markieren", "aria/Mark messages as read": "Nachrichten als gelesen markieren", "aria/Menu": "Menü", - "aria/Message,": "Nachricht,", "aria/Message Actions": "Nachrichtenaktionen", "aria/Message from {{ user }},": "Nachricht von {{ user }},", "aria/Message Options": "Nachrichtenoptionen", + "aria/Message,": "Nachricht,", "aria/Mute User": "Benutzer stummschalten", "aria/Notifications": "Benachrichtigungen", "aria/Open Attachment Selector": "Anhang-Auswahl öffnen", @@ -229,6 +229,8 @@ "Failed to end the poll due to {{reason}}": "Umfrage konnte aufgrund von {{reason}} nicht beendet werden", "Failed to jump to the first unread message": "Fehler beim Springen zur ersten ungelesenen Nachricht", "Failed to leave channel": "Kanal konnte nicht verlassen werden", + "Failed to load channels": "Kanäle konnten nicht geladen werden", + "Failed to load more channels": "Weitere Kanäle konnten nicht geladen werden", "Failed to mark channel as read": "Fehler beim Markieren des Kanals als gelesen", "Failed to play the recording": "Wiedergabe der Aufnahme fehlgeschlagen", "Failed to retrieve location": "Standort konnte nicht abgerufen werden", diff --git a/src/i18n/en.json b/src/i18n/en.json index 5ceae9c9f4..3d1e3b0d38 100644 --- a/src/i18n/en.json +++ b/src/i18n/en.json @@ -96,10 +96,10 @@ "aria/Mark Message Unread": "Mark Message Unread", "aria/Mark messages as read": "Mark messages as read", "aria/Menu": "Menu", - "aria/Message,": "Message,", "aria/Message Actions": "Message Actions", "aria/Message from {{ user }},": "Message from {{ user }},", "aria/Message Options": "Message Options", + "aria/Message,": "Message,", "aria/Mute User": "Mute User", "aria/Notifications": "Notifications", "aria/Open Attachment Selector": "Open Attachment Selector", @@ -229,6 +229,8 @@ "Failed to end the poll due to {{reason}}": "Failed to end the poll due to {{reason}}", "Failed to jump to the first unread message": "Failed to jump to the first unread message", "Failed to leave channel": "Failed to leave channel", + "Failed to load channels": "Failed to load channels", + "Failed to load more channels": "Failed to load more channels", "Failed to mark channel as read": "Failed to mark channel as read", "Failed to play the recording": "Failed to play the recording", "Failed to retrieve location": "Failed to retrieve location", diff --git a/src/i18n/es.json b/src/i18n/es.json index c676e9a370..566ac477e6 100644 --- a/src/i18n/es.json +++ b/src/i18n/es.json @@ -104,10 +104,10 @@ "aria/Mark Message Unread": "Marcar como no leído", "aria/Mark messages as read": "Marcar mensajes como leídos", "aria/Menu": "Menú", - "aria/Message,": "Mensaje,", "aria/Message Actions": "Acciones del mensaje", "aria/Message from {{ user }},": "Mensaje de {{ user }},", "aria/Message Options": "Opciones de mensaje", + "aria/Message,": "Mensaje,", "aria/Mute User": "Silenciar usuario", "aria/Notifications": "Notificaciones", "aria/Open Attachment Selector": "Abrir selector de adjuntos", @@ -237,6 +237,8 @@ "Failed to end the poll due to {{reason}}": "No se pudo terminar la encuesta debido a {{reason}}", "Failed to jump to the first unread message": "Error al saltar al primer mensaje no leído", "Failed to leave channel": "No se pudo salir del canal", + "Failed to load channels": "No se pudieron cargar los canales", + "Failed to load more channels": "No se pudieron cargar más canales", "Failed to mark channel as read": "Error al marcar el canal como leído", "Failed to play the recording": "No se pudo reproducir la grabación", "Failed to retrieve location": "No se pudo obtener la ubicación", diff --git a/src/i18n/fr.json b/src/i18n/fr.json index bc3054f716..2b3d13b7da 100644 --- a/src/i18n/fr.json +++ b/src/i18n/fr.json @@ -104,10 +104,10 @@ "aria/Mark Message Unread": "Marquer comme non lu", "aria/Mark messages as read": "Marquer les messages comme lus", "aria/Menu": "Menu", - "aria/Message,": "Message,", "aria/Message Actions": "Actions du message", "aria/Message from {{ user }},": "Message de {{ user }},", "aria/Message Options": "Options du message", + "aria/Message,": "Message,", "aria/Mute User": "Mettre en sourdine", "aria/Notifications": "Notifications", "aria/Open Attachment Selector": "Ouvrir le sélecteur de pièces jointes", @@ -237,6 +237,8 @@ "Failed to end the poll due to {{reason}}": "Impossible de terminer le sondage en raison de {{reason}}", "Failed to jump to the first unread message": "Échec du saut vers le premier message non lu", "Failed to leave channel": "Impossible de quitter le canal", + "Failed to load channels": "Impossible de charger les canaux", + "Failed to load more channels": "Impossible de charger davantage de canaux", "Failed to mark channel as read": "Échec du marquage du canal comme lu", "Failed to play the recording": "Impossible de lire l'enregistrement", "Failed to retrieve location": "Impossible de récupérer l'emplacement", diff --git a/src/i18n/hi.json b/src/i18n/hi.json index ca0ce2dc3a..194f35c756 100644 --- a/src/i18n/hi.json +++ b/src/i18n/hi.json @@ -96,10 +96,10 @@ "aria/Mark Message Unread": "अपठित चिह्नित करें", "aria/Mark messages as read": "संदेशों को पढ़ा हुआ चिह्नित करें", "aria/Menu": "मेन्यू", - "aria/Message,": "संदेश,", "aria/Message Actions": "संदेश कार्रवाइयाँ", "aria/Message from {{ user }},": "{{ user }} का संदेश,", "aria/Message Options": "संदेश विकल्प", + "aria/Message,": "संदेश,", "aria/Mute User": "उपयोगकर्ता म्यूट करें", "aria/Notifications": "सूचनाएं", "aria/Open Attachment Selector": "अटैचमेंट चयनकर्ता खोलें", @@ -230,6 +230,8 @@ "Failed to end the poll due to {{reason}}": "{{reason}} के कारण पोल समाप्त करने में विफल", "Failed to jump to the first unread message": "पहले अपठित संदेश पर जाने में विफल", "Failed to leave channel": "चैनल छोड़ने में विफल", + "Failed to load channels": "चैनल लोड करने में विफल", + "Failed to load more channels": "और चैनल लोड करने में विफल", "Failed to mark channel as read": "चैनल को पढ़ा हुआ चिह्नित करने में विफल।", "Failed to play the recording": "रेकॉर्डिंग प्ले करने में विफल", "Failed to retrieve location": "स्थान प्राप्त करने में विफल", diff --git a/src/i18n/it.json b/src/i18n/it.json index 4d73d13b99..e0946a2305 100644 --- a/src/i18n/it.json +++ b/src/i18n/it.json @@ -104,10 +104,10 @@ "aria/Mark Message Unread": "Contrassegna come non letto", "aria/Mark messages as read": "Segna i messaggi come letti", "aria/Menu": "Menu", - "aria/Message,": "Messaggio,", "aria/Message Actions": "Azioni del messaggio", "aria/Message from {{ user }},": "Messaggio di {{ user }},", "aria/Message Options": "Opzioni di messaggio", + "aria/Message,": "Messaggio,", "aria/Mute User": "Mute utente", "aria/Notifications": "Notifiche", "aria/Open Attachment Selector": "Apri selettore allegati", @@ -237,6 +237,8 @@ "Failed to end the poll due to {{reason}}": "Impossibile terminare il sondaggio a causa di {{reason}}", "Failed to jump to the first unread message": "Impossibile passare al primo messaggio non letto", "Failed to leave channel": "Impossibile lasciare il canale", + "Failed to load channels": "Impossibile caricare i canali", + "Failed to load more channels": "Impossibile caricare altri canali", "Failed to mark channel as read": "Impossibile contrassegnare il canale come letto", "Failed to play the recording": "Impossibile riprodurre la registrazione", "Failed to retrieve location": "Impossibile recuperare la posizione", diff --git a/src/i18n/ja.json b/src/i18n/ja.json index ba0e860603..c4c95114b5 100644 --- a/src/i18n/ja.json +++ b/src/i18n/ja.json @@ -95,10 +95,10 @@ "aria/Mark Message Unread": "未読としてマーク", "aria/Mark messages as read": "メッセージを既読にする", "aria/Menu": "メニュー", - "aria/Message,": "メッセージ,", "aria/Message Actions": "メッセージ操作", "aria/Message from {{ user }},": "{{ user }}さんからのメッセージ,", "aria/Message Options": "メッセージオプション", + "aria/Message,": "メッセージ,", "aria/Mute User": "ユーザーをミュート", "aria/Notifications": "通知", "aria/Open Attachment Selector": "添付ファイル選択を開く", @@ -228,6 +228,8 @@ "Failed to end the poll due to {{reason}}": "{{reason}}のためアンケートの終了に失敗しました", "Failed to jump to the first unread message": "最初の未読メッセージにジャンプできませんでした", "Failed to leave channel": "チャンネルの退出に失敗しました", + "Failed to load channels": "チャンネルの読み込みに失敗しました", + "Failed to load more channels": "さらにチャンネルを読み込めませんでした", "Failed to mark channel as read": "チャンネルを既読にすることができませんでした", "Failed to play the recording": "録音の再生に失敗しました", "Failed to retrieve location": "位置情報の取得に失敗しました", diff --git a/src/i18n/ko.json b/src/i18n/ko.json index ee5251c02d..e62f036769 100644 --- a/src/i18n/ko.json +++ b/src/i18n/ko.json @@ -95,10 +95,10 @@ "aria/Mark Message Unread": "읽지 않음으로 표시", "aria/Mark messages as read": "메시지를 읽음으로 표시", "aria/Menu": "메뉴", - "aria/Message,": "메시지,", "aria/Message Actions": "메시지 작업", "aria/Message from {{ user }},": "{{ user }}의 메시지,", "aria/Message Options": "메시지 옵션", + "aria/Message,": "메시지,", "aria/Mute User": "사용자 음소거", "aria/Notifications": "알림", "aria/Open Attachment Selector": "첨부 파일 선택기 열기", @@ -228,6 +228,8 @@ "Failed to end the poll due to {{reason}}": "{{reason}}(으)로 인해 투표 종료에 실패했습니다", "Failed to jump to the first unread message": "첫 번째 읽지 않은 메시지로 이동하지 못했습니다", "Failed to leave channel": "채널 나가기에 실패했습니다", + "Failed to load channels": "채널을 불러오지 못했습니다", + "Failed to load more channels": "채널을 더 불러오지 못했습니다", "Failed to mark channel as read": "채널을 읽음으로 표시하는 데 실패했습니다", "Failed to play the recording": "녹음을 재생하지 못했습니다", "Failed to retrieve location": "위치를 가져오지 못했습니다", diff --git a/src/i18n/nl.json b/src/i18n/nl.json index 4720d97e4a..5d1b16af1f 100644 --- a/src/i18n/nl.json +++ b/src/i18n/nl.json @@ -96,10 +96,10 @@ "aria/Mark Message Unread": "Markeren als ongelezen", "aria/Mark messages as read": "Markeer berichten als gelezen", "aria/Menu": "Menu", - "aria/Message,": "Bericht,", "aria/Message Actions": "Berichtacties", "aria/Message from {{ user }},": "Bericht van {{ user }},", "aria/Message Options": "Berichtopties", + "aria/Message,": "Bericht,", "aria/Mute User": "Gebruiker dempen", "aria/Notifications": "Meldingen", "aria/Open Attachment Selector": "Open bijlage selector", @@ -229,6 +229,8 @@ "Failed to end the poll due to {{reason}}": "Peiling kon niet worden beëindigd vanwege {{reason}}", "Failed to jump to the first unread message": "Niet gelukt om naar het eerste ongelezen bericht te springen", "Failed to leave channel": "Kanaal verlaten mislukt", + "Failed to load channels": "Kanalen konden niet worden geladen", + "Failed to load more channels": "Meer kanalen konden niet worden geladen", "Failed to mark channel as read": "Kanaal kon niet als gelezen worden gemarkeerd", "Failed to play the recording": "Kan de opname niet afspelen", "Failed to retrieve location": "Locatie kon niet worden opgehaald", diff --git a/src/i18n/pt.json b/src/i18n/pt.json index a2aae65133..6b44bac3b2 100644 --- a/src/i18n/pt.json +++ b/src/i18n/pt.json @@ -104,10 +104,10 @@ "aria/Mark Message Unread": "Marcar como não lida", "aria/Mark messages as read": "Marcar mensagens como lidas", "aria/Menu": "Menu", - "aria/Message,": "Mensagem,", "aria/Message Actions": "Ações da mensagem", "aria/Message from {{ user }},": "Mensagem de {{ user }},", "aria/Message Options": "Opções de mensagem", + "aria/Message,": "Mensagem,", "aria/Mute User": "Silenciar usuário", "aria/Notifications": "Notificações", "aria/Open Attachment Selector": "Abrir seletor de anexos", @@ -237,6 +237,8 @@ "Failed to end the poll due to {{reason}}": "Falha ao encerrar a enquete devido a {{reason}}", "Failed to jump to the first unread message": "Falha ao pular para a primeira mensagem não lida", "Failed to leave channel": "Falha ao sair do canal", + "Failed to load channels": "Falha ao carregar os canais", + "Failed to load more channels": "Falha ao carregar mais canais", "Failed to mark channel as read": "Falha ao marcar o canal como lido", "Failed to play the recording": "Falha ao reproduzir a gravação", "Failed to retrieve location": "Falha ao obter localização", diff --git a/src/i18n/ru.json b/src/i18n/ru.json index 7adacc95d2..830068fb96 100644 --- a/src/i18n/ru.json +++ b/src/i18n/ru.json @@ -113,10 +113,10 @@ "aria/Mark Message Unread": "Отметить как непрочитанное", "aria/Mark messages as read": "Отметить сообщения как прочитанные", "aria/Menu": "Меню", - "aria/Message,": "Сообщение,", "aria/Message Actions": "Действия с сообщением", "aria/Message from {{ user }},": "Сообщение от {{ user }},", "aria/Message Options": "Параметры сообщения", + "aria/Message,": "Сообщение,", "aria/Mute User": "Отключить уведомления", "aria/Notifications": "Уведомления", "aria/Open Attachment Selector": "Открыть выбор вложений", @@ -246,6 +246,8 @@ "Failed to end the poll due to {{reason}}": "Не удалось завершить опрос из-за {{reason}}", "Failed to jump to the first unread message": "Не удалось перейти к первому непрочитанному сообщению", "Failed to leave channel": "Не удалось покинуть канал", + "Failed to load channels": "Не удалось загрузить каналы", + "Failed to load more channels": "Не удалось загрузить больше каналов", "Failed to mark channel as read": "Не удалось пометить канал как прочитанный", "Failed to play the recording": "Не удалось воспроизвести запись", "Failed to retrieve location": "Не удалось получить местоположение", diff --git a/src/i18n/tr.json b/src/i18n/tr.json index 3463969b08..42758610e2 100644 --- a/src/i18n/tr.json +++ b/src/i18n/tr.json @@ -96,10 +96,10 @@ "aria/Mark Message Unread": "Okunmamış olarak işaretle", "aria/Mark messages as read": "Mesajları okundu olarak işaretle", "aria/Menu": "Menü", - "aria/Message,": "Mesaj,", "aria/Message Actions": "Mesaj eylemleri", "aria/Message from {{ user }},": "{{ user }} adlı kullanıcıdan mesaj,", "aria/Message Options": "Mesaj Seçenekleri", + "aria/Message,": "Mesaj,", "aria/Mute User": "Kullanıcıyı sustur", "aria/Notifications": "Bildirimler", "aria/Open Attachment Selector": "Ek Seçiciyi Aç", @@ -229,6 +229,8 @@ "Failed to end the poll due to {{reason}}": "{{reason}} nedeniyle anket sonlandırılamadı", "Failed to jump to the first unread message": "İlk okunmamış mesaja atlamada hata oluştu", "Failed to leave channel": "Kanaldan çıkılamadı", + "Failed to load channels": "Kanallar yüklenemedi", + "Failed to load more channels": "Daha fazla kanal yüklenemedi", "Failed to mark channel as read": "Kanalı okundu olarak işaretleme başarısız oldu", "Failed to play the recording": "Kayıt oynatılamadı", "Failed to retrieve location": "Konum alınamadı",