*/
.login-page {
padding-top: 10vh;
+ max-width: 300px;
+ margin: 0 auto;
}
.login-title {
- margin-bottom: 40px;
line-height: 1.5;
font-size: 24px;
font-weight: 300;
- text-align: center;
}
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
import * as React from 'react';
+import { connect } from 'react-redux';
+import { Alert } from 'sonar-ui-common/components/ui/Alert';
import { translate } from 'sonar-ui-common/helpers/l10n';
import GlobalMessagesContainer from '../../../app/components/GlobalMessagesContainer';
+import { Store } from '../../../store/rootReducer';
import './Login.css';
import LoginForm from './LoginForm';
import OAuthProviders from './OAuthProviders';
-interface Props {
+export interface LoginProps {
+ authorizationError?: boolean;
+ authenticationError?: boolean;
identityProviders: T.IdentityProvider[];
onSubmit: (login: string, password: string) => Promise<void>;
returnTo: string;
}
-export default function Login({ identityProviders, onSubmit, returnTo }: Props) {
+export function Login(props: LoginProps) {
+ const { authorizationError, authenticationError, identityProviders, returnTo } = props;
+ const displayError = authorizationError || authenticationError;
+
return (
<div className="login-page" id="login_form">
- <h1 className="login-title text-center">{translate('login.login_to_sonarqube')}</h1>
+ <h1 className="login-title text-center huge-spacer-bottom">
+ {translate('login.login_to_sonarqube')}
+ </h1>
- <GlobalMessagesContainer />
+ {displayError && (
+ <Alert className="huge-spacer-bottom" display="block" variant="error">
+ {translate('login.unauthorized_access_alert')}
+ </Alert>
+ )}
{identityProviders.length > 0 && (
<OAuthProviders identityProviders={identityProviders} returnTo={returnTo} />
)}
- <LoginForm collapsed={identityProviders.length > 0} onSubmit={onSubmit} returnTo={returnTo} />
+ <LoginForm
+ collapsed={identityProviders.length > 0}
+ onSubmit={props.onSubmit}
+ returnTo={returnTo}
+ />
+
+ <GlobalMessagesContainer />
</div>
);
}
+
+const mapStateToProps = (state: Store) => ({
+ authorizationError: state.appState.authorizationError,
+ authenticationError: state.appState.authenticationError
+});
+
+export default connect(mapStateToProps)(Login);
*/
import { shallow } from 'enzyme';
import * as React from 'react';
-import Login from '../Login';
+import { Login, LoginProps } from '../Login';
-const identityProvider = {
- backgroundColor: '#000',
- iconPath: '/some/path',
- key: 'foo',
- name: 'foo'
-};
-
-it('logs in with form alone', () => {
- const wrapper = shallow(<Login identityProviders={[]} onSubmit={jest.fn()} returnTo="" />);
- expect(wrapper).toMatchSnapshot();
+it('should render correctly', () => {
+ expect(shallowRender()).toMatchSnapshot('with identity providers');
+ expect(shallowRender({ identityProviders: [] })).toMatchSnapshot(
+ 'without any identity providers'
+ );
+ expect(shallowRender({ authorizationError: true })).toMatchSnapshot('with authorization error');
});
-it('logs in with identity provider', () => {
- const wrapper = shallow(
- <Login identityProviders={[identityProvider]} onSubmit={jest.fn()} returnTo="" />
+function shallowRender(props: Partial<LoginProps> = {}) {
+ return shallow<LoginProps>(
+ <Login
+ identityProviders={[
+ {
+ backgroundColor: '#000',
+ iconPath: '/some/path',
+ key: 'foo',
+ name: 'foo'
+ }
+ ]}
+ onSubmit={jest.fn()}
+ returnTo=""
+ {...props}
+ />
);
- expect(wrapper).toMatchSnapshot();
-});
+}
// Jest Snapshot v1, https://goo.gl/fbAQLP
-exports[`logs in with form alone 1`] = `
+exports[`should render correctly: with authorization error 1`] = `
<div
className="login-page"
id="login_form"
>
<h1
- className="login-title text-center"
+ className="login-title text-center huge-spacer-bottom"
>
login.login_to_sonarqube
</h1>
- <Connect(GlobalMessages) />
+ <Alert
+ className="huge-spacer-bottom"
+ display="block"
+ variant="error"
+ >
+ login.unauthorized_access_alert
+ </Alert>
+ <OAuthProviders
+ identityProviders={
+ Array [
+ Object {
+ "backgroundColor": "#000",
+ "iconPath": "/some/path",
+ "key": "foo",
+ "name": "foo",
+ },
+ ]
+ }
+ returnTo=""
+ />
<LoginForm
- collapsed={false}
+ collapsed={true}
onSubmit={[MockFunction]}
returnTo=""
/>
+ <Connect(GlobalMessages) />
</div>
`;
-exports[`logs in with identity provider 1`] = `
+exports[`should render correctly: with identity providers 1`] = `
<div
className="login-page"
id="login_form"
>
<h1
- className="login-title text-center"
+ className="login-title text-center huge-spacer-bottom"
>
login.login_to_sonarqube
</h1>
- <Connect(GlobalMessages) />
<OAuthProviders
identityProviders={
Array [
onSubmit={[MockFunction]}
returnTo=""
/>
+ <Connect(GlobalMessages) />
+</div>
+`;
+
+exports[`should render correctly: without any identity providers 1`] = `
+<div
+ className="login-page"
+ id="login_form"
+>
+ <h1
+ className="login-title text-center huge-spacer-bottom"
+ >
+ login.login_to_sonarqube
+ </h1>
+ <LoginForm
+ collapsed={false}
+ onSubmit={[MockFunction]}
+ returnTo=""
+ />
+ <Connect(GlobalMessages) />
</div>
`;
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`should render correctly 1`] = `
-<Login
+<Connect(Login)
identityProviders={
Array [
Object {
*/
import { uniqueId } from 'lodash';
import { Dispatch } from 'redux';
-import { requireAuthorization } from './appState';
import { ActionType } from './utils/actions';
enum MessageLevel {
type Action =
| ActionType<typeof addGlobalMessageActionCreator, 'ADD_GLOBAL_MESSAGE'>
| ActionType<typeof closeGlobalMessage, 'CLOSE_GLOBAL_MESSAGE'>
- | ActionType<typeof closeAllGlobalMessages, 'CLOSE_ALL_GLOBAL_MESSAGES'>
- | ActionType<typeof requireAuthorization, 'REQUIRE_AUTHORIZATION'>;
+ | ActionType<typeof closeAllGlobalMessages, 'CLOSE_ALL_GLOBAL_MESSAGES'>;
function addGlobalMessage(message: string, level: MessageLevel) {
return (dispatch: Dispatch) => {
case 'ADD_GLOBAL_MESSAGE':
return [{ id: action.id, message: action.message, level: action.level }];
- case 'REQUIRE_AUTHORIZATION':
- // FIXME l10n
- return [
- {
- id: uniqueId('global-message-'),
- message:
- 'You are not authorized to access this page. ' +
- 'Please log in with more privileges and try again.',
- level: MessageLevel.Error
- }
- ];
-
case 'CLOSE_GLOBAL_MESSAGE':
return state.filter(message => message.id !== action.id);