aboutsummaryrefslogtreecommitdiffstats
path: root/server/sonar-web
diff options
context:
space:
mode:
authorWouter Admiraal <wouter.admiraal@sonarsource.com>2021-04-27 15:51:18 +0200
committersonartech <sonartech@sonarsource.com>2021-04-29 20:03:27 +0000
commit3848fcffa478531ffb167049965122e7844c8175 (patch)
tree005d04910b65862815531cfa62ac9bcebef89254 /server/sonar-web
parent64cf912b1ce5e4b5db2ed6366d7c046daddebcb0 (diff)
downloadsonarqube-3848fcffa478531ffb167049965122e7844c8175.tar.gz
sonarqube-3848fcffa478531ffb167049965122e7844c8175.zip
SONAR-14604 Improve reset password form layout
Diffstat (limited to 'server/sonar-web')
-rw-r--r--server/sonar-web/src/main/js/app/components/ResetPassword.css35
-rw-r--r--server/sonar-web/src/main/js/app/components/ResetPassword.tsx39
-rw-r--r--server/sonar-web/src/main/js/app/components/__tests__/ResetPassword-test.tsx37
-rw-r--r--server/sonar-web/src/main/js/app/components/__tests__/__snapshots__/ResetPassword-test.tsx.snap57
-rw-r--r--server/sonar-web/src/main/js/apps/account/components/Security.tsx9
-rw-r--r--server/sonar-web/src/main/js/apps/account/components/__tests__/__snapshots__/Security-test.tsx.snap32
-rw-r--r--server/sonar-web/src/main/js/apps/change-admin-password/ChangeAdminPasswordAppRenderer.tsx2
-rw-r--r--server/sonar-web/src/main/js/apps/change-admin-password/__tests__/__snapshots__/ChangeAdminPasswordAppRenderer-test.tsx.snap8
-rw-r--r--server/sonar-web/src/main/js/components/common/ResetPasswordForm.tsx (renamed from server/sonar-web/src/main/js/components/common/ResetPassword.tsx)120
-rw-r--r--server/sonar-web/src/main/js/components/common/__tests__/ResetPasswordForm-test.tsx (renamed from server/sonar-web/src/main/js/components/common/__tests__/ResetPassword-test.tsx)6
-rw-r--r--server/sonar-web/src/main/js/components/common/__tests__/__snapshots__/ResetPassword-test.tsx.snap81
-rw-r--r--server/sonar-web/src/main/js/components/common/__tests__/__snapshots__/ResetPasswordForm-test.tsx.snap71
12 files changed, 259 insertions, 238 deletions
diff --git a/server/sonar-web/src/main/js/app/components/ResetPassword.css b/server/sonar-web/src/main/js/app/components/ResetPassword.css
deleted file mode 100644
index e0a344a0a42..00000000000
--- a/server/sonar-web/src/main/js/app/components/ResetPassword.css
+++ /dev/null
@@ -1,35 +0,0 @@
-/*
- * SonarQube
- * Copyright (C) 2009-2021 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.
- */
-.reset-page {
- padding-top: 10vh;
-}
-
-.reset-page h1 {
- line-height: 1.5;
- font-size: 24px;
- font-weight: 300;
- text-align: center;
-}
-
-.reset-form {
- width: 300px;
- margin-left: auto;
- margin-right: auto;
-}
diff --git a/server/sonar-web/src/main/js/app/components/ResetPassword.tsx b/server/sonar-web/src/main/js/app/components/ResetPassword.tsx
index 9a55f23a4d2..55ef8b4c88f 100644
--- a/server/sonar-web/src/main/js/app/components/ResetPassword.tsx
+++ b/server/sonar-web/src/main/js/app/components/ResetPassword.tsx
@@ -19,30 +19,37 @@
*/
import * as React from 'react';
import { translate } from 'sonar-ui-common/helpers/l10n';
-import ResetPasswordForm from '../../components/common/ResetPassword';
+import ResetPasswordForm from '../../components/common/ResetPasswordForm';
import { whenLoggedIn } from '../../components/hoc/whenLoggedIn';
+import { getBaseUrl } from '../../helpers/system';
import GlobalMessagesContainer from './GlobalMessagesContainer';
-import './ResetPassword.css';
export interface ResetPasswordProps {
currentUser: T.LoggedInUser;
}
-export function ResetPassword(props: ResetPasswordProps) {
- const { currentUser } = props;
- const redirect = () => {
- window.location.href = `/`; // force a refresh for the backend to handle additional redirects
- };
-
+export function ResetPassword({ currentUser }: ResetPasswordProps) {
return (
- <div className="reset-page">
- <h1 className="text-center spacer-bottom">{translate('my_account.reset_password')}</h1>
- <h2 className="text-center huge-spacer-bottom">
- {translate('my_account.reset_password.explain')}
- </h2>
- <GlobalMessagesContainer />
- <div className="reset-form">
- <ResetPasswordForm user={currentUser} onPasswordChange={redirect} />
+ <div className="page-wrapper-simple">
+ <div className="page-simple">
+ <GlobalMessagesContainer />
+
+ <h1 className="text-center huge">{translate('my_account.reset_password')}</h1>
+ <p className="text-center huge-spacer-top huge-spacer-bottom">
+ {translate('my_account.reset_password.explain')}
+ </p>
+
+ <div className="text-center">
+ <h2 className="big-spacer-bottom big">{translate('my_profile.password.title')}</h2>
+
+ <ResetPasswordForm
+ user={currentUser}
+ onPasswordChange={() => {
+ // Force a refresh for the backend to handle additional redirects.
+ window.location.href = getBaseUrl() + '/';
+ }}
+ />
+ </div>
</div>
</div>
);
diff --git a/server/sonar-web/src/main/js/app/components/__tests__/ResetPassword-test.tsx b/server/sonar-web/src/main/js/app/components/__tests__/ResetPassword-test.tsx
index 0f3093f3f51..56b33efcc57 100644
--- a/server/sonar-web/src/main/js/app/components/__tests__/ResetPassword-test.tsx
+++ b/server/sonar-web/src/main/js/app/components/__tests__/ResetPassword-test.tsx
@@ -19,13 +19,50 @@
*/
import { shallow } from 'enzyme';
import * as React from 'react';
+import ResetPasswordForm from '../../../components/common/ResetPasswordForm';
import { mockLoggedInUser } from '../../../helpers/testMocks';
import { ResetPassword, ResetPasswordProps } from '../ResetPassword';
+jest.mock('../../../helpers/system', () => ({
+ getBaseUrl: jest.fn().mockReturnValue('/context')
+}));
+
+const originalLocation = window.location;
+
+beforeAll(() => {
+ const location = {
+ ...window.location,
+ href: null
+ };
+ Object.defineProperty(window, 'location', {
+ writable: true,
+ value: location
+ });
+});
+
+afterAll(() => {
+ Object.defineProperty(window, 'location', {
+ writable: true,
+ value: originalLocation
+ });
+});
+
it('should render correctly', () => {
expect(shallowRender()).toMatchSnapshot();
});
+it('should navigate to the homepage after submission', () => {
+ const wrapper = shallowRender();
+ const form = wrapper.find(ResetPasswordForm);
+ const { onPasswordChange } = form.props();
+
+ if (onPasswordChange) {
+ onPasswordChange();
+ }
+
+ expect(window.location.href).toBe('/context/');
+});
+
function shallowRender(props: Partial<ResetPasswordProps> = {}) {
return shallow(<ResetPassword currentUser={mockLoggedInUser()} {...props} />);
}
diff --git a/server/sonar-web/src/main/js/app/components/__tests__/__snapshots__/ResetPassword-test.tsx.snap b/server/sonar-web/src/main/js/app/components/__tests__/__snapshots__/ResetPassword-test.tsx.snap
index ad2db130e85..cf445a7dcb5 100644
--- a/server/sonar-web/src/main/js/app/components/__tests__/__snapshots__/ResetPassword-test.tsx.snap
+++ b/server/sonar-web/src/main/js/app/components/__tests__/__snapshots__/ResetPassword-test.tsx.snap
@@ -2,34 +2,43 @@
exports[`should render correctly 1`] = `
<div
- className="reset-page"
+ className="page-wrapper-simple"
>
- <h1
- className="text-center spacer-bottom"
- >
- my_account.reset_password
- </h1>
- <h2
- className="text-center huge-spacer-bottom"
- >
- my_account.reset_password.explain
- </h2>
- <Connect(GlobalMessages) />
<div
- className="reset-form"
+ className="page-simple"
>
- <ResetPassword
- onPasswordChange={[Function]}
- user={
- Object {
- "groups": Array [],
- "isLoggedIn": true,
- "login": "luke",
- "name": "Skywalker",
- "scmAccounts": Array [],
+ <Connect(GlobalMessages) />
+ <h1
+ className="text-center huge"
+ >
+ my_account.reset_password
+ </h1>
+ <p
+ className="text-center huge-spacer-top huge-spacer-bottom"
+ >
+ my_account.reset_password.explain
+ </p>
+ <div
+ className="text-center"
+ >
+ <h2
+ className="big-spacer-bottom big"
+ >
+ my_profile.password.title
+ </h2>
+ <ResetPasswordForm
+ onPasswordChange={[Function]}
+ user={
+ Object {
+ "groups": Array [],
+ "isLoggedIn": true,
+ "login": "luke",
+ "name": "Skywalker",
+ "scmAccounts": Array [],
+ }
}
- }
- />
+ />
+ </div>
</div>
</div>
`;
diff --git a/server/sonar-web/src/main/js/apps/account/components/Security.tsx b/server/sonar-web/src/main/js/apps/account/components/Security.tsx
index e6c0af20238..b5300960175 100644
--- a/server/sonar-web/src/main/js/apps/account/components/Security.tsx
+++ b/server/sonar-web/src/main/js/apps/account/components/Security.tsx
@@ -21,7 +21,7 @@ import * as React from 'react';
import { Helmet } from 'react-helmet-async';
import { connect } from 'react-redux';
import { translate } from 'sonar-ui-common/helpers/l10n';
-import ResetPassword from '../../../components/common/ResetPassword';
+import ResetPasswordForm from '../../../components/common/ResetPasswordForm';
import { getCurrentUser, Store } from '../../../store/rootReducer';
import Tokens from './Tokens';
@@ -34,7 +34,12 @@ export function Security({ user }: SecurityProps) {
<div className="account-body account-container">
<Helmet defer={false} title={translate('my_account.security')} />
<Tokens login={user.login} />
- {user.local && <ResetPassword user={user} />}
+ {user.local && (
+ <section className="boxed-group">
+ <h2 className="spacer-bottom">{translate('my_profile.password.title')}</h2>
+ <ResetPasswordForm className="boxed-group-inner" user={user} />
+ </section>
+ )}
</div>
);
}
diff --git a/server/sonar-web/src/main/js/apps/account/components/__tests__/__snapshots__/Security-test.tsx.snap b/server/sonar-web/src/main/js/apps/account/components/__tests__/__snapshots__/Security-test.tsx.snap
index 4adb1b2c1c2..c120e9b1c33 100644
--- a/server/sonar-web/src/main/js/apps/account/components/__tests__/__snapshots__/Security-test.tsx.snap
+++ b/server/sonar-web/src/main/js/apps/account/components/__tests__/__snapshots__/Security-test.tsx.snap
@@ -12,18 +12,28 @@ exports[`should render correctly: local user 1`] = `
<Tokens
login="luke"
/>
- <ResetPassword
- user={
- Object {
- "groups": Array [],
- "isLoggedIn": true,
- "local": true,
- "login": "luke",
- "name": "Skywalker",
- "scmAccounts": Array [],
+ <section
+ className="boxed-group"
+ >
+ <h2
+ className="spacer-bottom"
+ >
+ my_profile.password.title
+ </h2>
+ <ResetPasswordForm
+ className="boxed-group-inner"
+ user={
+ Object {
+ "groups": Array [],
+ "isLoggedIn": true,
+ "local": true,
+ "login": "luke",
+ "name": "Skywalker",
+ "scmAccounts": Array [],
+ }
}
- }
- />
+ />
+ </section>
</div>
`;
diff --git a/server/sonar-web/src/main/js/apps/change-admin-password/ChangeAdminPasswordAppRenderer.tsx b/server/sonar-web/src/main/js/apps/change-admin-password/ChangeAdminPasswordAppRenderer.tsx
index 0f01456ccba..cee0dce669e 100644
--- a/server/sonar-web/src/main/js/apps/change-admin-password/ChangeAdminPasswordAppRenderer.tsx
+++ b/server/sonar-web/src/main/js/apps/change-admin-password/ChangeAdminPasswordAppRenderer.tsx
@@ -137,7 +137,7 @@ export default function ChangeAdminPasswordAppRenderer(props: ChangeAdminPasswor
<div className="form-field">
<SubmitButton disabled={!canSubmit || submitting}>
- {translate('change_verb')}
+ {translate('update_verb')}
{submitting && <i className="spinner spacer-left" />}
</SubmitButton>
</div>
diff --git a/server/sonar-web/src/main/js/apps/change-admin-password/__tests__/__snapshots__/ChangeAdminPasswordAppRenderer-test.tsx.snap b/server/sonar-web/src/main/js/apps/change-admin-password/__tests__/__snapshots__/ChangeAdminPasswordAppRenderer-test.tsx.snap
index 5c5895a5782..836b65c7375 100644
--- a/server/sonar-web/src/main/js/apps/change-admin-password/__tests__/__snapshots__/ChangeAdminPasswordAppRenderer-test.tsx.snap
+++ b/server/sonar-web/src/main/js/apps/change-admin-password/__tests__/__snapshots__/ChangeAdminPasswordAppRenderer-test.tsx.snap
@@ -79,7 +79,7 @@ exports[`should render correctly: cannot submit 1`] = `
<SubmitButton
disabled={true}
>
- change_verb
+ update_verb
</SubmitButton>
</div>
</form>
@@ -164,7 +164,7 @@ exports[`should render correctly: default 1`] = `
<SubmitButton
disabled={false}
>
- change_verb
+ update_verb
</SubmitButton>
</div>
</form>
@@ -249,7 +249,7 @@ exports[`should render correctly: submitting 1`] = `
<SubmitButton
disabled={true}
>
- change_verb
+ update_verb
<i
className="spinner spacer-left"
/>
@@ -368,7 +368,7 @@ exports[`should render correctly: trying to use default admin password 1`] = `
<SubmitButton
disabled={false}
>
- change_verb
+ update_verb
</SubmitButton>
</div>
</form>
diff --git a/server/sonar-web/src/main/js/components/common/ResetPassword.tsx b/server/sonar-web/src/main/js/components/common/ResetPasswordForm.tsx
index ff59d0a1173..8f46d2f8650 100644
--- a/server/sonar-web/src/main/js/components/common/ResetPassword.tsx
+++ b/server/sonar-web/src/main/js/components/common/ResetPasswordForm.tsx
@@ -26,6 +26,7 @@ import { translate } from 'sonar-ui-common/helpers/l10n';
import { changePassword } from '../../api/users';
interface Props {
+ className?: string;
user: T.LoggedInUser;
onPasswordChange?: () => void;
}
@@ -35,7 +36,7 @@ interface State {
success: boolean;
}
-export default class ResetPassword extends React.Component<Props, State> {
+export default class ResetPasswordForm extends React.Component<Props, State> {
oldPassword: HTMLInputElement | null = null;
password: HTMLInputElement | null = null;
passwordConfirmation: HTMLInputElement | null = null;
@@ -84,72 +85,69 @@ export default class ResetPassword extends React.Component<Props, State> {
};
render() {
+ const { className } = this.props;
const { success, errors } = this.state;
return (
- <section className="boxed-group">
- <h2 className="spacer-bottom">{translate('my_profile.password.title')}</h2>
+ <form className={className} onSubmit={this.handleChangePassword}>
+ {success && <Alert variant="success">{translate('my_profile.password.changed')}</Alert>}
- <form className="boxed-group-inner" onSubmit={this.handleChangePassword}>
- {success && <Alert variant="success">{translate('my_profile.password.changed')}</Alert>}
+ {errors &&
+ errors.map((e, i) => (
+ /* eslint-disable-next-line react/no-array-index-key */
+ <Alert key={i} variant="error">
+ {e}
+ </Alert>
+ ))}
- {errors &&
- errors.map((e, i) => (
- /* eslint-disable-next-line react/no-array-index-key */
- <Alert key={i} variant="error">
- {e}
- </Alert>
- ))}
+ <MandatoryFieldsExplanation className="form-field" />
- <MandatoryFieldsExplanation className="form-field" />
-
- <div className="form-field">
- <label htmlFor="old_password">
- {translate('my_profile.password.old')}
- <MandatoryFieldMarker />
- </label>
- <input
- autoComplete="off"
- id="old_password"
- name="old_password"
- ref={elem => (this.oldPassword = elem)}
- required={true}
- type="password"
- />
- </div>
- <div className="form-field">
- <label htmlFor="password">
- {translate('my_profile.password.new')}
- <MandatoryFieldMarker />
- </label>
- <input
- autoComplete="off"
- id="password"
- name="password"
- ref={elem => (this.password = elem)}
- required={true}
- type="password"
- />
- </div>
- <div className="form-field">
- <label htmlFor="password_confirmation">
- {translate('my_profile.password.confirm')}
- <MandatoryFieldMarker />
- </label>
- <input
- autoComplete="off"
- id="password_confirmation"
- name="password_confirmation"
- ref={elem => (this.passwordConfirmation = elem)}
- required={true}
- type="password"
- />
- </div>
- <div className="form-field">
- <SubmitButton id="change-password">{translate('update_verb')}</SubmitButton>
- </div>
- </form>
- </section>
+ <div className="form-field">
+ <label htmlFor="old_password">
+ {translate('my_profile.password.old')}
+ <MandatoryFieldMarker />
+ </label>
+ <input
+ autoComplete="off"
+ id="old_password"
+ name="old_password"
+ ref={elem => (this.oldPassword = elem)}
+ required={true}
+ type="password"
+ />
+ </div>
+ <div className="form-field">
+ <label htmlFor="password">
+ {translate('my_profile.password.new')}
+ <MandatoryFieldMarker />
+ </label>
+ <input
+ autoComplete="off"
+ id="password"
+ name="password"
+ ref={elem => (this.password = elem)}
+ required={true}
+ type="password"
+ />
+ </div>
+ <div className="form-field">
+ <label htmlFor="password_confirmation">
+ {translate('my_profile.password.confirm')}
+ <MandatoryFieldMarker />
+ </label>
+ <input
+ autoComplete="off"
+ id="password_confirmation"
+ name="password_confirmation"
+ ref={elem => (this.passwordConfirmation = elem)}
+ required={true}
+ type="password"
+ />
+ </div>
+ <div className="form-field">
+ <SubmitButton id="change-password">{translate('update_verb')}</SubmitButton>
+ </div>
+ </form>
);
}
}
diff --git a/server/sonar-web/src/main/js/components/common/__tests__/ResetPassword-test.tsx b/server/sonar-web/src/main/js/components/common/__tests__/ResetPasswordForm-test.tsx
index 904f693ed69..e940f20395e 100644
--- a/server/sonar-web/src/main/js/components/common/__tests__/ResetPassword-test.tsx
+++ b/server/sonar-web/src/main/js/components/common/__tests__/ResetPasswordForm-test.tsx
@@ -22,7 +22,7 @@ import * as React from 'react';
import { waitAndUpdate } from 'sonar-ui-common/helpers/testUtils';
import { changePassword } from '../../../api/users';
import { mockEvent, mockLoggedInUser } from '../../../helpers/testMocks';
-import ResetPassword from '../ResetPassword';
+import ResetPasswordForm from '../ResetPasswordForm';
jest.mock('../../../api/users', () => ({
changePassword: jest.fn().mockResolvedValue({})
@@ -74,6 +74,6 @@ it('renders correctly', () => {
expect(shallowRender()).toMatchSnapshot();
});
-function shallowRender(props?: Partial<ResetPassword['props']>) {
- return shallow<ResetPassword>(<ResetPassword user={mockLoggedInUser()} {...props} />);
+function shallowRender(props?: Partial<ResetPasswordForm['props']>) {
+ return shallow<ResetPasswordForm>(<ResetPasswordForm user={mockLoggedInUser()} {...props} />);
}
diff --git a/server/sonar-web/src/main/js/components/common/__tests__/__snapshots__/ResetPassword-test.tsx.snap b/server/sonar-web/src/main/js/components/common/__tests__/__snapshots__/ResetPassword-test.tsx.snap
deleted file mode 100644
index f2fbe03c093..00000000000
--- a/server/sonar-web/src/main/js/components/common/__tests__/__snapshots__/ResetPassword-test.tsx.snap
+++ /dev/null
@@ -1,81 +0,0 @@
-// Jest Snapshot v1, https://goo.gl/fbAQLP
-
-exports[`renders correctly 1`] = `
-<section
- className="boxed-group"
->
- <h2
- className="spacer-bottom"
- >
- my_profile.password.title
- </h2>
- <form
- className="boxed-group-inner"
- onSubmit={[Function]}
- >
- <MandatoryFieldsExplanation
- className="form-field"
- />
- <div
- className="form-field"
- >
- <label
- htmlFor="old_password"
- >
- my_profile.password.old
- <MandatoryFieldMarker />
- </label>
- <input
- autoComplete="off"
- id="old_password"
- name="old_password"
- required={true}
- type="password"
- />
- </div>
- <div
- className="form-field"
- >
- <label
- htmlFor="password"
- >
- my_profile.password.new
- <MandatoryFieldMarker />
- </label>
- <input
- autoComplete="off"
- id="password"
- name="password"
- required={true}
- type="password"
- />
- </div>
- <div
- className="form-field"
- >
- <label
- htmlFor="password_confirmation"
- >
- my_profile.password.confirm
- <MandatoryFieldMarker />
- </label>
- <input
- autoComplete="off"
- id="password_confirmation"
- name="password_confirmation"
- required={true}
- type="password"
- />
- </div>
- <div
- className="form-field"
- >
- <SubmitButton
- id="change-password"
- >
- update_verb
- </SubmitButton>
- </div>
- </form>
-</section>
-`;
diff --git a/server/sonar-web/src/main/js/components/common/__tests__/__snapshots__/ResetPasswordForm-test.tsx.snap b/server/sonar-web/src/main/js/components/common/__tests__/__snapshots__/ResetPasswordForm-test.tsx.snap
new file mode 100644
index 00000000000..3f32833747d
--- /dev/null
+++ b/server/sonar-web/src/main/js/components/common/__tests__/__snapshots__/ResetPasswordForm-test.tsx.snap
@@ -0,0 +1,71 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`renders correctly 1`] = `
+<form
+ onSubmit={[Function]}
+>
+ <MandatoryFieldsExplanation
+ className="form-field"
+ />
+ <div
+ className="form-field"
+ >
+ <label
+ htmlFor="old_password"
+ >
+ my_profile.password.old
+ <MandatoryFieldMarker />
+ </label>
+ <input
+ autoComplete="off"
+ id="old_password"
+ name="old_password"
+ required={true}
+ type="password"
+ />
+ </div>
+ <div
+ className="form-field"
+ >
+ <label
+ htmlFor="password"
+ >
+ my_profile.password.new
+ <MandatoryFieldMarker />
+ </label>
+ <input
+ autoComplete="off"
+ id="password"
+ name="password"
+ required={true}
+ type="password"
+ />
+ </div>
+ <div
+ className="form-field"
+ >
+ <label
+ htmlFor="password_confirmation"
+ >
+ my_profile.password.confirm
+ <MandatoryFieldMarker />
+ </label>
+ <input
+ autoComplete="off"
+ id="password_confirmation"
+ name="password_confirmation"
+ required={true}
+ type="password"
+ />
+ </div>
+ <div
+ className="form-field"
+ >
+ <SubmitButton
+ id="change-password"
+ >
+ update_verb
+ </SubmitButton>
+ </div>
+</form>
+`;