aboutsummaryrefslogtreecommitdiffstats
path: root/server
diff options
context:
space:
mode:
authorJeremy Davis <jeremy.davis@sonarsource.com>2024-01-05 10:54:29 +0100
committersonartech <sonartech@sonarsource.com>2024-01-05 20:02:36 +0000
commite0e25569b61b8f8f3c4eb2cfdfae89be7a7e86d2 (patch)
tree88ef838800877fc3a96c0136dd0fba37a588faa0 /server
parent4cdadd222ecfc5f09df38d468281c8e5b6ed58b0 (diff)
downloadsonarqube-e0e25569b61b8f8f3c4eb2cfdfae89be7a7e86d2.tar.gz
sonarqube-e0e25569b61b8f8f3c4eb2cfdfae89be7a7e86d2.zip
SONAR-21396 Migrate session pages to the new UI
Diffstat (limited to 'server')
-rwxr-xr-xserver/sonar-auth-bitbucket/src/main/java/org/sonar/auth/bitbucket/BitbucketIdentityProvider.java2
-rwxr-xr-xserver/sonar-auth-bitbucket/src/test/java/org/sonar/auth/bitbucket/BitbucketIdentityProviderTest.java2
-rw-r--r--server/sonar-auth-github/src/main/java/org/sonar/auth/github/GitHubIdentityProvider.java2
-rw-r--r--server/sonar-auth-github/src/test/java/org/sonar/auth/github/GitHubIdentityProviderTest.java2
-rw-r--r--server/sonar-web/design-system/src/components/buttons/ThirdPartyButton.tsx9
-rw-r--r--server/sonar-web/public/images/sonar-logo-horizontal.pngbin0 -> 2470 bytes
-rw-r--r--server/sonar-web/src/main/js/app/components/SimpleSessionsContainer.tsx2
-rw-r--r--server/sonar-web/src/main/js/apps/account/__tests__/Account-it.tsx2
-rw-r--r--server/sonar-web/src/main/js/apps/sessions/components/Login.css41
-rw-r--r--server/sonar-web/src/main/js/apps/sessions/components/Login.tsx80
-rw-r--r--server/sonar-web/src/main/js/apps/sessions/components/LoginForm.css38
-rw-r--r--server/sonar-web/src/main/js/apps/sessions/components/LoginForm.tsx73
-rw-r--r--server/sonar-web/src/main/js/apps/sessions/components/Logout.tsx21
-rw-r--r--server/sonar-web/src/main/js/apps/sessions/components/OAuthProviders.css29
-rw-r--r--server/sonar-web/src/main/js/apps/sessions/components/OAuthProviders.tsx90
-rw-r--r--server/sonar-web/src/main/js/apps/sessions/components/Unauthorized.tsx21
-rw-r--r--server/sonar-web/src/main/js/apps/sessions/components/__tests__/Login-it.tsx27
-rw-r--r--server/sonar-web/src/main/js/apps/sessions/components/__tests__/Logout-it.tsx2
-rw-r--r--server/sonar-web/src/main/js/apps/sessions/components/__tests__/__snapshots__/Login-it.tsx.snap19
-rw-r--r--server/sonar-web/src/main/js/helpers/testMocks.ts2
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
new file mode 100644
index 00000000000..310c519f9bf
--- /dev/null
+++ b/server/sonar-web/public/images/sonar-logo-horizontal.png
Binary files differ
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',