From 80870f1c553ea537be9f2b2d293ad97fb1dcd610 Mon Sep 17 00:00:00 2001 From: Immad Abdul Jabbar Date: Tue, 22 Oct 2024 15:30:36 +0200 Subject: [PATCH] =?UTF-8?q?feat(user-migration):=20add=20pages=20to=20migr?= =?UTF-8?q?ate=20privat=20to=20team=20user=20[WPB-1=E2=80=A6=20(#5058)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat(user-migration): add pages to migrate privat to team user [WPB-11128] * fixed minor issues and tests --- .eslintrc.json | 4 +- app-config/package.json | 4 +- package.json | 2 +- server/ServerConfig.ts | 2 + server/config.ts | 2 + src/i18n/en-US.json | 44 +++++ src/script/Environment.ts | 4 + src/script/PrivateRoot.tsx | 63 +++++++ src/script/Root.tsx | 17 +- src/script/component/MarkupTranslation.tsx | 23 +++ src/script/layout/MigrationLayout.tsx | 86 +++++++++ src/script/layout/WavesPattern.tsx | 60 +++++++ src/script/module/action/AccountAction.ts | 9 + src/script/module/action/SelfAction.ts | 31 ++++ src/script/module/action/TeamAction.ts | 39 +++++ src/script/module/action/index.tsx | 34 ++-- src/script/page/BotPasswordForgot.tsx | 6 +- src/script/page/BotPasswordReset.tsx | 6 +- src/script/page/ConversationJoin.test.tsx | 34 ++-- src/script/page/ConversationJoin.tsx | 6 +- src/script/page/DeleteAccount.tsx | 6 +- src/script/page/PasswordForgot.tsx | 6 +- src/script/page/PasswordReset.tsx | 6 +- src/script/page/UserProfile.test.tsx | 13 +- src/script/page/VerifyBotAccount.tsx | 6 +- src/script/page/VerifyEmailAccount.tsx | 6 +- .../page/migration/AcceptInvitation.tsx | 156 +++++++++++++++++ .../page/migration/ConfirmInvitation.tsx | 109 ++++++++++++ .../page/migration/OutlinedCheckIcon.tsx | 31 ++++ src/script/page/migration/ShieldIcon.tsx | 33 ++++ .../page/migration/TermsAcknowledgement.tsx | 155 +++++++++++++++++ src/script/page/migration/Welcome.tsx | 130 ++++++++++++++ src/script/page/migration/styles.ts | 164 ++++++++++++++++++ src/script/page/migration/utils.ts | 45 +++++ src/script/route.ts | 8 + src/script/util/urlUtil.ts | 10 +- yarn.lock | 10 +- 37 files changed, 1294 insertions(+), 76 deletions(-) create mode 100644 src/script/PrivateRoot.tsx create mode 100644 src/script/component/MarkupTranslation.tsx create mode 100644 src/script/layout/MigrationLayout.tsx create mode 100644 src/script/layout/WavesPattern.tsx create mode 100644 src/script/module/action/SelfAction.ts create mode 100644 src/script/module/action/TeamAction.ts create mode 100644 src/script/page/migration/AcceptInvitation.tsx create mode 100644 src/script/page/migration/ConfirmInvitation.tsx create mode 100644 src/script/page/migration/OutlinedCheckIcon.tsx create mode 100644 src/script/page/migration/ShieldIcon.tsx create mode 100644 src/script/page/migration/TermsAcknowledgement.tsx create mode 100644 src/script/page/migration/Welcome.tsx create mode 100644 src/script/page/migration/styles.ts create mode 100644 src/script/page/migration/utils.ts diff --git a/.eslintrc.json b/.eslintrc.json index 3fcaa0e9a0..ab88c74f0d 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -30,7 +30,9 @@ ], "import/no-default-export": "off", "jest/no-jasmine-globals": "warn", - "jest/no-try-expect": "off" + "jest/no-try-expect": "off", + "no-magic-numbers": "off", + "react/no-unknown-property": ["error", {"ignore": ["css"]}] }, "settings": { "react": { diff --git a/app-config/package.json b/app-config/package.json index 378b17fd54..8d226d9f94 100644 --- a/app-config/package.json +++ b/app-config/package.json @@ -1,7 +1,7 @@ { "dependencies": { "wire-web-config-default-ey": "https://github.com/wireapp/wire-web-config-ey.git#v0.28.21-0", - "wire-web-config-default-main": "https://github.com/wireapp/wire-web-config-wire#v0.31.33", - "wire-web-config-default-staging": "https://github.com/wireapp/wire-web-config-default#v0.31.32" + "wire-web-config-default-main": "https://github.com/wireapp/wire-web-config-wire#v0.31.36", + "wire-web-config-default-staging": "https://github.com/wireapp/wire-web-config-default#v0.31.35" } } diff --git a/package.json b/package.json index e9cd5382b5..ac89965298 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "dependencies": { - "@wireapp/api-client": "27.6.2", + "@wireapp/api-client": "27.7.0", "@wireapp/commons": "5.2.13", "@wireapp/react-ui-kit": "9.24.0", "core-js": "3.38.1", diff --git a/server/ServerConfig.ts b/server/ServerConfig.ts index 078a30a8c8..abc0eebe8f 100644 --- a/server/ServerConfig.ts +++ b/server/ServerConfig.ts @@ -45,6 +45,8 @@ export interface ServerConfig { TEAMS_BASE: string; WEBAPP_BASE: string; WEBSITE_BASE: string; + URL_TERMS_OF_USE_TEAMS: string; + URL_SUPPORT_BACKUP_HISTORY: string; }; VERSION: string; }; diff --git a/server/config.ts b/server/config.ts index e5d5992a2a..cc6928f09d 100644 --- a/server/config.ts +++ b/server/config.ts @@ -130,6 +130,8 @@ const config: ServerConfig = { TEAMS_BASE: process.env.URL_TEAMS_BASE, WEBAPP_BASE: process.env.URL_WEBAPP_BASE, WEBSITE_BASE: process.env.URL_WEBSITE_BASE, + URL_TERMS_OF_USE_TEAMS: process.env.URL_TERMS_OF_USE_TEAMS, + URL_SUPPORT_BACKUP_HISTORY: process.env.URL_SUPPORT_BACKUP_HISTORY, }, VERSION: readFile(VERSION_FILE, '0.0.0'), }, diff --git a/src/i18n/en-US.json b/src/i18n/en-US.json index 67c79cc8f1..af6cf6c3a7 100644 --- a/src/i18n/en-US.json +++ b/src/i18n/en-US.json @@ -89,5 +89,49 @@ "downloadApp": "Download {{brandName}}", "openWithApp": "Open in App", "openWithBrowser": "Open in Browser" + }, + "migration": { + "errorLogin": "Unable to login with these credentials.", + "wrongCredentialsError": "Please enter valid credentials.", + "passwordLabel": "Password (personal account)", + "passwordPlaceholder": "Password", + "continueBtnText": "Continue", + "layoutSubHeader": "The most secure collaboration", + "layoutListHeader1": "Use the same level of security and enjoy extra features and benefits for free, like:", + "layoutListHeader2": "What’s next", + "layoutListItem1": "Join video conferences", + "layoutListItem2": "Guest rooms and external communication", + "layoutListItem3": "Availability status", + "layoutListItem4": "Option to upgrade to Wire for Enterprise to get full collaboration benefits", + "layoutListItem5": "Open Wire and get connected with your new team members", + "layoutListItem6": "Enjoy conference calls with a single click", + "welcomePageHeader": "Congratulations, you joined your team on Wire!", + "welcomePageSubHeader": "Welcome to {{teamName}}
We transferred your personal account into a team member account.", + "welcomePageAppOpenText": "Open Wire", + "welcomePageTMOpenText": "Open Team Management", + "welcomePageOr": "or", + "confirmPageHeader": "Join your team", + "confirmPageSubHeader": "To ensure a secure transfer and prevent unauthorized access, please enter the password of your personal account again.", + "termsPageHeader": "Join your team", + "termsPageSubHeader": "You’re about to join the team.", + "termsPageListHeader": "Things to know", + "termsPageListItem1": "Your personal account will be transferred to a team account.", + "termsPageListItem2": "This change is permanent and irrevocable.", + "termsPageListItem3": "One team limit: You can only be part of one team with this email address.", + "termsPageListItem4": "You keep your contacts and your conversation history.", + "termsPageListItem5": "Profile details: You keep your email, profile name, and username, as well as your password and profile picture.", + "termsPageAccountManagerHeader": "Account Management", + "termsPageAccountManagerText": "As you will be part of a team, the admin can remove you from the team. In that case, you will immediately lose access to your account, contacts, history, and username.", + "termsPageRecommendationsHeader": "We recommend to", + "termsPageRecommendationItem1": "Verify the admin’s identity before accepting, and if you’re unsure, close this window.", + "termsPageRecommendationItem2": "Back up your contacts", + "termsPageMigrationTerms": "I agree to the migration terms and understand that this change is irreversible.", + "termsPageTermsOfUse": "I accept the", + "termsPageTermsOfUseLink": "Terms of Use (Business).", + "invitationPageHeader": "Join your team", + "invitationPageSubHeader": "Log in with your personal account's email or username and password.", + "invitationPagLoginLabel": "Email or username", + "emailOrUsernamePlaceholder": "Enter email or username", + "forgotPassword": "forgot password?" } } diff --git a/src/script/Environment.ts b/src/script/Environment.ts index bd12cbba15..4f77dc892d 100644 --- a/src/script/Environment.ts +++ b/src/script/Environment.ts @@ -49,6 +49,8 @@ declare global { TEAMS_BASE: string; WEBAPP_BASE: string; WEBSITE_BASE: string; + URL_TERMS_OF_USE_TEAMS: string; + URL_SUPPORT_BACKUP_HISTORY: string; }; VERSION: string; FEATURE: { @@ -95,3 +97,5 @@ export const REDIRECT_VERIFY_URL = window.wire.env.URL.REDIRECT_VERIFY_BASE; export const SUPPORT_URL = window.wire.env.URL.SUPPORT_BASE; export const TEAMS_URL = window.wire.env.URL.TEAMS_BASE; export const WEBSITE_URL = window.wire.env.URL.WEBSITE_BASE; +export const URL_SUPPORT_BACKUP_HISTORY = window.wire.env.URL.URL_SUPPORT_BACKUP_HISTORY; +export const URL_TERMS_OF_USE_TEAMS = window.wire.env.URL.URL_TERMS_OF_USE_TEAMS; diff --git a/src/script/PrivateRoot.tsx b/src/script/PrivateRoot.tsx new file mode 100644 index 0000000000..45b57f772e --- /dev/null +++ b/src/script/PrivateRoot.tsx @@ -0,0 +1,63 @@ +/* + * Wire + * Copyright (C) 2024 Wire Swiss GmbH + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see http://www.gnu.org/licenses/. + * + */ + +import {Outlet, useNavigate} from 'react-router-dom'; + +import {Loading, Opacity, TransitionContainer} from '@wireapp/react-ui-kit'; + +import {MigrationLayout} from './layout/MigrationLayout'; +import {useEffect, useState} from 'react'; +import {useActionContext} from './module/action'; +import {ROUTE} from './route'; + +export const PrivateRoot = () => { + const {accountAction} = useActionContext(); + const navigate = useNavigate(); + const [loading, setLoading] = useState(true); + + useEffect(() => { + accountAction + .init() + .catch(() => { + navigate(ROUTE.ACCEPT_INVITATION); + }) + .finally(() => setLoading(false)); + }, []); + + if (loading) { + return ( + + ); + } + + return ( + + + + + + + + ); +}; diff --git a/src/script/Root.tsx b/src/script/Root.tsx index 312520a2b7..da97b81593 100644 --- a/src/script/Root.tsx +++ b/src/script/Root.tsx @@ -18,12 +18,15 @@ */ import {FlexBox, Loading, StyledApp, THEME_ID} from '@wireapp/react-ui-kit'; -import React, {lazy, Suspense, useEffect} from 'react'; +import {lazy, Suspense, useEffect} from 'react'; import {Route, Routes, BrowserRouter, Navigate} from 'react-router-dom'; import {ROUTE} from 'script/route'; import {QUERY_KEY} from './util/urlUtil'; - -interface Props {} +import {PrivateRoot} from './PrivateRoot'; +import {TermsAcknowledgement} from './page/migration/TermsAcknowledgement'; +import {ConfirmInvitation} from './page/migration/ConfirmInvitation'; +import {Welcome} from './page/migration/Welcome'; +import {AcceptInvitation} from './page/migration/AcceptInvitation'; const LazyIndex = lazy(() => import('./page/Index')); const LazyDeleteAccount = lazy(() => import('./page/DeleteAccount')); @@ -36,7 +39,7 @@ const LazyVerifyBotAccount = lazy(() => import('./page/VerifyBotAccount')); const LazyConversationJoin = lazy(() => import('./page/ConversationJoin')); const LazyUserProfile = lazy(() => import('./page/UserProfile')); -const Root: React.FC = () => { +const Root = () => { useEffect(() => { const queryParams = new URLSearchParams(window.location.search); const hlParam = queryParams.get(QUERY_KEY.LANG); @@ -70,6 +73,12 @@ const Root: React.FC = () => { } /> } /> } /> + } /> + }> + } /> + } /> + } /> + } /> diff --git a/src/script/component/MarkupTranslation.tsx b/src/script/component/MarkupTranslation.tsx new file mode 100644 index 0000000000..4d9b602339 --- /dev/null +++ b/src/script/component/MarkupTranslation.tsx @@ -0,0 +1,23 @@ +/* + * Wire + * Copyright (C) 2024 Wire Swiss GmbH + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see http://www.gnu.org/licenses/. + * + */ + +// Handle HTML markup within translation text +export default function MarkupTranslation({translation}: {translation: string[]}) { + return ; +} diff --git a/src/script/layout/MigrationLayout.tsx b/src/script/layout/MigrationLayout.tsx new file mode 100644 index 0000000000..0ca6945356 --- /dev/null +++ b/src/script/layout/MigrationLayout.tsx @@ -0,0 +1,86 @@ +/* + * Wire + * Copyright (C) 2024 Wire Swiss GmbH + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see http://www.gnu.org/licenses/. + * + */ + +import {CSSObject} from '@emotion/react'; +import {Bold, COLOR_V2, FlexBox, Logo, QUERY, QueryKeys, Text, useMatchMedia} from '@wireapp/react-ui-kit'; +import {ReactNode} from 'react'; +import {WavesPattern} from './WavesPattern'; +import {useLocation} from 'react-router-dom'; +import {ROUTE} from 'script/route'; +import {useTranslation} from 'react-i18next'; + +export const MigrationLayout = ({children}: {children: ReactNode}) => { + const location = useLocation(); + const [t] = useTranslation('migration'); + const isTablet = useMatchMedia(QUERY[QueryKeys.TABLET_DOWN]); + const isWelcomePage = location.pathname === ROUTE.WELCOME; + + return ( + + {!isTablet && ( +
+ +
+ + {t('layoutSubHeader')} + +
+
+ + {isWelcomePage ? t('layoutListHeader2') : t('layoutListHeader1')} + +
    + {isWelcomePage ? ( + <> +
  • {t('layoutListItem5')}
  • +
  • {t('layoutListItem6')}
  • + + ) : ( + <> +
  • {t('layoutListItem1')}
  • +
  • {t('layoutListItem2')}
  • +
  • {t('layoutListItem3')}
  • +
  • {t('layoutListItem4')}
  • + + )} +
+
+
+ +
+ )} +
{children}
+
+ ); +}; + +const leftSectionCss: CSSObject = { + background: 'black', + width: '45%', + margin: 0, + height: '100vh', + maxWidth: '30rem', + padding: '6rem 3.75rem', + position: 'relative', + minHeight: '50rem', +}; + +const whiteFontCss: CSSObject = { + color: 'white', +}; diff --git a/src/script/layout/WavesPattern.tsx b/src/script/layout/WavesPattern.tsx new file mode 100644 index 0000000000..32c10e0a0b --- /dev/null +++ b/src/script/layout/WavesPattern.tsx @@ -0,0 +1,60 @@ +/* + * Wire + * Copyright (C) 2024 Wire Swiss GmbH + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see http://www.gnu.org/licenses/. + * + */ + +export const WavesPattern = () => ( + + + + + + +); diff --git a/src/script/module/action/AccountAction.ts b/src/script/module/action/AccountAction.ts index b1e1c0402a..94a8914be0 100644 --- a/src/script/module/action/AccountAction.ts +++ b/src/script/module/action/AccountAction.ts @@ -17,6 +17,7 @@ * */ import {APIClient} from '@wireapp/api-client'; +import {LoginData} from '@wireapp/api-client/lib/auth'; export class AccountAction { private readonly apiClient: APIClient; @@ -56,4 +57,12 @@ export class AccountAction { validateConversationJoin = (key: string, code: string) => { return this.apiClient.api.conversation.postConversationCodeCheck({code, key}); }; + + login = (login: LoginData) => { + return this.apiClient.login(login); + }; + + init = () => { + return this.apiClient.init(); + }; } diff --git a/src/script/module/action/SelfAction.ts b/src/script/module/action/SelfAction.ts new file mode 100644 index 0000000000..8b812d87a8 --- /dev/null +++ b/src/script/module/action/SelfAction.ts @@ -0,0 +1,31 @@ +/* + * Wire + * Copyright (C) 2024 Wire Swiss GmbH + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see http://www.gnu.org/licenses/. + * + */ +import {APIClient} from '@wireapp/api-client'; + +export class SelfAction { + private readonly apiClient: APIClient; + + constructor(apiClient: APIClient) { + this.apiClient = apiClient; + } + + getSelf = async () => { + return this.apiClient.api.self.getSelf(); + }; +} diff --git a/src/script/module/action/TeamAction.ts b/src/script/module/action/TeamAction.ts new file mode 100644 index 0000000000..bd6fa65612 --- /dev/null +++ b/src/script/module/action/TeamAction.ts @@ -0,0 +1,39 @@ +/* + * Wire + * Copyright (C) 2024 Wire Swiss GmbH + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see http://www.gnu.org/licenses/. + * + */ +import {APIClient} from '@wireapp/api-client'; + +export class TeamAction { + private readonly apiClient: APIClient; + + constructor(apiClient: APIClient) { + this.apiClient = apiClient; + } + + getMember = async (teamId: string, userId: string) => { + return this.apiClient.api.teams.member.getMember(teamId, userId); + }; + + getTeam = async (teamId: string) => { + return this.apiClient.api.teams.team.getTeam(teamId); + }; + + acceptInvitation = async (payload: {code: string; password: string}) => { + return this.apiClient.api.teams.invitation.acceptInvitation(payload); + }; +} diff --git a/src/script/module/action/index.tsx b/src/script/module/action/index.tsx index 3a1f6175a7..82f3ca0f5d 100644 --- a/src/script/module/action/index.tsx +++ b/src/script/module/action/index.tsx @@ -18,29 +18,39 @@ */ import {APIClient} from '@wireapp/api-client'; -import React, {HTMLProps} from 'react'; +import React, {HTMLProps, useContext} from 'react'; import * as Environment from 'script/Environment'; import {AccountAction} from './AccountAction'; +import {SelfAction} from './SelfAction'; +import {TeamAction} from './TeamAction'; interface ActionProviderProps extends HTMLProps { - contextData?: typeof actionRoot; + contextData?: ReturnType; } -const actionRoot: { +const getActionRoot = (): { accountAction: AccountAction; -} = { - accountAction: new AccountAction( - new APIClient({ - urls: {name: 'backend', rest: Environment.HOST_HTTP, ws: undefined}, - }), - ), + selfAction: SelfAction; + teamAction: TeamAction; +} => { + const apiClient = new APIClient({ + urls: {name: 'backend', rest: Environment.HOST_HTTP, ws: undefined}, + }); + + return { + accountAction: new AccountAction(apiClient), + selfAction: new SelfAction(apiClient), + teamAction: new TeamAction(apiClient), + }; }; -const ActionContext = React.createContext(actionRoot); +const ActionContext = React.createContext(getActionRoot()); const ActionProvider = ({children, contextData}: ActionProviderProps) => ( - {children} + {children} ); -export {actionRoot, ActionContext, ActionProvider}; +const useActionContext = () => useContext(ActionContext); + +export {getActionRoot, ActionContext, ActionProvider, useActionContext}; diff --git a/src/script/page/BotPasswordForgot.tsx b/src/script/page/BotPasswordForgot.tsx index 6a8e478486..8319173356 100644 --- a/src/script/page/BotPasswordForgot.tsx +++ b/src/script/page/BotPasswordForgot.tsx @@ -17,10 +17,10 @@ * */ import {Button, COLOR, ContainerXS, Form, H1, Input, Text} from '@wireapp/react-ui-kit'; -import React, {useContext, useRef, useState} from 'react'; +import React, {useRef, useState} from 'react'; import {useTranslation} from 'react-i18next'; import Document from 'script/component/Document'; -import {ActionContext} from 'script/module/action'; +import {useActionContext} from 'script/module/action'; import ValidationError from 'script/module/action/ValidationError'; const HTTP_STATUS_EMAIL_NOT_IN_USE = 400; @@ -34,7 +34,7 @@ const BotPasswordForgot = () => { const [success, setSuccess] = useState(false); const [t] = useTranslation('forgot'); - const {accountAction} = useContext(ActionContext); + const {accountAction} = useActionContext(); const initiatePasswordReset = async (event: React.FormEvent) => { event.preventDefault(); try { diff --git a/src/script/page/BotPasswordReset.tsx b/src/script/page/BotPasswordReset.tsx index bf8fb438fc..4967362e4a 100644 --- a/src/script/page/BotPasswordReset.tsx +++ b/src/script/page/BotPasswordReset.tsx @@ -18,12 +18,12 @@ */ import {ValidationUtil} from '@wireapp/commons'; import {Button, COLOR, ContainerXS, Form, H1, Input, Text} from '@wireapp/react-ui-kit'; -import React, {useContext, useRef, useState} from 'react'; +import React, {useRef, useState} from 'react'; import {useTranslation} from 'react-i18next'; import {useLocation} from 'react-router-dom'; import Document from 'script/component/Document'; import {NEW_PASSWORD_MINIMUM_LENGTH} from 'script/Environment'; -import {ActionContext} from 'script/module/action'; +import {useActionContext} from 'script/module/action'; import ValidationError from 'script/module/action/ValidationError'; const HTTP_STATUS_INVALID_LINK = 400; @@ -45,7 +45,7 @@ const PasswordReset = () => { const [t] = useTranslation('reset'); const [error, setError] = useState(''); const [success, setSuccess] = useState(false); - const {accountAction} = useContext(ActionContext); + const {accountAction} = useActionContext(); const completePasswordReset = async (event: React.FormEvent) => { event.preventDefault(); try { diff --git a/src/script/page/ConversationJoin.test.tsx b/src/script/page/ConversationJoin.test.tsx index fda058ade1..0313b58a46 100644 --- a/src/script/page/ConversationJoin.test.tsx +++ b/src/script/page/ConversationJoin.test.tsx @@ -21,23 +21,27 @@ import '../util/test/mock/matchMediaMock'; import {ConversationJoin} from './ConversationJoin'; import TestPage from '../util/test/TestPage'; -import {ActionProvider, actionRoot} from '../module/action/'; +import {ActionProvider, useActionContext} from '../module/action/'; import {RecursivePartial} from '@wireapp/commons/lib/util/TypeUtil'; import {act} from 'react-dom/test-utils'; import {pathWithParams} from '@wireapp/commons/lib/util/UrlUtil'; import {Runtime} from '@wireapp/commons'; +import {AccountAction} from 'script/module/action/AccountAction'; jest.mock('script/util/SVGProvider', () => { return {logo: undefined}; }); class ConversationJoinPage extends TestPage { - constructor(root?: RecursivePartial) { - super(() => ( - - - - )); + constructor(root?: RecursivePartial) { + super(() => { + const actionContext = useActionContext(); + return ( + + + + ); + }); } getOpenApp = () => this.queryByTestId('do-conversation-join-app'); @@ -55,9 +59,7 @@ describe('ConversationJoin', () => { const path = pathWithParams('/conversation-join', {key, code}); window.history.pushState({}, 'Test page', path); - const conversationJoinPage = new ConversationJoinPage({ - accountAction: {validateConversationJoin: validateConversationJoinSpy}, - }); + const conversationJoinPage = new ConversationJoinPage({validateConversationJoin: validateConversationJoinSpy}); await act(async () => expect(validateConversationJoinSpy).toHaveBeenCalledWith(key, code)); expect(conversationJoinPage.getOpenApp()).toBeDefined(); @@ -72,9 +74,7 @@ describe('ConversationJoin', () => { window.history.pushState({}, 'Test page', path); const conversationJoinPage = new ConversationJoinPage({ - accountAction: { - validateConversationJoin: validateConversationJoinSpy, - }, + validateConversationJoin: validateConversationJoinSpy, }); await act(async () => expect(validateConversationJoinSpy).toHaveBeenCalled()); @@ -96,9 +96,7 @@ describe('ConversationJoin', () => { window.history.pushState({}, 'Test page', path); const conversationJoinPage = new ConversationJoinPage({ - accountAction: { - validateConversationJoin: validateConversationJoinSpy, - }, + validateConversationJoin: validateConversationJoinSpy, }); await act(async () => expect(validateConversationJoinSpy).toHaveBeenCalled()); @@ -118,9 +116,7 @@ describe('ConversationJoin', () => { window.history.pushState({}, 'Test page', path); const conversationJoinPage = new ConversationJoinPage({ - accountAction: { - validateConversationJoin: validateConversationJoinSpy, - }, + validateConversationJoin: validateConversationJoinSpy, }); await act(async () => expect(validateConversationJoinSpy).toHaveBeenCalled()); diff --git a/src/script/page/ConversationJoin.tsx b/src/script/page/ConversationJoin.tsx index 4d946715f3..e9929a137b 100644 --- a/src/script/page/ConversationJoin.tsx +++ b/src/script/page/ConversationJoin.tsx @@ -30,13 +30,13 @@ import { Text, useMatchMedia, } from '@wireapp/react-ui-kit'; -import {useContext, useEffect, useState} from 'react'; +import {useEffect, useState} from 'react'; import {useTranslation} from 'react-i18next'; import {useLocation} from 'react-router-dom'; import Document from 'script/component/Document'; import {OpenWireButtons, hasDisplayedButtons} from 'script/component/OpenWireButtons'; import {BRAND_NAME, IS_SELF_HOSTED} from 'script/Environment'; -import {ActionContext} from 'script/module/action'; +import {useActionContext} from 'script/module/action'; const QUERY_CODE_KEY = 'code'; const QUERY_KEY_KEY = 'key'; @@ -47,7 +47,7 @@ export const ConversationJoin = () => { const translationNamespaces = IS_SELF_HOSTED ? ['conversationJoinSelfHosted', 'conversationJoin'] : undefined; const [t] = useTranslation(['conversationJoin', 'conversationJoinSelfHosted']); const isMobile = useMatchMedia(QUERY[QueryKeys.TABLET_DOWN]); - const {accountAction} = useContext(ActionContext); + const {accountAction} = useActionContext(); const [isLoading, setIsLoading] = useState(false); const [error, setError] = useState(''); diff --git a/src/script/page/DeleteAccount.tsx b/src/script/page/DeleteAccount.tsx index 0e2e4004db..b255b81aca 100644 --- a/src/script/page/DeleteAccount.tsx +++ b/src/script/page/DeleteAccount.tsx @@ -17,12 +17,12 @@ * */ import {Button, COLOR, ContainerSM, ContainerXS, ContainerXXS, Form, H1, Text, TextLink} from '@wireapp/react-ui-kit'; -import React, {useContext, useState} from 'react'; +import React, {useState} from 'react'; import {useTranslation} from 'react-i18next'; import {useLocation} from 'react-router-dom'; import Document from 'script/component/Document'; import {ACCOUNT_DELETE_SURVEY_URL, BRAND_NAME} from 'script/Environment'; -import {ActionContext} from 'script/module/action'; +import {useActionContext} from 'script/module/action'; const QUERY_CODE_KEY = 'code'; const QUERY_KEY_KEY = 'key'; @@ -34,7 +34,7 @@ const DeleteAccount = () => { const key = params.get(QUERY_KEY_KEY); const [t] = useTranslation('delete'); - const {accountAction} = useContext(ActionContext); + const {accountAction} = useActionContext(); const [success, setSuccess] = useState(false); const [error, setError] = useState(''); const deleteAccount = async (event: React.FormEvent) => { diff --git a/src/script/page/PasswordForgot.tsx b/src/script/page/PasswordForgot.tsx index 5480970573..b3dc1bbc91 100644 --- a/src/script/page/PasswordForgot.tsx +++ b/src/script/page/PasswordForgot.tsx @@ -17,11 +17,11 @@ * */ import {Button, ButtonLink, ContainerXS, ErrorMessage, Form, H1, Input, Text} from '@wireapp/react-ui-kit'; -import React, {useContext, useRef, useState} from 'react'; +import React, {useRef, useState} from 'react'; import {useTranslation} from 'react-i18next'; import {WEBAPP_URL} from 'script/Environment'; import Document from 'script/component/Document'; -import {ActionContext} from 'script/module/action'; +import {useActionContext} from 'script/module/action'; import ValidationError from 'script/module/action/ValidationError'; const HTTP_STATUS_EMAIL_ALREADY_SENT = 409; @@ -34,7 +34,7 @@ const PasswordForgot = () => { const [success, setSuccess] = useState(false); const [t] = useTranslation('forgot'); - const {accountAction} = useContext(ActionContext); + const {accountAction} = useActionContext(); const initiatePasswordReset = async (event: React.FormEvent) => { event.preventDefault(); try { diff --git a/src/script/page/PasswordReset.tsx b/src/script/page/PasswordReset.tsx index a0a5d39dcb..8cee1fb24d 100644 --- a/src/script/page/PasswordReset.tsx +++ b/src/script/page/PasswordReset.tsx @@ -18,12 +18,12 @@ */ import {ValidationUtil} from '@wireapp/commons'; import {Button, COLOR, ContainerXS, Form, H1, Input, Text} from '@wireapp/react-ui-kit'; -import React, {useContext, useRef, useState} from 'react'; +import React, {useRef, useState} from 'react'; import {useTranslation} from 'react-i18next'; import {useLocation} from 'react-router-dom'; import Document from 'script/component/Document'; import {NEW_PASSWORD_MINIMUM_LENGTH} from 'script/Environment'; -import {ActionContext} from 'script/module/action'; +import {useActionContext} from 'script/module/action'; import ValidationError from 'script/module/action/ValidationError'; const HTTP_STATUS_INVALID_LINK = 400; @@ -45,7 +45,7 @@ const PasswordReset = () => { const [t] = useTranslation('reset'); const [error, setError] = useState(''); const [success, setSuccess] = useState(false); - const {accountAction} = useContext(ActionContext); + const {accountAction} = useActionContext(); const completePasswordReset = async (event: React.FormEvent) => { event.preventDefault(); try { diff --git a/src/script/page/UserProfile.test.tsx b/src/script/page/UserProfile.test.tsx index 89035ea3be..10b526a68a 100644 --- a/src/script/page/UserProfile.test.tsx +++ b/src/script/page/UserProfile.test.tsx @@ -21,8 +21,7 @@ import '../util/test/mock/matchMediaMock'; import {UserProfile} from './UserProfile'; import TestPage from '../util/test/TestPage'; -import {ActionProvider, actionRoot} from '../module/action'; -import {RecursivePartial} from '@wireapp/commons/lib/util/TypeUtil'; +import {ActionProvider} from '../module/action'; import {Runtime} from '@wireapp/commons'; import {pathWithParams} from '@wireapp/commons/lib/util/UrlUtil'; @@ -31,9 +30,9 @@ jest.mock('script/util/SVGProvider', () => { }); class UserProfilePage extends TestPage { - constructor(root?: RecursivePartial) { + constructor() { super(() => ( - + )); @@ -53,7 +52,7 @@ describe('UserProfile', () => { const path = pathWithParams('/user-profile'); window.history.pushState({}, 'Test page', path); - const conversationJoinPage = new UserProfilePage({}); + const conversationJoinPage = new UserProfilePage(); expect(conversationJoinPage.getOpenApp()).toBeDefined(); expect(conversationJoinPage.getOpenWebapp()).toBeNull(); @@ -70,7 +69,7 @@ describe('UserProfile', () => { const path = pathWithParams('/user-profile'); window.history.pushState({}, 'Test page', path); - const conversationJoinPage = new UserProfilePage({}); + const conversationJoinPage = new UserProfilePage(); expect(conversationJoinPage.getOpenApp()).toBeDefined(); expect(conversationJoinPage.getOpenWebapp()).toBeDefined(); @@ -85,7 +84,7 @@ describe('UserProfile', () => { const path = pathWithParams('/user-profile'); window.history.pushState({}, 'Test page', path); - const conversationJoinPage = new UserProfilePage({}); + const conversationJoinPage = new UserProfilePage(); expect(conversationJoinPage.getOpenApp()).toBeDefined(); expect(conversationJoinPage.getOpenWebapp()).toBeDefined(); diff --git a/src/script/page/VerifyBotAccount.tsx b/src/script/page/VerifyBotAccount.tsx index b69577112c..b4d76bbef7 100644 --- a/src/script/page/VerifyBotAccount.tsx +++ b/src/script/page/VerifyBotAccount.tsx @@ -18,14 +18,14 @@ */ import {Runtime} from '@wireapp/commons'; import {ContainerXS, FlexBox, H1, Loading, Text} from '@wireapp/react-ui-kit'; -import React, {useContext, useEffect, useState} from 'react'; +import React, {useEffect, useState} from 'react'; import {useTranslation} from 'react-i18next'; import {DirectDownloadButton} from 'script/component/DirectDownloadButton'; import Document from 'script/component/Document'; import {OpenWebappButton} from 'script/component/OpenWebappButton'; import {WebsiteDownloadButton} from 'script/component/WebsiteDownloadButton'; import {BRAND_NAME, REDIRECT_VERIFY_URL, WEBAPP_URL} from 'script/Environment'; -import {ActionContext} from 'script/module/action'; +import {useActionContext} from 'script/module/action'; const QUERY_CODE_KEY = 'code'; const QUERY_KEY_KEY = 'key'; @@ -38,7 +38,7 @@ const VerifyBotAccount = () => { const [t] = useTranslation('verify'); const [success, setSuccess] = useState(false); const [error, setError] = useState(''); - const {accountAction} = useContext(ActionContext); + const {accountAction} = useActionContext(); const redirectPhone = (Runtime.isAndroid() || Runtime.isIOS()) && REDIRECT_VERIFY_URL; const loginImmediately = !Runtime.isDesktopOS(); diff --git a/src/script/page/VerifyEmailAccount.tsx b/src/script/page/VerifyEmailAccount.tsx index 65f2175b68..0cac8490ff 100644 --- a/src/script/page/VerifyEmailAccount.tsx +++ b/src/script/page/VerifyEmailAccount.tsx @@ -18,7 +18,7 @@ */ import {Runtime} from '@wireapp/commons'; import {ContainerXS, FlexBox, H1, Loading, Text} from '@wireapp/react-ui-kit'; -import {useContext, useEffect, useState} from 'react'; +import {useEffect, useState} from 'react'; import {useTranslation} from 'react-i18next'; import {useLocation} from 'react-router-dom'; import {DirectDownloadButton} from 'script/component/DirectDownloadButton'; @@ -26,7 +26,7 @@ import Document from 'script/component/Document'; import {OpenWebappButton} from 'script/component/OpenWebappButton'; import {WebsiteDownloadButton} from 'script/component/WebsiteDownloadButton'; import {BRAND_NAME, REDIRECT_VERIFY_URL, WEBAPP_URL} from 'script/Environment'; -import {ActionContext} from 'script/module/action'; +import {useActionContext} from 'script/module/action'; const QUERY_CODE_KEY = 'code'; const QUERY_KEY_KEY = 'key'; @@ -40,7 +40,7 @@ const VerifyEmailAccount = () => { const [t] = useTranslation('verify'); const [success, setSuccess] = useState(false); const [error, setError] = useState(''); - const {accountAction} = useContext(ActionContext); + const {accountAction} = useActionContext(); const redirectPhone = (Runtime.isAndroid() || Runtime.isIOS()) && REDIRECT_VERIFY_URL; const loginImmediately = !Runtime.isDesktopOS(); useEffect(() => { diff --git a/src/script/page/migration/AcceptInvitation.tsx b/src/script/page/migration/AcceptInvitation.tsx new file mode 100644 index 0000000000..e01c15cd6c --- /dev/null +++ b/src/script/page/migration/AcceptInvitation.tsx @@ -0,0 +1,156 @@ +/* + * Wire + * Copyright (C) 2024 Wire Swiss GmbH + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see http://www.gnu.org/licenses/. + * + */ + +import { + Button, + ErrorMessage, + Form, + Input, + Link, + Logo, + QUERY, + QueryKeys, + Text, + useMatchMedia, +} from '@wireapp/react-ui-kit'; +import {MigrationLayout} from 'script/layout/MigrationLayout'; +import {useNavigate, useSearchParams} from 'react-router-dom'; +import {ROUTE} from 'script/route'; +import React, {FormEvent, useEffect, useState} from 'react'; +import {QUERY_KEY} from 'script/util/urlUtil'; +import {loginContainerCss, loginSubHeaderCss, headerCss, buttonCss, forgotPasswordCss} from './styles'; +import {getTeamInvitationCode, setTeamInvitationCode} from './utils'; +import {useTranslation} from 'react-i18next'; +import {LoginData} from '@wireapp/api-client/lib/auth'; +import {ClientType} from '@wireapp/api-client/lib/client'; +import {useActionContext} from 'script/module/action'; + +export const AcceptInvitation = () => { + const [searchParams] = useSearchParams(); + const {accountAction} = useActionContext(); + const [t] = useTranslation(['migration', 'login']); + const [password, setPassword] = useState(''); + const [email, setEmail] = useState(''); + const [loading, setLoading] = useState(false); + const [error, setError] = useState(''); + const navigate = useNavigate(); + const isTablet = useMatchMedia(QUERY[QueryKeys.TABLET_DOWN]); + const code = searchParams.get(QUERY_KEY.TEAM_CODE); + const cachedCode = getTeamInvitationCode(); + + useEffect(() => { + if (!code && !cachedCode) { + navigate(ROUTE.HOME); + } + + if (code) { + setTeamInvitationCode(code); + } + + setLoading(true); + accountAction + .init() + .then(() => { + navigate(ROUTE.TERMS_ACKNOWLEDGEMENT); + }) + .finally(() => setLoading(false)); + }, []); + + const handleLogin = async (event: FormEvent) => { + event.preventDefault(); + setError(''); + + const login: LoginData = { + clientType: ClientType.PERMANENT, + password, + }; + + if (email.indexOf('@') > 0) { + login.email = email; + } else { + login.handle = email; + } + + try { + await accountAction.login(login); + navigate(ROUTE.TERMS_ACKNOWLEDGEMENT); + } catch (error) { + setError(t('errorLogin')); + console.warn('Unable to login', error); + } + }; + + return ( + +
+ {isTablet && } + + {t('invitationPageHeader')} + {t('invitationPageSubHeader')} + +
+ ) => setEmail(event.target.value)} + placeholder={t('emailOrUsernamePlaceholder')} + required + title={t('invitationPagLoginLabel')} + value={email} + data-uie-name="enter-login-identifier" + /> + + ) => setPassword(event.target.value)} + pattern=".{1,1024}" + placeholder={t('password')} + required + title={t('passwordTitle')} + type="password" + value={password} + data-uie-name="enter-login-password" + /> +
+ + {t('forgotPassword')} + +
+ + +
+ + {error} +
+
+ ); +}; diff --git a/src/script/page/migration/ConfirmInvitation.tsx b/src/script/page/migration/ConfirmInvitation.tsx new file mode 100644 index 0000000000..4b35187e84 --- /dev/null +++ b/src/script/page/migration/ConfirmInvitation.tsx @@ -0,0 +1,109 @@ +/* + * Wire + * Copyright (C) 2024 Wire Swiss GmbH + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see http://www.gnu.org/licenses/. + * + */ + +import { + Button, + ErrorMessage, + Form, + Input, + Link, + Logo, + QUERY, + QueryKeys, + Text, + useMatchMedia, +} from '@wireapp/react-ui-kit'; +import {loginContainerCss, loginSubHeaderCss, headerCss, forgotPasswordCss, buttonCss} from './styles'; +import React, {useState} from 'react'; +import {ROUTE} from 'script/route'; +import {useNavigate} from 'react-router-dom'; +import {getTeamInvitationCode, removeTeamInvitationCode} from './utils'; +import {useTranslation} from 'react-i18next'; +import {useActionContext} from 'script/module/action'; + +export const ConfirmInvitation = () => { + const isTablet = useMatchMedia(QUERY[QueryKeys.TABLET_DOWN]); + const navigate = useNavigate(); + const [t] = useTranslation(['migration', 'login']); + const {teamAction} = useActionContext(); + + const [password, setPassword] = useState(''); + const [error, setError] = useState(''); + const [loading, setLoading] = useState(false); + const code = getTeamInvitationCode(); + + const handleSubmit = (event: any) => { + event.preventDefault(); + setLoading(true); + teamAction + .acceptInvitation({ + code, + password, + }) + .then(() => { + removeTeamInvitationCode(); + navigate(ROUTE.WELCOME); + }) + .catch(() => { + setError(t('wrongCredentialsError')); + }) + .finally(() => { + setLoading(false); + }); + }; + + return ( +
+ {isTablet && } + {t('confirmPageHeader')} + {t('confirmPageSubHeader')} +
+ ) => setPassword(event.target.value)} + pattern=".{1,1024}" + placeholder={t('passwordPlaceholder')} + required + title={t('passwordLabel')} + type="password" + value={password} + data-uie-name="enter-login-password" + /> +
+ + {t('forgotPassword')} + +
+ +
+ {error} +
+ ); +}; diff --git a/src/script/page/migration/OutlinedCheckIcon.tsx b/src/script/page/migration/OutlinedCheckIcon.tsx new file mode 100644 index 0000000000..03ca511b9d --- /dev/null +++ b/src/script/page/migration/OutlinedCheckIcon.tsx @@ -0,0 +1,31 @@ +/* + * Wire + * Copyright (C) 2024 Wire Swiss GmbH + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see http://www.gnu.org/licenses/. + * + */ + +export const OutlinedCheckIcon = () => { + return ( + + + + ); +}; diff --git a/src/script/page/migration/ShieldIcon.tsx b/src/script/page/migration/ShieldIcon.tsx new file mode 100644 index 0000000000..f967a0a9cc --- /dev/null +++ b/src/script/page/migration/ShieldIcon.tsx @@ -0,0 +1,33 @@ +/* + * Wire + * Copyright (C) 2024 Wire Swiss GmbH + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see http://www.gnu.org/licenses/. + * + */ + +export const ShieldIcon = () => { + return ( + + + + + + ); +}; diff --git a/src/script/page/migration/TermsAcknowledgement.tsx b/src/script/page/migration/TermsAcknowledgement.tsx new file mode 100644 index 0000000000..814c3faca3 --- /dev/null +++ b/src/script/page/migration/TermsAcknowledgement.tsx @@ -0,0 +1,155 @@ +/* + * Wire + * Copyright (C) 2024 Wire Swiss GmbH + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see http://www.gnu.org/licenses/. + * + */ + +import { + Bold, + Button, + Checkbox, + COLOR_V2, + Link, + Logo, + QUERY, + QueryKeys, + Text, + useMatchMedia, +} from '@wireapp/react-ui-kit'; +import { + termsContainerCss, + termsSubHeaderCss, + headerCss, + termsContentHeaderCss, + termsListCss, + termsListItemCss, + termsContentGrayBox, + termsContentGrayBoxContent, + termsContentBlueBox, + termsContentBlueBoxContent, + buttonCss, + termsCheckboxLabelCss, +} from './styles'; +import {ShieldIcon} from './ShieldIcon'; +import {OutlinedCheckIcon} from './OutlinedCheckIcon'; +import React, {useState} from 'react'; +import {useNavigate} from 'react-router-dom'; +import {EXTERNAL_ROUTE, ROUTE} from 'script/route'; +import {useTranslation} from 'react-i18next'; +import MarkupTranslation from 'script/component/MarkupTranslation'; + +export const TermsAcknowledgement = () => { + const navigate = useNavigate(); + const {t} = useTranslation('migration'); + const isTablet = useMatchMedia(QUERY[QueryKeys.TABLET_DOWN]); + const [isMigrationAccepted, setIsMigrationAccepted] = useState(false); + const [isTermOfUseAccepted, setIsTermOfUseAccepted] = useState(false); + + return ( +
+ {isTablet && ( +
+ +
+ )} + {t('termsPageHeader')} + {t('termsPageHeader')} +
+ {t('termsPageListHeader')} +
    +
  • + +
  • +
  • + +
  • +
  • + +
  • +
  • + +
  • +
  • + +
  • +
+
+
+
+ {t('termsPageAccountManagerHeader')} +
{t('termsPageAccountManagerText')}
+
+
+
+ {t('termsPageRecommendationsHeader')} +
+
+ +
+
{t('termsPageRecommendationItem1')}
+
+
+
+ +
+ + + {t('termsPageRecommendationItem2')} + +
+
+
+ ) => { + setIsMigrationAccepted(event.target.checked); + }} + id="do-accept-migration" + data-uie-name="do-accept-migration" + > + {t('termsPageMigrationTerms')} + + ) => { + setIsTermOfUseAccepted(event.target.checked); + }} + id="do-accept-terms" + data-uie-name="do-accept-terms" + > + + {t('termsPageTermsOfUse')}{' '} + + {t('termsPageTermsOfUseLink')} + + + +
+
+ +
+
+ ); +}; diff --git a/src/script/page/migration/Welcome.tsx b/src/script/page/migration/Welcome.tsx new file mode 100644 index 0000000000..6fe1bbb020 --- /dev/null +++ b/src/script/page/migration/Welcome.tsx @@ -0,0 +1,130 @@ +/* + * Wire + * Copyright (C) 2024 Wire Swiss GmbH + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see http://www.gnu.org/licenses/. + * + */ + +import { + ArrowIcon, + Button, + ButtonVariant, + COLOR, + FlexBox, + Line, + Loading, + Logo, + QUERY, + QueryKeys, + Text, + useMatchMedia, +} from '@wireapp/react-ui-kit'; +import {loginContainerCss, loginSubHeaderCss, headerCss} from './styles'; + +import {useNavigate} from 'react-router-dom'; +import {EXTERNAL_ROUTE, ROUTE} from 'script/route'; +import {useTranslation} from 'react-i18next'; +import {useEffect, useState} from 'react'; +import {useActionContext} from 'script/module/action'; +import {isOwner, MemberData} from '@wireapp/api-client/lib/team/member'; +import {TeamData} from '@wireapp/api-client/lib/team'; +import {secureOpen} from 'script/util/urlUtil'; +import MarkupTranslation from 'script/component/MarkupTranslation'; + +export const Welcome = () => { + const [t] = useTranslation(['migration']); + const {teamAction, selfAction} = useActionContext(); + const isTablet = useMatchMedia(QUERY[QueryKeys.TABLET_DOWN]); + const navigate = useNavigate(); + const isMobile = useMatchMedia(QUERY[QueryKeys.TABLET_DOWN]); + const [loading, setLoading] = useState(true); + const [team, setTeam] = useState(); + const [selfMember, setSelfMember] = useState(); + + const getData = async () => { + const self = await selfAction.getSelf(); + const member = await teamAction.getMember(self.team, self.id); + const team = await teamAction.getTeam(self.team); + + return {member, team}; + }; + + useEffect(() => { + getData() + .then(res => { + setSelfMember(res.member); + setTeam(res.team); + }) + .catch(() => { + navigate(ROUTE.HOME); + }) + .finally(() => { + setLoading(false); + }); + }, []); + + if (loading) { + return ( +
+ +
+ ); + } + + return ( +
+ {isTablet && } + {t('welcomePageHeader')} + + + + + {isOwner(selfMember.permissions) && ( + <> + + + + {t('welcomePageOr')} + + + + + + )} +
+ ); +}; diff --git a/src/script/page/migration/styles.ts b/src/script/page/migration/styles.ts new file mode 100644 index 0000000000..a7da3efff7 --- /dev/null +++ b/src/script/page/migration/styles.ts @@ -0,0 +1,164 @@ +/* + * Wire + * Copyright (C) 2024 Wire Swiss GmbH + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see http://www.gnu.org/licenses/. + * + */ + +import {CSSObject} from '@emotion/react'; +import {COLOR_V2, QUERY, QueryKeys} from '@wireapp/react-ui-kit'; + +export const headerCss: CSSObject = { + color: COLOR_V2.GRAY_90, + fontSize: '3rem', + display: 'block', + textAlign: 'center', + [QUERY[QueryKeys.TABLET_DOWN]]: { + fontSize: '2rem', + marginTop: '1.5rem', + }, +}; + +export const loginSubHeaderCss: CSSObject = { + color: COLOR_V2.GRAY_70, + lineHeight: '2.5rem', + margin: '5rem 0', + fontSize: '1.75rem', + display: 'block', + textAlign: 'center', + + [QUERY[QueryKeys.TABLET_DOWN]]: { + lineHeight: '1.75rem', + margin: '1rem 0 5rem 0', + fontSize: '1rem', + }, +}; + +export const loginContainerCss: CSSObject = { + maxWidth: '32rem', + textAlign: 'center', + padding: '1.5rem', + margin: '4rem auto', + [QUERY[QueryKeys.TABLET_DOWN]]: { + marginTop: '1.5rem', + margin: '2rem auto', + }, +}; + +export const termsContainerCss: CSSObject = { + margin: '0 auto', + textAlign: 'left', + padding: '1.5rem 4rem', + maxWidth: '55rem', + [QUERY[QueryKeys.TABLET_DOWN]]: { + marginTop: '1.5rem', + margin: '0 auto', + padding: '1.5rem 0', + }, +}; + +export const termsSubHeaderCss: CSSObject = { + ...loginSubHeaderCss, + margin: '1.8rem 0', + [QUERY[QueryKeys.TABLET_DOWN]]: { + ...(loginSubHeaderCss[QUERY[QueryKeys.TABLET_DOWN]] as CSSObject), + margin: '1rem 2rem', + fontSize: '1.25rem', + }, +}; + +export const termsContentHeaderCss: CSSObject = { + fontSize: '1.25rem', + marginBottom: '1rem', + display: 'block', + [QUERY[QueryKeys.TABLET_DOWN]]: { + fontSize: '1rem', + }, +}; + +export const termsListCss: CSSObject = { + display: 'flex', + flexDirection: 'column', + gap: '1rem', + paddingLeft: '2rem', + [QUERY[QueryKeys.TABLET_DOWN]]: { + paddingLeft: '1.5rem', + }, +}; + +export const termsListItemCss: CSSObject = { + fontSize: '1.25rem', + lineHeight: '1.75rem', + color: COLOR_V2.GRAY_70, + [QUERY[QueryKeys.TABLET_DOWN]]: { + fontSize: '1rem', + }, +}; + +export const termsContentGrayBox: CSSObject = { + background: COLOR_V2.GRAY_20, + padding: '1rem', + borderRadius: '1rem', + margin: '0 2rem', +}; +export const termsContentGrayBoxContent: CSSObject = { + fontSize: '1.25rem', + lineHeight: '1.75rem', + color: COLOR_V2.GRAY_90, + [QUERY[QueryKeys.TABLET_DOWN]]: { + fontSize: '1rem', + }, +}; + +export const termsContentBlueBox: CSSObject = { + background: COLOR_V2.BLUE_LIGHT_50, + padding: '1rem', + borderRadius: '1rem', + margin: '1rem 0', + display: 'flex', +}; + +export const termsContentBlueBoxContent: CSSObject = { + marginLeft: '1.5rem', + fontSize: '1.25rem', + lineHeight: '1.75rem', + color: COLOR_V2.GRAY_80, + textTransform: 'none', + [QUERY[QueryKeys.TABLET_DOWN]]: { + fontSize: '1rem', + }, +}; + +export const forgotPasswordCss: CSSObject = { + textAlign: 'right', + marginTop: '-0.25rem', + marginBottom: '0.75rem', + '& a:link': { + color: COLOR_V2.GRAY_60, + }, +}; + +export const buttonCss: CSSObject = { + width: '100%', + ':disabled': { + background: COLOR_V2.GRAY_60, + }, +}; + +export const termsCheckboxLabelCss: CSSObject = { + color: '#34383B', + fontSize: '0.875rem', + fontWeight: 'normal', +}; diff --git a/src/script/page/migration/utils.ts b/src/script/page/migration/utils.ts new file mode 100644 index 0000000000..57a7195768 --- /dev/null +++ b/src/script/page/migration/utils.ts @@ -0,0 +1,45 @@ +/* + * Wire + * Copyright (C) 2024 Wire Swiss GmbH + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see http://www.gnu.org/licenses/. + * + */ + +const teamInvitationCodeKey = 'team_invitation_code'; + +export const setTeamInvitationCode = (code: string) => { + try { + sessionStorage.setItem(teamInvitationCodeKey, code); + } catch { + console.warn('Session storage not supported'); + } +}; + +export const getTeamInvitationCode = () => { + try { + return sessionStorage.getItem(teamInvitationCodeKey); + } catch { + console.warn('Session storage not supported'); + return null; + } +}; + +export const removeTeamInvitationCode = () => { + try { + sessionStorage.removeItem(teamInvitationCodeKey); + } catch { + console.warn('Session storage not supported'); + } +}; diff --git a/src/script/route.ts b/src/script/route.ts index 337abcce30..767a501329 100644 --- a/src/script/route.ts +++ b/src/script/route.ts @@ -21,6 +21,10 @@ import * as Environment from 'script/Environment'; const EXTERNAL_ROUTE = { ACCOUNT_DELETE_SURVEY_URL: Environment.ACCOUNT_DELETE_SURVEY_URL, + APP_WIRE: Environment.WEBAPP_URL, + TEAM_SETTINGS: Environment.TEAMS_URL, + TERMS_OF_USE_TEAMS: Environment.URL_TERMS_OF_USE_TEAMS, + SUPPORT_BACKUP_HISTORY: Environment.URL_SUPPORT_BACKUP_HISTORY, }; const ROUTE = { @@ -34,6 +38,10 @@ const ROUTE = { USER_PROFILE: '/user-profile', VERIFY_ACCOUNT_BOT: '/verify/bot', VERIFY_ACCOUNT_EMAIL: '/verify', + ACCEPT_INVITATION: '/accept-invitation', + TERMS_ACKNOWLEDGEMENT: '/migration/terms-acknowledgement', + CONFIRM_INVITATION: '/migration/confirm-invitation', + WELCOME: '/migration/welcome', }; export {ROUTE, EXTERNAL_ROUTE}; diff --git a/src/script/util/urlUtil.ts b/src/script/util/urlUtil.ts index 931e226685..6941f8fbaf 100644 --- a/src/script/util/urlUtil.ts +++ b/src/script/util/urlUtil.ts @@ -31,6 +31,7 @@ export enum QUERY_KEY { PWA_AWARE = 'pwa_aware', TRACKING = 'tracking', LANG = 'hl', + TEAM_CODE = 'team_code', } const FORWARDED_QUERY_KEYS = [QUERY_KEY.LOCALE, QUERY_KEY.TRACKING]; @@ -63,4 +64,11 @@ function hasURLParameter(parameterName: QUERY_KEY) { .includes(parameterName); } -export {FORWARDED_QUERY_KEYS, pathWithParams, getURLParameter, hasURLParameter}; +function secureOpen(url: string) { + const newWindow = window.open(); + newWindow.opener = null; + newWindow.location.assign(url); + return newWindow; +} + +export {FORWARDED_QUERY_KEYS, pathWithParams, getURLParameter, hasURLParameter, secureOpen}; diff --git a/yarn.lock b/yarn.lock index 84790aa925..5bc5245ea1 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4128,9 +4128,9 @@ __metadata: languageName: node linkType: hard -"@wireapp/api-client@npm:27.6.2": - version: 27.6.2 - resolution: "@wireapp/api-client@npm:27.6.2" +"@wireapp/api-client@npm:27.7.0": + version: 27.7.0 + resolution: "@wireapp/api-client@npm:27.7.0" dependencies: "@wireapp/commons": ^5.2.13 "@wireapp/priority-queue": ^2.1.11 @@ -4145,7 +4145,7 @@ __metadata: tough-cookie: 4.1.4 ws: 8.18.0 zod: 3.23.8 - checksum: 57434ad57e4b7517d2228964ac40ea38f545d3557e2503e209e6df5fcdb6c6b7bf696bf49470129739966a58668c5cad24ccfa3c515d8136ae9739be3ea83d8f + checksum: 0e484f46694fcb058ea16a813f50d430cc1993dc762c0c8867d5c7a6b214b70f20427f2529956dc39e34f9270731fc029fd028a0bdaa726ef9985de14e182988 languageName: node linkType: hard @@ -14176,7 +14176,7 @@ __metadata: "@types/webpack-env": 1.18.5 "@typescript-eslint/eslint-plugin": 8.10.0 "@typescript-eslint/parser": 7.17.0 - "@wireapp/api-client": 27.6.2 + "@wireapp/api-client": 27.7.0 "@wireapp/commons": 5.2.13 "@wireapp/copy-config": 2.2.10 "@wireapp/eslint-config": 1.4.0