diff --git a/src/i18n/en-US.json b/src/i18n/en-US.json index 169f897c200..4d99fb577ae 100644 --- a/src/i18n/en-US.json +++ b/src/i18n/en-US.json @@ -806,7 +806,7 @@ "guestRoomToggleName": "Allow Guests", "historyInfo.learnMore": "Learn more", "historyInfo.noHistoryHeadline": "It’s the first time you’re using {brandName} on this device.", - "historyInfo.noHistoryInfo": "For privacy reasons, {newline}your conversation history will not appear here.", + "historyInfo.noHistoryInfo": "For privacy reasons, your history will not appear here.", "historyInfo.ok": "OK", "index.createAccount": "Create account", "index.createAccountForOrganizations": "Wire for Free", @@ -819,7 +819,8 @@ "index.login": "Log in", "index.loginInfo": "Already have an account?", "index.ssoLogin": "Log in with SSO", - "index.welcome": "Welcome to {brandName}", + "index.welcome": "Welcome to {brandName}!", + "index.or": "or", "initDecryption": "Decrypting messages", "initEvents": "Loading messages", "initProgress": " — {number1} of {number2}", @@ -1463,7 +1464,6 @@ "ssoLogin.subhead": "Enter the company SSO access code.", "ssoLogin.subheadCode": "Please enter your SSO code", "ssoLogin.subheadCodeOrEmail": "Please enter your email or SSO code.", - "ssoLogin.subheadEmailEnvironmentSwitchWarning": "If your email matches an enterprise installation of {brandName}, this app will connect to that server.", "startedAudioCallingAlert": "You are calling {conversationName}.", "startedGroupCallingAlert": "You started a conference call with {conversationName}.", "startedVideoCallingAlert": "You are calling {conversationName}, you camera is {cameraStatus}.", @@ -1664,5 +1664,21 @@ "wireLinux": "{brandName} for Linux", "wireMacos": "{brandName} for macOS", "wireWindows": "{brandName} for Windows", - "wire_for_web": "{brandName} for Web" -} + "wire_for_web": "{brandName} for Web", + "layoutSidebarHeader": "Collaborate without Compromise", + "layoutSidebarContent": "Connect, message, and share files with ease, protected by the industry’s most secure end-to-end encryption", + "layoutSidebarLink": "Learn more", + "redirectHeader": "Connect to your custom backend?", + "redirectSubHeader": "If you continue, we will redirect you to your team’s customized backend {backendName} to log in.", + "redirectBackendName": "Backend name:", + "redirectBackendURL": "Backend URL:", + "redirectBackendWSURL": "Backend WSURL:", + "redirectBlacklistURL": "Blacklist URL:", + "redirectTeamsURL": "Teams URL:", + "redirectAccountURL": "Accounts URL:", + "redirectWebsiteURL": "Website URL:", + "redirectHideDetails": "Hide details", + "redirectShowDetails": "Show details", + "redirectConnect": "Connect", + "redirectCancel": "Cancel" +} \ No newline at end of file diff --git a/src/script/auth/assets/WavesPattern.tsx b/src/script/auth/assets/WavesPattern.tsx new file mode 100644 index 00000000000..32c10e0a0bd --- /dev/null +++ b/src/script/auth/assets/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/auth/component/Layout.tsx b/src/script/auth/component/Layout.tsx new file mode 100644 index 00000000000..827a0de067a --- /dev/null +++ b/src/script/auth/component/Layout.tsx @@ -0,0 +1,72 @@ +/* + * 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 {ReactNode} from 'react'; + +import {CSSObject} from '@emotion/react'; + +import {Bold, COLOR_V2, FlexBox, Link, Logo, QUERY, QueryKeys, Text, useMatchMedia} from '@wireapp/react-ui-kit'; + +import {t} from 'Util/LocalizerUtil'; + +import {Config} from '../../Config'; +import {WavesPattern} from '../assets/WavesPattern'; + +export const Layout = ({children}: {children: ReactNode}) => { + const isTablet = useMatchMedia(QUERY[QueryKeys.TABLET_DOWN]); + + return ( + + {!isTablet && ( +
+ +
+ + {t('layoutSidebarHeader')} + +
+
+ {t('layoutSidebarContent')} +
+ + {t('layoutSidebarLink')} + +
+
+ +
+ )} +
{children}
+
+ ); +}; + +const leftSectionCss: CSSObject = { + background: 'black', + margin: 0, + height: '100vh', + maxWidth: '26rem', + padding: '6rem 3.75rem', + position: 'relative', + minHeight: '42rem', +}; + +const whiteFontCss: CSSObject = { + color: 'white', +}; diff --git a/src/script/auth/component/LoginForm.tsx b/src/script/auth/component/LoginForm.tsx index 2bb5fd10dbd..246368d0988 100644 --- a/src/script/auth/component/LoginForm.tsx +++ b/src/script/auth/component/LoginForm.tsx @@ -20,6 +20,7 @@ import React, {useRef, useState} from 'react'; import {LoginData} from '@wireapp/api-client/lib/auth'; +import {useSearchParams} from 'react-router-dom'; import {Button, Input, Loading} from '@wireapp/react-ui-kit'; @@ -27,6 +28,7 @@ import {t} from 'Util/LocalizerUtil'; import {isValidEmail, isValidUsername} from 'Util/ValidationUtil'; import {ValidationError} from '../module/action/ValidationError'; +import {QUERY_KEY} from '../route'; interface LoginFormProps { isFetching: boolean; @@ -36,11 +38,13 @@ interface LoginFormProps { const LoginForm = ({isFetching, onSubmit}: LoginFormProps) => { const emailInput = useRef(null); const passwordInput = useRef(null); + const [params] = useSearchParams(); + const defaultEmail = params.get(QUERY_KEY.EMAIL); const [validEmailInput, setValidEmailInput] = useState(true); const [validPasswordInput, setValidPasswordInput] = useState(true); - const [email, setEmail] = useState(''); + const [email, setEmail] = useState(decodeURIComponent(defaultEmail || '')); const [password, setPassword] = useState(''); const handleSubmit = (event: React.FormEvent): void => { @@ -97,6 +101,7 @@ const LoginForm = ({isFetching, onSubmit}: LoginFormProps) => { return (
) => { diff --git a/src/script/auth/page/ClientManager.tsx b/src/script/auth/page/ClientManager.tsx index d281020a898..d29e75f7e28 100644 --- a/src/script/auth/page/ClientManager.tsx +++ b/src/script/auth/page/ClientManager.tsx @@ -23,7 +23,7 @@ import {connect} from 'react-redux'; import {AnyAction, Dispatch} from 'redux'; import {UrlUtil, StringUtil, Runtime} from '@wireapp/commons'; -import {Button, ButtonVariant, ContainerXS, H1, Muted, QUERY, useMatchMedia, useTimeout} from '@wireapp/react-ui-kit'; +import {Button, ButtonVariant, ContainerXS, Muted, QUERY, Text, useMatchMedia, useTimeout} from '@wireapp/react-ui-kit'; import {t} from 'Util/LocalizerUtil'; @@ -70,7 +70,7 @@ const ClientManagerComponent = ({doGetAllClients, doLogout}: Props & ConnectedPr }; return ( - + -

+ {t('clientManager.headline')} -

+ {t('clientManager.logout')} diff --git a/src/script/auth/page/CreatePersonalAccount.tsx b/src/script/auth/page/CreatePersonalAccount.tsx index 201dd506a17..2d3b52ebd2b 100644 --- a/src/script/auth/page/CreatePersonalAccount.tsx +++ b/src/script/auth/page/CreatePersonalAccount.tsx @@ -80,7 +80,7 @@ const CreatePersonalAccountComponent = ({ ); return ( - +
{backArrow}
diff --git a/src/script/auth/page/CustomBackend.tsx b/src/script/auth/page/CustomBackend.tsx new file mode 100644 index 00000000000..ea2a3b53b72 --- /dev/null +++ b/src/script/auth/page/CustomBackend.tsx @@ -0,0 +1,147 @@ +/* + * 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 {useEffect, useState} from 'react'; + +import {BackendConfig} from '@wireapp/api-client/lib/account/BackendConfig'; +import {useNavigate, useSearchParams} from 'react-router-dom'; +import {container} from 'tsyringe'; + +import {Button, ButtonVariant, Container, Muted, Text} from '@wireapp/react-ui-kit'; + +import {APIClient} from 'src/script/service/APIClientSingleton'; +import {t} from 'Util/LocalizerUtil'; + +import {Page} from './Page'; + +import {QUERY_KEY, ROUTE} from '../route'; +import {getSearchParams} from '../util/urlUtil'; + +export function CustomBackend() { + const [searchParams] = useSearchParams(); + const url = searchParams.get(QUERY_KEY.CONFIG_URL); + const navigate = useNavigate(); + const [config, setConfig] = useState(); + const [isDetailVisible, setIsDetailVisible] = useState(false); + + if (!url) { + navigate(ROUTE.INDEX); + } + + const apiClient = container.resolve(APIClient); + + useEffect(() => { + if (url) { + apiClient.api.account + .getBackendConfig(url) + .then(res => { + setConfig(res); + }) + .catch(() => { + navigate(ROUTE.INDEX); + }); + } + }, [apiClient.api.account, navigate, url]); + + const details = [ + { + label: t('redirectBackendName'), + text: config?.backendName, + }, + { + label: t('redirectBackendURL'), + text: config?.backendURL, + }, + { + label: t('redirectBackendWSURL'), + text: config?.backendWSURL, + }, + { + label: t('redirectBlacklistURL'), + text: config?.blacklistURL, + }, + { + label: t('redirectTeamsURL'), + text: config?.teamsURL, + }, + { + label: t('redirectAccountURL'), + text: config?.accountURL, + }, + { + label: t('redirectWebsiteURL'), + text: config?.websiteURL, + }, + ]; + + const toggleDetails = () => { + setIsDetailVisible(visibility => !visibility); + }; + + const onConnect = () => { + if (config?.webAppURL) { + window.location.assign( + `/auth?${getSearchParams({[QUERY_KEY.DESTINATION_URL]: encodeURIComponent(`https://local.zinfra.io:8081/auth/#/login?email=${searchParams.get(QUERY_KEY.EMAIL)}`)})}#${ + ROUTE.CUSTOM_ENV_REDIRECT + }`, + ); + } + }; + + return ( + + + + {t('redirectHeader')} + + + {t('redirectSubHeader', {backendName: config?.backendName || ''})} + + {isDetailVisible && ( +
+ {details.map(({label, text}) => ( +
+ {label} +
+ {text} +
+ ))} +
+ )} + + {isDetailVisible ? t('redirectHideDetails') : t('redirectShowDetails')} + +
+ + +
+
+
+ ); +} diff --git a/src/script/auth/page/CustomEnvironmentRedirect.tsx b/src/script/auth/page/CustomEnvironmentRedirect.tsx index 91b4e4114f7..5899e6e5830 100644 --- a/src/script/auth/page/CustomEnvironmentRedirect.tsx +++ b/src/script/auth/page/CustomEnvironmentRedirect.tsx @@ -63,13 +63,9 @@ const CustomEnvironmentRedirectComponent = ({doNavigate, doSendNavigationEvent}: }, [destinationUrl]); return ( - + - + -
+ + )} )} @@ -530,4 +568,25 @@ const mapDispatchToProps = (dispatch: Dispatch) => const Login = connect(mapStateToProps, mapDispatchToProps)(LoginComponent); +export const separator: CSSObject = { + display: 'flex', + alignItems: 'center', + margin: '40px 0px', + + '> span': { + fontSize: '0.75rem', + fontWeight: '500', + lineHeight: '14px', + paddingInline: '25px', + textTransform: 'uppercase', + }, + + '&::before, &::after': { + content: '" "', + height: '1px', + backgroundColor: '#DCE0E3', + width: '100%', + }, +}; + export {Login}; diff --git a/src/script/auth/page/Page.tsx b/src/script/auth/page/Page.tsx index d98d5af8d17..bdec1174ed9 100644 --- a/src/script/auth/page/Page.tsx +++ b/src/script/auth/page/Page.tsx @@ -23,6 +23,7 @@ import {TeamData} from '@wireapp/api-client/lib/team'; import {connect} from 'react-redux'; import {Navigate} from 'react-router-dom'; +import {Layout} from '../component/Layout'; import {RootState} from '../module/reducer'; import {RegistrationDataState} from '../module/reducer/authReducer'; import * as AuthSelector from '../module/selector/AuthSelector'; @@ -32,6 +33,7 @@ interface Props extends React.HTMLProps { hasAccountData?: boolean; hasTeamData?: boolean; isAuthenticated?: boolean; + withSideBar?: boolean; } const hasInvalidAccountData = (account: RegistrationDataState) => !account.name || !account.email || !account.password; @@ -52,6 +54,7 @@ const PageComponent = ({ isStateAuthenticated, account, children, + withSideBar, }: Props & ConnectedProps) => { if ( (hasAccountData && hasInvalidAccountData(account) && !isStateAuthenticated) || @@ -60,6 +63,11 @@ const PageComponent = ({ ) { return ; } + + if (withSideBar) { + return {children} ; + } + return <>{children}; }; diff --git a/src/script/auth/page/Root.tsx b/src/script/auth/page/Root.tsx index b717400a8bd..8d815891964 100644 --- a/src/script/auth/page/Root.tsx +++ b/src/script/auth/page/Root.tsx @@ -34,6 +34,7 @@ import {ConversationJoin} from './ConversationJoin'; import {ConversationJoinInvalid} from './ConversationJoinInvalid'; import {CreateAccount} from './CreateAccount'; import {CreatePersonalAccount} from './CreatePersonalAccount'; +import {CustomBackend} from './CustomBackend'; import {CustomEnvironmentRedirect} from './CustomEnvironmentRedirect'; import {HistoryInfo} from './HistoryInfo'; import {Index} from './Index'; @@ -138,7 +139,10 @@ const RootComponent: FC = ({ const brandName = Config.getConfig().BRAND_NAME; return ( - + {isFetchingSSOSettings ? ( @@ -163,11 +167,12 @@ const RootComponent: FC = ({ } /> } /> } /> + } /> - + } /> diff --git a/src/script/auth/page/SetAccountType.tsx b/src/script/auth/page/SetAccountType.tsx index 79566abb87e..51b8fce3905 100644 --- a/src/script/auth/page/SetAccountType.tsx +++ b/src/script/auth/page/SetAccountType.tsx @@ -33,8 +33,11 @@ import { Link, Logo, ProfileIcon, + QUERY, + QueryKeys, TeamIcon, Text, + useMatchMedia, } from '@wireapp/react-ui-kit'; import {t} from 'Util/LocalizerUtil'; @@ -50,6 +53,7 @@ type Props = React.HTMLProps; const SetAccountType = ({}: Props) => { const isMacOsWrapper = Runtime.isDesktopApp() && Runtime.isMacOS(); + const isTablet = useMatchMedia(QUERY[QueryKeys.TABLET_DOWN]); const backArrow = ( @@ -68,7 +72,7 @@ const SetAccountType = ({}: Props) => { }; return ( - + {(Config.getConfig().FEATURE.ENABLE_DOMAIN_DISCOVERY || Config.getConfig().FEATURE.ENABLE_SSO || Config.getConfig().FEATURE.ENABLE_ACCOUNT_REGISTRATION) && ( @@ -90,9 +94,7 @@ const SetAccountType = ({}: Props) => { - - - + {isTablet && } diff --git a/src/script/auth/page/SingleSignOn.tsx b/src/script/auth/page/SingleSignOn.tsx index e6f3bce3e5d..8de0c7d419a 100644 --- a/src/script/auth/page/SingleSignOn.tsx +++ b/src/script/auth/page/SingleSignOn.tsx @@ -27,22 +27,24 @@ import {useParams} from 'react-router-dom'; import {AnyAction, Dispatch} from 'redux'; import { - ArrowIcon, COLOR, Column, Columns, Container, ContainerXS, - H1, IsMobile, Link, Logo, Muted, Overlay, + QUERY, + QueryKeys, Text, + useMatchMedia, } from '@wireapp/react-ui-kit'; import {WebAppEvents} from '@wireapp/webapp-events'; +import {LogoFullIcon} from 'Components/Icon'; import {calculateChildWindowPosition} from 'Util/DOM/caculateChildWindowPosition'; import {t} from 'Util/LocalizerUtil'; import {getLogger} from 'Util/Logger'; @@ -52,10 +54,8 @@ import {SingleSignOnForm} from './SingleSignOnForm'; import {Config} from '../../Config'; import {AppAlreadyOpen} from '../component/AppAlreadyOpen'; -import {RouterLink} from '../component/RouterLink'; import {RootState, bindActionCreators} from '../module/reducer'; import * as AuthSelector from '../module/selector/AuthSelector'; -import {ROUTE} from '../route'; type Props = React.HTMLAttributes; @@ -64,6 +64,7 @@ const logger = getLogger('SingleSignOn'); const SingleSignOnComponent = ({hasDefaultSSOCode}: Props & ConnectedProps & DispatchProps) => { const ssoWindowRef = useRef(); const params = useParams<{code?: string}>(); + const isTablet = useMatchMedia(QUERY[QueryKeys.TABLET_DOWN]); const [isOverlayOpen, setIsOverlayOpen] = useState(false); const handleSSOWindow = (code: string): Promise => { @@ -182,14 +183,8 @@ const SingleSignOnComponent = ({hasDefaultSSOCode}: Props & ConnectedProps & Dis ssoWindowRef.current?.focus(); }; - const backArrow = ( - - - - ); - return ( - + {isOverlayOpen && ( @@ -222,18 +217,21 @@ const SingleSignOnComponent = ({hasDefaultSSOCode}: Props & ConnectedProps & Dis )} - {!hasDefaultSSOCode && ( - -
{backArrow}
-
- )} - + + + {isTablet && ( +