mirror of
https://github.com/kingomarnajjar/flex-template-web.git
synced 2026-07-31 02:26:50 +10:00
Password reset page
This commit is contained in:
parent
c7db98a668
commit
388a08e732
6 changed files with 286 additions and 52 deletions
|
|
@ -8,6 +8,8 @@ import * as validators from '../../util/validators';
|
|||
|
||||
import css from './PasswordResetForm.css';
|
||||
|
||||
const PASSWORD_MIN_LENGTH = 8;
|
||||
|
||||
const PasswordResetFormComponent = props => {
|
||||
const {
|
||||
rootClassName,
|
||||
|
|
@ -30,7 +32,16 @@ const PasswordResetFormComponent = props => {
|
|||
const passwordRequiredMessage = intl.formatMessage({
|
||||
id: 'PasswordResetForm.passwordRequired',
|
||||
});
|
||||
const passwordMinLengthMessage = intl.formatMessage(
|
||||
{
|
||||
id: 'PasswordResetForm.passwordTooShort',
|
||||
},
|
||||
{
|
||||
minLength: PASSWORD_MIN_LENGTH,
|
||||
}
|
||||
);
|
||||
const passwordRequired = validators.required(passwordRequiredMessage);
|
||||
const passwordMinLength = validators.minLength(passwordMinLengthMessage, PASSWORD_MIN_LENGTH);
|
||||
|
||||
const classes = classNames(rootClassName || css.root, className);
|
||||
const submitDisabled = invalid || submitting || inProgress;
|
||||
|
|
@ -44,7 +55,7 @@ const PasswordResetFormComponent = props => {
|
|||
id={`${form}.password`}
|
||||
label={passwordLabel}
|
||||
placeholder={passwordPlaceholder}
|
||||
validate={passwordRequired}
|
||||
validate={[passwordRequired, passwordMinLength]}
|
||||
/>
|
||||
<PrimaryButton className={css.submitButton} type="submit" disabled={submitDisabled}>
|
||||
<FormattedMessage id="PasswordResetForm.submitButtonText" />
|
||||
|
|
|
|||
|
|
@ -0,0 +1,70 @@
|
|||
@import '../../marketplace.css';
|
||||
|
||||
.root {
|
||||
@apply --backgroundImage;
|
||||
|
||||
flex-grow: 1;
|
||||
display: flex;
|
||||
|
||||
@media (--viewportMedium) {
|
||||
justify-content: center;
|
||||
align-items: flex-start;
|
||||
}
|
||||
}
|
||||
|
||||
.content {
|
||||
flex-grow: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
padding: 30px 24px 98px 24px;
|
||||
background-color: var(--matterColorLight);
|
||||
border-radius: 2px;
|
||||
|
||||
@media (--viewportMedium) {
|
||||
flex-basis: 480px;
|
||||
flex-grow: 0;
|
||||
min-height: 573px;
|
||||
padding: 60px;
|
||||
margin-top: 7.5vh;
|
||||
margin-bottom: 7.5vh;
|
||||
}
|
||||
}
|
||||
|
||||
.error {
|
||||
color: var(--failColor);
|
||||
}
|
||||
|
||||
.mainHeading {
|
||||
margin-top: 26px;
|
||||
|
||||
@media (--viewportMedium) {
|
||||
margin-top: 23px;
|
||||
}
|
||||
}
|
||||
|
||||
.helpText {
|
||||
margin-top: 12px;
|
||||
|
||||
@media (--viewportMedium) {
|
||||
margin-top: 15px;
|
||||
}
|
||||
}
|
||||
|
||||
.form {
|
||||
flex: 1;
|
||||
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
margin-top: 37px;
|
||||
|
||||
@media (--viewportMedium) {
|
||||
margin-top: 38px;
|
||||
}
|
||||
}
|
||||
|
||||
.buttonLink {
|
||||
@apply --marketplaceButtonStylesPrimary;
|
||||
|
||||
/* Pull the button to the bottom of the flex container */
|
||||
margin-top: auto;
|
||||
}
|
||||
51
src/containers/PasswordResetPage/PasswordResetPage.duck.js
Normal file
51
src/containers/PasswordResetPage/PasswordResetPage.duck.js
Normal file
|
|
@ -0,0 +1,51 @@
|
|||
// ================ Action types ================ //
|
||||
|
||||
export const RESET_PASSWORD_REQUEST = 'app/PasswordResetPage/RESET_PASSWORD_REQUEST';
|
||||
export const RESET_PASSWORD_SUCCESS = 'app/PasswordResetPage/RESET_PASSWORD_SUCCESS';
|
||||
export const RESET_PASSWORD_ERROR = 'app/PasswordResetPage/RESET_PASSWORD_ERROR';
|
||||
|
||||
// ================ Reducer ================ //
|
||||
|
||||
const initialState = {
|
||||
resetPasswordInProgress: false,
|
||||
resetPasswordError: null,
|
||||
};
|
||||
|
||||
export default function reducer(state = initialState, action = {}) {
|
||||
const { type, payload } = action;
|
||||
switch (type) {
|
||||
case RESET_PASSWORD_REQUEST:
|
||||
return { ...state, resetPasswordInProgress: true, resetPasswordError: null };
|
||||
case RESET_PASSWORD_SUCCESS:
|
||||
return { ...state, resetPasswordInProgress: false };
|
||||
case RESET_PASSWORD_ERROR:
|
||||
console.error(payload); // eslint-disable-line no-console
|
||||
return { ...state, resetPasswordInProgress: false, resetPasswordError: payload };
|
||||
default:
|
||||
return state;
|
||||
}
|
||||
}
|
||||
|
||||
// ================ Action creators ================ //
|
||||
|
||||
export const resetPasswordRequest = () => ({ type: RESET_PASSWORD_REQUEST });
|
||||
|
||||
export const resetPasswordSuccess = () => ({ type: RESET_PASSWORD_SUCCESS });
|
||||
|
||||
export const resetPasswordError = e => ({
|
||||
type: RESET_PASSWORD_ERROR,
|
||||
error: true,
|
||||
payload: e,
|
||||
});
|
||||
|
||||
// ================ Thunks ================ //
|
||||
|
||||
export const resetPassword = (email, passwordResetToken, newPassword) =>
|
||||
(dispatch, getState, sdk) => {
|
||||
dispatch(resetPasswordRequest());
|
||||
const params = { email, passwordResetToken, newPassword };
|
||||
return sdk.passwordReset
|
||||
.reset(params)
|
||||
.then(() => dispatch(resetPasswordSuccess()))
|
||||
.catch(e => dispatch(resetPasswordError(e)));
|
||||
};
|
||||
|
|
@ -1,70 +1,155 @@
|
|||
import React, { PropTypes } from 'react';
|
||||
import React, { PropTypes, Component } from 'react';
|
||||
import { compose } from 'redux';
|
||||
import { connect } from 'react-redux';
|
||||
import { withRouter } from 'react-router-dom';
|
||||
import { injectIntl, intlShape } from 'react-intl';
|
||||
import { FormattedMessage, injectIntl, intlShape } from 'react-intl';
|
||||
import * as propTypes from '../../util/propTypes';
|
||||
import { PageLayout, Topbar } from '../../components';
|
||||
import { PageLayout, Topbar, NamedLink, KeysIcon, KeysIconSuccess } from '../../components';
|
||||
import { PasswordResetForm } from '../../containers';
|
||||
import { logout, authenticationInProgress } from '../../ducks/Auth.duck';
|
||||
import { manageDisableScrolling, isScrollingDisabled } from '../../ducks/UI.duck';
|
||||
import { parse } from '../../util/urlHelpers';
|
||||
import { resetPassword } from './PasswordResetPage.duck';
|
||||
|
||||
// import css from './PasswordResetPage.css';
|
||||
import css from './PasswordResetPage.css';
|
||||
|
||||
export const PasswordResetPageComponent = props => {
|
||||
const {
|
||||
authInfoError,
|
||||
authInProgress,
|
||||
currentUser,
|
||||
currentUserHasListings,
|
||||
intl,
|
||||
isAuthenticated,
|
||||
logoutError,
|
||||
notificationCount,
|
||||
onLogout,
|
||||
onManageDisableScrolling,
|
||||
scrollingDisabled,
|
||||
location,
|
||||
history,
|
||||
} = props;
|
||||
const { bool, func, instanceOf, number, object, shape, string } = PropTypes;
|
||||
|
||||
const title = intl.formatMessage({
|
||||
id: 'PasswordResetPage.title',
|
||||
});
|
||||
|
||||
return (
|
||||
<PageLayout
|
||||
title={title}
|
||||
authInfoError={authInfoError}
|
||||
logoutError={logoutError}
|
||||
scrollingDisabled={scrollingDisabled}
|
||||
>
|
||||
<Topbar
|
||||
isAuthenticated={isAuthenticated}
|
||||
authInProgress={authInProgress}
|
||||
currentUser={currentUser}
|
||||
currentUserHasListings={currentUserHasListings}
|
||||
notificationCount={notificationCount}
|
||||
history={history}
|
||||
location={location}
|
||||
onLogout={onLogout}
|
||||
onManageDisableScrolling={onManageDisableScrolling}
|
||||
/>
|
||||
|
||||
<p>TODO</p>
|
||||
|
||||
</PageLayout>
|
||||
);
|
||||
const parseUrlParams = location => {
|
||||
const params = parse(location.search);
|
||||
const { t: token, e: email } = params;
|
||||
return { token, email };
|
||||
};
|
||||
|
||||
export class PasswordResetPageComponent extends Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = { newPasswordSubmitted: false };
|
||||
}
|
||||
render() {
|
||||
const {
|
||||
authInfoError,
|
||||
authInProgress,
|
||||
currentUser,
|
||||
currentUserHasListings,
|
||||
intl,
|
||||
isAuthenticated,
|
||||
logoutError,
|
||||
notificationCount,
|
||||
onLogout,
|
||||
onManageDisableScrolling,
|
||||
scrollingDisabled,
|
||||
location,
|
||||
history,
|
||||
resetPasswordInProgress,
|
||||
resetPasswordError,
|
||||
onSubmitPassword,
|
||||
} = this.props;
|
||||
|
||||
const title = intl.formatMessage({
|
||||
id: 'PasswordResetPage.title',
|
||||
});
|
||||
|
||||
const { token, email } = parseUrlParams(location);
|
||||
const paramsValid = !!(token && email);
|
||||
|
||||
const handleSubmit = values => {
|
||||
const { password } = values;
|
||||
this.setState({ newPasswordSubmitted: false });
|
||||
onSubmitPassword(email, token, password).then(() => {
|
||||
this.setState({ newPasswordSubmitted: true });
|
||||
});
|
||||
};
|
||||
|
||||
const paramsErrorContent = (
|
||||
<div className={css.content}>
|
||||
<p className={css.error}>
|
||||
<FormattedMessage id="PasswordResetPage.invalidUrlParams" />
|
||||
</p>
|
||||
</div>
|
||||
);
|
||||
|
||||
const resetFormContent = (
|
||||
<div className={css.content}>
|
||||
<KeysIcon />
|
||||
<h1 className={css.mainHeading}>
|
||||
<FormattedMessage id="PasswordResetPage.mainHeading" />
|
||||
</h1>
|
||||
<p className={css.helpText}>
|
||||
<FormattedMessage id="PasswordResetPage.helpText" />
|
||||
</p>
|
||||
{resetPasswordError
|
||||
? <p className={css.error}>
|
||||
<FormattedMessage id="PasswordResetPage.resetFailed" />
|
||||
</p>
|
||||
: null}
|
||||
<PasswordResetForm
|
||||
className={css.form}
|
||||
onSubmit={handleSubmit}
|
||||
inProgress={resetPasswordInProgress}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
|
||||
const resetDoneContent = (
|
||||
<div className={css.content}>
|
||||
<KeysIconSuccess />
|
||||
<h1 className={css.mainHeading}>
|
||||
<FormattedMessage id="PasswordResetPage.passwordChangedHeading" />
|
||||
</h1>
|
||||
<p className={css.helpText}>
|
||||
<FormattedMessage id="PasswordResetPage.passwordChangedHelpText" />
|
||||
</p>
|
||||
<NamedLink name="LoginPage" className={css.buttonLink}>
|
||||
<FormattedMessage id="PasswordResetPage.loginButtonText" />
|
||||
</NamedLink>
|
||||
</div>
|
||||
);
|
||||
|
||||
let content;
|
||||
|
||||
if (!paramsValid) {
|
||||
content = paramsErrorContent;
|
||||
} else if (!resetPasswordError && this.state.newPasswordSubmitted) {
|
||||
content = resetDoneContent;
|
||||
} else {
|
||||
content = resetFormContent;
|
||||
}
|
||||
|
||||
return (
|
||||
<PageLayout
|
||||
title={title}
|
||||
authInfoError={authInfoError}
|
||||
logoutError={logoutError}
|
||||
scrollingDisabled={scrollingDisabled}
|
||||
>
|
||||
<Topbar
|
||||
isAuthenticated={isAuthenticated}
|
||||
authInProgress={authInProgress}
|
||||
currentUser={currentUser}
|
||||
currentUserHasListings={currentUserHasListings}
|
||||
notificationCount={notificationCount}
|
||||
history={history}
|
||||
location={location}
|
||||
onLogout={onLogout}
|
||||
onManageDisableScrolling={onManageDisableScrolling}
|
||||
/>
|
||||
<div className={css.root}>
|
||||
{content}
|
||||
</div>
|
||||
</PageLayout>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
PasswordResetPageComponent.defaultProps = {
|
||||
authInfoError: null,
|
||||
currentUser: null,
|
||||
logoutError: null,
|
||||
notificationCount: 0,
|
||||
resetPasswordError: null,
|
||||
};
|
||||
|
||||
const { bool, func, instanceOf, number, object, shape, string } = PropTypes;
|
||||
|
||||
PasswordResetPageComponent.propTypes = {
|
||||
authInfoError: instanceOf(Error),
|
||||
authInProgress: bool.isRequired,
|
||||
|
|
@ -77,6 +162,10 @@ PasswordResetPageComponent.propTypes = {
|
|||
onManageDisableScrolling: func.isRequired,
|
||||
scrollingDisabled: bool.isRequired,
|
||||
|
||||
resetPasswordInProgress: bool.isRequired,
|
||||
resetPasswordError: instanceOf(Error),
|
||||
onSubmitPassword: func.isRequired,
|
||||
|
||||
// from withRouter
|
||||
history: object.isRequired,
|
||||
location: shape({
|
||||
|
|
@ -98,6 +187,7 @@ const mapStateToProps = state => {
|
|||
currentUserHasListings,
|
||||
currentUserNotificationCount: notificationCount,
|
||||
} = state.user;
|
||||
const { resetPasswordInProgress, resetPasswordError } = state.PasswordResetPage;
|
||||
return {
|
||||
authInfoError,
|
||||
authInProgress: authenticationInProgress(state),
|
||||
|
|
@ -107,6 +197,8 @@ const mapStateToProps = state => {
|
|||
logoutError,
|
||||
notificationCount,
|
||||
scrollingDisabled: isScrollingDisabled(state),
|
||||
resetPasswordInProgress,
|
||||
resetPasswordError,
|
||||
};
|
||||
};
|
||||
|
||||
|
|
@ -114,6 +206,7 @@ const mapDispatchToProps = dispatch => ({
|
|||
onLogout: () => dispatch(logout()),
|
||||
onManageDisableScrolling: (componentId, disableScrolling) =>
|
||||
dispatch(manageDisableScrolling(componentId, disableScrolling)),
|
||||
onSubmitPassword: (email, token, password) => dispatch(resetPassword(email, token, password)),
|
||||
});
|
||||
|
||||
const PasswordResetPage = compose(
|
||||
|
|
|
|||
|
|
@ -7,11 +7,12 @@ import CheckoutPage from './CheckoutPage/CheckoutPage.duck';
|
|||
import EditListingPage from './EditListingPage/EditListingPage.duck';
|
||||
import InboxPage from './InboxPage/InboxPage.duck';
|
||||
import ListingPage from './ListingPage/ListingPage.duck';
|
||||
import ManageListingsPage from './ManageListingsPage/ManageListingsPage.duck';
|
||||
import OrderPage from './OrderPage/OrderPage.duck';
|
||||
import PasswordResetPage from './PasswordResetPage/PasswordResetPage.duck';
|
||||
import ProfileSettingsPage from './ProfileSettingsPage/ProfileSettingsPage.duck';
|
||||
import SalePage from './SalePage/SalePage.duck';
|
||||
import SearchPage from './SearchPage/SearchPage.duck';
|
||||
import ManageListingsPage from './ManageListingsPage/ManageListingsPage.duck';
|
||||
import ProfileSettingsPage from './ProfileSettingsPage/ProfileSettingsPage.duck';
|
||||
|
||||
export {
|
||||
CheckoutPage,
|
||||
|
|
@ -20,6 +21,7 @@ export {
|
|||
ListingPage,
|
||||
ManageListingsPage,
|
||||
OrderPage,
|
||||
PasswordResetPage,
|
||||
ProfileSettingsPage,
|
||||
SalePage,
|
||||
SearchPage,
|
||||
|
|
|
|||
|
|
@ -208,7 +208,14 @@
|
|||
"PasswordResetForm.passwordLabel": "Your new password",
|
||||
"PasswordResetForm.passwordPlaceholder": "Enter your new password...",
|
||||
"PasswordResetForm.passwordRequired": "This field is required",
|
||||
"PasswordResetForm.passwordTooShort": "Password should be at least {minLength} characters",
|
||||
"PasswordResetForm.submitButtonText": "Reset password",
|
||||
"PasswordResetPage.helpText": "Please provide a new password.",
|
||||
"PasswordResetPage.loginButtonText": "Log in",
|
||||
"PasswordResetPage.mainHeading": "Reset your password",
|
||||
"PasswordResetPage.passwordChangedHeading": "Password changed",
|
||||
"PasswordResetPage.passwordChangedHelpText": "Your password has been changed successfully.",
|
||||
"PasswordResetPage.resetFailed": "Reset failed. Please try again.",
|
||||
"PasswordResetPage.title": "Reset password",
|
||||
"PayoutDetailsForm.addressTitle": "Address",
|
||||
"PayoutDetailsForm.bankDetails": "Bank details",
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue