Remove FTUE onboarding as it is incompatible with SSO/OIDC (#28943)
* Remove FTUE onboarding as it is incompatible with SSO/OIDC Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> * Update tests Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> * Remove stale screenshots Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> --------- Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>
This commit is contained in:
committed by
GitHub
parent
2559cba482
commit
60f70b93e0
@@ -35,7 +35,6 @@ import { UIComponent } from "../../settings/UIFeature";
|
||||
import AccessibleButton, { ButtonEvent } from "../views/elements/AccessibleButton";
|
||||
import PosthogTrackers from "../../PosthogTrackers";
|
||||
import PageType from "../../PageTypes";
|
||||
import { UserOnboardingButton } from "../views/user-onboarding/UserOnboardingButton";
|
||||
import { Landmark, LandmarkNavigation } from "../../accessibility/LandmarkNavigation";
|
||||
|
||||
interface IProps {
|
||||
@@ -398,10 +397,6 @@ export default class LeftPanel extends React.Component<IProps, IState> {
|
||||
{shouldShowComponent(UIComponent.FilterContainer) && this.renderSearchDialExplore()}
|
||||
{this.renderBreadcrumbs()}
|
||||
{!this.props.isMinimized && <RoomListHeader onVisibilityChange={this.refreshStickyHeaders} />}
|
||||
<UserOnboardingButton
|
||||
selected={this.props.pageType === PageType.HomePage}
|
||||
minimized={this.props.isMinimized}
|
||||
/>
|
||||
<nav className="mx_LeftPanel_roomListWrapper" aria-label={_t("common|rooms")}>
|
||||
<div
|
||||
className={roomListClasses}
|
||||
|
||||
@@ -61,7 +61,7 @@ import { TimelineRenderingType } from "../../contexts/RoomContext";
|
||||
import { KeyBindingAction } from "../../accessibility/KeyboardShortcuts";
|
||||
import { SwitchSpacePayload } from "../../dispatcher/payloads/SwitchSpacePayload";
|
||||
import LeftPanelLiveShareWarning from "../views/beacon/LeftPanelLiveShareWarning";
|
||||
import { UserOnboardingPage } from "../views/user-onboarding/UserOnboardingPage";
|
||||
import HomePage from "./HomePage";
|
||||
import { PipContainer } from "./PipContainer";
|
||||
import { monitorSyncedPushRules } from "../../utils/pushRules/monitorSyncedPushRules";
|
||||
import { ConfigOptions } from "../../SdkConfig";
|
||||
@@ -678,7 +678,7 @@ class LoggedInView extends React.Component<IProps, IState> {
|
||||
break;
|
||||
|
||||
case PageTypes.HomePage:
|
||||
pageElement = <UserOnboardingPage justRegistered={this.props.justRegistered} />;
|
||||
pageElement = <HomePage justRegistered={this.props.justRegistered} />;
|
||||
break;
|
||||
|
||||
case PageTypes.UserView:
|
||||
|
||||
@@ -55,7 +55,6 @@ import { FontWatcher } from "../../settings/watchers/FontWatcher";
|
||||
import { storeRoomAliasInCache } from "../../RoomAliasCache";
|
||||
import ToastStore from "../../stores/ToastStore";
|
||||
import * as StorageManager from "../../utils/StorageManager";
|
||||
import { UseCase } from "../../settings/enums/UseCase";
|
||||
import type LoggedInViewType from "./LoggedInView";
|
||||
import LoggedInView from "./LoggedInView";
|
||||
import { Action } from "../../dispatcher/actions";
|
||||
@@ -114,7 +113,6 @@ import { ShowThreadPayload } from "../../dispatcher/payloads/ShowThreadPayload";
|
||||
import { RightPanelPhases } from "../../stores/right-panel/RightPanelStorePhases";
|
||||
import RightPanelStore from "../../stores/right-panel/RightPanelStore";
|
||||
import { TimelineRenderingType } from "../../contexts/RoomContext";
|
||||
import { UseCaseSelection } from "../views/elements/UseCaseSelection";
|
||||
import { ValidatedServerConfig } from "../../utils/ValidatedServerConfig";
|
||||
import { isLocalRoom } from "../../utils/localRoom/isLocalRoom";
|
||||
import { SDKContext, SdkContextClass } from "../../contexts/SDKContext";
|
||||
@@ -866,8 +864,7 @@ export default class MatrixChat extends React.PureComponent<IProps, IState> {
|
||||
this.state.view !== Views.LOGIN &&
|
||||
this.state.view !== Views.REGISTER &&
|
||||
this.state.view !== Views.COMPLETE_SECURITY &&
|
||||
this.state.view !== Views.E2E_SETUP &&
|
||||
this.state.view !== Views.USE_CASE_SELECTION
|
||||
this.state.view !== Views.E2E_SETUP
|
||||
) {
|
||||
this.onLoggedIn();
|
||||
}
|
||||
@@ -1359,12 +1356,7 @@ export default class MatrixChat extends React.PureComponent<IProps, IState> {
|
||||
await this.onShowPostLoginScreen();
|
||||
}
|
||||
|
||||
private async onShowPostLoginScreen(useCase?: UseCase): Promise<void> {
|
||||
if (useCase) {
|
||||
PosthogAnalytics.instance.setProperty("ftueUseCaseSelection", useCase);
|
||||
SettingsStore.setValue("FTUE.useCaseSelection", null, SettingLevel.ACCOUNT, useCase);
|
||||
}
|
||||
|
||||
private async onShowPostLoginScreen(): Promise<void> {
|
||||
this.setStateForNewView({ view: Views.LOGGED_IN });
|
||||
// If a specific screen is set to be shown after login, show that above
|
||||
// all else, as it probably means the user clicked on something already.
|
||||
@@ -2010,33 +2002,10 @@ export default class MatrixChat extends React.PureComponent<IProps, IState> {
|
||||
|
||||
// complete security / e2e setup has finished
|
||||
private onCompleteSecurityE2eSetupFinished = (): void => {
|
||||
if (MatrixClientPeg.currentUserIsJustRegistered() && SettingsStore.getValue("FTUE.useCaseSelection") === null) {
|
||||
this.setStateForNewView({ view: Views.USE_CASE_SELECTION });
|
||||
|
||||
// Listen to changes in settings and hide the use case screen if appropriate - this is necessary because
|
||||
// account settings can still be changing at this point in app init (due to the initial sync being cached,
|
||||
// then subsequent syncs being received from the server)
|
||||
//
|
||||
// This seems unlikely for something that should happen directly after registration, but if a user does
|
||||
// their initial login on another device/browser than they registered on, we want to avoid asking this
|
||||
// question twice
|
||||
//
|
||||
// initPosthogAnalyticsToast pioneered this technique, we’re just reusing it here.
|
||||
SettingsStore.watchSetting(
|
||||
"FTUE.useCaseSelection",
|
||||
null,
|
||||
(originalSettingName, changedInRoomId, atLevel, newValueAtLevel, newValue) => {
|
||||
if (newValue !== null && this.state.view === Views.USE_CASE_SELECTION) {
|
||||
this.onShowPostLoginScreen();
|
||||
}
|
||||
},
|
||||
);
|
||||
} else {
|
||||
// This is async but we makign this function async to wait for it isn't useful
|
||||
this.onShowPostLoginScreen().catch((e) => {
|
||||
logger.error("Exception showing post-login screen", e);
|
||||
});
|
||||
}
|
||||
// This is async but we making this function async to wait for it isn't useful
|
||||
this.onShowPostLoginScreen().catch((e) => {
|
||||
logger.error("Exception showing post-login screen", e);
|
||||
});
|
||||
};
|
||||
|
||||
private getFragmentAfterLogin(): string {
|
||||
@@ -2156,8 +2125,6 @@ export default class MatrixChat extends React.PureComponent<IProps, IState> {
|
||||
fragmentAfterLogin={fragmentAfterLogin}
|
||||
/>
|
||||
);
|
||||
} else if (this.state.view === Views.USE_CASE_SELECTION) {
|
||||
view = <UseCaseSelection onFinished={(useCase): Promise<void> => this.onShowPostLoginScreen(useCase)} />;
|
||||
} else if (this.state.view === Views.LOCK_STOLEN) {
|
||||
view = <SessionLockStolenView />;
|
||||
} else {
|
||||
|
||||
@@ -17,7 +17,7 @@ import RightPanel from "./RightPanel";
|
||||
import Spinner from "../views/elements/Spinner";
|
||||
import ResizeNotifier from "../../utils/ResizeNotifier";
|
||||
import { RightPanelPhases } from "../../stores/right-panel/RightPanelStorePhases";
|
||||
import { UserOnboardingPage } from "../views/user-onboarding/UserOnboardingPage";
|
||||
import HomePage from "./HomePage.tsx";
|
||||
import MatrixClientContext from "../../contexts/MatrixClientContext";
|
||||
|
||||
interface IProps {
|
||||
@@ -93,7 +93,7 @@ export default class UserView extends React.Component<IProps, IState> {
|
||||
defaultSize={420}
|
||||
analyticsRoomType="user_profile"
|
||||
>
|
||||
<UserOnboardingPage />
|
||||
<HomePage />
|
||||
</MainSplit>
|
||||
);
|
||||
} else {
|
||||
|
||||
@@ -1,136 +0,0 @@
|
||||
/*
|
||||
Copyright 2024 New Vector Ltd.
|
||||
Copyright 2022 The Matrix.org Foundation C.I.C.
|
||||
|
||||
SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial
|
||||
Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
import React, { FC } from "react";
|
||||
|
||||
import { Icon as FDroidBadge } from "../../../../res/img/badges/f-droid.svg";
|
||||
import { Icon as GooglePlayBadge } from "../../../../res/img/badges/google-play.svg";
|
||||
import { Icon as IOSBadge } from "../../../../res/img/badges/ios.svg";
|
||||
import { _t } from "../../../languageHandler";
|
||||
import SdkConfig from "../../../SdkConfig";
|
||||
import AccessibleButton from "../elements/AccessibleButton";
|
||||
import QRCode from "../elements/QRCode";
|
||||
import Heading from "../typography/Heading";
|
||||
import BaseDialog from "./BaseDialog";
|
||||
|
||||
interface Props {
|
||||
onFinished(): void;
|
||||
}
|
||||
|
||||
export const showAppDownloadDialogPrompt = (): boolean => {
|
||||
const desktopBuilds = SdkConfig.getObject("desktop_builds");
|
||||
const mobileBuilds = SdkConfig.getObject("mobile_builds");
|
||||
|
||||
return (
|
||||
!!desktopBuilds?.get("available") ||
|
||||
!!mobileBuilds?.get("ios") ||
|
||||
!!mobileBuilds?.get("android") ||
|
||||
!!mobileBuilds?.get("fdroid")
|
||||
);
|
||||
};
|
||||
|
||||
export const AppDownloadDialog: FC<Props> = ({ onFinished }) => {
|
||||
const brand = SdkConfig.get("brand");
|
||||
const desktopBuilds = SdkConfig.getObject("desktop_builds");
|
||||
const mobileBuilds = SdkConfig.getObject("mobile_builds");
|
||||
|
||||
const urlAppStore = mobileBuilds?.get("ios");
|
||||
|
||||
const urlGooglePlay = mobileBuilds?.get("android");
|
||||
const urlFDroid = mobileBuilds?.get("fdroid");
|
||||
const urlAndroid = urlGooglePlay ?? urlFDroid;
|
||||
|
||||
return (
|
||||
<BaseDialog
|
||||
title={_t("onboarding|download_brand", { brand })}
|
||||
className="mx_AppDownloadDialog"
|
||||
fixedWidth
|
||||
onFinished={onFinished}
|
||||
>
|
||||
{desktopBuilds?.get("available") && (
|
||||
<div className="mx_AppDownloadDialog_desktop">
|
||||
<Heading size="3">{_t("onboarding|download_brand_desktop", { brand })}</Heading>
|
||||
<AccessibleButton
|
||||
kind="primary"
|
||||
element="a"
|
||||
href={desktopBuilds?.get("url")}
|
||||
target="_blank"
|
||||
onClick={() => {}}
|
||||
>
|
||||
{_t("onboarding|download_brand_desktop", { brand })}
|
||||
</AccessibleButton>
|
||||
</div>
|
||||
)}
|
||||
<div className="mx_AppDownloadDialog_mobile">
|
||||
{urlAppStore && (
|
||||
<div className="mx_AppDownloadDialog_app">
|
||||
<Heading size="3">{_t("common|ios")}</Heading>
|
||||
<QRCode data={urlAppStore} margin={0} width={172} />
|
||||
<div className="mx_AppDownloadDialog_info">
|
||||
{_t("onboarding|qr_or_app_links", {
|
||||
appLinks: "",
|
||||
qrCode: "",
|
||||
})}
|
||||
</div>
|
||||
<div className="mx_AppDownloadDialog_links">
|
||||
<AccessibleButton
|
||||
element="a"
|
||||
href={urlAppStore}
|
||||
target="_blank"
|
||||
aria-label={_t("onboarding|download_app_store")}
|
||||
onClick={() => {}}
|
||||
>
|
||||
<IOSBadge />
|
||||
</AccessibleButton>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
{urlAndroid && (
|
||||
<div className="mx_AppDownloadDialog_app">
|
||||
<Heading size="3">{_t("common|android")}</Heading>
|
||||
<QRCode data={urlAndroid} margin={0} width={172} />
|
||||
<div className="mx_AppDownloadDialog_info">
|
||||
{_t("onboarding|qr_or_app_links", {
|
||||
appLinks: "",
|
||||
qrCode: "",
|
||||
})}
|
||||
</div>
|
||||
<div className="mx_AppDownloadDialog_links">
|
||||
{urlGooglePlay && (
|
||||
<AccessibleButton
|
||||
element="a"
|
||||
href={urlGooglePlay}
|
||||
target="_blank"
|
||||
aria-label={_t("onboarding|download_google_play")}
|
||||
onClick={() => {}}
|
||||
>
|
||||
<GooglePlayBadge />
|
||||
</AccessibleButton>
|
||||
)}
|
||||
{urlFDroid && (
|
||||
<AccessibleButton
|
||||
element="a"
|
||||
href={urlFDroid}
|
||||
target="_blank"
|
||||
aria-label={_t("onboarding|download_f_droid")}
|
||||
onClick={() => {}}
|
||||
>
|
||||
<FDroidBadge />
|
||||
</AccessibleButton>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
<div className="mx_AppDownloadDialog_legal">
|
||||
<p>{_t("onboarding|apple_trademarks")}</p>
|
||||
<p>{_t("onboarding|google_trademarks")}</p>
|
||||
</div>
|
||||
</BaseDialog>
|
||||
);
|
||||
};
|
||||
@@ -1,78 +0,0 @@
|
||||
/*
|
||||
Copyright 2024 New Vector Ltd.
|
||||
Copyright 2022 The Matrix.org Foundation C.I.C.
|
||||
|
||||
SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial
|
||||
Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
import classNames from "classnames";
|
||||
import React, { useEffect, useState } from "react";
|
||||
|
||||
import { _t } from "../../../languageHandler";
|
||||
import { UseCase } from "../../../settings/enums/UseCase";
|
||||
import SplashPage from "../../structures/SplashPage";
|
||||
import AccessibleButton from "../elements/AccessibleButton";
|
||||
import { UseCaseSelectionButton } from "./UseCaseSelectionButton";
|
||||
|
||||
interface Props {
|
||||
onFinished: (useCase: UseCase) => void;
|
||||
}
|
||||
|
||||
const TIMEOUT = 1500;
|
||||
|
||||
export function UseCaseSelection({ onFinished }: Props): JSX.Element {
|
||||
const [selection, setSelected] = useState<UseCase | null>(null);
|
||||
|
||||
// Call onFinished 1.5s after `selection` becomes truthy, to give time for the animation to run
|
||||
useEffect(() => {
|
||||
if (selection) {
|
||||
let handler: number | null = window.setTimeout(() => {
|
||||
handler = null;
|
||||
onFinished(selection);
|
||||
}, TIMEOUT);
|
||||
return () => {
|
||||
if (handler !== null) clearTimeout(handler);
|
||||
handler = null;
|
||||
};
|
||||
}
|
||||
}, [selection, onFinished]);
|
||||
|
||||
return (
|
||||
<SplashPage
|
||||
className={classNames("mx_UseCaseSelection", {
|
||||
mx_UseCaseSelection_selected: selection !== null,
|
||||
})}
|
||||
>
|
||||
<div className="mx_UseCaseSelection_title mx_UseCaseSelection_slideIn">
|
||||
<h1>{_t("onboarding|use_case_heading1")}</h1>
|
||||
</div>
|
||||
<div className="mx_UseCaseSelection_info mx_UseCaseSelection_slideInDelayed">
|
||||
<h2>{_t("onboarding|use_case_heading2")}</h2>
|
||||
<h3>{_t("onboarding|use_case_heading3")}</h3>
|
||||
</div>
|
||||
<div className="mx_UseCaseSelection_options mx_UseCaseSelection_slideInDelayed">
|
||||
<UseCaseSelectionButton
|
||||
useCase={UseCase.PersonalMessaging}
|
||||
selected={selection === UseCase.PersonalMessaging}
|
||||
onClick={setSelected}
|
||||
/>
|
||||
<UseCaseSelectionButton
|
||||
useCase={UseCase.WorkMessaging}
|
||||
selected={selection === UseCase.WorkMessaging}
|
||||
onClick={setSelected}
|
||||
/>
|
||||
<UseCaseSelectionButton
|
||||
useCase={UseCase.CommunityMessaging}
|
||||
selected={selection === UseCase.CommunityMessaging}
|
||||
onClick={setSelected}
|
||||
/>
|
||||
</div>
|
||||
<div className="mx_UseCaseSelection_skip mx_UseCaseSelection_slideInDelayed">
|
||||
<AccessibleButton kind="link" onClick={async () => setSelected(UseCase.Skip)}>
|
||||
{_t("action|skip")}
|
||||
</AccessibleButton>
|
||||
</div>
|
||||
</SplashPage>
|
||||
);
|
||||
}
|
||||
@@ -1,54 +0,0 @@
|
||||
/*
|
||||
Copyright 2024 New Vector Ltd.
|
||||
Copyright 2022 The Matrix.org Foundation C.I.C.
|
||||
|
||||
SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial
|
||||
Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
import classNames from "classnames";
|
||||
import React from "react";
|
||||
|
||||
import { _t } from "../../../languageHandler";
|
||||
import { UseCase } from "../../../settings/enums/UseCase";
|
||||
import AccessibleButton from "./AccessibleButton";
|
||||
|
||||
interface Props {
|
||||
useCase: UseCase;
|
||||
selected: boolean;
|
||||
onClick: (useCase: UseCase) => void;
|
||||
}
|
||||
|
||||
export function UseCaseSelectionButton({ useCase, onClick, selected }: Props): JSX.Element {
|
||||
let label: string | undefined;
|
||||
switch (useCase) {
|
||||
case UseCase.PersonalMessaging:
|
||||
label = _t("onboarding|use_case_personal_messaging");
|
||||
break;
|
||||
case UseCase.WorkMessaging:
|
||||
label = _t("onboarding|use_case_work_messaging");
|
||||
break;
|
||||
case UseCase.CommunityMessaging:
|
||||
label = _t("onboarding|use_case_community_messaging");
|
||||
break;
|
||||
}
|
||||
|
||||
return (
|
||||
<AccessibleButton
|
||||
className={classNames("mx_UseCaseSelectionButton", {
|
||||
mx_UseCaseSelectionButton_selected: selected,
|
||||
})}
|
||||
onClick={async () => onClick(useCase)}
|
||||
>
|
||||
<div
|
||||
className={classNames("mx_UseCaseSelectionButton_icon", {
|
||||
mx_UseCaseSelectionButton_messaging: useCase === UseCase.PersonalMessaging,
|
||||
mx_UseCaseSelectionButton_work: useCase === UseCase.WorkMessaging,
|
||||
mx_UseCaseSelectionButton_community: useCase === UseCase.CommunityMessaging,
|
||||
})}
|
||||
/>
|
||||
<span>{label}</span>
|
||||
<div className="mx_UseCaseSelectionButton_selectedIcon" />
|
||||
</AccessibleButton>
|
||||
);
|
||||
}
|
||||
@@ -22,7 +22,6 @@ import { UserTab } from "../../../dialogs/UserTab";
|
||||
import { OpenToTabPayload } from "../../../../../dispatcher/payloads/OpenToTabPayload";
|
||||
import { Action } from "../../../../../dispatcher/actions";
|
||||
import SdkConfig from "../../../../../SdkConfig";
|
||||
import { showUserOnboardingPage } from "../../../user-onboarding/UserOnboardingPage";
|
||||
import { SettingsSubsection } from "../../shared/SettingsSubsection";
|
||||
import SettingsTab from "../SettingsTab";
|
||||
import { SettingsSection } from "../../shared/SettingsSection";
|
||||
@@ -117,7 +116,7 @@ const SpellCheckSection: React.FC = () => {
|
||||
};
|
||||
|
||||
export default class PreferencesUserSettingsTab extends React.Component<IProps, IState> {
|
||||
private static ROOM_LIST_SETTINGS: BooleanSettingKey[] = ["breadcrumbs", "FTUE.userOnboardingButton"];
|
||||
private static ROOM_LIST_SETTINGS: BooleanSettingKey[] = ["breadcrumbs"];
|
||||
|
||||
private static SPACES_SETTINGS: BooleanSettingKey[] = ["Spaces.allRoomsInHome"];
|
||||
|
||||
@@ -237,10 +236,7 @@ export default class PreferencesUserSettingsTab extends React.Component<IProps,
|
||||
};
|
||||
|
||||
public render(): React.ReactNode {
|
||||
const useCase = SettingsStore.getValue("FTUE.useCaseSelection");
|
||||
const roomListSettings = PreferencesUserSettingsTab.ROOM_LIST_SETTINGS
|
||||
// Only show the user onboarding setting if the user should see the user onboarding page
|
||||
.filter((it) => it !== "FTUE.userOnboardingButton" || showUserOnboardingPage(useCase));
|
||||
const roomListSettings = PreferencesUserSettingsTab.ROOM_LIST_SETTINGS;
|
||||
|
||||
const browserTimezoneLabel: string = _t("settings|preferences|default_timezone", {
|
||||
timezone: TimezoneHandler.shortBrowserTimezone(),
|
||||
|
||||
@@ -1,80 +0,0 @@
|
||||
/*
|
||||
Copyright 2024 New Vector Ltd.
|
||||
Copyright 2022 The Matrix.org Foundation C.I.C.
|
||||
|
||||
SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial
|
||||
Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
import classNames from "classnames";
|
||||
import React, { useCallback } from "react";
|
||||
|
||||
import { Action } from "../../../dispatcher/actions";
|
||||
import defaultDispatcher from "../../../dispatcher/dispatcher";
|
||||
import { useSettingValue } from "../../../hooks/useSettings";
|
||||
import { _t } from "../../../languageHandler";
|
||||
import PosthogTrackers from "../../../PosthogTrackers";
|
||||
import { SettingLevel } from "../../../settings/SettingLevel";
|
||||
import SettingsStore from "../../../settings/SettingsStore";
|
||||
import AccessibleButton, { ButtonEvent } from "../../views/elements/AccessibleButton";
|
||||
import Heading from "../../views/typography/Heading";
|
||||
import { showUserOnboardingPage } from "./UserOnboardingPage";
|
||||
|
||||
interface Props {
|
||||
selected: boolean;
|
||||
minimized: boolean;
|
||||
}
|
||||
|
||||
export function UserOnboardingButton({ selected, minimized }: Props): JSX.Element {
|
||||
const useCase = useSettingValue("FTUE.useCaseSelection");
|
||||
const visible = useSettingValue("FTUE.userOnboardingButton");
|
||||
|
||||
if (!visible || minimized || !showUserOnboardingPage(useCase)) {
|
||||
return <></>;
|
||||
}
|
||||
|
||||
return <UserOnboardingButtonInternal selected={selected} minimized={minimized} />;
|
||||
}
|
||||
|
||||
function UserOnboardingButtonInternal({ selected, minimized }: Props): JSX.Element {
|
||||
const onDismiss = useCallback((ev: ButtonEvent) => {
|
||||
ev.preventDefault();
|
||||
ev.stopPropagation();
|
||||
|
||||
PosthogTrackers.trackInteraction("WebRoomListUserOnboardingIgnoreButton", ev);
|
||||
SettingsStore.setValue("FTUE.userOnboardingButton", null, SettingLevel.ACCOUNT, false);
|
||||
}, []);
|
||||
|
||||
const onClick = useCallback((ev: ButtonEvent) => {
|
||||
ev.preventDefault();
|
||||
ev.stopPropagation();
|
||||
|
||||
PosthogTrackers.trackInteraction("WebRoomListUserOnboardingButton", ev);
|
||||
defaultDispatcher.fire(Action.ViewHomePage);
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<AccessibleButton
|
||||
className={classNames("mx_UserOnboardingButton", {
|
||||
mx_UserOnboardingButton_selected: selected,
|
||||
mx_UserOnboardingButton_minimized: minimized,
|
||||
})}
|
||||
onClick={onClick}
|
||||
>
|
||||
{!minimized && (
|
||||
<>
|
||||
<div className="mx_UserOnboardingButton_content">
|
||||
<Heading size="4" className="mx_Heading_h4">
|
||||
{_t("common|welcome")}
|
||||
</Heading>
|
||||
<AccessibleButton
|
||||
className="mx_UserOnboardingButton_close"
|
||||
onClick={onDismiss}
|
||||
aria-label={_t("action|dismiss")}
|
||||
/>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
</AccessibleButton>
|
||||
);
|
||||
}
|
||||
@@ -1,82 +0,0 @@
|
||||
/*
|
||||
Copyright 2024 New Vector Ltd.
|
||||
Copyright 2022 The Matrix.org Foundation C.I.C.
|
||||
|
||||
SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial
|
||||
Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
import * as React from "react";
|
||||
|
||||
import defaultDispatcher from "../../../dispatcher/dispatcher";
|
||||
import { _t } from "../../../languageHandler";
|
||||
import PosthogTrackers from "../../../PosthogTrackers";
|
||||
import SdkConfig from "../../../SdkConfig";
|
||||
import { UseCase } from "../../../settings/enums/UseCase";
|
||||
import AccessibleButton, { ButtonEvent } from "../../views/elements/AccessibleButton";
|
||||
import Heading from "../../views/typography/Heading";
|
||||
|
||||
const onClickSendDm = (ev: ButtonEvent): void => {
|
||||
PosthogTrackers.trackInteraction("WebUserOnboardingHeaderSendDm", ev);
|
||||
defaultDispatcher.dispatch({ action: "view_create_chat" });
|
||||
};
|
||||
|
||||
interface Props {
|
||||
useCase: UseCase | null;
|
||||
}
|
||||
|
||||
export function UserOnboardingHeader({ useCase }: Props): JSX.Element {
|
||||
let title: string;
|
||||
let description = _t("onboarding|free_e2ee_messaging_unlimited_voip", {
|
||||
brand: SdkConfig.get("brand"),
|
||||
});
|
||||
let image: string;
|
||||
let actionLabel: string;
|
||||
|
||||
switch (useCase) {
|
||||
/* eslint-disable @typescript-eslint/no-require-imports */
|
||||
case UseCase.PersonalMessaging:
|
||||
title = _t("onboarding|personal_messaging_title");
|
||||
image = require("../../../../res/img/user-onboarding/PersonalMessaging.png");
|
||||
actionLabel = _t("onboarding|personal_messaging_action");
|
||||
break;
|
||||
case UseCase.WorkMessaging:
|
||||
title = _t("onboarding|work_messaging_title");
|
||||
description = _t("onboarding|free_e2ee_messaging_unlimited_voip", {
|
||||
brand: SdkConfig.get("brand"),
|
||||
});
|
||||
image = require("../../../../res/img/user-onboarding/WorkMessaging.png");
|
||||
actionLabel = _t("onboarding|work_messaging_action");
|
||||
break;
|
||||
case UseCase.CommunityMessaging:
|
||||
title = _t("onboarding|community_messaging_title");
|
||||
description = _t("onboarding|community_messaging_description");
|
||||
image = require("../../../../res/img/user-onboarding/CommunityMessaging.png");
|
||||
actionLabel = _t("onboarding|community_messaging_action");
|
||||
break;
|
||||
default:
|
||||
title = _t("onboarding|welcome_to_brand", {
|
||||
brand: SdkConfig.get("brand"),
|
||||
});
|
||||
image = require("../../../../res/img/user-onboarding/PersonalMessaging.png");
|
||||
actionLabel = _t("onboarding|personal_messaging_action");
|
||||
break;
|
||||
/* eslint-enable @typescript-eslint/no-require-imports */
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="mx_UserOnboardingHeader">
|
||||
<div className="mx_UserOnboardingHeader_content">
|
||||
<Heading size="1">
|
||||
{title}
|
||||
<span className="mx_UserOnboardingHeader_dot">.</span>
|
||||
</Heading>
|
||||
<p>{description}</p>
|
||||
<AccessibleButton onClick={onClickSendDm} kind="primary">
|
||||
{actionLabel}
|
||||
</AccessibleButton>
|
||||
</div>
|
||||
<img className="mx_UserOnboardingHeader_image" src={image} alt="" />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -1,68 +0,0 @@
|
||||
/*
|
||||
Copyright 2024 New Vector Ltd.
|
||||
Copyright 2022 The Matrix.org Foundation C.I.C.
|
||||
|
||||
SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial
|
||||
Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
import * as React from "react";
|
||||
|
||||
import { UserOnboardingTaskWithResolvedCompletion } from "../../../hooks/useUserOnboardingTasks";
|
||||
import { _t } from "../../../languageHandler";
|
||||
import SdkConfig from "../../../SdkConfig";
|
||||
import ProgressBar from "../../views/elements/ProgressBar";
|
||||
import Heading from "../../views/typography/Heading";
|
||||
import { UserOnboardingTask } from "./UserOnboardingTask";
|
||||
|
||||
export const getUserOnboardingCounters = (
|
||||
tasks: UserOnboardingTaskWithResolvedCompletion[],
|
||||
): {
|
||||
completed: number;
|
||||
waiting: number;
|
||||
total: number;
|
||||
} => {
|
||||
const completed = tasks.filter((task) => task.completed === true).length;
|
||||
const waiting = tasks.filter((task) => task.completed === false).length;
|
||||
|
||||
return {
|
||||
completed: completed,
|
||||
waiting: waiting,
|
||||
total: completed + waiting,
|
||||
};
|
||||
};
|
||||
|
||||
interface Props {
|
||||
tasks: UserOnboardingTaskWithResolvedCompletion[];
|
||||
}
|
||||
|
||||
export function UserOnboardingList({ tasks }: Props): JSX.Element {
|
||||
const { completed, waiting, total } = getUserOnboardingCounters(tasks);
|
||||
|
||||
return (
|
||||
<div className="mx_UserOnboardingList" data-testid="user-onboarding-list">
|
||||
<div className="mx_UserOnboardingList_header">
|
||||
<Heading size="3" className="mx_UserOnboardingList_title">
|
||||
{waiting > 0
|
||||
? _t("onboarding|only_n_steps_to_go", {
|
||||
count: waiting,
|
||||
})
|
||||
: _t("onboarding|you_did_it")}
|
||||
</Heading>
|
||||
<div className="mx_UserOnboardingList_hint">
|
||||
{_t("onboarding|complete_these", {
|
||||
brand: SdkConfig.get("brand"),
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
<div className="mx_UserOnboardingList_progress">
|
||||
<ProgressBar value={completed} max={total} animated />
|
||||
</div>
|
||||
<ol className="mx_UserOnboardingList_list">
|
||||
{tasks.map((task) => (
|
||||
<UserOnboardingTask key={task.id} completed={task.completed} task={task} />
|
||||
))}
|
||||
</ol>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -1,78 +0,0 @@
|
||||
/*
|
||||
Copyright 2024 New Vector Ltd.
|
||||
Copyright 2020-2022 The Matrix.org Foundation C.I.C.
|
||||
|
||||
SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial
|
||||
Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
import { useEffect, useState } from "react";
|
||||
import * as React from "react";
|
||||
|
||||
import { useInitialSyncComplete } from "../../../hooks/useIsInitialSyncComplete";
|
||||
import { useSettingValue } from "../../../hooks/useSettings";
|
||||
import { useUserOnboardingContext } from "../../../hooks/useUserOnboardingContext";
|
||||
import { useUserOnboardingTasks } from "../../../hooks/useUserOnboardingTasks";
|
||||
import { MatrixClientPeg } from "../../../MatrixClientPeg";
|
||||
import SdkConfig from "../../../SdkConfig";
|
||||
import { UseCase } from "../../../settings/enums/UseCase";
|
||||
import { getHomePageUrl } from "../../../utils/pages";
|
||||
import AutoHideScrollbar from "../../structures/AutoHideScrollbar";
|
||||
import EmbeddedPage from "../../structures/EmbeddedPage";
|
||||
import HomePage from "../../structures/HomePage";
|
||||
import { UserOnboardingHeader } from "./UserOnboardingHeader";
|
||||
import { UserOnboardingList } from "./UserOnboardingList";
|
||||
import { useMatrixClientContext } from "../../../contexts/MatrixClientContext";
|
||||
|
||||
interface Props {
|
||||
justRegistered?: boolean;
|
||||
}
|
||||
|
||||
// We decided to only show the new user onboarding page to new users
|
||||
// For now, that means we set the cutoff at 2022-07-01 00:00 UTC
|
||||
const USER_ONBOARDING_CUTOFF_DATE = new Date(1_656_633_600);
|
||||
export function showUserOnboardingPage(useCase: UseCase | null): boolean {
|
||||
return useCase !== null || MatrixClientPeg.userRegisteredAfter(USER_ONBOARDING_CUTOFF_DATE);
|
||||
}
|
||||
|
||||
const ANIMATION_DURATION = 2800;
|
||||
export function UserOnboardingPage({ justRegistered = false }: Props): JSX.Element {
|
||||
const cli = useMatrixClientContext();
|
||||
const config = SdkConfig.get();
|
||||
const pageUrl = getHomePageUrl(config, cli);
|
||||
|
||||
const useCase = useSettingValue("FTUE.useCaseSelection");
|
||||
const context = useUserOnboardingContext();
|
||||
const tasks = useUserOnboardingTasks(context);
|
||||
|
||||
const initialSyncComplete = useInitialSyncComplete();
|
||||
const [showList, setShowList] = useState<boolean>(false);
|
||||
useEffect(() => {
|
||||
if (initialSyncComplete) {
|
||||
const handler = window.setTimeout(() => {
|
||||
setShowList(true);
|
||||
}, ANIMATION_DURATION);
|
||||
return () => {
|
||||
clearTimeout(handler);
|
||||
};
|
||||
} else {
|
||||
setShowList(false);
|
||||
}
|
||||
}, [initialSyncComplete, setShowList]);
|
||||
|
||||
// Only show new onboarding list to users who registered after a given date or have chosen a use case
|
||||
if (!showUserOnboardingPage(useCase)) {
|
||||
return <HomePage justRegistered={justRegistered} />;
|
||||
}
|
||||
|
||||
if (pageUrl) {
|
||||
return <EmbeddedPage className="mx_HomePage" url={pageUrl} scrollbar={true} />;
|
||||
}
|
||||
|
||||
return (
|
||||
<AutoHideScrollbar className="mx_UserOnboardingPage">
|
||||
<UserOnboardingHeader useCase={useCase} />
|
||||
{showList && <UserOnboardingList tasks={tasks} />}
|
||||
</AutoHideScrollbar>
|
||||
);
|
||||
}
|
||||
@@ -1,59 +0,0 @@
|
||||
/*
|
||||
Copyright 2024 New Vector Ltd.
|
||||
Copyright 2022 The Matrix.org Foundation C.I.C.
|
||||
|
||||
SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial
|
||||
Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
import classNames from "classnames";
|
||||
import * as React from "react";
|
||||
|
||||
import { UserOnboardingTaskWithResolvedCompletion } from "../../../hooks/useUserOnboardingTasks";
|
||||
import AccessibleButton from "../../views/elements/AccessibleButton";
|
||||
import Heading from "../../views/typography/Heading";
|
||||
|
||||
interface Props {
|
||||
task: UserOnboardingTaskWithResolvedCompletion;
|
||||
completed?: boolean;
|
||||
}
|
||||
|
||||
export function UserOnboardingTask({ task, completed = false }: Props): JSX.Element {
|
||||
const title = typeof task.title === "function" ? task.title() : task.title;
|
||||
const description = typeof task.description === "function" ? task.description() : task.description;
|
||||
|
||||
return (
|
||||
<li
|
||||
data-testid="user-onboarding-task"
|
||||
className={classNames("mx_UserOnboardingTask", {
|
||||
mx_UserOnboardingTask_completed: completed,
|
||||
})}
|
||||
>
|
||||
<div
|
||||
className="mx_UserOnboardingTask_number"
|
||||
role="checkbox"
|
||||
aria-disabled="true"
|
||||
aria-checked={completed}
|
||||
aria-labelledby={`mx_UserOnboardingTask_${task.id}`}
|
||||
/>
|
||||
<div id={`mx_UserOnboardingTask_${task.id}`} className="mx_UserOnboardingTask_content">
|
||||
<Heading size="4" className="mx_UserOnboardingTask_title">
|
||||
{title}
|
||||
</Heading>
|
||||
<div className="mx_UserOnboardingTask_description">{description}</div>
|
||||
</div>
|
||||
{task.action && (!task.action.hideOnComplete || !completed) && (
|
||||
<AccessibleButton
|
||||
element="a"
|
||||
className="mx_UserOnboardingTask_action"
|
||||
kind="primary_outline"
|
||||
href={task.action.href}
|
||||
target="_blank"
|
||||
onClick={task.action.onClick ?? null}
|
||||
>
|
||||
{task.action.label}
|
||||
</AccessibleButton>
|
||||
)}
|
||||
</li>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user