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:
@@ -1,17 +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 { ClientEvent } from "matrix-js-sdk/src/matrix";
|
||||
|
||||
import { useEventEmitterState } from "./useEventEmitter";
|
||||
import { useMatrixClientContext } from "../contexts/MatrixClientContext";
|
||||
|
||||
export function useInitialSyncComplete(): boolean {
|
||||
const cli = useMatrixClientContext();
|
||||
return useEventEmitterState(cli, ClientEvent.Sync, () => cli.isInitialSyncComplete());
|
||||
}
|
||||
@@ -1,124 +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 { logger } from "matrix-js-sdk/src/logger";
|
||||
import { ClientEvent, MatrixClient } from "matrix-js-sdk/src/matrix";
|
||||
import { useCallback, useEffect, useMemo, useRef, useState } from "react";
|
||||
|
||||
import { Notifier, NotifierEvent } from "../Notifier";
|
||||
import DMRoomMap from "../utils/DMRoomMap";
|
||||
import { useMatrixClientContext } from "../contexts/MatrixClientContext";
|
||||
import { useSettingValue } from "./useSettings";
|
||||
import { useEventEmitter, useTypedEventEmitter } from "./useEventEmitter";
|
||||
|
||||
export interface UserOnboardingContext {
|
||||
hasAvatar: boolean;
|
||||
hasDevices: boolean;
|
||||
hasDmRooms: boolean;
|
||||
showNotificationsPrompt: boolean;
|
||||
}
|
||||
|
||||
const USER_ONBOARDING_CONTEXT_INTERVAL = 5000;
|
||||
|
||||
/**
|
||||
* Returns a persistent, non-changing reference to a function
|
||||
* This function proxies all its calls to the current value of the given input callback
|
||||
*
|
||||
* This allows you to use the current value of e.g., a state in a callback that’s used by e.g., a useEventEmitter or
|
||||
* similar hook without re-registering the hook when the state changes
|
||||
* @param value changing callback
|
||||
*/
|
||||
function useRefOf<T extends any[], R>(value: (...values: T) => R): (...values: T) => R {
|
||||
const ref = useRef(value);
|
||||
ref.current = value;
|
||||
return useCallback((...values: T) => ref.current(...values), []);
|
||||
}
|
||||
|
||||
function useUserOnboardingContextValue<T>(defaultValue: T, callback: (cli: MatrixClient) => Promise<T>): T {
|
||||
const [value, setValue] = useState<T>(defaultValue);
|
||||
const cli = useMatrixClientContext();
|
||||
|
||||
const handler = useRefOf(callback);
|
||||
|
||||
useEffect(() => {
|
||||
if (value) {
|
||||
return;
|
||||
}
|
||||
|
||||
let handle: number | null = null;
|
||||
let enabled = true;
|
||||
const repeater = async (): Promise<void> => {
|
||||
if (handle !== null) {
|
||||
clearTimeout(handle);
|
||||
handle = null;
|
||||
}
|
||||
setValue(await handler(cli));
|
||||
if (enabled) {
|
||||
handle = window.setTimeout(repeater, USER_ONBOARDING_CONTEXT_INTERVAL);
|
||||
}
|
||||
};
|
||||
repeater().catch((err) => logger.warn("could not update user onboarding context", err));
|
||||
cli.on(ClientEvent.AccountData, repeater);
|
||||
return () => {
|
||||
enabled = false;
|
||||
cli.off(ClientEvent.AccountData, repeater);
|
||||
if (handle !== null) {
|
||||
clearTimeout(handle);
|
||||
handle = null;
|
||||
}
|
||||
};
|
||||
}, [cli, handler, value]);
|
||||
return value;
|
||||
}
|
||||
|
||||
function useShowNotificationsPrompt(): boolean {
|
||||
const client = useMatrixClientContext();
|
||||
|
||||
const [value, setValue] = useState<boolean>(client.pushRules ? Notifier.shouldShowPrompt() : true);
|
||||
|
||||
const updateValue = useCallback(() => {
|
||||
setValue(client.pushRules ? Notifier.shouldShowPrompt() : true);
|
||||
}, [client]);
|
||||
|
||||
useEventEmitter(Notifier, NotifierEvent.NotificationHiddenChange, () => {
|
||||
updateValue();
|
||||
});
|
||||
|
||||
const setting = useSettingValue("notificationsEnabled");
|
||||
useEffect(() => {
|
||||
updateValue();
|
||||
}, [setting, updateValue]);
|
||||
|
||||
// shouldShowPrompt is dependent on the client having push rules. There isn't an event for the client
|
||||
// fetching its push rules, but we'll know it has them by the time it sync, so we update this on sync.
|
||||
useTypedEventEmitter(client, ClientEvent.Sync, updateValue);
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
export function useUserOnboardingContext(): UserOnboardingContext {
|
||||
const hasAvatar = useUserOnboardingContextValue(false, async (cli) => {
|
||||
const profile = await cli.getProfileInfo(cli.getUserId()!);
|
||||
return Boolean(profile?.avatar_url);
|
||||
});
|
||||
const hasDevices = useUserOnboardingContextValue(false, async (cli) => {
|
||||
const myDevice = cli.getDeviceId();
|
||||
const devices = await cli.getDevices();
|
||||
return Boolean(devices.devices.find((device) => device.device_id !== myDevice));
|
||||
});
|
||||
const hasDmRooms = useUserOnboardingContextValue(false, async () => {
|
||||
const dmRooms = DMRoomMap.shared().getUniqueRoomsWithIndividuals() ?? {};
|
||||
return Boolean(Object.keys(dmRooms).length);
|
||||
});
|
||||
const showNotificationsPrompt = useShowNotificationsPrompt();
|
||||
|
||||
return useMemo(
|
||||
() => ({ hasAvatar, hasDevices, hasDmRooms, showNotificationsPrompt }),
|
||||
[hasAvatar, hasDevices, hasDmRooms, showNotificationsPrompt],
|
||||
);
|
||||
}
|
||||
@@ -1,161 +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 { useMemo } from "react";
|
||||
|
||||
import { AppDownloadDialog, showAppDownloadDialogPrompt } from "../components/views/dialogs/AppDownloadDialog";
|
||||
import { UserTab } from "../components/views/dialogs/UserTab";
|
||||
import { ButtonEvent } from "../components/views/elements/AccessibleButton";
|
||||
import { Action } from "../dispatcher/actions";
|
||||
import defaultDispatcher from "../dispatcher/dispatcher";
|
||||
import { _t } from "../languageHandler";
|
||||
import Modal from "../Modal";
|
||||
import { Notifier } from "../Notifier";
|
||||
import PosthogTrackers from "../PosthogTrackers";
|
||||
import SdkConfig from "../SdkConfig";
|
||||
import { UseCase } from "../settings/enums/UseCase";
|
||||
import { useSettingValue } from "./useSettings";
|
||||
import { UserOnboardingContext } from "./useUserOnboardingContext";
|
||||
|
||||
interface UserOnboardingTask {
|
||||
id: string;
|
||||
title: string | (() => string);
|
||||
description: string | (() => string);
|
||||
relevant?: UseCase[];
|
||||
action?: {
|
||||
label: string;
|
||||
onClick?: (ev: ButtonEvent) => void;
|
||||
href?: string;
|
||||
hideOnComplete?: boolean;
|
||||
};
|
||||
completed: (ctx: UserOnboardingContext) => boolean;
|
||||
disabled?(): boolean;
|
||||
}
|
||||
|
||||
export interface UserOnboardingTaskWithResolvedCompletion extends Omit<UserOnboardingTask, "completed"> {
|
||||
completed: boolean;
|
||||
}
|
||||
|
||||
const onClickStartDm = (ev: ButtonEvent): void => {
|
||||
PosthogTrackers.trackInteraction("WebUserOnboardingTaskSendDm", ev);
|
||||
defaultDispatcher.dispatch({ action: "view_create_chat" });
|
||||
};
|
||||
|
||||
const tasks: UserOnboardingTask[] = [
|
||||
{
|
||||
id: "create-account",
|
||||
title: _t("auth|create_account_title"),
|
||||
description: _t("onboarding|you_made_it"),
|
||||
completed: () => true,
|
||||
},
|
||||
{
|
||||
id: "find-friends",
|
||||
title: _t("onboarding|find_friends"),
|
||||
description: _t("onboarding|find_friends_description"),
|
||||
completed: (ctx: UserOnboardingContext) => ctx.hasDmRooms,
|
||||
relevant: [UseCase.PersonalMessaging, UseCase.Skip],
|
||||
action: {
|
||||
label: _t("onboarding|find_friends_action"),
|
||||
onClick: onClickStartDm,
|
||||
},
|
||||
},
|
||||
{
|
||||
id: "find-coworkers",
|
||||
title: _t("onboarding|find_coworkers"),
|
||||
description: _t("onboarding|get_stuff_done"),
|
||||
completed: (ctx: UserOnboardingContext) => ctx.hasDmRooms,
|
||||
relevant: [UseCase.WorkMessaging],
|
||||
action: {
|
||||
label: _t("onboarding|find_people"),
|
||||
onClick: onClickStartDm,
|
||||
},
|
||||
},
|
||||
{
|
||||
id: "find-community-members",
|
||||
title: _t("onboarding|find_community_members"),
|
||||
description: _t("onboarding|get_stuff_done"),
|
||||
completed: (ctx: UserOnboardingContext) => ctx.hasDmRooms,
|
||||
relevant: [UseCase.CommunityMessaging],
|
||||
action: {
|
||||
label: _t("onboarding|find_people"),
|
||||
onClick: onClickStartDm,
|
||||
},
|
||||
},
|
||||
{
|
||||
id: "download-apps",
|
||||
title: () =>
|
||||
_t("onboarding|download_app", {
|
||||
brand: SdkConfig.get("brand"),
|
||||
}),
|
||||
description: () =>
|
||||
_t("onboarding|download_app_description", {
|
||||
brand: SdkConfig.get("brand"),
|
||||
}),
|
||||
completed: (ctx: UserOnboardingContext) => ctx.hasDevices,
|
||||
action: {
|
||||
label: _t("onboarding|download_app_action"),
|
||||
onClick: (ev: ButtonEvent) => {
|
||||
PosthogTrackers.trackInteraction("WebUserOnboardingTaskDownloadApps", ev);
|
||||
Modal.createDialog(AppDownloadDialog, {}, "mx_AppDownloadDialog_wrapper", false, true);
|
||||
},
|
||||
},
|
||||
disabled(): boolean {
|
||||
return !showAppDownloadDialogPrompt();
|
||||
},
|
||||
},
|
||||
{
|
||||
id: "setup-profile",
|
||||
title: _t("onboarding|set_up_profile"),
|
||||
description: _t("onboarding|set_up_profile_description"),
|
||||
completed: (ctx: UserOnboardingContext) => ctx.hasAvatar,
|
||||
action: {
|
||||
label: _t("onboarding|set_up_profile_action"),
|
||||
onClick: (ev: ButtonEvent) => {
|
||||
PosthogTrackers.trackInteraction("WebUserOnboardingTaskSetupProfile", ev);
|
||||
defaultDispatcher.dispatch({
|
||||
action: Action.ViewUserSettings,
|
||||
initialTabId: UserTab.Account,
|
||||
});
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
id: "permission-notifications",
|
||||
title: _t("onboarding|enable_notifications"),
|
||||
description: _t("onboarding|enable_notifications_description"),
|
||||
completed: (ctx: UserOnboardingContext) => !ctx.showNotificationsPrompt,
|
||||
action: {
|
||||
label: _t("onboarding|enable_notifications_action"),
|
||||
onClick: (ev: ButtonEvent) => {
|
||||
PosthogTrackers.trackInteraction("WebUserOnboardingTaskEnableNotifications", ev);
|
||||
defaultDispatcher.dispatch({
|
||||
action: Action.ViewUserSettings,
|
||||
initialTabId: UserTab.Notifications,
|
||||
});
|
||||
Notifier.setPromptHidden(true);
|
||||
},
|
||||
hideOnComplete: !Notifier.isPossible(),
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
export function useUserOnboardingTasks(context: UserOnboardingContext): UserOnboardingTaskWithResolvedCompletion[] {
|
||||
const useCase = useSettingValue("FTUE.useCaseSelection") ?? UseCase.Skip;
|
||||
|
||||
return useMemo<UserOnboardingTaskWithResolvedCompletion[]>(() => {
|
||||
return tasks
|
||||
.filter((task) => {
|
||||
if (task.disabled?.()) return false;
|
||||
return !task.relevant || task.relevant.includes(useCase);
|
||||
})
|
||||
.map((task) => ({
|
||||
...task,
|
||||
completed: task.completed(context),
|
||||
}));
|
||||
}, [context, useCase]);
|
||||
}
|
||||
Reference in New Issue
Block a user