diff options
author | Jeremy Davis <jeremy.davis@sonarsource.com> | 2024-01-05 10:54:29 +0100 |
---|---|---|
committer | sonartech <sonartech@sonarsource.com> | 2024-01-05 20:02:36 +0000 |
commit | e0e25569b61b8f8f3c4eb2cfdfae89be7a7e86d2 (patch) | |
tree | 88ef838800877fc3a96c0136dd0fba37a588faa0 /server | |
parent | 4cdadd222ecfc5f09df38d468281c8e5b6ed58b0 (diff) | |
download | sonarqube-e0e25569b61b8f8f3c4eb2cfdfae89be7a7e86d2.tar.gz sonarqube-e0e25569b61b8f8f3c4eb2cfdfae89be7a7e86d2.zip |
SONAR-21396 Migrate session pages to the new UI
Diffstat (limited to 'server')
20 files changed, 170 insertions, 294 deletions
diff --git a/server/sonar-auth-bitbucket/src/main/java/org/sonar/auth/bitbucket/BitbucketIdentityProvider.java b/server/sonar-auth-bitbucket/src/main/java/org/sonar/auth/bitbucket/BitbucketIdentityProvider.java index 3aee3299071..0cc02380c11 100755 --- a/server/sonar-auth-bitbucket/src/main/java/org/sonar/auth/bitbucket/BitbucketIdentityProvider.java +++ b/server/sonar-auth-bitbucket/src/main/java/org/sonar/auth/bitbucket/BitbucketIdentityProvider.java @@ -78,7 +78,7 @@ public class BitbucketIdentityProvider implements OAuth2IdentityProvider { @Override public Display getDisplay() { return Display.builder() - .setIconPath("/images/alm/bitbucket-white.svg") + .setIconPath("/images/alm/bitbucket.svg") .setBackgroundColor("#0052cc") .build(); } diff --git a/server/sonar-auth-bitbucket/src/test/java/org/sonar/auth/bitbucket/BitbucketIdentityProviderTest.java b/server/sonar-auth-bitbucket/src/test/java/org/sonar/auth/bitbucket/BitbucketIdentityProviderTest.java index 380dee89e9f..5588c76b8c3 100755 --- a/server/sonar-auth-bitbucket/src/test/java/org/sonar/auth/bitbucket/BitbucketIdentityProviderTest.java +++ b/server/sonar-auth-bitbucket/src/test/java/org/sonar/auth/bitbucket/BitbucketIdentityProviderTest.java @@ -42,7 +42,7 @@ public class BitbucketIdentityProviderTest { public void check_fields() { assertThat(underTest.getKey()).isEqualTo("bitbucket"); assertThat(underTest.getName()).isEqualTo("Bitbucket"); - assertThat(underTest.getDisplay().getIconPath()).isEqualTo("/images/alm/bitbucket-white.svg"); + assertThat(underTest.getDisplay().getIconPath()).isEqualTo("/images/alm/bitbucket.svg"); assertThat(underTest.getDisplay().getBackgroundColor()).isEqualTo("#0052cc"); } diff --git a/server/sonar-auth-github/src/main/java/org/sonar/auth/github/GitHubIdentityProvider.java b/server/sonar-auth-github/src/main/java/org/sonar/auth/github/GitHubIdentityProvider.java index bf671a80bfe..37135fe984f 100644 --- a/server/sonar-auth-github/src/main/java/org/sonar/auth/github/GitHubIdentityProvider.java +++ b/server/sonar-auth-github/src/main/java/org/sonar/auth/github/GitHubIdentityProvider.java @@ -62,7 +62,7 @@ public class GitHubIdentityProvider implements OAuth2IdentityProvider { @Override public Display getDisplay() { return Display.builder() - .setIconPath("/images/alm/github-white.svg") + .setIconPath("/images/alm/github.svg") .setBackgroundColor("#444444") .build(); } diff --git a/server/sonar-auth-github/src/test/java/org/sonar/auth/github/GitHubIdentityProviderTest.java b/server/sonar-auth-github/src/test/java/org/sonar/auth/github/GitHubIdentityProviderTest.java index b4495dafc4b..910f59161fe 100644 --- a/server/sonar-auth-github/src/test/java/org/sonar/auth/github/GitHubIdentityProviderTest.java +++ b/server/sonar-auth-github/src/test/java/org/sonar/auth/github/GitHubIdentityProviderTest.java @@ -46,7 +46,7 @@ public class GitHubIdentityProviderTest { public void check_fields() { assertThat(underTest.getKey()).isEqualTo("github"); assertThat(underTest.getName()).isEqualTo("GitHub"); - assertThat(underTest.getDisplay().getIconPath()).isEqualTo("/images/alm/github-white.svg"); + assertThat(underTest.getDisplay().getIconPath()).isEqualTo("/images/alm/github.svg"); assertThat(underTest.getDisplay().getBackgroundColor()).isEqualTo("#444444"); } diff --git a/server/sonar-web/design-system/src/components/buttons/ThirdPartyButton.tsx b/server/sonar-web/design-system/src/components/buttons/ThirdPartyButton.tsx index 50c83b8ff82..4f85fb6f50e 100644 --- a/server/sonar-web/design-system/src/components/buttons/ThirdPartyButton.tsx +++ b/server/sonar-web/design-system/src/components/buttons/ThirdPartyButton.tsx @@ -28,11 +28,16 @@ interface ThirdPartyProps extends Omit<ButtonProps, 'Icon'> { name: string; } -export function ThirdPartyButton({ children, iconPath, name, ...buttonProps }: ThirdPartyProps) { +export function ThirdPartyButton({ + children, + iconPath, + name, + ...buttonProps +}: Readonly<ThirdPartyProps>) { const size = 16; return ( <ThirdPartyButtonStyled {...buttonProps}> - <img alt={name} className="sw-mr-1" height={size} src={iconPath} width={size} /> + <img alt={name} className="sw-mr-2" height={size} src={iconPath} width={size} /> {children} </ThirdPartyButtonStyled> ); diff --git a/server/sonar-web/public/images/sonar-logo-horizontal.png b/server/sonar-web/public/images/sonar-logo-horizontal.png Binary files differnew file mode 100644 index 00000000000..310c519f9bf --- /dev/null +++ b/server/sonar-web/public/images/sonar-logo-horizontal.png diff --git a/server/sonar-web/src/main/js/app/components/SimpleSessionsContainer.tsx b/server/sonar-web/src/main/js/app/components/SimpleSessionsContainer.tsx index 46eed492a2a..8d7d42033b3 100644 --- a/server/sonar-web/src/main/js/app/components/SimpleSessionsContainer.tsx +++ b/server/sonar-web/src/main/js/app/components/SimpleSessionsContainer.tsx @@ -28,7 +28,7 @@ export default function SimpleSessionsContainer() { <PageTracker /> <div className="global-container"> - <div className="page-wrapper" id="container"> + <div className="page-wrapper new-background" id="container"> <Outlet /> </div> <GlobalFooter hideLoggedInInfo /> diff --git a/server/sonar-web/src/main/js/apps/account/__tests__/Account-it.tsx b/server/sonar-web/src/main/js/apps/account/__tests__/Account-it.tsx index a2fc52f2feb..b12ec3fbfa0 100644 --- a/server/sonar-web/src/main/js/apps/account/__tests__/Account-it.tsx +++ b/server/sonar-web/src/main/js/apps/account/__tests__/Account-it.tsx @@ -145,7 +145,7 @@ jest.mock('../../../api/users', () => ({ { key: 'github', name: 'GitHub', - iconPath: '/images/alm/github-white.svg', + iconPath: '/images/alm/github.svg', backgroundColor: '#444444', }, ], diff --git a/server/sonar-web/src/main/js/apps/sessions/components/Login.css b/server/sonar-web/src/main/js/apps/sessions/components/Login.css deleted file mode 100644 index 301206b59c3..00000000000 --- a/server/sonar-web/src/main/js/apps/sessions/components/Login.css +++ /dev/null @@ -1,41 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2024 SonarSource SA - * mailto:info AT sonarsource DOT com - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser 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 - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program; if not, write to the Free Software Foundation, - * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - */ -.login-page { - padding-top: 10vh; - max-width: 300px; - margin: 0 auto; - align-items: center; - display: flex; - flex-direction: column; -} - -.login-title { - line-height: 1.5; - font-size: 24px; - font-weight: 300; -} - -.login-message { - width: 450px; - background-color: var(--info50); - border: 1px solid var(--info200); - border-radius: 2px; - color: rgba(0, 0, 0, 0.87); -} diff --git a/server/sonar-web/src/main/js/apps/sessions/components/Login.tsx b/server/sonar-web/src/main/js/apps/sessions/components/Login.tsx index e9f2e82319e..611756f59ce 100644 --- a/server/sonar-web/src/main/js/apps/sessions/components/Login.tsx +++ b/server/sonar-web/src/main/js/apps/sessions/components/Login.tsx @@ -17,16 +17,24 @@ * along with this program; if not, write to the Free Software Foundation, * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ +import styled from '@emotion/styled'; +import { + Card, + FlagMessage, + PageContentFontWrapper, + Spinner, + Title, + themeBorder, + themeColor, +} from 'design-system'; import * as React from 'react'; import { Helmet } from 'react-helmet-async'; import { Location } from '../../../components/hoc/withRouter'; -import { Alert } from '../../../components/ui/Alert'; -import Spinner from '../../../components/ui/Spinner'; import { translate } from '../../../helpers/l10n'; import { sanitizeUserInput } from '../../../helpers/sanitize'; +import { getBaseUrl } from '../../../helpers/system'; import { getReturnUrl } from '../../../helpers/urls'; import { IdentityProvider } from '../../../types/types'; -import './Login.css'; import LoginForm from './LoginForm'; import OAuthProviders from './OAuthProviders'; @@ -38,42 +46,54 @@ export interface LoginProps { location: Location; } -export default function Login(props: LoginProps) { +export default function Login(props: Readonly<LoginProps>) { const { identityProviders, loading, location, message } = props; const returnTo = getReturnUrl(location); const displayError = Boolean(location.query.authorizationError); return ( - <div className="login-page" id="login_form"> - <h1 className="login-title text-center big-spacer-bottom"> - {translate('login.login_to_sonarqube')} - </h1> + <div className="sw-flex sw-flex-col sw-items-center" id="login_form"> <Helmet defer={false} title={translate('login.page')} /> - {loading ? ( - <Spinner loading={loading} /> - ) : ( - <> - {displayError && ( - <Alert className="big-spacer-bottom" display="block" variant="error"> - {translate('login.unauthorized_access_alert')} - </Alert> - )} + <img alt="" className="sw-mt-32" src={`${getBaseUrl()}/images/sonar-logo-horizontal.png`} /> + <Card className="sw-my-14 sw-p-0 sw-w-abs-350"> + <PageContentFontWrapper className="sw-body-md sw-flex sw-flex-col sw-items-center sw-py-8 sw-px-4"> + <img + alt="" + className="sw-mb-6" + src={`${getBaseUrl()}/images/embed-doc/sq-icon.svg`} + width={28} + /> + <Title className="sw-mb-6">{translate('login.login_to_sonarqube')}</Title> + <Spinner loading={loading}> + <> + {displayError && ( + <FlagMessage className="sw-mb-6" variant="error"> + {translate('login.unauthorized_access_alert')} + </FlagMessage> + )} - {message && ( - <div - className="login-message markdown big-padded spacer-top huge-spacer-bottom" - // eslint-disable-next-line react/no-danger - dangerouslySetInnerHTML={{ __html: sanitizeUserInput(message) }} - /> - )} + {message !== undefined && message.length > 0 && ( + <StyledMessage + className="markdown sw-rounded-2 sw-p-4 sw-mb-6" + // eslint-disable-next-line react/no-danger + dangerouslySetInnerHTML={{ __html: sanitizeUserInput(message) }} + /> + )} - {identityProviders.length > 0 && ( - <OAuthProviders identityProviders={identityProviders} returnTo={returnTo} /> - )} + {identityProviders.length > 0 && ( + <OAuthProviders identityProviders={identityProviders} returnTo={returnTo} /> + )} - <LoginForm collapsed={identityProviders.length > 0} onSubmit={props.onSubmit} /> - </> - )} + <LoginForm collapsed={identityProviders.length > 0} onSubmit={props.onSubmit} /> + </> + </Spinner> + </PageContentFontWrapper> + </Card> </div> ); } + +const StyledMessage = styled.div` + background: ${themeColor('highlightedSection')}; + border: ${themeBorder('default', 'highlightedSectionBorder')}; +`; diff --git a/server/sonar-web/src/main/js/apps/sessions/components/LoginForm.css b/server/sonar-web/src/main/js/apps/sessions/components/LoginForm.css deleted file mode 100644 index e49a4c60621..00000000000 --- a/server/sonar-web/src/main/js/apps/sessions/components/LoginForm.css +++ /dev/null @@ -1,38 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2024 SonarSource SA - * mailto:info AT sonarsource DOT com - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser 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 - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program; if not, write to the Free Software Foundation, - * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - */ -.login-form { - width: 300px; - margin-left: auto; - margin-right: auto; -} - -.login-input { - width: 100% !important; - height: auto !important; - padding: 5px 12px !important; - font-size: 20px; - font-weight: 300; -} - -.login-label { - display: none; - margin-bottom: 8px; - font-size: 15px; -} diff --git a/server/sonar-web/src/main/js/apps/sessions/components/LoginForm.tsx b/server/sonar-web/src/main/js/apps/sessions/components/LoginForm.tsx index 142cb826f9b..5db5d5d0efd 100644 --- a/server/sonar-web/src/main/js/apps/sessions/components/LoginForm.tsx +++ b/server/sonar-web/src/main/js/apps/sessions/components/LoginForm.tsx @@ -17,12 +17,16 @@ * along with this program; if not, write to the Free Software Foundation, * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ +import { + ButtonPrimary, + ButtonSecondary, + FormField, + InputField, + Link, + Spinner, +} from 'design-system'; import * as React from 'react'; -import Link from '../../../components/common/Link'; -import { ButtonLink, SubmitButton } from '../../../components/controls/buttons'; -import Spinner from '../../../components/ui/Spinner'; import { translate } from '../../../helpers/l10n'; -import './LoginForm.css'; interface Props { collapsed?: boolean; @@ -36,6 +40,9 @@ interface State { password: string; } +const LOGIN_INPUT_ID = 'login-input'; +const PASSWORD_INPUT_ID = 'password-input'; + export default class LoginForm extends React.PureComponent<Props, State> { constructor(props: Props) { super(props); @@ -72,62 +79,50 @@ export default class LoginForm extends React.PureComponent<Props, State> { render() { if (this.state.collapsed) { return ( - <div className="text-center"> - <ButtonLink - aria-expanded={false} - className="small js-more-options" - onClick={this.handleMoreOptionsClick} - > - {translate('login.more_options')} - </ButtonLink> - </div> + <ButtonSecondary + className="sw-w-full sw-justify-center" + aria-expanded={false} + onClick={this.handleMoreOptionsClick} + > + {translate('login.more_options')} + </ButtonSecondary> ); } return ( - <form className="login-form" onSubmit={this.handleSubmit}> - <div className="big-spacer-bottom"> - <label className="login-label" htmlFor="login"> - {translate('login')} - </label> - <input + <form className="sw-w-full" onSubmit={this.handleSubmit}> + <FormField label={translate('login')} htmlFor={LOGIN_INPUT_ID} required> + <InputField autoFocus - className="login-input" - id="login" + id={LOGIN_INPUT_ID} maxLength={255} name="login" onChange={this.handleLoginChange} - placeholder={translate('login')} required type="text" value={this.state.login} + size="full" /> - </div> + </FormField> - <div className="big-spacer-bottom"> - <label className="login-label" htmlFor="password"> - {translate('password')} - </label> - <input - className="login-input" - id="password" + <FormField label={translate('password')} htmlFor={PASSWORD_INPUT_ID} required> + <InputField + id={PASSWORD_INPUT_ID} name="password" onChange={this.handlePwdChange} - placeholder={translate('password')} required type="password" value={this.state.password} + size="full" /> - </div> + </FormField> <div> - <div className="text-right overflow-hidden"> - <Spinner className="spacer-right" loading={this.state.loading} /> - <SubmitButton disabled={this.state.loading}> + <div className="sw-overflow-hidden sw-flex sw-items-center sw-justify-end sw-gap-3"> + <Spinner loading={this.state.loading} /> + <Link to="/">{translate('go_back')}</Link> + <ButtonPrimary disabled={this.state.loading} type="submit"> {translate('sessions.log_in')} - </SubmitButton> - <Link className="spacer-left" to="/"> - {translate('cancel')} - </Link> + </ButtonPrimary> </div> </div> </form> diff --git a/server/sonar-web/src/main/js/apps/sessions/components/Logout.tsx b/server/sonar-web/src/main/js/apps/sessions/components/Logout.tsx index 0025d734ae9..92f0943b897 100644 --- a/server/sonar-web/src/main/js/apps/sessions/components/Logout.tsx +++ b/server/sonar-web/src/main/js/apps/sessions/components/Logout.tsx @@ -17,6 +17,7 @@ * along with this program; if not, write to the Free Software Foundation, * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ +import { CenteredLayout, PageContentFontWrapper } from 'design-system'; import * as React from 'react'; import { logOut } from '../../../api/auth'; import RecentHistory from '../../../app/components/RecentHistory'; @@ -24,8 +25,8 @@ import { addGlobalErrorMessage } from '../../../helpers/globalMessages'; import { translate } from '../../../helpers/l10n'; import { getBaseUrl } from '../../../helpers/system'; -export default class Logout extends React.PureComponent { - componentDidMount() { +export default function Logout() { + React.useEffect(() => { logOut() .then(() => { RecentHistory.clear(); @@ -34,13 +35,13 @@ export default class Logout extends React.PureComponent { .catch(() => { addGlobalErrorMessage(translate('login.logout_failed')); }); - } + }, []); - render() { - return ( - <div className="page page-limited"> - <div className="text-center">{translate('logging_out')}</div> - </div> - ); - } + return ( + <CenteredLayout> + <PageContentFontWrapper className="sw-body-md sw-mt-14 sw-text-center"> + {translate('logging_out')} + </PageContentFontWrapper> + </CenteredLayout> + ); } diff --git a/server/sonar-web/src/main/js/apps/sessions/components/OAuthProviders.css b/server/sonar-web/src/main/js/apps/sessions/components/OAuthProviders.css deleted file mode 100644 index 96167bfb9d9..00000000000 --- a/server/sonar-web/src/main/js/apps/sessions/components/OAuthProviders.css +++ /dev/null @@ -1,29 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2024 SonarSource SA - * mailto:info AT sonarsource DOT com - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser 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 - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program; if not, write to the Free Software Foundation, - * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - */ -.oauth-providers-help { - position: absolute; - top: 15px; - right: -24px; -} - -.oauth-providers + .login-form { - padding-top: 30px; - border-top: 1px solid var(--barBorderColor); -} diff --git a/server/sonar-web/src/main/js/apps/sessions/components/OAuthProviders.tsx b/server/sonar-web/src/main/js/apps/sessions/components/OAuthProviders.tsx index dc66082c9b5..6dcdf83f8d9 100644 --- a/server/sonar-web/src/main/js/apps/sessions/components/OAuthProviders.tsx +++ b/server/sonar-web/src/main/js/apps/sessions/components/OAuthProviders.tsx @@ -17,76 +17,52 @@ * along with this program; if not, write to the Free Software Foundation, * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ -import styled from '@emotion/styled'; -import classNames from 'classnames'; +import { BasicSeparator, ThirdPartyButton } from 'design-system'; import * as React from 'react'; import HelpTooltip from '../../../components/controls/HelpTooltip'; -import IdentityProviderLink from '../../../components/controls/IdentityProviderLink'; import { translateWithParameters } from '../../../helpers/l10n'; import { getBaseUrl } from '../../../helpers/system'; import { IdentityProvider } from '../../../types/types'; -import './OAuthProviders.css'; interface Props { - className?: string; - formatLabel?: (name: string) => React.ReactNode; identityProviders: IdentityProvider[]; returnTo: string; } -export default function OAuthProviders(props: Props) { - const formatFunction = props.formatLabel || defaultFormatLabel; - return ( - <Container className={classNames('oauth-providers', props.className)}> - {props.identityProviders.map((identityProvider) => ( - <OAuthProvider - format={formatFunction} - identityProvider={identityProvider} - key={identityProvider.key} - returnTo={props.returnTo} - /> - ))} - </Container> +export default function OAuthProviders({ identityProviders, returnTo }: Readonly<Props>) { + const authenticate = React.useCallback( + (key: string) => { + // We need a real page refresh, as the login mechanism is handled on the server + window.location.replace( + `${getBaseUrl()}/sessions/init/${key}?return_to=${encodeURIComponent(returnTo)}`, + ); + }, + [returnTo], ); -} - -interface ItemProps { - format: (name: string) => React.ReactNode; - identityProvider: IdentityProvider; - returnTo: string; -} -function OAuthProvider({ format, identityProvider, returnTo }: ItemProps) { return ( - <IdentityProviderWrapper> - <IdentityProviderLink - backgroundColor={identityProvider.backgroundColor} - iconPath={identityProvider.iconPath} - name={identityProvider.name} - url={ - `${getBaseUrl()}/sessions/init/${identityProvider.key}` + - `?return_to=${encodeURIComponent(returnTo)}` - } - > - <span>{format(identityProvider.name)}</span> - </IdentityProviderLink> - {identityProvider.helpMessage && ( - <HelpTooltip className="oauth-providers-help" overlay={identityProvider.helpMessage} /> - )} - </IdentityProviderWrapper> + <> + <div className="sw-w-full sw-flex sw-flex-col sw-gap-4" id="oauth-providers"> + {identityProviders.map((identityProvider) => ( + <div key={identityProvider.key}> + <ThirdPartyButton + className="sw-w-full sw-justify-center" + name={identityProvider.name} + iconPath={identityProvider.iconPath} + onClick={() => authenticate(identityProvider.key)} + > + <span>{translateWithParameters('login.login_with_x', identityProvider.name)}</span> + </ThirdPartyButton> + {identityProvider.helpMessage && ( + <HelpTooltip + className="oauth-providers-help" + overlay={identityProvider.helpMessage} + /> + )} + </div> + ))} + </div> + <BasicSeparator className="sw-my-6 sw-w-full" /> + </> ); } - -function defaultFormatLabel(name: string) { - return translateWithParameters('login.login_with_x', name); -} - -const Container = styled.div` - display: inline-flex; - flex-direction: column; - align-items: stretch; -`; - -const IdentityProviderWrapper = styled.div` - margin-bottom: 30px; -`; diff --git a/server/sonar-web/src/main/js/apps/sessions/components/Unauthorized.tsx b/server/sonar-web/src/main/js/apps/sessions/components/Unauthorized.tsx index 4162c4ea969..d94e2c0a8cc 100644 --- a/server/sonar-web/src/main/js/apps/sessions/components/Unauthorized.tsx +++ b/server/sonar-web/src/main/js/apps/sessions/components/Unauthorized.tsx @@ -17,32 +17,33 @@ * along with this program; if not, write to the Free Software Foundation, * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ +import { Card, CenteredLayout, Link, PageContentFontWrapper } from 'design-system'; import * as React from 'react'; import { Helmet } from 'react-helmet-async'; -import Link from '../../../components/common/Link'; import { getCookie } from '../../../helpers/cookies'; import { translate } from '../../../helpers/l10n'; export default function Unauthorized() { const message = decodeURIComponent(getCookie('AUTHENTICATION-ERROR') || ''); return ( - <div className="page-wrapper-simple" id="bd"> + <CenteredLayout id="bd"> <Helmet defer={false} title={translate('unauthorized.page')} /> - <div className="page-simple" id="nonav"> - <div className="text-center"> + <PageContentFontWrapper className="sw-body-md sw-flex sw-justify-center" id="nonav"> + <Card className="sw-w-abs-500 sw-my-14 sw-text-center"> <p id="unauthorized">{translate('unauthorized.message')}</p> {Boolean(message) && ( - <p className="spacer-top"> - {translate('unauthorized.reason')} {message} + <p className="sw-mt-4"> + {translate('unauthorized.reason')} + <br /> {message} </p> )} - <div className="big-spacer-top"> + <div className="sw-mt-8"> <Link to="/">{translate('layout.home')}</Link> </div> - </div> - </div> - </div> + </Card> + </PageContentFontWrapper> + </CenteredLayout> ); } diff --git a/server/sonar-web/src/main/js/apps/sessions/components/__tests__/Login-it.tsx b/server/sonar-web/src/main/js/apps/sessions/components/__tests__/Login-it.tsx index 9bc294d22fd..02d3bd88483 100644 --- a/server/sonar-web/src/main/js/apps/sessions/components/__tests__/Login-it.tsx +++ b/server/sonar-web/src/main/js/apps/sessions/components/__tests__/Login-it.tsx @@ -25,6 +25,7 @@ import { getIdentityProviders } from '../../../../api/users'; import { addGlobalErrorMessage } from '../../../../helpers/globalMessages'; import { mockLocation } from '../../../../helpers/testMocks'; import { renderComponent } from '../../../../helpers/testReactTestingUtils'; +import { byLabelText, byRole } from '../../../../helpers/testSelector'; import { LoginContainer } from '../LoginContainer'; jest.mock('../../../../api/users', () => { @@ -80,23 +81,21 @@ it('should behave correctly', async () => { expect(heading).toBeInTheDocument(); // OAuth provider. - const link = screen.getByRole('link', { name: 'Github login.login_with_x.Github' }); + const link = screen.getByRole('button', { name: 'Github login.login_with_x.Github' }); expect(link).toBeInTheDocument(); - expect(link).toHaveAttribute('href', '/sessions/init/github?return_to=%2Fsome%2Fpath'); - expect(link).toMatchSnapshot('OAuthProvider link'); // Login form collapsed by default. - expect(screen.queryByLabelText('login')).not.toBeInTheDocument(); + expect(ui.loginInput.query()).not.toBeInTheDocument(); // Open login form, log in. await user.click(screen.getByRole('button', { name: 'login.more_options' })); - const cancelLink = await screen.findByRole('link', { name: 'cancel' }); + const cancelLink = await ui.backLink.find(); expect(cancelLink).toBeInTheDocument(); expect(cancelLink).toHaveAttribute('href', '/'); - const loginField = screen.getByLabelText('login'); - const passwordField = screen.getByLabelText('password'); + const loginField = ui.loginInput.get(); + const passwordField = ui.passwordInput.get(); const submitButton = screen.getByRole('button', { name: 'sessions.log_in' }); // Incorrect login. @@ -118,7 +117,7 @@ it('should behave correctly', async () => { }); it('should not show any OAuth providers if none are configured', async () => { - (getIdentityProviders as jest.Mock).mockResolvedValueOnce({ identityProviders: [] }); + jest.mocked(getIdentityProviders).mockResolvedValueOnce({ identityProviders: [] }); renderLoginContainer(); const heading = await screen.findByRole('heading', { name: 'login.login_to_sonarqube' }); @@ -126,7 +125,7 @@ it('should not show any OAuth providers if none are configured', async () => { // No OAuth providers, login form display by default. expect(screen.queryByRole('link', { name: 'login.login_with_x' })).not.toBeInTheDocument(); - expect(screen.getByLabelText('login')).toBeInTheDocument(); + expect(ui.loginInput.get()).toBeInTheDocument(); }); it("should show a warning if there's an authorization error", async () => { @@ -142,14 +141,14 @@ it("should show a warning if there's an authorization error", async () => { it('should display a login message if enabled & provided', async () => { const message = 'Welcome to SQ! Please use your Skynet credentials'; - (getLoginMessage as jest.Mock).mockResolvedValueOnce({ message }); + jest.mocked(getLoginMessage).mockResolvedValueOnce({ message }); renderLoginContainer({}); expect(await screen.findByText(message)).toBeInTheDocument(); }); it('should handle errors', async () => { - (getLoginMessage as jest.Mock).mockRejectedValueOnce('nope'); + jest.mocked(getLoginMessage).mockRejectedValueOnce('nope'); renderLoginContainer({}); const heading = await screen.findByRole('heading', { name: 'login.login_to_sonarqube' }); @@ -161,3 +160,9 @@ function renderLoginContainer(props: Partial<LoginContainer['props']> = {}) { <LoginContainer location={mockLocation({ query: { return_to: '/some/path' } })} {...props} />, ); } + +const ui = { + loginInput: byLabelText(/login/), + passwordInput: byLabelText(/password/), + backLink: byRole('link', { name: 'go_back' }), +}; diff --git a/server/sonar-web/src/main/js/apps/sessions/components/__tests__/Logout-it.tsx b/server/sonar-web/src/main/js/apps/sessions/components/__tests__/Logout-it.tsx index a60a42cd797..7e9d559f14f 100644 --- a/server/sonar-web/src/main/js/apps/sessions/components/__tests__/Logout-it.tsx +++ b/server/sonar-web/src/main/js/apps/sessions/components/__tests__/Logout-it.tsx @@ -78,7 +78,7 @@ it('should behave correctly', async () => { }); it('should correctly handle a failing log out', async () => { - (logOut as jest.Mock).mockRejectedValueOnce(false); + jest.mocked(logOut).mockRejectedValueOnce(false); renderLogout(); await waitFor(() => { diff --git a/server/sonar-web/src/main/js/apps/sessions/components/__tests__/__snapshots__/Login-it.tsx.snap b/server/sonar-web/src/main/js/apps/sessions/components/__tests__/__snapshots__/Login-it.tsx.snap deleted file mode 100644 index 96449a1dc42..00000000000 --- a/server/sonar-web/src/main/js/apps/sessions/components/__tests__/__snapshots__/Login-it.tsx.snap +++ /dev/null @@ -1,19 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`should behave correctly: OAuthProvider link 1`] = ` -<a - class="identity-provider-link" - href="/sessions/init/github?return_to=%2Fsome%2Fpath" - style="background-color: rgb(0, 0, 0);" -> - <img - alt="Github" - height="20" - src="/path/icon.svg" - width="20" - /> - <span> - login.login_with_x.Github - </span> -</a> -`; diff --git a/server/sonar-web/src/main/js/helpers/testMocks.ts b/server/sonar-web/src/main/js/helpers/testMocks.ts index 4178e537e2e..0a4b5e9f32e 100644 --- a/server/sonar-web/src/main/js/helpers/testMocks.ts +++ b/server/sonar-web/src/main/js/helpers/testMocks.ts @@ -78,7 +78,7 @@ import { CurrentUser, LoggedInUser, RestUserDetailed, User } from '../types/user export function mockAlmApplication(overrides: Partial<AlmApplication> = {}): AlmApplication { return { backgroundColor: '#444444', - iconPath: '/images/sonarcloud/github-white.svg', + iconPath: '/images/alm/github.svg', installationUrl: 'https://github.com/apps/greg-sonarcloud/installations/new', key: 'github', name: 'GitHub', |