diff --git a/packages/shared-components/src/room/RoomStatusBar/RoomStatusBarView.stories.tsx b/packages/shared-components/src/room/RoomStatusBar/RoomStatusBarView.stories.tsx index e436d65563..d84ac20deb 100644 --- a/packages/shared-components/src/room/RoomStatusBar/RoomStatusBarView.stories.tsx +++ b/packages/shared-components/src/room/RoomStatusBar/RoomStatusBarView.stories.tsx @@ -8,7 +8,12 @@ import { type Meta, type StoryFn } from "@storybook/react-vite"; import React, { type JSX } from "react"; import { useMockedViewModel } from "../../useMockedViewModel"; -import { RoomStatusBarView, type RoomStatusBarViewActions, type RoomStatusBarViewSnapshot } from "./RoomStatusBarView"; +import { + RoomStatusBarState, + RoomStatusBarView, + type RoomStatusBarViewActions, + type RoomStatusBarViewSnapshot, +} from "./RoomStatusBarView"; import { fn } from "storybook/test"; type RoomStatusBarProps = RoomStatusBarViewSnapshot & RoomStatusBarViewActions; @@ -49,9 +54,7 @@ const Template: StoryFn = (args) => void; } +export enum RoomStatusBarState { + ConnectionLost, + NeedsConsent, + ResourceLimited, + UnsentMessages, + LocalRoomFailed, +} + +export interface RoomStatusBarNotVisible { + state: null; +} + export interface RoomStatusBarNoConnection { - connectionLost: true; + state: RoomStatusBarState.ConnectionLost; } export interface RoomStatusBarConsentState { + state: RoomStatusBarState.NeedsConsent; consentUri: string; } export interface RoomStatusBarResourceLimitedState { + state: RoomStatusBarState.ResourceLimited; resourceLimit: "monthly_active_user" | "hs_disabled" | string; adminContactHref?: string; } export interface RoomStatusBarUnsentMessagesState { + state: RoomStatusBarState.UnsentMessages; isResending: boolean; } export interface RoomStatusBarLocalRoomError { - shouldRetryRoomCreation: boolean; + state: RoomStatusBarState.LocalRoomFailed; } -export interface RoomStatusBarViewSnapshot { - state: - | RoomStatusBarNoConnection - | RoomStatusBarConsentState - | RoomStatusBarResourceLimitedState - | RoomStatusBarUnsentMessagesState - | RoomStatusBarLocalRoomError - | null; -} +export type RoomStatusBarViewSnapshot = + | RoomStatusBarNoConnection + | RoomStatusBarConsentState + | RoomStatusBarResourceLimitedState + | RoomStatusBarUnsentMessagesState + | RoomStatusBarLocalRoomError + | RoomStatusBarNotVisible; /** * The view model for the banner. @@ -88,7 +101,7 @@ interface RoomStatusBarViewProps { */ export function RoomStatusBarView({ vm }: Readonly): JSX.Element { const { translate: _t } = useI18n(); - const { state } = useViewModel(vm); + const snapshot = useViewModel(vm); const bannerTitleId = useId(); const deleteAllClick = useCallback>( @@ -120,158 +133,154 @@ export function RoomStatusBarView({ vm }: Readonly): JSX vm.onTermsAndConditionsClicked?.(); }, [vm.onTermsAndConditionsClicked]); - if (state === null) { + if (snapshot.state === null) { // Nothing to show! return <>; } - if ("connectionLost" in state) { - return ( - -
- - {_t("room|status_bar|server_connectivity_lost_title")} - - - {_t("room|status_bar|server_connectivity_lost_description")} - -
-
- ); - } - - if ("consentUri" in state) { - return ( - - {_t("terms|tac_button")} - - } - > -
- - {_t("room|status_bar|requires_consent_agreement_title")} - -
-
- ); - } - - if ("resourceLimit" in state) { - const title = - { - monthly_active_user: _t("room|status_bar|monthly_user_limit_reached_title"), - hs_disabled: _t("room|status_bar|homeserver_blocked_title"), - }[state.resourceLimit] || _t("room|status_bar|exceeded_resource_limit_title"); - - return ( - +
+ + {_t("room|status_bar|server_connectivity_lost_title")} + + + {_t("room|status_bar|server_connectivity_lost_description")} + +
+
+ ); + case RoomStatusBarState.NeedsConsent: + return ( + - Contact admin + {_t("terms|tac_button")} - ) - } - > -
- - {title} - - - {_t("room|status_bar|exceeded_resource_limit_description")} - -
-
- ); - } - - if ("shouldRetryRoomCreation" in state) { - return ( - - {_t("action|retry")} - - } - > - - {_t("room|status_bar|failed_to_create_room_title")} - - - ); - } - - const actions = state.isResending ? ( - - ) : ( - <> - {vm.onDeleteAllClick && ( - - )} - {vm.onResendAllClick && ( - - )} - - ); +
+ + {_t("room|status_bar|requires_consent_agreement_title")} + +
+ + ); + case RoomStatusBarState.ResourceLimited: + const title = + { + monthly_active_user: _t("room|status_bar|monthly_user_limit_reached_title"), + hs_disabled: _t("room|status_bar|homeserver_blocked_title"), + }[snapshot.resourceLimit] || _t("room|status_bar|exceeded_resource_limit_title"); - return ( - -
- - {_t("room|status_bar|some_messages_not_sent")} - - {_t("room|status_bar|select_messages_to_retry")} -
-
- ); + return ( + + Contact admin + + ) + } + > +
+ + {title} + + + {_t("room|status_bar|exceeded_resource_limit_description")} + +
+
+ ); + case RoomStatusBarState.LocalRoomFailed: + return ( + + {_t("action|retry")} + + } + > + + {_t("room|status_bar|failed_to_create_room_title")} + + + ); + case RoomStatusBarState.UnsentMessages: + const actions = snapshot.isResending ? ( + + ) : ( + <> + {vm.onDeleteAllClick && ( + + )} + {vm.onResendAllClick && ( + + )} + + ); + + return ( + +
+ + {_t("room|status_bar|some_messages_not_sent")} + + {_t("room|status_bar|select_messages_to_retry")} +
+
+ ); + default: + throw Error(`Unexpected unknown state for RoomStatusBar ${snapshot["state"]}`); + } } diff --git a/src/viewmodels/room/RoomStatusBar.ts b/src/viewmodels/room/RoomStatusBar.ts index e3d7df0e88..00c9ebb958 100644 --- a/src/viewmodels/room/RoomStatusBar.ts +++ b/src/viewmodels/room/RoomStatusBar.ts @@ -7,6 +7,7 @@ import { BaseViewModel, + RoomStatusBarState, type RoomStatusBarViewModel as RoomStatusBarViewModelInterface, type RoomStatusBarViewSnapshot, } from "@element-hq/web-shared-components"; @@ -49,14 +50,17 @@ export class RoomStatusBarViewModel private static readonly determineStateForUnreadMessages = ( room: Room, hasClickedTermsAndConditions: boolean, - ): RoomStatusBarViewSnapshot["state"] => { + ): RoomStatusBarViewSnapshot => { const unsentMessages = room.getPendingEvents().filter((ev) => ev.status === EventStatus.NOT_SENT); if (unsentMessages.length === 0) { - return null; + return { + state: null, + }; } if (hasClickedTermsAndConditions) { // The user has just clicked (and we assume accepted) the terms and contitions, so show them the retry buttons return { + state: RoomStatusBarState.UnsentMessages, isResending: false, }; } @@ -66,7 +70,7 @@ export class RoomStatusBarViewModel if (m.error.errcode === "M_CONSENT_NOT_GIVEN") { // This is the most important thing to show, so break here if we find one. return { - // This MUST exist. + state: RoomStatusBarState.NeedsConsent, consentUri: m.error.data.consent_uri, }; } @@ -77,11 +81,13 @@ export class RoomStatusBarViewModel } if (resourceLimitError) { return { + state: RoomStatusBarState.ResourceLimited, resourceLimit: resourceLimitError.data.limit_type ?? "", adminContactHref: resourceLimitError.data.admin_contact, }; } return { + state: RoomStatusBarState.UnsentMessages, isResending: false, }; }; @@ -95,9 +101,7 @@ export class RoomStatusBarViewModel if (room instanceof LocalRoom) { if (room.isError) { return { - state: { - shouldRetryRoomCreation: true, - }, + state: RoomStatusBarState.LocalRoomFailed, }; } else { // Local rooms do not have to worry about these other conditions :) @@ -108,9 +112,8 @@ export class RoomStatusBarViewModel // If we're in the process of resending, don't flicker. if (isResending) { return { - state: { - isResending, - }, + state: RoomStatusBarState.UnsentMessages, + isResending, }; } const syncState = client.getSyncState(); @@ -128,15 +131,13 @@ export class RoomStatusBarViewModel }; } else { return { - state: { - connectionLost: true, - }, + state: RoomStatusBarState.ConnectionLost, }; } } // Then check messages. - return { state: this.determineStateForUnreadMessages(room, hasClickedTermsAndConditions) }; + return this.determineStateForUnreadMessages(room, hasClickedTermsAndConditions); }; private readonly client: MatrixClient;