[add] Jhipster base
This commit is contained in:
@@ -0,0 +1,51 @@
|
||||
import axios from 'axios';
|
||||
|
||||
import { REQUEST, SUCCESS, FAILURE } from 'app/shared/reducers/action-type.util';
|
||||
|
||||
export const ACTION_TYPES = {
|
||||
ACTIVATE_ACCOUNT: 'activate/ACTIVATE_ACCOUNT',
|
||||
RESET: 'activate/RESET'
|
||||
};
|
||||
|
||||
const initialState = {
|
||||
activationSuccess: false,
|
||||
activationFailure: false
|
||||
};
|
||||
|
||||
export type ActivateState = Readonly<typeof initialState>;
|
||||
|
||||
// Reducer
|
||||
export default (state: ActivateState = initialState, action): ActivateState => {
|
||||
switch (action.type) {
|
||||
case REQUEST(ACTION_TYPES.ACTIVATE_ACCOUNT):
|
||||
return {
|
||||
...state
|
||||
};
|
||||
case FAILURE(ACTION_TYPES.ACTIVATE_ACCOUNT):
|
||||
return {
|
||||
...state,
|
||||
activationFailure: true
|
||||
};
|
||||
case SUCCESS(ACTION_TYPES.ACTIVATE_ACCOUNT):
|
||||
return {
|
||||
...state,
|
||||
activationSuccess: true
|
||||
};
|
||||
case ACTION_TYPES.RESET:
|
||||
return {
|
||||
...initialState
|
||||
};
|
||||
default:
|
||||
return state;
|
||||
}
|
||||
};
|
||||
|
||||
// Actions
|
||||
export const activateAction = key => ({
|
||||
type: ACTION_TYPES.ACTIVATE_ACCOUNT,
|
||||
payload: axios.get('api/activate?key=' + key)
|
||||
});
|
||||
|
||||
export const reset = () => ({
|
||||
type: ACTION_TYPES.RESET
|
||||
});
|
||||
@@ -0,0 +1,66 @@
|
||||
import React from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
import { Link, RouteComponentProps } from 'react-router-dom';
|
||||
import { Row, Col, Alert } from 'reactstrap';
|
||||
|
||||
import { IRootState } from 'app/shared/reducers';
|
||||
import { activateAction, reset } from './activate.reducer';
|
||||
|
||||
const successAlert = (
|
||||
<Alert color="success">
|
||||
<strong>Your user account has been activated.</strong> Please
|
||||
<Link to="/login" className="alert-link">
|
||||
sign in
|
||||
</Link>.
|
||||
</Alert>
|
||||
);
|
||||
|
||||
const failureAlert = (
|
||||
<Alert color="danger">
|
||||
<strong>Your user could not be activated.</strong> Please use the registration form to sign up.
|
||||
</Alert>
|
||||
);
|
||||
|
||||
export interface IActivateProps extends StateProps, DispatchProps, RouteComponentProps<{ key: any }> {}
|
||||
|
||||
export class ActivatePage extends React.Component<IActivateProps> {
|
||||
componentWillUnmount() {
|
||||
this.props.reset();
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
const { key } = this.props.match.params;
|
||||
this.props.activateAction(key);
|
||||
}
|
||||
|
||||
render() {
|
||||
const { activationSuccess, activationFailure } = this.props;
|
||||
|
||||
return (
|
||||
<div>
|
||||
<Row className="justify-content-center">
|
||||
<Col md="8">
|
||||
<h1>Activation</h1>
|
||||
{activationSuccess ? successAlert : undefined}
|
||||
{activationFailure ? failureAlert : undefined}
|
||||
</Col>
|
||||
</Row>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const mapStateToProps = ({ activate }: IRootState) => ({
|
||||
activationSuccess: activate.activationSuccess,
|
||||
activationFailure: activate.activationFailure
|
||||
});
|
||||
|
||||
const mapDispatchToProps = { activateAction, reset };
|
||||
|
||||
type StateProps = ReturnType<typeof mapStateToProps>;
|
||||
type DispatchProps = typeof mapDispatchToProps;
|
||||
|
||||
export default connect(
|
||||
mapStateToProps,
|
||||
mapDispatchToProps
|
||||
)(ActivatePage);
|
||||
17
front-end/src/main/webapp/app/modules/account/index.tsx
Normal file
17
front-end/src/main/webapp/app/modules/account/index.tsx
Normal file
@@ -0,0 +1,17 @@
|
||||
import React from 'react';
|
||||
|
||||
import ErrorBoundaryRoute from 'app/shared/error/error-boundary-route';
|
||||
|
||||
import Settings from './settings/settings';
|
||||
import Password from './password/password';
|
||||
import Sessions from './sessions/sessions';
|
||||
|
||||
const Routes = ({ match }) => (
|
||||
<div>
|
||||
<ErrorBoundaryRoute path={`${match.url}/settings`} component={Settings} />
|
||||
<ErrorBoundaryRoute path={`${match.url}/password`} component={Password} />
|
||||
<ErrorBoundaryRoute path={`${match.url}/sessions`} component={Sessions} />
|
||||
</div>
|
||||
);
|
||||
|
||||
export default Routes;
|
||||
@@ -0,0 +1,95 @@
|
||||
import React from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
import { Alert, Col, Row, Button } from 'reactstrap';
|
||||
import { AvForm, AvField } from 'availity-reactstrap-validation';
|
||||
|
||||
import { RouteComponentProps } from 'react-router-dom';
|
||||
|
||||
import { IRootState } from 'app/shared/reducers';
|
||||
import { handlePasswordResetFinish, reset } from '../password-reset.reducer';
|
||||
import PasswordStrengthBar from 'app/shared/layout/password/password-strength-bar';
|
||||
|
||||
export interface IPasswordResetFinishProps extends DispatchProps, RouteComponentProps<{ key: string }> {}
|
||||
|
||||
export interface IPasswordResetFinishState {
|
||||
password: string;
|
||||
key: string;
|
||||
}
|
||||
|
||||
export class PasswordResetFinishPage extends React.Component<IPasswordResetFinishProps, IPasswordResetFinishState> {
|
||||
state: IPasswordResetFinishState = {
|
||||
password: '',
|
||||
key: this.props.match.params.key
|
||||
};
|
||||
|
||||
componentWillUnmount() {
|
||||
this.props.reset();
|
||||
}
|
||||
|
||||
handleValidSubmit = (event, values) => {
|
||||
this.props.handlePasswordResetFinish(this.state.key, values.newPassword);
|
||||
};
|
||||
|
||||
updatePassword = event => {
|
||||
this.setState({ password: event.target.value });
|
||||
};
|
||||
|
||||
getResetForm() {
|
||||
return (
|
||||
<AvForm onValidSubmit={this.handleValidSubmit}>
|
||||
<AvField
|
||||
name="newPassword"
|
||||
label="New password"
|
||||
placeholder={'New password'}
|
||||
type="password"
|
||||
validate={{
|
||||
required: { value: true, errorMessage: 'Your password is required.' },
|
||||
minLength: { value: 4, errorMessage: 'Your password is required to be at least 4 characters.' },
|
||||
maxLength: { value: 50, errorMessage: 'Your password cannot be longer than 50 characters.' }
|
||||
}}
|
||||
onChange={this.updatePassword}
|
||||
/>
|
||||
<PasswordStrengthBar password={this.state.password} />
|
||||
<AvField
|
||||
name="confirmPassword"
|
||||
label="New password confirmation"
|
||||
placeholder="Confirm the new password"
|
||||
type="password"
|
||||
validate={{
|
||||
required: { value: true, errorMessage: 'Your confirmation password is required.' },
|
||||
minLength: { value: 4, errorMessage: 'Your confirmation password is required to be at least 4 characters.' },
|
||||
maxLength: { value: 50, errorMessage: 'Your confirmation password cannot be longer than 50 characters.' },
|
||||
match: { value: 'newPassword', errorMessage: 'The password and its confirmation do not match!' }
|
||||
}}
|
||||
/>
|
||||
<Button color="success" type="submit">
|
||||
Validate new password
|
||||
</Button>
|
||||
</AvForm>
|
||||
);
|
||||
}
|
||||
|
||||
render() {
|
||||
const { key } = this.state;
|
||||
|
||||
return (
|
||||
<div>
|
||||
<Row className="justify-content-center">
|
||||
<Col md="4">
|
||||
<h1>Reset password</h1>
|
||||
<div>{key ? this.getResetForm() : null}</div>
|
||||
</Col>
|
||||
</Row>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const mapDispatchToProps = { handlePasswordResetFinish, reset };
|
||||
|
||||
type DispatchProps = typeof mapDispatchToProps;
|
||||
|
||||
export default connect(
|
||||
null,
|
||||
mapDispatchToProps
|
||||
)(PasswordResetFinishPage);
|
||||
@@ -0,0 +1,61 @@
|
||||
import React from 'react';
|
||||
|
||||
import { connect } from 'react-redux';
|
||||
import { AvForm, AvField } from 'availity-reactstrap-validation';
|
||||
import { Button, Alert, Col, Row } from 'reactstrap';
|
||||
|
||||
import { IRootState } from 'app/shared/reducers';
|
||||
import { handlePasswordResetInit, reset } from '../password-reset.reducer';
|
||||
|
||||
export type IPasswordResetInitProps = DispatchProps;
|
||||
|
||||
export class PasswordResetInit extends React.Component<IPasswordResetInitProps> {
|
||||
componentWillUnmount() {
|
||||
this.props.reset();
|
||||
}
|
||||
|
||||
handleValidSubmit = (event, values) => {
|
||||
this.props.handlePasswordResetInit(values.email);
|
||||
event.preventDefault();
|
||||
};
|
||||
|
||||
render() {
|
||||
return (
|
||||
<div>
|
||||
<Row className="justify-content-center">
|
||||
<Col md="8">
|
||||
<h1>Reset your password</h1>
|
||||
<Alert color="warning">
|
||||
<p>Enter the email address you used to register</p>
|
||||
</Alert>
|
||||
<AvForm onValidSubmit={this.handleValidSubmit}>
|
||||
<AvField
|
||||
name="email"
|
||||
label="Email"
|
||||
placeholder="Your email"
|
||||
type="email"
|
||||
validate={{
|
||||
required: { value: true, errorMessage: 'Your email is required.' },
|
||||
minLength: { value: 5, errorMessage: 'Your email is required to be at least 5 characters.' },
|
||||
maxLength: { value: 254, errorMessage: 'Your email cannot be longer than 50 characters.' }
|
||||
}}
|
||||
/>
|
||||
<Button color="primary" type="submit">
|
||||
Reset password
|
||||
</Button>
|
||||
</AvForm>
|
||||
</Col>
|
||||
</Row>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const mapDispatchToProps = { handlePasswordResetInit, reset };
|
||||
|
||||
type DispatchProps = typeof mapDispatchToProps;
|
||||
|
||||
export default connect(
|
||||
null,
|
||||
mapDispatchToProps
|
||||
)(PasswordResetInit);
|
||||
@@ -0,0 +1,74 @@
|
||||
import axios from 'axios';
|
||||
|
||||
import { REQUEST, SUCCESS, FAILURE } from 'app/shared/reducers/action-type.util';
|
||||
|
||||
export const ACTION_TYPES = {
|
||||
RESET_PASSWORD_INIT: 'passwordReset/RESET_PASSWORD_INIT',
|
||||
RESET_PASSWORD_FINISH: 'passwordReset/RESET_PASSWORD_FINISH',
|
||||
RESET: 'passwordReset/RESET'
|
||||
};
|
||||
|
||||
const initialState = {
|
||||
loading: false,
|
||||
resetPasswordSuccess: false,
|
||||
resetPasswordFailure: false
|
||||
};
|
||||
|
||||
export type PasswordResetState = Readonly<typeof initialState>;
|
||||
|
||||
// Reducer
|
||||
export default (state: PasswordResetState = initialState, action): PasswordResetState => {
|
||||
switch (action.type) {
|
||||
case REQUEST(ACTION_TYPES.RESET_PASSWORD_FINISH):
|
||||
case REQUEST(ACTION_TYPES.RESET_PASSWORD_INIT):
|
||||
return {
|
||||
...state,
|
||||
loading: true
|
||||
};
|
||||
case FAILURE(ACTION_TYPES.RESET_PASSWORD_FINISH):
|
||||
case FAILURE(ACTION_TYPES.RESET_PASSWORD_INIT):
|
||||
return {
|
||||
...initialState,
|
||||
loading: false,
|
||||
resetPasswordFailure: true
|
||||
};
|
||||
case SUCCESS(ACTION_TYPES.RESET_PASSWORD_FINISH):
|
||||
case SUCCESS(ACTION_TYPES.RESET_PASSWORD_INIT):
|
||||
return {
|
||||
...initialState,
|
||||
loading: false,
|
||||
resetPasswordSuccess: true
|
||||
};
|
||||
case ACTION_TYPES.RESET:
|
||||
return {
|
||||
...initialState
|
||||
};
|
||||
default:
|
||||
return state;
|
||||
}
|
||||
};
|
||||
|
||||
const apiUrl = 'api/account/reset-password';
|
||||
|
||||
// Actions
|
||||
export const handlePasswordResetInit = mail => ({
|
||||
type: ACTION_TYPES.RESET_PASSWORD_INIT,
|
||||
// If the content-type isn't set that way, axios will try to encode the body and thus modify the data sent to the server.
|
||||
payload: axios.post(`${apiUrl}/init`, mail, { headers: { ['Content-Type']: 'text/plain' } }),
|
||||
meta: {
|
||||
successMessage: 'Check your emails for details on how to reset your password.',
|
||||
errorMessage: "<strong>Email address isn't registered!</strong> Please check and try again"
|
||||
}
|
||||
});
|
||||
|
||||
export const handlePasswordResetFinish = (key, newPassword) => ({
|
||||
type: ACTION_TYPES.RESET_PASSWORD_FINISH,
|
||||
payload: axios.post(`${apiUrl}/finish`, { key, newPassword }),
|
||||
meta: {
|
||||
successMessage: '<strong>Your password has been reset.</strong> Please '
|
||||
}
|
||||
});
|
||||
|
||||
export const reset = () => ({
|
||||
type: ACTION_TYPES.RESET
|
||||
});
|
||||
@@ -0,0 +1,66 @@
|
||||
import axios from 'axios';
|
||||
|
||||
import { REQUEST, SUCCESS, FAILURE } from 'app/shared/reducers/action-type.util';
|
||||
|
||||
export const ACTION_TYPES = {
|
||||
UPDATE_PASSWORD: 'account/UPDATE_PASSWORD',
|
||||
RESET: 'account/RESET'
|
||||
};
|
||||
|
||||
const initialState = {
|
||||
loading: false,
|
||||
errorMessage: null,
|
||||
updateSuccess: false,
|
||||
updateFailure: false
|
||||
};
|
||||
|
||||
export type PasswordState = Readonly<typeof initialState>;
|
||||
|
||||
// Reducer
|
||||
export default (state: PasswordState = initialState, action): PasswordState => {
|
||||
switch (action.type) {
|
||||
case REQUEST(ACTION_TYPES.UPDATE_PASSWORD):
|
||||
return {
|
||||
...initialState,
|
||||
errorMessage: null,
|
||||
updateSuccess: false,
|
||||
loading: true
|
||||
};
|
||||
case FAILURE(ACTION_TYPES.UPDATE_PASSWORD):
|
||||
return {
|
||||
...initialState,
|
||||
loading: false,
|
||||
updateSuccess: false,
|
||||
updateFailure: true
|
||||
};
|
||||
case SUCCESS(ACTION_TYPES.UPDATE_PASSWORD):
|
||||
return {
|
||||
...initialState,
|
||||
loading: false,
|
||||
updateSuccess: true,
|
||||
updateFailure: false
|
||||
};
|
||||
case ACTION_TYPES.RESET:
|
||||
return {
|
||||
...initialState
|
||||
};
|
||||
default:
|
||||
return state;
|
||||
}
|
||||
};
|
||||
|
||||
// Actions
|
||||
const apiUrl = 'api/account';
|
||||
|
||||
export const savePassword = (currentPassword, newPassword) => ({
|
||||
type: ACTION_TYPES.UPDATE_PASSWORD,
|
||||
payload: axios.post(`${apiUrl}/change-password`, { currentPassword, newPassword }),
|
||||
meta: {
|
||||
successMessage: '<strong>Password changed!</strong>',
|
||||
errorMessage: '<strong>An error has occurred!</strong> The password could not be changed.'
|
||||
}
|
||||
});
|
||||
|
||||
export const reset = () => ({
|
||||
type: ACTION_TYPES.RESET
|
||||
});
|
||||
@@ -0,0 +1,119 @@
|
||||
import React from 'react';
|
||||
|
||||
import { connect } from 'react-redux';
|
||||
import { AvForm, AvField } from 'availity-reactstrap-validation';
|
||||
import { Row, Col, Button } from 'reactstrap';
|
||||
|
||||
import { IRootState } from 'app/shared/reducers';
|
||||
import { getSession } from 'app/shared/reducers/authentication';
|
||||
import PasswordStrengthBar from 'app/shared/layout/password/password-strength-bar';
|
||||
import { savePassword, reset } from './password.reducer';
|
||||
|
||||
export interface IUserPasswordProps extends StateProps, DispatchProps {}
|
||||
|
||||
export interface IUserPasswordState {
|
||||
password: string;
|
||||
}
|
||||
|
||||
export class PasswordPage extends React.Component<IUserPasswordProps, IUserPasswordState> {
|
||||
state: IUserPasswordState = {
|
||||
password: ''
|
||||
};
|
||||
|
||||
componentDidMount() {
|
||||
this.props.reset();
|
||||
this.props.getSession();
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
this.props.reset();
|
||||
}
|
||||
|
||||
handleValidSubmit = (event, values) => {
|
||||
this.props.savePassword(values.currentPassword, values.newPassword);
|
||||
};
|
||||
|
||||
updatePassword = event => {
|
||||
this.setState({ password: event.target.value });
|
||||
};
|
||||
|
||||
render() {
|
||||
const { account } = this.props;
|
||||
|
||||
return (
|
||||
<div>
|
||||
<Row className="justify-content-center">
|
||||
<Col md="8">
|
||||
<h2 id="password-title">Password for {account.login}</h2>
|
||||
<AvForm id="password-form" onValidSubmit={this.handleValidSubmit}>
|
||||
<AvField
|
||||
name="currentPassword"
|
||||
label="Current password"
|
||||
placeholder="Current password"
|
||||
type="password"
|
||||
validate={{
|
||||
required: { value: true, errorMessage: 'Your password is required.' }
|
||||
}}
|
||||
/>
|
||||
<AvField
|
||||
name="newPassword"
|
||||
label="New password"
|
||||
placeholder="New password"
|
||||
type="password"
|
||||
validate={{
|
||||
required: { value: true, errorMessage: 'Your password is required.' },
|
||||
minLength: { value: 4, errorMessage: 'Your password is required to be at least 4 characters.' },
|
||||
maxLength: { value: 50, errorMessage: 'Your password cannot be longer than 50 characters.' }
|
||||
}}
|
||||
onChange={this.updatePassword}
|
||||
/>
|
||||
<PasswordStrengthBar password={this.state.password} />
|
||||
<AvField
|
||||
name="confirmPassword"
|
||||
label="New password confirmation"
|
||||
placeholder="Confirm the new password"
|
||||
type="password"
|
||||
validate={{
|
||||
required: {
|
||||
value: true,
|
||||
errorMessage: 'Your confirmation password is required.'
|
||||
},
|
||||
minLength: {
|
||||
value: 4,
|
||||
errorMessage: 'Your confirmation password is required to be at least 4 characters.'
|
||||
},
|
||||
maxLength: {
|
||||
value: 50,
|
||||
errorMessage: 'Your confirmation password cannot be longer than 50 characters.'
|
||||
},
|
||||
match: {
|
||||
value: 'newPassword',
|
||||
errorMessage: 'The password and its confirmation do not match!'
|
||||
}
|
||||
}}
|
||||
/>
|
||||
<Button color="success" type="submit">
|
||||
Save
|
||||
</Button>
|
||||
</AvForm>
|
||||
</Col>
|
||||
</Row>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const mapStateToProps = ({ authentication }: IRootState) => ({
|
||||
account: authentication.account,
|
||||
isAuthenticated: authentication.isAuthenticated
|
||||
});
|
||||
|
||||
const mapDispatchToProps = { getSession, savePassword, reset };
|
||||
|
||||
type StateProps = ReturnType<typeof mapStateToProps>;
|
||||
type DispatchProps = typeof mapDispatchToProps;
|
||||
|
||||
export default connect(
|
||||
mapStateToProps,
|
||||
mapDispatchToProps
|
||||
)(PasswordPage);
|
||||
@@ -0,0 +1,58 @@
|
||||
import axios from 'axios';
|
||||
|
||||
import { REQUEST, SUCCESS, FAILURE } from 'app/shared/reducers/action-type.util';
|
||||
|
||||
export const ACTION_TYPES = {
|
||||
CREATE_ACCOUNT: 'register/CREATE_ACCOUNT',
|
||||
RESET: 'register/RESET'
|
||||
};
|
||||
|
||||
const initialState = {
|
||||
loading: false,
|
||||
registrationSuccess: false,
|
||||
registrationFailure: false,
|
||||
errorMessage: null
|
||||
};
|
||||
|
||||
export type RegisterState = Readonly<typeof initialState>;
|
||||
|
||||
// Reducer
|
||||
export default (state: RegisterState = initialState, action): RegisterState => {
|
||||
switch (action.type) {
|
||||
case REQUEST(ACTION_TYPES.CREATE_ACCOUNT):
|
||||
return {
|
||||
...state,
|
||||
loading: true
|
||||
};
|
||||
case FAILURE(ACTION_TYPES.CREATE_ACCOUNT):
|
||||
return {
|
||||
...initialState,
|
||||
registrationFailure: true,
|
||||
errorMessage: action.payload.response.data.errorKey
|
||||
};
|
||||
case SUCCESS(ACTION_TYPES.CREATE_ACCOUNT):
|
||||
return {
|
||||
...initialState,
|
||||
registrationSuccess: true
|
||||
};
|
||||
case ACTION_TYPES.RESET:
|
||||
return {
|
||||
...initialState
|
||||
};
|
||||
default:
|
||||
return state;
|
||||
}
|
||||
};
|
||||
|
||||
// Actions
|
||||
export const handleRegister = (login, email, password, langKey = 'en') => ({
|
||||
type: ACTION_TYPES.CREATE_ACCOUNT,
|
||||
payload: axios.post('api/register', { login, email, password, langKey }),
|
||||
meta: {
|
||||
successMessage: '<strong>Registration saved!</strong> Please check your email for confirmation.'
|
||||
}
|
||||
});
|
||||
|
||||
export const reset = () => ({
|
||||
type: ACTION_TYPES.RESET
|
||||
});
|
||||
@@ -0,0 +1,120 @@
|
||||
import React from 'react';
|
||||
|
||||
import { connect } from 'react-redux';
|
||||
import { AvForm, AvField } from 'availity-reactstrap-validation';
|
||||
import { Row, Col, Alert, Button } from 'reactstrap';
|
||||
|
||||
import PasswordStrengthBar from 'app/shared/layout/password/password-strength-bar';
|
||||
import { IRootState } from 'app/shared/reducers';
|
||||
import { handleRegister, reset } from './register.reducer';
|
||||
|
||||
export type IRegisterProps = DispatchProps;
|
||||
|
||||
export interface IRegisterState {
|
||||
password: string;
|
||||
}
|
||||
|
||||
export class RegisterPage extends React.Component<IRegisterProps, IRegisterState> {
|
||||
state: IRegisterState = {
|
||||
password: ''
|
||||
};
|
||||
|
||||
componentWillUnmount() {
|
||||
this.props.reset();
|
||||
}
|
||||
|
||||
handleValidSubmit = (event, values) => {
|
||||
this.props.handleRegister(values.username, values.email, values.firstPassword);
|
||||
event.preventDefault();
|
||||
};
|
||||
|
||||
updatePassword = event => {
|
||||
this.setState({ password: event.target.value });
|
||||
};
|
||||
|
||||
render() {
|
||||
return (
|
||||
<div>
|
||||
<Row className="justify-content-center">
|
||||
<Col md="8">
|
||||
<h1 id="register-title">Registration</h1>
|
||||
</Col>
|
||||
</Row>
|
||||
<Row className="justify-content-center">
|
||||
<Col md="8">
|
||||
<AvForm id="register-form" onValidSubmit={this.handleValidSubmit}>
|
||||
<AvField
|
||||
name="username"
|
||||
label="Username"
|
||||
placeholder="Your username"
|
||||
validate={{
|
||||
required: { value: true, errorMessage: 'Your username is required.' },
|
||||
pattern: { value: '^[_.@A-Za-z0-9-]*$', errorMessage: 'Your username can only contain letters and digits.' },
|
||||
minLength: { value: 1, errorMessage: 'Your username is required to be at least 1 character.' },
|
||||
maxLength: { value: 50, errorMessage: 'Your username cannot be longer than 50 characters.' }
|
||||
}}
|
||||
/>
|
||||
<AvField
|
||||
name="email"
|
||||
label="Email"
|
||||
placeholder="Your email"
|
||||
type="email"
|
||||
validate={{
|
||||
required: { value: true, errorMessage: 'Your email is required.' },
|
||||
minLength: { value: 5, errorMessage: 'Your email is required to be at least 5 characters.' },
|
||||
maxLength: { value: 254, errorMessage: 'Your email cannot be longer than 50 characters.' }
|
||||
}}
|
||||
/>
|
||||
<AvField
|
||||
name="firstPassword"
|
||||
label="New password"
|
||||
placeholder="New password"
|
||||
type="password"
|
||||
onChange={this.updatePassword}
|
||||
validate={{
|
||||
required: { value: true, errorMessage: 'Your password is required.' },
|
||||
minLength: { value: 4, errorMessage: 'Your password is required to be at least 4 characters.' },
|
||||
maxLength: { value: 50, errorMessage: 'Your password cannot be longer than 50 characters.' }
|
||||
}}
|
||||
/>
|
||||
<PasswordStrengthBar password={this.state.password} />
|
||||
<AvField
|
||||
name="secondPassword"
|
||||
label="New password confirmation"
|
||||
placeholder="Confirm the new password"
|
||||
type="password"
|
||||
validate={{
|
||||
required: { value: true, errorMessage: 'Your confirmation password is required.' },
|
||||
minLength: { value: 4, errorMessage: 'Your confirmation password is required to be at least 4 characters.' },
|
||||
maxLength: { value: 50, errorMessage: 'Your confirmation password cannot be longer than 50 characters.' },
|
||||
match: { value: 'firstPassword', errorMessage: 'The password and its confirmation do not match!' }
|
||||
}}
|
||||
/>
|
||||
<Button id="register-submit" color="primary" type="submit">
|
||||
Register
|
||||
</Button>
|
||||
</AvForm>
|
||||
<p> </p>
|
||||
<Alert color="warning">
|
||||
<span>If you want to</span>
|
||||
<a className="alert-link"> sign in</a>
|
||||
<span>
|
||||
, you can try the default accounts:
|
||||
<br />- Administrator (login="admin" and password="admin")
|
||||
<br />- User (login="user" and password="user").
|
||||
</span>
|
||||
</Alert>
|
||||
</Col>
|
||||
</Row>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const mapDispatchToProps = { handleRegister, reset };
|
||||
type DispatchProps = typeof mapDispatchToProps;
|
||||
|
||||
export default connect(
|
||||
null,
|
||||
mapDispatchToProps
|
||||
)(RegisterPage);
|
||||
@@ -0,0 +1,67 @@
|
||||
import axios from 'axios';
|
||||
|
||||
import { REQUEST, SUCCESS, FAILURE } from 'app/shared/reducers/action-type.util';
|
||||
|
||||
export const ACTION_TYPES = {
|
||||
FIND_ALL: 'sessions/FIND_ALL',
|
||||
INVALIDATE: 'sessions/INVALIDATE'
|
||||
};
|
||||
|
||||
const initialState = {
|
||||
loading: false,
|
||||
sessions: [],
|
||||
updateSuccess: false,
|
||||
updateFailure: false
|
||||
};
|
||||
|
||||
export type SessionsState = Readonly<typeof initialState>;
|
||||
|
||||
// Reducer
|
||||
export default (state: SessionsState = initialState, action): SessionsState => {
|
||||
switch (action.type) {
|
||||
case REQUEST(ACTION_TYPES.FIND_ALL):
|
||||
case REQUEST(ACTION_TYPES.INVALIDATE):
|
||||
return {
|
||||
...state,
|
||||
loading: true
|
||||
};
|
||||
case FAILURE(ACTION_TYPES.FIND_ALL):
|
||||
return {
|
||||
...state,
|
||||
loading: false
|
||||
};
|
||||
case FAILURE(ACTION_TYPES.INVALIDATE):
|
||||
return {
|
||||
...state,
|
||||
loading: false,
|
||||
updateFailure: true
|
||||
};
|
||||
case SUCCESS(ACTION_TYPES.FIND_ALL):
|
||||
return {
|
||||
...state,
|
||||
loading: false,
|
||||
sessions: action.payload.data
|
||||
};
|
||||
case SUCCESS(ACTION_TYPES.INVALIDATE):
|
||||
return {
|
||||
...state,
|
||||
loading: false,
|
||||
updateSuccess: true
|
||||
};
|
||||
|
||||
default:
|
||||
return state;
|
||||
}
|
||||
};
|
||||
|
||||
// Actions
|
||||
const apiURL = '/api/account/sessions/';
|
||||
export const findAll = () => ({
|
||||
type: ACTION_TYPES.FIND_ALL,
|
||||
payload: axios.get(apiURL)
|
||||
});
|
||||
|
||||
export const invalidateSession = series => ({
|
||||
type: ACTION_TYPES.INVALIDATE,
|
||||
payload: axios.delete(`${apiURL}${series}`)
|
||||
});
|
||||
@@ -0,0 +1,99 @@
|
||||
import React from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
import { Alert, Table, Button } from 'reactstrap';
|
||||
|
||||
import { getSession } from 'app/shared/reducers/authentication';
|
||||
import { IRootState } from 'app/shared/reducers';
|
||||
import { findAll, invalidateSession } from './sessions.reducer';
|
||||
|
||||
export interface ISessionsProps extends StateProps, DispatchProps {}
|
||||
|
||||
export class SessionsPage extends React.Component<ISessionsProps> {
|
||||
componentDidMount() {
|
||||
this.props.getSession();
|
||||
this.props.findAll();
|
||||
}
|
||||
|
||||
doSessionInvalidation = series => () => {
|
||||
this.props.invalidateSession(series);
|
||||
this.props.findAll();
|
||||
};
|
||||
|
||||
refreshList = () => {
|
||||
this.props.findAll();
|
||||
};
|
||||
|
||||
render() {
|
||||
const { account, sessions, updateSuccess, updateFailure } = this.props;
|
||||
return (
|
||||
<div>
|
||||
<h2>
|
||||
Active sessions for [<b>{account.login}</b>]
|
||||
</h2>
|
||||
|
||||
{updateSuccess ? (
|
||||
<Alert color="success">
|
||||
<strong>Session invalidated!</strong>
|
||||
</Alert>
|
||||
) : null}
|
||||
|
||||
{updateFailure ? (
|
||||
<Alert color="danger">
|
||||
<span>
|
||||
<strong>An error has occured!</strong> The session could not be invalidated.
|
||||
</span>
|
||||
</Alert>
|
||||
) : null}
|
||||
|
||||
<Button color="primary" onClick={this.refreshList}>
|
||||
Refresh
|
||||
</Button>
|
||||
|
||||
<div className="table-responsive">
|
||||
<Table className="table-striped">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>IP Address</th>
|
||||
<th>User agent</th>
|
||||
<th>Date</th>
|
||||
<th />
|
||||
</tr>
|
||||
</thead>
|
||||
|
||||
<tbody>
|
||||
{sessions.map(s => (
|
||||
<tr>
|
||||
<td>{s.ipAddress}</td>
|
||||
<td>{s.userAgent}</td>
|
||||
<td>{s.tokenDate}</td>
|
||||
<td>
|
||||
<Button color="primary" onClick={this.doSessionInvalidation(s.series)}>
|
||||
Invalidate
|
||||
</Button>
|
||||
</td>
|
||||
</tr>
|
||||
))}
|
||||
</tbody>
|
||||
</Table>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const mapStateToProps = ({ authentication, sessions }: IRootState) => ({
|
||||
account: authentication.account,
|
||||
sessions: sessions.sessions,
|
||||
updateSuccess: sessions.updateSuccess,
|
||||
updateFailure: sessions.updateFailure
|
||||
});
|
||||
|
||||
const mapDispatchToProps = { getSession, findAll, invalidateSession };
|
||||
|
||||
type StateProps = ReturnType<typeof mapStateToProps>;
|
||||
type DispatchProps = typeof mapDispatchToProps;
|
||||
|
||||
export default connect(
|
||||
mapStateToProps,
|
||||
mapDispatchToProps
|
||||
)(SessionsPage);
|
||||
@@ -0,0 +1,69 @@
|
||||
import axios from 'axios';
|
||||
|
||||
import { REQUEST, SUCCESS, FAILURE } from 'app/shared/reducers/action-type.util';
|
||||
import { getSession } from 'app/shared/reducers/authentication';
|
||||
|
||||
export const ACTION_TYPES = {
|
||||
UPDATE_ACCOUNT: 'account/UPDATE_ACCOUNT',
|
||||
RESET: 'account/RESET'
|
||||
};
|
||||
|
||||
const initialState = {
|
||||
loading: false,
|
||||
errorMessage: null,
|
||||
updateSuccess: false,
|
||||
updateFailure: false
|
||||
};
|
||||
|
||||
export type SettingsState = Readonly<typeof initialState>;
|
||||
|
||||
// Reducer
|
||||
export default (state: SettingsState = initialState, action): SettingsState => {
|
||||
switch (action.type) {
|
||||
case REQUEST(ACTION_TYPES.UPDATE_ACCOUNT):
|
||||
return {
|
||||
...state,
|
||||
errorMessage: null,
|
||||
updateSuccess: false,
|
||||
loading: true
|
||||
};
|
||||
case FAILURE(ACTION_TYPES.UPDATE_ACCOUNT):
|
||||
return {
|
||||
...state,
|
||||
loading: false,
|
||||
updateSuccess: false,
|
||||
updateFailure: true
|
||||
};
|
||||
case SUCCESS(ACTION_TYPES.UPDATE_ACCOUNT):
|
||||
return {
|
||||
...state,
|
||||
loading: false,
|
||||
updateSuccess: true,
|
||||
updateFailure: false
|
||||
};
|
||||
case ACTION_TYPES.RESET:
|
||||
return {
|
||||
...initialState
|
||||
};
|
||||
default:
|
||||
return state;
|
||||
}
|
||||
};
|
||||
|
||||
// Actions
|
||||
const apiUrl = 'api/account';
|
||||
|
||||
export const saveAccountSettings = account => async dispatch => {
|
||||
await dispatch({
|
||||
type: ACTION_TYPES.UPDATE_ACCOUNT,
|
||||
payload: axios.post(apiUrl, account),
|
||||
meta: {
|
||||
successMessage: '<strong>Settings saved!</strong>'
|
||||
}
|
||||
});
|
||||
dispatch(getSession());
|
||||
};
|
||||
|
||||
export const reset = () => ({
|
||||
type: ACTION_TYPES.RESET
|
||||
});
|
||||
@@ -0,0 +1,110 @@
|
||||
import React from 'react';
|
||||
import { Button, Col, Alert, Row } from 'reactstrap';
|
||||
import { connect } from 'react-redux';
|
||||
|
||||
import { AvForm, AvField } from 'availity-reactstrap-validation';
|
||||
|
||||
import { IRootState } from 'app/shared/reducers';
|
||||
import { getSession } from 'app/shared/reducers/authentication';
|
||||
import { saveAccountSettings, reset } from './settings.reducer';
|
||||
|
||||
export interface IUserSettingsProps extends StateProps, DispatchProps {}
|
||||
|
||||
export interface IUserSettingsState {
|
||||
account: any;
|
||||
}
|
||||
|
||||
export class SettingsPage extends React.Component<IUserSettingsProps, IUserSettingsState> {
|
||||
componentDidMount() {
|
||||
this.props.getSession();
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
this.props.reset();
|
||||
}
|
||||
|
||||
handleValidSubmit = (event, values) => {
|
||||
const account = {
|
||||
...this.props.account,
|
||||
...values
|
||||
};
|
||||
|
||||
this.props.saveAccountSettings(account);
|
||||
event.persist();
|
||||
};
|
||||
|
||||
render() {
|
||||
const { account } = this.props;
|
||||
|
||||
return (
|
||||
<div>
|
||||
<Row className="justify-content-center">
|
||||
<Col md="8">
|
||||
<h2 id="settings-title">User settings for {account.login}</h2>
|
||||
<AvForm id="settings-form" onValidSubmit={this.handleValidSubmit}>
|
||||
{/* First name */}
|
||||
<AvField
|
||||
className="form-control"
|
||||
name="firstName"
|
||||
label="First Name"
|
||||
id="firstName"
|
||||
placeholder="Your first name"
|
||||
validate={{
|
||||
required: { value: true, errorMessage: 'Your first name is required.' },
|
||||
minLength: { value: 1, errorMessage: 'Your first name is required to be at least 1 character' },
|
||||
maxLength: { value: 50, errorMessage: 'Your first name cannot be longer than 50 characters' }
|
||||
}}
|
||||
value={account.firstName}
|
||||
/>
|
||||
{/* Last name */}
|
||||
<AvField
|
||||
className="form-control"
|
||||
name="lastName"
|
||||
label="Last Name"
|
||||
id="lastName"
|
||||
placeholder="Your last name"
|
||||
validate={{
|
||||
required: { value: true, errorMessage: 'Your last name is required.' },
|
||||
minLength: { value: 1, errorMessage: 'Your last name is required to be at least 1 character' },
|
||||
maxLength: { value: 50, errorMessage: 'Your last name cannot be longer than 50 characters' }
|
||||
}}
|
||||
value={account.lastName}
|
||||
/>
|
||||
{/* Email */}
|
||||
<AvField
|
||||
name="email"
|
||||
label="Email"
|
||||
placeholder="Your email"
|
||||
type="email"
|
||||
validate={{
|
||||
required: { value: true, errorMessage: 'Your email is required.' },
|
||||
minLength: { value: 5, errorMessage: 'Your email is required to be at least 5 characters.' },
|
||||
maxLength: { value: 254, errorMessage: 'Your email cannot be longer than 50 characters.' }
|
||||
}}
|
||||
value={account.email}
|
||||
/>
|
||||
<Button color="primary" type="submit">
|
||||
Save
|
||||
</Button>
|
||||
</AvForm>
|
||||
</Col>
|
||||
</Row>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const mapStateToProps = ({ authentication }: IRootState) => ({
|
||||
account: authentication.account,
|
||||
isAuthenticated: authentication.isAuthenticated
|
||||
});
|
||||
|
||||
const mapDispatchToProps = { getSession, saveAccountSettings, reset };
|
||||
|
||||
type StateProps = ReturnType<typeof mapStateToProps>;
|
||||
type DispatchProps = typeof mapDispatchToProps;
|
||||
|
||||
export default connect(
|
||||
mapStateToProps,
|
||||
mapDispatchToProps
|
||||
)(SettingsPage);
|
||||
Reference in New Issue
Block a user