From f5f79158ed5a950626bc89439b8319e362a75de1 Mon Sep 17 00:00:00 2001 From: Janne Mareike Koschinski Date: Wed, 13 Jul 2022 15:43:44 +0200 Subject: [PATCH] Implement Use Case Selection screen (#8984) * Introduce new splash page wrapper * Introduce new use case selection screen --- cypress/e2e/1-register/register.spec.ts | 2 + cypress/e2e/12-spotlight/spotlight.spec.ts | 2 +- package.json | 2 +- res/css/_components.scss | 3 + res/css/structures/_SplashPage.scss | 63 +++++++++ res/css/views/elements/_UseCaseSelection.scss | 129 ++++++++++++++++++ .../elements/_UseCaseSelectionButton.scss | 104 ++++++++++++++ res/img/element-icons/chat-bubble.svg | 3 + res/img/element-icons/group-members.svg | 9 +- res/img/element-icons/view-community.svg | 9 +- res/img/globe.svg | 4 +- res/img/noise.png | Bin 0 -> 3200 bytes res/themes/dark/css/_dark.scss | 25 ++++ src/PosthogTrackers.ts | 1 + src/Views.ts | 3 + src/components/structures/MatrixChat.tsx | 46 ++++++- src/components/structures/SplashPage.tsx | 27 ++++ .../views/elements/UseCaseSelection.tsx | 86 ++++++++++++ .../views/elements/UseCaseSelectionButton.tsx | 59 ++++++++ src/i18n/strings/en_EN.json | 8 +- src/settings/Settings.tsx | 4 + src/settings/enums/UseCase.tsx | 22 +++ test/end-to-end-tests/src/usecases/signup.ts | 4 + yarn.lock | 8 +- 24 files changed, 597 insertions(+), 26 deletions(-) create mode 100644 res/css/structures/_SplashPage.scss create mode 100644 res/css/views/elements/_UseCaseSelection.scss create mode 100644 res/css/views/elements/_UseCaseSelectionButton.scss create mode 100644 res/img/element-icons/chat-bubble.svg create mode 100644 res/img/noise.png create mode 100644 src/components/structures/SplashPage.tsx create mode 100644 src/components/views/elements/UseCaseSelection.tsx create mode 100644 src/components/views/elements/UseCaseSelectionButton.tsx create mode 100644 src/settings/enums/UseCase.tsx diff --git a/cypress/e2e/1-register/register.spec.ts b/cypress/e2e/1-register/register.spec.ts index 3d710cd965..2bbc23e0c5 100644 --- a/cypress/e2e/1-register/register.spec.ts +++ b/cypress/e2e/1-register/register.spec.ts @@ -65,6 +65,8 @@ describe("Registration", () => { cy.startMeasuring("from-submit-to-home"); cy.get(".mx_InteractiveAuthEntryComponents_termsSubmit").click(); + cy.get(".mx_UseCaseSelection_skip .mx_AccessibleButton").click(); + cy.url().should('contain', '/#/home'); cy.stopMeasuring("from-submit-to-home"); diff --git a/cypress/e2e/12-spotlight/spotlight.spec.ts b/cypress/e2e/12-spotlight/spotlight.spec.ts index 9b2f290cfa..7b5ca76fe1 100644 --- a/cypress/e2e/12-spotlight/spotlight.spec.ts +++ b/cypress/e2e/12-spotlight/spotlight.spec.ts @@ -357,7 +357,7 @@ describe("Spotlight", () => { cy.spotlightSearch().clear().type("b"); // our debouncing logic only starts the search after a short timeout, // so we wait a few milliseconds. - cy.wait(300); + cy.wait(1000); cy.get(".mx_Spinner").should("not.exist").then(() => { cy.spotlightResults().should("have.length", 2).then(() => { cy.spotlightResults().eq(0) diff --git a/package.json b/package.json index 71051a1538..541ea9145c 100644 --- a/package.json +++ b/package.json @@ -56,7 +56,7 @@ }, "dependencies": { "@babel/runtime": "^7.12.5", - "@matrix-org/analytics-events": "^0.1.1", + "@matrix-org/analytics-events": "^0.1.2", "@matrix-org/react-sdk-module-api": "^0.0.3", "@sentry/browser": "^6.11.0", "@sentry/tracing": "^6.11.0", diff --git a/res/css/_components.scss b/res/css/_components.scss index 4a712d33b4..0e7187e646 100644 --- a/res/css/_components.scss +++ b/res/css/_components.scss @@ -54,6 +54,7 @@ @import "./structures/_SpaceHierarchy.scss"; @import "./structures/_SpacePanel.scss"; @import "./structures/_SpaceRoomView.scss"; +@import "./structures/_SplashPage.scss"; @import "./structures/_TabbedView.scss"; @import "./structures/_ToastContainer.scss"; @import "./structures/_UploadBar.scss"; @@ -189,6 +190,8 @@ @import "./views/elements/_ToggleSwitch.scss"; @import "./views/elements/_Tooltip.scss"; @import "./views/elements/_TooltipButton.scss"; +@import "./views/elements/_UseCaseSelection.scss"; +@import "./views/elements/_UseCaseSelectionButton.scss"; @import "./views/elements/_Validation.scss"; @import "./views/emojipicker/_EmojiPicker.scss"; @import "./views/location/_LocationPicker.scss"; diff --git a/res/css/structures/_SplashPage.scss b/res/css/structures/_SplashPage.scss new file mode 100644 index 0000000000..5a0c2324d9 --- /dev/null +++ b/res/css/structures/_SplashPage.scss @@ -0,0 +1,63 @@ +/* +Copyright 2022 The Matrix.org Foundation C.I.C. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +.mx_SplashPage { + position: relative; + height: 100%; + + &::before { + content: ""; + display: block; + position: absolute; + z-index: -1; + opacity: 0.6; + background-image: + radial-gradient( + 53.85% 66.75% at 87.55% 0%, + hsla(250, 76%, 71%, 0.261) 0%, + hsla(250, 100%, 88%, 0) 100% + ), + radial-gradient( + 41.93% 41.93% at 0% 0%, + hsla(222, 29%, 20%, 0.28) 0%, + hsla(250, 100%, 88%, 0) 100% + ), + radial-gradient( + 100% 100% at 0% 0%, + hsla(250, 100%, 88%, 0.174) 0%, + hsla(0, 100%, 86%, 0) 100% + ), + radial-gradient( + 106.35% 96.26% at 100% 0%, + hsla(250, 100%, 88%, 0.4) 0%, + hsla(167, 76%, 82%, 0) 100% + ); + /* blur to reduce color banding issues due to alpha-blending multiple gradients */ + filter: blur(8px); + inset: -9px; + mask: + /* mask to dither resulting combined gradient */ + url("$(res)/img/noise.png"), + /* gradient to apply different amounts of dithering to different parts of the gradient */ + linear-gradient( + to bottom, + /* 10% dithering at the top */ + rgba(0, 0, 0, 0.9) 20%, + /* 80% dithering at the bottom */ + rgba(0, 0, 0, 0.2) 100% + ); + } +} diff --git a/res/css/views/elements/_UseCaseSelection.scss b/res/css/views/elements/_UseCaseSelection.scss new file mode 100644 index 0000000000..bcf1d4c1aa --- /dev/null +++ b/res/css/views/elements/_UseCaseSelection.scss @@ -0,0 +1,129 @@ +/* +Copyright 2022 The Matrix.org Foundation C.I.C. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +.mx_UseCaseSelection { + display: grid; + grid-template-rows: 1fr 1fr max-content 2fr; + height: 100%; + grid-gap: $spacing-40; + + .mx_UseCaseSelection_title { + display: flex; + flex-direction: column; + justify-content: end; + + h1 { + font-weight: 600; + font-size: $font-32px; + text-align: center; + } + } + + .mx_UseCaseSelection_info { + display: flex; + flex-direction: column; + gap: $spacing-8; + align-self: end; + + h2 { + margin: 0; + font-weight: 500; + font-size: $font-24px; + text-align: center; + } + + h4 { + margin: 0; + font-weight: 400; + font-size: $font-16px; + color: $secondary-content; + text-align: center; + } + } + + .mx_UseCaseSelection_options { + display: grid; + grid-template-columns: repeat(auto-fit, 232px); + gap: $spacing-32; + align-self: stretch; + justify-content: center; + } + + .mx_UseCaseSelection_skip { + display: flex; + flex-direction: column; + align-self: start; + } +} + +.mx_UseCaseSelection_slideIn { + animation-delay: 800ms; + animation-duration: 300ms; + animation-timing-function: cubic-bezier(0, 0, 0.58, 1); + animation-name: mx_UseCaseSelection_slideInLong; + animation-fill-mode: backwards; + will-change: opacity; +} + +.mx_UseCaseSelection_slideInDelayed { + animation-delay: 1500ms; + animation-duration: 300ms; + animation-timing-function: cubic-bezier(0, 0, 0.58, 1); + animation-name: mx_UseCaseSelection_slideInShort; + animation-fill-mode: backwards; + will-change: transform, opacity; +} + +.mx_UseCaseSelection_selected { + .mx_UseCaseSelection_slideIn, .mx_UseCaseSelection_slideInDelayed { + animation-delay: 800ms; + animation-duration: 300ms; + animation-fill-mode: forwards; + animation-name: mx_UseCaseSelection_fadeOut; + will-change: opacity; + } +} + +@keyframes mx_UseCaseSelection_slideInLong { + 0% { + transform: translate(0, 20px); + opacity: 0; + } + 100% { + transform: translate(0, 0); + opacity: 1; + } +} + +@keyframes mx_UseCaseSelection_slideInShort { + 0% { + transform: translate(0, 8px); + opacity: 0; + } + 100% { + transform: translate(0, 0); + opacity: 1; + } +} + +@keyframes mx_UseCaseSelection_fadeOut { + 0% { + opacity: 1; + } + 100% { + opacity: 0; + } +} diff --git a/res/css/views/elements/_UseCaseSelectionButton.scss b/res/css/views/elements/_UseCaseSelectionButton.scss new file mode 100644 index 0000000000..7a44e79347 --- /dev/null +++ b/res/css/views/elements/_UseCaseSelectionButton.scss @@ -0,0 +1,104 @@ +/* +Copyright 2022 The Matrix.org Foundation C.I.C. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +.mx_UseCaseSelectionButton { + display: flex; + flex-direction: column; + align-items: center; + padding: $spacing-24 $spacing-16; + background: $background; + border: 1px solid $quinary-content; + border-radius: 8px; + text-align: center; + position: relative; + transition-property: box-shadow, transform; + transition-duration: 300ms; + + .mx_UseCaseSelectionButton_icon { + /* workaround: design expects a layering of two colors */ + background: linear-gradient(0deg, rgba(172, 59, 168, 0.15), rgba(172, 59, 168, 0.15)), #FFFFFF; + border-radius: 14px; + padding: $spacing-8; + margin-bottom: $spacing-16; + + &::before { + content: ""; + display: block; + /* this has to remain the same color across all themes, + as its background has a fixed color as well */ + background: #1E1E1E; + mask-position: center; + mask-repeat: no-repeat; + mask-size: contain; + width: 22px; + height: 22px; + } + + &.mx_UseCaseSelectionButton_messaging::before { + mask-image: url('$(res)/img/element-icons/chat-bubble.svg'); + } + + &.mx_UseCaseSelectionButton_work::before { + mask-image: url('$(res)/img/element-icons/view-community.svg'); + } + + &.mx_UseCaseSelectionButton_community::before { + mask-image: url('$(res)/img/globe.svg'); + } + } + + &:hover, &:focus { + box-shadow: 0 $spacing-4 $spacing-8 rgba(0, 0, 0, 0.08); + transform: translate(0, -$spacing-8); + } + + .mx_UseCaseSelectionButton_selectedIcon { + right: -12px; + top: -12px; + position: absolute; + border-radius: 24px; + background: $accent; + padding: 6px; + transition-property: opacity, transform; + transition-duration: 150ms; + opacity: 0; + transform: scale(0.6); + + &::before { + content: ""; + display: block; + background: $background; + mask-position: center; + mask-repeat: no-repeat; + mask-size: contain; + width: 12px; + height: 12px; + + mask-image: url('$(res)/img/element-icons/check-white.svg'); + } + } + + &.mx_UseCaseSelectionButton_selected { + border: 2px solid $accent; + padding: calc($spacing-24 - 1px) calc($spacing-16 - 1px); + box-shadow: 0 $spacing-4 $spacing-8 rgba(0, 0, 0, 0.08); + + .mx_UseCaseSelectionButton_selectedIcon { + opacity: 1; + transform: scale(1); + } + } +} diff --git a/res/img/element-icons/chat-bubble.svg b/res/img/element-icons/chat-bubble.svg new file mode 100644 index 0000000000..3b0e765843 --- /dev/null +++ b/res/img/element-icons/chat-bubble.svg @@ -0,0 +1,3 @@ + + + diff --git a/res/img/element-icons/group-members.svg b/res/img/element-icons/group-members.svg index 553ba3b1af..7fa9c39232 100644 --- a/res/img/element-icons/group-members.svg +++ b/res/img/element-icons/group-members.svg @@ -1,8 +1,3 @@ - - - - - - - + + diff --git a/res/img/element-icons/view-community.svg b/res/img/element-icons/view-community.svg index ee33aa525e..9bd7115a47 100644 --- a/res/img/element-icons/view-community.svg +++ b/res/img/element-icons/view-community.svg @@ -1,8 +1,3 @@ - - - - - - - + + diff --git a/res/img/globe.svg b/res/img/globe.svg index 635fa91cce..954a16d478 100644 --- a/res/img/globe.svg +++ b/res/img/globe.svg @@ -1,3 +1,3 @@ - - + + diff --git a/res/img/noise.png b/res/img/noise.png new file mode 100644 index 0000000000000000000000000000000000000000..05351f89b6abc88a8f22ab6afa75174500053602 GIT binary patch literal 3200 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, ScreenName> = { [Views.WELCOME]: "Welcome", [Views.LOGIN]: "Login", [Views.REGISTER]: "Register", + [Views.USE_CASE_SELECTION]: "UseCaseSelection", [Views.FORGOT_PASSWORD]: "ForgotPassword", [Views.COMPLETE_SECURITY]: "CompleteSecurity", [Views.E2E_SETUP]: "E2ESetup", diff --git a/src/Views.ts b/src/Views.ts index 4c734f7bba..447dbeab84 100644 --- a/src/Views.ts +++ b/src/Views.ts @@ -38,6 +38,9 @@ enum Views { // flow to setup SSSS / cross-signing on this account E2E_SETUP, + // screen that allows users to select which use case they’ll use matrix for + USE_CASE_SELECTION, + // we are logged in with an active matrix client. The logged_in state also // includes guests users as they too are logged in at the client level. LOGGED_IN, diff --git a/src/components/structures/MatrixChat.tsx b/src/components/structures/MatrixChat.tsx index 3f6ffd1154..ea2852df3d 100644 --- a/src/components/structures/MatrixChat.tsx +++ b/src/components/structures/MatrixChat.tsx @@ -68,6 +68,7 @@ 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"; @@ -129,6 +130,7 @@ import { SnakedObject } from "../../utils/SnakedObject"; import { leaveRoomBehaviour } from "../../utils/leave-behaviour"; import VideoChannelStore from "../../stores/VideoChannelStore"; import { IRoomStateEventsActionPayload } from "../../actions/MatrixActionCreators"; +import { UseCaseSelection } from '../views/elements/UseCaseSelection'; // legacy export export { default as Views } from "../../Views"; @@ -755,7 +757,8 @@ export default class MatrixChat extends React.PureComponent { 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.E2E_SETUP && + this.state.view !== Views.USE_CASE_SELECTION ) { this.onLoggedIn(); } @@ -1206,6 +1209,40 @@ export default class MatrixChat extends React.PureComponent { private async onLoggedIn() { ThemeController.isLogin = false; this.themeWatcher.recheck(); + StorageManager.tryPersistStorage(); + + 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 { + return this.onShowPostLoginScreen(); + } + } + + private async onShowPostLoginScreen(useCase?: UseCase) { + if (useCase) { + PosthogAnalytics.instance.setProperty("ftueUseCaseSelection", useCase); + SettingsStore.setValue("FTUE.useCaseSelection", null, SettingLevel.ACCOUNT, useCase); + } + 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. @@ -1243,8 +1280,7 @@ export default class MatrixChat extends React.PureComponent { this.showScreenAfterLogin(); } - StorageManager.tryPersistStorage(); - + // Will be moved to a pre-login flow as well if (PosthogAnalytics.instance.isEnabled() && SettingsStore.isLevelSupported(SettingLevel.ACCOUNT)) { this.initPosthogAnalyticsToast(); } @@ -2005,6 +2041,10 @@ export default class MatrixChat extends React.PureComponent { fragmentAfterLogin={fragmentAfterLogin} /> ); + } else if (this.state.view === Views.USE_CASE_SELECTION) { + view = ( + this.onShowPostLoginScreen(useCase)} /> + ); } else { logger.error(`Unknown view ${this.state.view}`); } diff --git a/src/components/structures/SplashPage.tsx b/src/components/structures/SplashPage.tsx new file mode 100644 index 0000000000..539e2f9ae0 --- /dev/null +++ b/src/components/structures/SplashPage.tsx @@ -0,0 +1,27 @@ +/* +Copyright 2022 The Matrix.org Foundation C.I.C. +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + http://www.apache.org/licenses/LICENSE-2.0 +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +import classNames from "classnames"; +import React, { DetailedHTMLProps, HTMLAttributes, ReactNode } from "react"; + +interface Props extends DetailedHTMLProps, HTMLElement> { + className?: string; + children?: ReactNode; +} + +export default function SplashPage({ children, className, ...other }: Props) { + const classes = classNames(className, "mx_SplashPage"); + return
+ { children } +
; +} diff --git a/src/components/views/elements/UseCaseSelection.tsx b/src/components/views/elements/UseCaseSelection.tsx new file mode 100644 index 0000000000..4e8a1e8342 --- /dev/null +++ b/src/components/views/elements/UseCaseSelection.tsx @@ -0,0 +1,86 @@ +/* +Copyright 2022 The Matrix.org Foundation C.I.C. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +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) { + const [selection, setSelected] = useState(null); + + useEffect(() => { + if (selection) { + setSelected(selection); + let handler: number | null = setTimeout(() => { + handler = null; + onFinished(selection); + }, TIMEOUT); + return () => { + clearTimeout(handler); + handler = null; + }; + } + }, [selection, onFinished]); + + return ( + +
+

{ _t("You're in") }

+
+
+

{ _t("Who will you chat to the most?") }

+

{ _t("We'll help you get connected.") }

+
+
+ + + +
+
+ setSelected(UseCase.Skip)}> + { _t("Skip") } + +
+ + ); +} diff --git a/src/components/views/elements/UseCaseSelectionButton.tsx b/src/components/views/elements/UseCaseSelectionButton.tsx new file mode 100644 index 0000000000..59e022df05 --- /dev/null +++ b/src/components/views/elements/UseCaseSelectionButton.tsx @@ -0,0 +1,59 @@ +/* +Copyright 2022 The Matrix.org Foundation C.I.C. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +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) { + let label: string; + switch (useCase) { + case UseCase.PersonalMessaging: + label = _t("Friends and family"); + break; + case UseCase.WorkMessaging: + label = _t("Coworkers and teams"); + break; + case UseCase.CommunityMessaging: + label = _t("Online community members"); + break; + } + + return ( + onClick(useCase)}> +
+ { label } +
+ + ); +} diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json index ad82fd4318..3cb4800634 100644 --- a/src/i18n/strings/en_EN.json +++ b/src/i18n/strings/en_EN.json @@ -2375,6 +2375,13 @@ "Continue with %(provider)s": "Continue with %(provider)s", "Sign in with single sign-on": "Sign in with single sign-on", "And %(count)s more...|other": "And %(count)s more...", + "You're in": "You're in", + "Who will you chat to the most?": "Who will you chat to the most?", + "We'll help you get connected.": "We'll help you get connected.", + "Skip": "Skip", + "Friends and family": "Friends and family", + "Coworkers and teams": "Coworkers and teams", + "Online community members": "Online community members", "Enter a server name": "Enter a server name", "Looks good": "Looks good", "You are not allowed to view this server's rooms list": "You are not allowed to view this server's rooms list", @@ -2744,7 +2751,6 @@ "Please check your email and click on the link it contains. Once this is done, click continue.": "Please check your email and click on the link it contains. Once this is done, click continue.", "Email address": "Email address", "This will allow you to reset your password and receive notifications.": "This will allow you to reset your password and receive notifications.", - "Skip": "Skip", "Share Room": "Share Room", "Link to most recent message": "Link to most recent message", "Share User": "Share User", diff --git a/src/settings/Settings.tsx b/src/settings/Settings.tsx index d783c19db3..8aa6a30f63 100644 --- a/src/settings/Settings.tsx +++ b/src/settings/Settings.tsx @@ -698,6 +698,10 @@ export const SETTINGS: {[setting: string]: ISetting} = { displayName: _td('Send analytics data'), default: null, }, + "FTUE.useCaseSelection": { + supportedLevels: LEVELS_ACCOUNT_SETTINGS, + default: null, + }, "autocompleteDelay": { supportedLevels: LEVELS_DEVICE_ONLY_SETTINGS_WITH_CONFIG, default: 200, diff --git a/src/settings/enums/UseCase.tsx b/src/settings/enums/UseCase.tsx new file mode 100644 index 0000000000..be101b0974 --- /dev/null +++ b/src/settings/enums/UseCase.tsx @@ -0,0 +1,22 @@ +/* +Copyright 2022 The Matrix.org Foundation C.I.C. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +export enum UseCase { + PersonalMessaging = "PersonalMessaging", + WorkMessaging = "WorkMessaging", + CommunityMessaging = "CommunityMessaging", + Skip = "Skip", +} diff --git a/test/end-to-end-tests/src/usecases/signup.ts b/test/end-to-end-tests/src/usecases/signup.ts index 86d2720535..5099d7584e 100644 --- a/test/end-to-end-tests/src/usecases/signup.ts +++ b/test/end-to-end-tests/src/usecases/signup.ts @@ -77,6 +77,10 @@ export async function signup( const acceptButton = await session.query('.mx_InteractiveAuthEntryComponents_termsSubmit'); await acceptButton.click(); + //now click the 'Skip' button to skip the use case selection + const skipUseCaseButton = await session.query('.mx_UseCaseSelection_skip .mx_AccessibleButton'); + await skipUseCaseButton.click(); + //wait for registration to finish so the hash gets set //onhashchange better? diff --git a/yarn.lock b/yarn.lock index 10b1f6c351..96b1f59b0b 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1570,10 +1570,10 @@ resolved "https://registry.yarnpkg.com/@mapbox/whoots-js/-/whoots-js-3.1.0.tgz#497c67a1cef50d1a2459ba60f315e448d2ad87fe" integrity sha512-Es6WcD0nO5l+2BOQS4uLfNPYQaNDfbot3X1XUoloz+x0mPDS3eeORZJl06HXjwBG1fOGwCRnzK88LMdxKRrd6Q== -"@matrix-org/analytics-events@^0.1.1": - version "0.1.1" - resolved "https://registry.yarnpkg.com/@matrix-org/analytics-events/-/analytics-events-0.1.1.tgz#ae95b0c1fb86a094c5f51d121f10e6a1b1ddca68" - integrity sha512-PIDkfYMNmph6x/rfgtIeQXUWj9hGzTLnOCFUYZFBnoTiS4UXkH73bz77Ho12uoUezUz4v40mxTXdrFxp8Zo6zA== +"@matrix-org/analytics-events@^0.1.2": + version "0.1.2" + resolved "https://registry.yarnpkg.com/@matrix-org/analytics-events/-/analytics-events-0.1.2.tgz#820b1a78d1471f21da96274d92eb161b41f9e880" + integrity sha512-TumnmENiuTtSmfcVwzovLDq4pRgRlUmuq1bxOtli9XWMgscs3Z6URu5PJcvuFj87L7bKJrGCNS3zR+DMUxc7kg== "@matrix-org/olm@https://gitlab.matrix.org/api/v4/projects/27/packages/npm/@matrix-org/olm/-/@matrix-org/olm-3.2.8.tgz": version "3.2.8"