mirror of
https://github.com/kingomarnajjar/flex-template-web.git
synced 2026-07-29 05:00:43 +10:00
Merge pull request #382 from sharetribe/request-new-password
Request new password
This commit is contained in:
commit
fdc86022d7
24 changed files with 814 additions and 228 deletions
|
|
@ -43,8 +43,7 @@ describe('Application', () => {
|
|||
'/u/1234': 'Profile page with display name: 1234',
|
||||
'/login': 'Login',
|
||||
'/signup': 'Sign up',
|
||||
'/password': 'Request new password',
|
||||
'/password/forgotten': 'Request new password',
|
||||
'/recover-password': 'Request a new password',
|
||||
'/this-url-should-not-be-found': 'Page not found',
|
||||
'/reset-password?t=token&e=email': 'Reset password',
|
||||
};
|
||||
|
|
|
|||
|
|
@ -17,6 +17,7 @@ class TextInputFieldComponent extends Component {
|
|||
rootClassName,
|
||||
className,
|
||||
clearOnUnmount,
|
||||
customErrorText,
|
||||
id,
|
||||
label,
|
||||
type,
|
||||
|
|
@ -33,9 +34,13 @@ class TextInputFieldComponent extends Component {
|
|||
const { valid, invalid, touched, error } = meta;
|
||||
const isTextarea = type === 'textarea';
|
||||
|
||||
const errorText = customErrorText || error;
|
||||
|
||||
// Error message and input error styles are only shown if the
|
||||
// field has been touched and the validation has failed.
|
||||
const hasError = touched && invalid && error;
|
||||
const hasError = touched && invalid && errorText;
|
||||
|
||||
const fieldMeta = { touched, error: errorText };
|
||||
|
||||
const inputClasses = classNames(css.input, {
|
||||
[css.inputSuccess]: valid,
|
||||
|
|
@ -50,7 +55,7 @@ class TextInputFieldComponent extends Component {
|
|||
<div className={classes}>
|
||||
{label ? <label htmlFor={id}>{label}</label> : null}
|
||||
{isTextarea ? <ExpandingTextarea {...inputProps} /> : <input {...inputProps} />}
|
||||
<ValidationError fieldMeta={meta} />
|
||||
<ValidationError fieldMeta={fieldMeta} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
@ -60,6 +65,7 @@ TextInputFieldComponent.defaultProps = {
|
|||
rootClassName: null,
|
||||
className: null,
|
||||
clearOnUnmount: false,
|
||||
customErrorText: null,
|
||||
id: null,
|
||||
label: null,
|
||||
};
|
||||
|
|
@ -72,6 +78,10 @@ TextInputFieldComponent.propTypes = {
|
|||
|
||||
clearOnUnmount: bool,
|
||||
|
||||
// Error message that can be manually passed to input field,
|
||||
// overrides default validation message
|
||||
customErrorText: string,
|
||||
|
||||
// Label is optional, but if it is given, an id is also required so
|
||||
// the label can reference the input in the `for` attribute
|
||||
id: string,
|
||||
|
|
|
|||
|
|
@ -10,17 +10,23 @@
|
|||
|
||||
.password {
|
||||
margin-top: 24px;
|
||||
margin-bottom: 24px;
|
||||
|
||||
@media (--viewportMedium) {
|
||||
margin-top: 30px;
|
||||
}
|
||||
}
|
||||
|
||||
.button {
|
||||
margin-top: 24px;
|
||||
align-self: stretch;
|
||||
|
||||
@media (--viewportMedium) {
|
||||
margin-top: 72px;
|
||||
}
|
||||
.bottomWrapper {
|
||||
margin-top: auto;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.recoveryLink {
|
||||
@apply --marketplaceH5FontStyles;
|
||||
color: var(--matterColor);
|
||||
}
|
||||
|
||||
.recoveryLinkHelp {
|
||||
color: var(--matterColorAnti);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@ import { compose } from 'redux';
|
|||
import { FormattedMessage, injectIntl, intlShape } from 'react-intl';
|
||||
import { reduxForm, propTypes as formPropTypes } from 'redux-form';
|
||||
import classNames from 'classnames';
|
||||
import { Button, TextInputField } from '../../components';
|
||||
import { Button, TextInputField, NamedLink } from '../../components';
|
||||
import * as validators from '../../util/validators';
|
||||
|
||||
import css from './LoginForm.css';
|
||||
|
|
@ -68,6 +68,15 @@ const LoginFormComponent = props => {
|
|||
validate={passwordRequired}
|
||||
/>
|
||||
</div>
|
||||
<p className={css.bottomWrapper}>
|
||||
<NamedLink name="PasswordRecoveryPage" className={css.recoveryLink}>
|
||||
<FormattedMessage id="LoginForm.forgotPassword" />
|
||||
{' '}
|
||||
<span className={css.recoveryLinkHelp}>
|
||||
<FormattedMessage id="LoginForm.forgotPasswordHelp" />
|
||||
</span>
|
||||
</NamedLink>
|
||||
</p>
|
||||
<Button className={css.button} type="submit" disabled={submitDisabled}>
|
||||
<FormattedMessage id="LoginForm.logIn" />
|
||||
</Button>
|
||||
|
|
|
|||
|
|
@ -1,10 +1,18 @@
|
|||
import React from 'react';
|
||||
import { renderDeep } from '../../util/test-helpers';
|
||||
import { RoutesProvider } from '../../components';
|
||||
import routesConfiguration from '../../routesConfiguration';
|
||||
import { flattenRoutes } from '../../util/routes';
|
||||
import LoginForm from './LoginForm';
|
||||
|
||||
describe('LoginForm', () => {
|
||||
it('matches snapshot', () => {
|
||||
const tree = renderDeep(<LoginForm />);
|
||||
const flattenedRoutes = flattenRoutes(routesConfiguration);
|
||||
const tree = renderDeep(
|
||||
<RoutesProvider flattenedRoutes={flattenedRoutes}>
|
||||
<LoginForm />
|
||||
</RoutesProvider>
|
||||
);
|
||||
expect(tree).toMatchSnapshot();
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -42,6 +42,26 @@ exports[`LoginForm matches snapshot 1`] = `
|
|||
value="" />
|
||||
</div>
|
||||
</div>
|
||||
<p
|
||||
className={undefined}>
|
||||
<a
|
||||
className=""
|
||||
href="/recover-password"
|
||||
onClick={[Function]}
|
||||
style={Object {}}
|
||||
title={null}>
|
||||
<span>
|
||||
Forgot your password?
|
||||
</span>
|
||||
|
||||
<span
|
||||
className={undefined}>
|
||||
<span>
|
||||
No problem.
|
||||
</span>
|
||||
</span>
|
||||
</a>
|
||||
</p>
|
||||
<button
|
||||
className=""
|
||||
disabled={true}
|
||||
|
|
|
|||
|
|
@ -1,21 +0,0 @@
|
|||
import React from 'react';
|
||||
import { Field, reduxForm, propTypes as formPropTypes } from 'redux-form';
|
||||
import { Button } from '../../components';
|
||||
|
||||
const PasswordForgottenForm = props => {
|
||||
const { handleSubmit, pristine, submitting } = props;
|
||||
return (
|
||||
<form onSubmit={handleSubmit}>
|
||||
<label htmlFor="email">Email</label>
|
||||
<Field name="email" component="input" type="email" />
|
||||
<p>We will send you instructions to your email.</p>
|
||||
<Button type="submit" disabled={pristine || submitting}>Send</Button>
|
||||
</form>
|
||||
);
|
||||
};
|
||||
|
||||
PasswordForgottenForm.propTypes = { ...formPropTypes };
|
||||
|
||||
const defaultFormName = 'PasswordForgottenForm';
|
||||
|
||||
export default reduxForm({ form: defaultFormName })(PasswordForgottenForm);
|
||||
|
|
@ -1,10 +0,0 @@
|
|||
import React from 'react';
|
||||
import { renderDeep } from '../../util/test-helpers';
|
||||
import PasswordForgottenForm from './PasswordForgottenForm';
|
||||
|
||||
describe('PasswordForgottenForm', () => {
|
||||
it('matches snapshot', () => {
|
||||
const tree = renderDeep(<PasswordForgottenForm />);
|
||||
expect(tree).toMatchSnapshot();
|
||||
});
|
||||
});
|
||||
|
|
@ -1,27 +0,0 @@
|
|||
exports[`PasswordForgottenForm matches snapshot 1`] = `
|
||||
<form
|
||||
onSubmit={[Function]}>
|
||||
<label
|
||||
htmlFor="email">
|
||||
Email
|
||||
</label>
|
||||
<input
|
||||
name="email"
|
||||
onBlur={[Function]}
|
||||
onChange={[Function]}
|
||||
onDragStart={[Function]}
|
||||
onDrop={[Function]}
|
||||
onFocus={[Function]}
|
||||
type="email"
|
||||
value="" />
|
||||
<p>
|
||||
We will send you instructions to your email.
|
||||
</p>
|
||||
<button
|
||||
className=""
|
||||
disabled={true}
|
||||
type="submit">
|
||||
Send
|
||||
</button>
|
||||
</form>
|
||||
`;
|
||||
|
|
@ -1,133 +0,0 @@
|
|||
import React, { PropTypes } from 'react';
|
||||
import { compose } from 'redux';
|
||||
import { connect } from 'react-redux';
|
||||
import { withRouter } from 'react-router-dom';
|
||||
import * as propTypes from '../../util/propTypes';
|
||||
import { sendVerificationEmail } from '../../ducks/user.duck';
|
||||
import { logout, authenticationInProgress } from '../../ducks/Auth.duck';
|
||||
import { manageDisableScrolling, isScrollingDisabled } from '../../ducks/UI.duck';
|
||||
import { PageLayout, Topbar } from '../../components';
|
||||
import { PasswordForgottenForm } from '../../containers';
|
||||
|
||||
const sendPasswordResetEmail = values => {
|
||||
// eslint-disable-next-line no-console
|
||||
console.log('submit with values:', values);
|
||||
};
|
||||
|
||||
export const PasswordForgottenPageComponent = props => {
|
||||
const {
|
||||
authInfoError,
|
||||
authInProgress,
|
||||
currentUser,
|
||||
currentUserHasListings,
|
||||
currentUserHasOrders,
|
||||
history,
|
||||
isAuthenticated,
|
||||
location,
|
||||
logoutError,
|
||||
notificationCount,
|
||||
onLogout,
|
||||
onManageDisableScrolling,
|
||||
sendVerificationEmailInProgress,
|
||||
sendVerificationEmailError,
|
||||
onResendVerificationEmail,
|
||||
} = props;
|
||||
|
||||
return (
|
||||
<PageLayout
|
||||
authInfoError={authInfoError}
|
||||
logoutError={logoutError}
|
||||
title="Request new password"
|
||||
>
|
||||
<Topbar
|
||||
authInProgress={authInProgress}
|
||||
currentUser={currentUser}
|
||||
currentUserHasListings={currentUserHasListings}
|
||||
currentUserHasOrders={currentUserHasOrders}
|
||||
history={history}
|
||||
isAuthenticated={isAuthenticated}
|
||||
location={location}
|
||||
notificationCount={notificationCount}
|
||||
onLogout={onLogout}
|
||||
onManageDisableScrolling={onManageDisableScrolling}
|
||||
onResendVerificationEmail={onResendVerificationEmail}
|
||||
sendVerificationEmailInProgress={sendVerificationEmailInProgress}
|
||||
sendVerificationEmailError={sendVerificationEmailError}
|
||||
/>
|
||||
<PasswordForgottenForm onSubmit={sendPasswordResetEmail} />
|
||||
</PageLayout>
|
||||
);
|
||||
};
|
||||
|
||||
PasswordForgottenPageComponent.defaultProps = {
|
||||
authInfoError: null,
|
||||
currentUser: null,
|
||||
currentUserHasOrders: null,
|
||||
logoutError: null,
|
||||
notificationCount: 0,
|
||||
sendVerificationEmailError: null,
|
||||
};
|
||||
|
||||
const { bool, func, instanceOf, number, object, shape } = PropTypes;
|
||||
|
||||
PasswordForgottenPageComponent.propTypes = {
|
||||
authInfoError: instanceOf(Error),
|
||||
authInProgress: bool.isRequired,
|
||||
currentUser: propTypes.currentUser,
|
||||
currentUserHasListings: bool.isRequired,
|
||||
currentUserHasOrders: bool,
|
||||
isAuthenticated: bool.isRequired,
|
||||
logoutError: instanceOf(Error),
|
||||
notificationCount: number,
|
||||
onLogout: func.isRequired,
|
||||
onManageDisableScrolling: func.isRequired,
|
||||
sendVerificationEmailInProgress: bool.isRequired,
|
||||
sendVerificationEmailError: instanceOf(Error),
|
||||
onResendVerificationEmail: func.isRequired,
|
||||
|
||||
// from withRouter
|
||||
history: shape({
|
||||
push: func.isRequired,
|
||||
}).isRequired,
|
||||
location: shape({ state: object }).isRequired,
|
||||
};
|
||||
|
||||
const mapStateToProps = state => {
|
||||
// PageLayout needs authInfoError and logoutError, Topbar needs isAuthenticated
|
||||
const { authInfoError, isAuthenticated, logoutError } = state.Auth;
|
||||
// Topbar needs user info.
|
||||
const {
|
||||
currentUser,
|
||||
currentUserHasListings,
|
||||
currentUserHasOrders,
|
||||
currentUserNotificationCount: notificationCount,
|
||||
sendVerificationEmailInProgress,
|
||||
sendVerificationEmailError,
|
||||
} = state.user;
|
||||
return {
|
||||
authInfoError,
|
||||
authInProgress: authenticationInProgress(state),
|
||||
currentUser,
|
||||
currentUserHasListings,
|
||||
currentUserHasOrders,
|
||||
currentUserNotificationCount: notificationCount,
|
||||
isAuthenticated,
|
||||
logoutError,
|
||||
scrollingDisabled: isScrollingDisabled(state),
|
||||
sendVerificationEmailInProgress,
|
||||
sendVerificationEmailError,
|
||||
};
|
||||
};
|
||||
|
||||
const mapDispatchToProps = dispatch => ({
|
||||
onLogout: historyPush => dispatch(logout(historyPush)),
|
||||
onManageDisableScrolling: (componentId, disableScrolling) =>
|
||||
dispatch(manageDisableScrolling(componentId, disableScrolling)),
|
||||
onResendVerificationEmail: () => dispatch(sendVerificationEmail()),
|
||||
});
|
||||
|
||||
const PasswordForgottenPage = compose(connect(mapStateToProps, mapDispatchToProps), withRouter)(
|
||||
PasswordForgottenPageComponent
|
||||
);
|
||||
|
||||
export default PasswordForgottenPage;
|
||||
40
src/containers/PasswordRecoveryForm/PasswordRecoveryForm.css
Normal file
40
src/containers/PasswordRecoveryForm/PasswordRecoveryForm.css
Normal file
|
|
@ -0,0 +1,40 @@
|
|||
@import '../../marketplace.css';
|
||||
|
||||
.root {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
flex: 1;
|
||||
justify-content: space-between;
|
||||
height: 100%;
|
||||
min-height: 306px;
|
||||
}
|
||||
|
||||
.email {
|
||||
margin-top: 37px;
|
||||
|
||||
@media (--viewportMedium) {
|
||||
margin-top: 38px;
|
||||
}
|
||||
}
|
||||
|
||||
.bottomWrapper {
|
||||
margin-top: auto;
|
||||
}
|
||||
|
||||
.bottomText {
|
||||
@apply --marketplaceH5FontStyles;
|
||||
color: var(--matterColorAnti);
|
||||
margin: 0;
|
||||
|
||||
@media (--viewportMedium) {
|
||||
margin: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.submitButton {
|
||||
margin-top: 26px;
|
||||
}
|
||||
|
||||
.loginLink {
|
||||
color: var(--matterColor);
|
||||
}
|
||||
|
|
@ -1,8 +1,8 @@
|
|||
/* eslint-disable no-console */
|
||||
import PasswordForgottenForm from './PasswordForgottenForm';
|
||||
import PasswordRecoveryForm from './PasswordRecoveryForm';
|
||||
|
||||
export const Empty = {
|
||||
component: PasswordForgottenForm,
|
||||
component: PasswordRecoveryForm,
|
||||
props: {
|
||||
onSubmit(values) {
|
||||
console.log('submit forgotten password email:', values);
|
||||
101
src/containers/PasswordRecoveryForm/PasswordRecoveryForm.js
Normal file
101
src/containers/PasswordRecoveryForm/PasswordRecoveryForm.js
Normal file
|
|
@ -0,0 +1,101 @@
|
|||
import React, { PropTypes } from 'react';
|
||||
import { compose } from 'redux';
|
||||
import { FormattedMessage, injectIntl, intlShape } from 'react-intl';
|
||||
import { reduxForm, propTypes as formPropTypes } from 'redux-form';
|
||||
import classNames from 'classnames';
|
||||
import { PrimaryButton, TextInputField, NamedLink } from '../../components';
|
||||
import * as validators from '../../util/validators';
|
||||
|
||||
import css from './PasswordRecoveryForm.css';
|
||||
|
||||
const isNotFoundError = error => error && error.status === 404;
|
||||
|
||||
const PasswordRecoveryFormComponent = props => {
|
||||
const {
|
||||
rootClassName,
|
||||
className,
|
||||
handleSubmit,
|
||||
pristine,
|
||||
submitting,
|
||||
form,
|
||||
initialValues,
|
||||
intl,
|
||||
recoveryError,
|
||||
} = props;
|
||||
|
||||
// email
|
||||
const emailLabel = intl.formatMessage({
|
||||
id: 'PasswordRecoveryForm.emailLabel',
|
||||
});
|
||||
const emailPlaceholder = intl.formatMessage({
|
||||
id: 'PasswordRecoveryForm.emailPlaceholder',
|
||||
});
|
||||
const emailRequiredMessage = intl.formatMessage({
|
||||
id: 'PasswordRecoveryForm.emailRequired',
|
||||
});
|
||||
const emailNotFoundMessage = intl.formatMessage({
|
||||
id: 'PasswordRecoveryForm.emailNotFound',
|
||||
});
|
||||
|
||||
const emailRequired = validators.required(emailRequiredMessage);
|
||||
// In case a given email is not found, pass a custom error message
|
||||
// to be rendered with the input component
|
||||
const customErrorText = isNotFoundError(recoveryError) ? emailNotFoundMessage : null;
|
||||
const initialEmail = initialValues ? initialValues.email : null;
|
||||
const buttonDisabled = (pristine && !initialEmail) || submitting;
|
||||
const classes = classNames(rootClassName || css.root, className);
|
||||
|
||||
const loginLink = (
|
||||
<NamedLink name="LoginPage" className={css.loginLink}>
|
||||
<FormattedMessage id="PasswordRecoveryForm.loginLinkText" />
|
||||
</NamedLink>
|
||||
);
|
||||
|
||||
return (
|
||||
<form className={classes} onSubmit={handleSubmit}>
|
||||
<TextInputField
|
||||
className={css.email}
|
||||
type="email"
|
||||
name="email"
|
||||
id={`${form}.email`}
|
||||
label={emailLabel}
|
||||
placeholder={emailPlaceholder}
|
||||
validate={emailRequired}
|
||||
customErrorText={customErrorText}
|
||||
/>
|
||||
|
||||
<div className={css.bottomWrapper}>
|
||||
<p className={css.bottomText}>
|
||||
<FormattedMessage id="PasswordRecoveryForm.loginLinkInfo" values={{ loginLink }} />
|
||||
</p>
|
||||
<PrimaryButton className={css.submitButton} type="submit" disabled={buttonDisabled}>
|
||||
<FormattedMessage id="PasswordRecoveryForm.sendInstructions" />
|
||||
</PrimaryButton>
|
||||
</div>
|
||||
</form>
|
||||
);
|
||||
};
|
||||
|
||||
PasswordRecoveryFormComponent.defaultProps = {
|
||||
rootClassName: null,
|
||||
className: null,
|
||||
recoveryError: null,
|
||||
};
|
||||
|
||||
const { instanceOf, string } = PropTypes;
|
||||
|
||||
PasswordRecoveryFormComponent.propTypes = {
|
||||
...formPropTypes,
|
||||
rootClassName: string,
|
||||
className: string,
|
||||
recoveryError: instanceOf(Error),
|
||||
intl: intlShape.isRequired,
|
||||
};
|
||||
|
||||
const defaultFormName = 'PasswordRecoveryForm';
|
||||
|
||||
const PasswordRecoveryForm = compose(reduxForm({ form: defaultFormName }), injectIntl)(
|
||||
PasswordRecoveryFormComponent
|
||||
);
|
||||
|
||||
export default PasswordRecoveryForm;
|
||||
53
src/containers/PasswordRecoveryPage/DoorIcon.js
Normal file
53
src/containers/PasswordRecoveryPage/DoorIcon.js
Normal file
|
|
@ -0,0 +1,53 @@
|
|||
import React, { PropTypes } from 'react';
|
||||
|
||||
const DoorIcon = props => {
|
||||
const { className } = props;
|
||||
return (
|
||||
<svg
|
||||
className={className}
|
||||
width="52"
|
||||
height="59"
|
||||
viewBox="0 0 52 59"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<g strokeWidth="2.5" fill="none" fillRule="evenodd" strokeLinejoin="round">
|
||||
<path
|
||||
stroke="#C0392B"
|
||||
fill="#C0392B"
|
||||
strokeLinecap="round"
|
||||
d="M28.182 42.364H50V26H28.182z"
|
||||
/>
|
||||
<path d="M39.09 38v-4.364" stroke="#FFF" strokeLinecap="round" />
|
||||
<path
|
||||
d="M31.455 26v-2.455c0-4.067 3.176-7.363 7.09-7.363 3.915 0 7.09 3.296 7.09 7.363V26"
|
||||
stroke="#C0392B"
|
||||
strokeLinecap="round"
|
||||
/>
|
||||
<path
|
||||
d="M40.182 32.545c0 .603-.49 1.09-1.09 1.09-.603 0-1.092-.487-1.092-1.09 0-.602.49-1.09 1.09-1.09.603 0 1.092.488 1.092 1.09z"
|
||||
stroke="#FFF"
|
||||
/>
|
||||
<path
|
||||
stroke="#C0392B"
|
||||
strokeLinecap="round"
|
||||
d="M23.818 7.455h19.637V14M43.455 47.818v4.364H23.818M2 52.798l21.818 4.838V2L2 6.838z"
|
||||
/>
|
||||
<path
|
||||
d="M19.455 30.91c0 2.107-1.71 3.817-3.82 3.817-2.106 0-3.817-1.71-3.817-3.818 0-2.11 1.71-3.82 3.818-3.82s3.82 1.71 3.82 3.82z"
|
||||
stroke="#C0392B"
|
||||
strokeLinecap="round"
|
||||
/>
|
||||
</g>
|
||||
</svg>
|
||||
);
|
||||
};
|
||||
|
||||
DoorIcon.defaultProps = { className: null };
|
||||
|
||||
const { string } = PropTypes;
|
||||
|
||||
DoorIcon.propTypes = {
|
||||
className: string,
|
||||
};
|
||||
|
||||
export default DoorIcon;
|
||||
118
src/containers/PasswordRecoveryPage/PasswordRecoveryPage.css
Normal file
118
src/containers/PasswordRecoveryPage/PasswordRecoveryPage.css
Normal file
|
|
@ -0,0 +1,118 @@
|
|||
@import '../../marketplace.css';
|
||||
|
||||
:root {
|
||||
--baseContent: {
|
||||
flex-grow: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
padding: 32px 24px 98px 24px;
|
||||
background-color: var(--matterColorLight);
|
||||
border-radius: 2px;
|
||||
|
||||
@media (--viewportMedium) {
|
||||
flex-basis: 480px;
|
||||
flex-grow: 0;
|
||||
padding: 55px;
|
||||
margin-top: 7.5vh;
|
||||
margin-bottom: 7.5vh;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.root {
|
||||
@apply --backgroundImage;
|
||||
|
||||
display: flex;
|
||||
flex: 1;
|
||||
|
||||
@media (--viewportMedium) {
|
||||
justify-content: center;
|
||||
align-items: flex-start;
|
||||
}
|
||||
}
|
||||
|
||||
.submitEmailContent {
|
||||
@apply --baseContent;
|
||||
}
|
||||
|
||||
.emailSubmittedContent {
|
||||
@apply --baseContent;
|
||||
padding-bottom: 99px;
|
||||
|
||||
@media (--viewportMedium) {
|
||||
padding-bottom: 62px;
|
||||
min-height: 508px;
|
||||
}
|
||||
}
|
||||
|
||||
.emailNotVerifiedContent {
|
||||
@apply --baseContent;
|
||||
padding-top: 30px;
|
||||
|
||||
@media (--viewportMedium) {
|
||||
padding-top: 53px;
|
||||
padding-bottom: 80px;
|
||||
}
|
||||
}
|
||||
|
||||
.genericErrorContent {
|
||||
@apply --baseContent;
|
||||
}
|
||||
|
||||
.keysIconRoot {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.title {
|
||||
@apply --marketplaceModalTitle;
|
||||
margin-top: 31px;
|
||||
margin-bottom: 0px;
|
||||
}
|
||||
|
||||
.message {
|
||||
margin-top: 23px;
|
||||
margin-bottom: 0px;
|
||||
|
||||
@media (--viewportMedium) {
|
||||
margin-top: 24px;
|
||||
margin-bottom: 0px;
|
||||
}
|
||||
}
|
||||
|
||||
.submittedEmail {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.emailNotVerifiedBottomText {
|
||||
margin-top: 30px;
|
||||
margin-bottom: 0px;
|
||||
|
||||
@media (--viewportMedium) {
|
||||
margin-top: 32px;
|
||||
}
|
||||
}
|
||||
|
||||
.bottomWrapper {
|
||||
margin-top: auto;
|
||||
}
|
||||
|
||||
.bottomInfo {
|
||||
@apply --marketplaceH5FontStyles;
|
||||
margin: 0;
|
||||
color: var(--matterColorAnti);
|
||||
|
||||
@media (--viewportMedium) {
|
||||
margin: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.bottomLink {
|
||||
@apply --marketplaceH5FontStyles;
|
||||
margin: 0;
|
||||
color: var(--matterColor);
|
||||
|
||||
@media (--viewportMedium) {
|
||||
margin: 0;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,80 @@
|
|||
// ================ Action types ================ //
|
||||
|
||||
export const RECOVERY_REQUEST = 'app/PasswordRecoveryPage/RECOVERY_REQUEST';
|
||||
export const RECOVERY_SUCCESS = 'app/PasswordRecoveryPage/RECOVERY_SUCCESS';
|
||||
export const RECOVERY_ERROR = 'app/PasswordRecoveryPage/RECOVERY_ERROR';
|
||||
export const RETYPE_EMAIL = 'app/PasswordRecoveryPage/RETYPE_EMAIL';
|
||||
export const CLEAR_RECOVERY_ERROR = 'app/PasswordRecoveryPage/CLEAR_RECOVERY_ERROR';
|
||||
|
||||
// ================ Reducer ================ //
|
||||
|
||||
const initialState = {
|
||||
initialEmail: null,
|
||||
submittedEmail: null,
|
||||
recoveryError: null,
|
||||
recoveryInProgress: false,
|
||||
passwordRequested: false,
|
||||
};
|
||||
|
||||
export default function reducer(state = initialState, action = {}) {
|
||||
const { type, payload } = action;
|
||||
switch (type) {
|
||||
case RECOVERY_REQUEST:
|
||||
return {
|
||||
...state,
|
||||
submittedEmail: null,
|
||||
recoveryInProgress: true,
|
||||
recoveryError: null,
|
||||
};
|
||||
case RECOVERY_SUCCESS:
|
||||
return {
|
||||
...state,
|
||||
submittedEmail: payload.email,
|
||||
initialEmail: payload.email,
|
||||
recoveryInProgress: false,
|
||||
passwordRequested: true,
|
||||
};
|
||||
case RECOVERY_ERROR:
|
||||
return {
|
||||
...state,
|
||||
recoveryInProgress: false,
|
||||
recoveryError: payload.error,
|
||||
initialEmail: payload.email,
|
||||
};
|
||||
case RETYPE_EMAIL:
|
||||
return {
|
||||
...state,
|
||||
initialEmail: state.submittedEmail,
|
||||
submittedEmail: null,
|
||||
passwordRequested: false,
|
||||
};
|
||||
case CLEAR_RECOVERY_ERROR:
|
||||
return { ...state, recoveryError: null };
|
||||
default:
|
||||
return state;
|
||||
}
|
||||
}
|
||||
|
||||
// ================ Action creators ================ //
|
||||
|
||||
export const passwordRecoveryRequest = () => ({ type: RECOVERY_REQUEST });
|
||||
export const passwordRecoverySuccess = email => ({ type: RECOVERY_SUCCESS, payload: { email } });
|
||||
export const passwordRecoveryError = (error, email) => ({
|
||||
type: RECOVERY_ERROR,
|
||||
payload: { error, email },
|
||||
error: true,
|
||||
});
|
||||
export const retypePasswordRecoveryEmail = () => ({ type: RETYPE_EMAIL });
|
||||
export const clearPasswordRecoveryError = () => ({ type: CLEAR_RECOVERY_ERROR });
|
||||
|
||||
// ================ Thunks ================ //
|
||||
|
||||
export const recoverPassword = email =>
|
||||
(dispatch, getState, sdk) => {
|
||||
dispatch(passwordRecoveryRequest());
|
||||
|
||||
return sdk.passwordReset
|
||||
.request({ email })
|
||||
.then(() => dispatch(passwordRecoverySuccess(email)))
|
||||
.catch(error => dispatch(passwordRecoveryError(error, email)));
|
||||
};
|
||||
283
src/containers/PasswordRecoveryPage/PasswordRecoveryPage.js
Normal file
283
src/containers/PasswordRecoveryPage/PasswordRecoveryPage.js
Normal file
|
|
@ -0,0 +1,283 @@
|
|||
import React, { PropTypes } from 'react';
|
||||
import { compose } from 'redux';
|
||||
import { connect } from 'react-redux';
|
||||
import { withRouter } from 'react-router-dom';
|
||||
import { FormattedMessage, injectIntl, intlShape } from 'react-intl';
|
||||
import * as propTypes from '../../util/propTypes';
|
||||
import { sendVerificationEmail } from '../../ducks/user.duck';
|
||||
import { logout, authenticationInProgress } from '../../ducks/Auth.duck';
|
||||
import { manageDisableScrolling, isScrollingDisabled } from '../../ducks/UI.duck';
|
||||
import { recoverPassword, retypePasswordRecoveryEmail, clearPasswordRecoveryError } from './PasswordRecoveryPage.duck';
|
||||
import { PageLayout, Topbar, InlineTextButton, KeysIcon } from '../../components';
|
||||
import { PasswordRecoveryForm } from '../../containers';
|
||||
|
||||
import DoorIcon from './DoorIcon';
|
||||
import css from './PasswordRecoveryPage.css';
|
||||
|
||||
export const PasswordRecoveryPageComponent = props => {
|
||||
const {
|
||||
authInfoError,
|
||||
authInProgress,
|
||||
currentUser,
|
||||
currentUserHasListings,
|
||||
currentUserHasOrders,
|
||||
history,
|
||||
isAuthenticated,
|
||||
location,
|
||||
logoutError,
|
||||
notificationCount,
|
||||
onLogout,
|
||||
onManageDisableScrolling,
|
||||
sendVerificationEmailInProgress,
|
||||
sendVerificationEmailError,
|
||||
onResendVerificationEmail,
|
||||
initialEmail,
|
||||
submittedEmail,
|
||||
recoveryError,
|
||||
recoveryInProgress,
|
||||
passwordRequested,
|
||||
onChange,
|
||||
onSubmitEmail,
|
||||
onRetypeEmail,
|
||||
intl,
|
||||
} = props;
|
||||
|
||||
const title = intl.formatMessage({
|
||||
id: 'PasswordRecoveryPage.title',
|
||||
});
|
||||
|
||||
const resendEmailLink = (
|
||||
<InlineTextButton className={css.bottomLink} onClick={() => onSubmitEmail(submittedEmail)}>
|
||||
<FormattedMessage id="PasswordRecoveryPage.resendEmailLinkText" />
|
||||
</InlineTextButton>
|
||||
);
|
||||
|
||||
const fixEmailLink = (
|
||||
<InlineTextButton className={css.bottomLink} onClick={onRetypeEmail}>
|
||||
<FormattedMessage id="PasswordRecoveryPage.fixEmailLinkText" />
|
||||
</InlineTextButton>
|
||||
);
|
||||
|
||||
const submitEmailContent = (
|
||||
<div className={css.submitEmailContent}>
|
||||
<KeysIcon />
|
||||
<h1 className={css.title}>
|
||||
<FormattedMessage id="PasswordRecoveryPage.forgotPasswordTitle" />
|
||||
</h1>
|
||||
<p className={css.message}>
|
||||
<FormattedMessage id="PasswordRecoveryPage.forgotPasswordMessage" />
|
||||
</p>
|
||||
<PasswordRecoveryForm
|
||||
onChange={onChange}
|
||||
onSubmit={values => onSubmitEmail(values.email)}
|
||||
initialValues={{ email: initialEmail }}
|
||||
recoveryError={recoveryError}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
|
||||
const submittedEmailText = passwordRequested
|
||||
? <span className={css.submittedEmail}>{initialEmail}</span>
|
||||
: <span className={css.submittedEmail}>{submittedEmail}</span>;
|
||||
|
||||
const emailSubmittedContent = (
|
||||
<div className={css.emailSubmittedContent}>
|
||||
<KeysIcon />
|
||||
<h1 className={css.title}>
|
||||
<FormattedMessage id="PasswordRecoveryPage.emailSubmittedTitle" />
|
||||
</h1>
|
||||
<p className={css.message}>
|
||||
<FormattedMessage
|
||||
id="PasswordRecoveryPage.emailSubmittedMessage"
|
||||
values={{ submittedEmailText }}
|
||||
/>
|
||||
</p>
|
||||
<div className={css.bottomWrapper}>
|
||||
<p className={css.bottomInfo}>
|
||||
{recoveryInProgress
|
||||
? <FormattedMessage id="PasswordRecoveryPage.resendingEmailInfo" />
|
||||
: <FormattedMessage
|
||||
id="PasswordRecoveryPage.resendEmailInfo"
|
||||
values={{ resendEmailLink }}
|
||||
/>}
|
||||
</p>
|
||||
<p className={css.bottomInfo}>
|
||||
<FormattedMessage id="PasswordRecoveryPage.fixEmailInfo" values={{ fixEmailLink }} />
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
const initialEmailText = <span className={css.submittedEmail}>{initialEmail}</span>;
|
||||
const emailNotVerifiedContent = (
|
||||
<div className={css.emailNotVerifiedContent}>
|
||||
<DoorIcon />
|
||||
<h1 className={css.title}>
|
||||
<FormattedMessage id="PasswordRecoveryPage.emailNotVerifiedTitle" />
|
||||
</h1>
|
||||
<p className={css.message}>
|
||||
<FormattedMessage
|
||||
id="PasswordRecoveryPage.emailNotVerifiedMessage"
|
||||
values={{ initialEmailText }}
|
||||
/>
|
||||
</p>
|
||||
<p className={css.emailNotVerifiedBottomText}>
|
||||
<FormattedMessage id="PasswordRecoveryPage.emailNotVerifiedContactAdmin" />
|
||||
</p>
|
||||
</div>
|
||||
);
|
||||
|
||||
const genericErrorContent = (
|
||||
<div className={css.genericErrorContent}>
|
||||
<KeysIcon />
|
||||
<h1 className={css.title}>
|
||||
<FormattedMessage id="PasswordRecoveryPage.actionFailedTitle" />
|
||||
</h1>
|
||||
<p>
|
||||
<FormattedMessage id="PasswordRecoveryPage.actionFailedMessage" />
|
||||
</p>
|
||||
</div>
|
||||
);
|
||||
|
||||
let content;
|
||||
if (recoveryError && recoveryError.status === 409) {
|
||||
content = emailNotVerifiedContent;
|
||||
} else if (recoveryError && recoveryError.status === 404) {
|
||||
content = submitEmailContent;
|
||||
} else if (recoveryError) {
|
||||
content = genericErrorContent;
|
||||
} else if (submittedEmail || passwordRequested) {
|
||||
content = emailSubmittedContent;
|
||||
} else {
|
||||
content = submitEmailContent;
|
||||
}
|
||||
|
||||
return (
|
||||
<PageLayout authInfoError={authInfoError} logoutError={logoutError} title={title}>
|
||||
<Topbar
|
||||
authInProgress={authInProgress}
|
||||
currentUser={currentUser}
|
||||
currentUserHasListings={currentUserHasListings}
|
||||
currentUserHasOrders={currentUserHasOrders}
|
||||
history={history}
|
||||
isAuthenticated={isAuthenticated}
|
||||
location={location}
|
||||
notificationCount={notificationCount}
|
||||
onLogout={onLogout}
|
||||
onManageDisableScrolling={onManageDisableScrolling}
|
||||
onResendVerificationEmail={onResendVerificationEmail}
|
||||
sendVerificationEmailInProgress={sendVerificationEmailInProgress}
|
||||
sendVerificationEmailError={sendVerificationEmailError}
|
||||
/>
|
||||
<div className={css.root}>
|
||||
{content}
|
||||
</div>
|
||||
|
||||
</PageLayout>
|
||||
);
|
||||
};
|
||||
|
||||
PasswordRecoveryPageComponent.defaultProps = {
|
||||
authInfoError: null,
|
||||
currentUser: null,
|
||||
currentUserHasOrders: null,
|
||||
logoutError: null,
|
||||
notificationCount: 0,
|
||||
sendVerificationEmailError: null,
|
||||
initialEmail: null,
|
||||
submittedEmail: null,
|
||||
recoveryError: null,
|
||||
};
|
||||
|
||||
const { bool, func, instanceOf, number, object, shape, string } = PropTypes;
|
||||
|
||||
PasswordRecoveryPageComponent.propTypes = {
|
||||
authInfoError: instanceOf(Error),
|
||||
authInProgress: bool.isRequired,
|
||||
currentUser: propTypes.currentUser,
|
||||
currentUserHasListings: bool.isRequired,
|
||||
currentUserHasOrders: bool,
|
||||
isAuthenticated: bool.isRequired,
|
||||
logoutError: instanceOf(Error),
|
||||
notificationCount: number,
|
||||
onLogout: func.isRequired,
|
||||
onManageDisableScrolling: func.isRequired,
|
||||
sendVerificationEmailInProgress: bool.isRequired,
|
||||
sendVerificationEmailError: instanceOf(Error),
|
||||
onResendVerificationEmail: func.isRequired,
|
||||
initialEmail: string,
|
||||
submittedEmail: string,
|
||||
recoveryError: instanceOf(Error),
|
||||
recoveryInProgress: bool.isRequired,
|
||||
passwordRequested: bool.isRequired,
|
||||
onChange: func.isRequired,
|
||||
onSubmitEmail: func.isRequired,
|
||||
onRetypeEmail: func.isRequired,
|
||||
|
||||
// from injectIntl
|
||||
intl: intlShape.isRequired,
|
||||
|
||||
// from withRouter
|
||||
history: shape({
|
||||
push: func.isRequired,
|
||||
}).isRequired,
|
||||
location: shape({ state: object }).isRequired,
|
||||
};
|
||||
|
||||
const mapStateToProps = state => {
|
||||
// PageLayout needs authInfoError and logoutError, Topbar needs isAuthenticated
|
||||
const { authInfoError, isAuthenticated, logoutError } = state.Auth;
|
||||
// Topbar needs user info.
|
||||
const {
|
||||
currentUser,
|
||||
currentUserHasListings,
|
||||
currentUserHasOrders,
|
||||
currentUserNotificationCount: notificationCount,
|
||||
sendVerificationEmailInProgress,
|
||||
sendVerificationEmailError,
|
||||
} = state.user;
|
||||
|
||||
const {
|
||||
initialEmail,
|
||||
submittedEmail,
|
||||
recoveryError,
|
||||
recoveryInProgress,
|
||||
passwordRequested,
|
||||
} = state.PasswordRecoveryPage;
|
||||
return {
|
||||
authInfoError,
|
||||
authInProgress: authenticationInProgress(state),
|
||||
currentUser,
|
||||
currentUserHasListings,
|
||||
currentUserHasOrders,
|
||||
currentUserNotificationCount: notificationCount,
|
||||
isAuthenticated,
|
||||
logoutError,
|
||||
scrollingDisabled: isScrollingDisabled(state),
|
||||
sendVerificationEmailInProgress,
|
||||
sendVerificationEmailError,
|
||||
initialEmail,
|
||||
submittedEmail,
|
||||
recoveryError,
|
||||
recoveryInProgress,
|
||||
passwordRequested,
|
||||
};
|
||||
};
|
||||
|
||||
const mapDispatchToProps = dispatch => ({
|
||||
onLogout: historyPush => dispatch(logout(historyPush)),
|
||||
onManageDisableScrolling: (componentId, disableScrolling) =>
|
||||
dispatch(manageDisableScrolling(componentId, disableScrolling)),
|
||||
onResendVerificationEmail: () => dispatch(sendVerificationEmail()),
|
||||
onChange: () => dispatch(clearPasswordRecoveryError()),
|
||||
onSubmitEmail: email => dispatch(recoverPassword(email)),
|
||||
onRetypeEmail: () => dispatch(retypePasswordRecoveryEmail()),
|
||||
});
|
||||
|
||||
const PasswordRecoveryPage = compose(
|
||||
connect(mapStateToProps, mapDispatchToProps),
|
||||
withRouter,
|
||||
injectIntl
|
||||
)(PasswordRecoveryPageComponent);
|
||||
|
||||
export default PasswordRecoveryPage;
|
||||
|
|
@ -1,13 +1,14 @@
|
|||
import React from 'react';
|
||||
import { renderShallow } from '../../util/test-helpers';
|
||||
import { PasswordForgottenPageComponent } from './PasswordForgottenPage';
|
||||
import { fakeIntl } from '../../util/test-data';
|
||||
import { PasswordRecoveryPageComponent } from './PasswordRecoveryPage';
|
||||
|
||||
const noop = () => null;
|
||||
|
||||
describe('ContactDetailsPage', () => {
|
||||
it('matches snapshot', () => {
|
||||
const tree = renderShallow(
|
||||
<PasswordForgottenPageComponent
|
||||
<PasswordRecoveryPageComponent
|
||||
params={{ displayName: 'my-shop' }}
|
||||
history={{ push: noop }}
|
||||
location={{ search: '' }}
|
||||
|
|
@ -19,6 +20,12 @@ describe('ContactDetailsPage', () => {
|
|||
onManageDisableScrolling={noop}
|
||||
sendVerificationEmailInProgress={false}
|
||||
onResendVerificationEmail={noop}
|
||||
recoveryInProgress={false}
|
||||
passwordRequested={false}
|
||||
onChange={noop}
|
||||
onSubmitEmail={noop}
|
||||
onRetypeEmail={noop}
|
||||
intl={fakeIntl}
|
||||
/>
|
||||
);
|
||||
expect(tree).toMatchSnapshot();
|
||||
|
|
@ -2,7 +2,7 @@ exports[`ContactDetailsPage matches snapshot 1`] = `
|
|||
<withRouter(PageLayout)
|
||||
authInfoError={null}
|
||||
logoutError={null}
|
||||
title="Request new password">
|
||||
title="PasswordRecoveryPage.title">
|
||||
<Topbar
|
||||
authInProgress={false}
|
||||
currentUser={null}
|
||||
|
|
@ -25,7 +25,30 @@ exports[`ContactDetailsPage matches snapshot 1`] = `
|
|||
onResendVerificationEmail={[Function]}
|
||||
sendVerificationEmailError={null}
|
||||
sendVerificationEmailInProgress={false} />
|
||||
<ReduxForm
|
||||
onSubmit={[Function]} />
|
||||
<div>
|
||||
<div>
|
||||
<KeysIcon
|
||||
className={null} />
|
||||
<h1>
|
||||
<FormattedMessage
|
||||
id="PasswordRecoveryPage.forgotPasswordTitle"
|
||||
values={Object {}} />
|
||||
</h1>
|
||||
<p>
|
||||
<FormattedMessage
|
||||
id="PasswordRecoveryPage.forgotPasswordMessage"
|
||||
values={Object {}} />
|
||||
</p>
|
||||
<ReduxForm
|
||||
initialValues={
|
||||
Object {
|
||||
"email": null,
|
||||
}
|
||||
}
|
||||
onChange={[Function]}
|
||||
onSubmit={[Function]}
|
||||
recoveryError={null} />
|
||||
</div>
|
||||
</div>
|
||||
</withRouter(PageLayout)>
|
||||
`;
|
||||
|
|
@ -18,10 +18,10 @@ import LoginForm from './LoginForm/LoginForm';
|
|||
import ManageListingsPage from './ManageListingsPage/ManageListingsPage';
|
||||
import NotFoundPage from './NotFoundPage/NotFoundPage';
|
||||
import OrderPage from './OrderPage/OrderPage';
|
||||
import PasswordForgottenForm from './PasswordForgottenForm/PasswordForgottenForm';
|
||||
import PasswordForgottenPage from './PasswordForgottenPage/PasswordForgottenPage';
|
||||
import PasswordResetForm from './PasswordResetForm/PasswordResetForm';
|
||||
import PasswordResetPage from './PasswordResetPage/PasswordResetPage';
|
||||
import PasswordRecoveryForm from './PasswordRecoveryForm/PasswordRecoveryForm';
|
||||
import PasswordRecoveryPage from './PasswordRecoveryPage/PasswordRecoveryPage';
|
||||
import PayoutDetailsForm from './PayoutDetailsForm/PayoutDetailsForm';
|
||||
import PayoutPreferencesPage from './PayoutPreferencesPage/PayoutPreferencesPage';
|
||||
import ProfilePage from './ProfilePage/ProfilePage';
|
||||
|
|
@ -56,10 +56,10 @@ export {
|
|||
ManageListingsPage,
|
||||
NotFoundPage,
|
||||
OrderPage,
|
||||
PasswordForgottenForm,
|
||||
PasswordForgottenPage,
|
||||
PasswordResetForm,
|
||||
PasswordResetPage,
|
||||
PasswordRecoveryForm,
|
||||
PasswordRecoveryPage,
|
||||
PayoutDetailsForm,
|
||||
PayoutPreferencesPage,
|
||||
ProfilePage,
|
||||
|
|
|
|||
|
|
@ -13,6 +13,7 @@ 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 PasswordRecoveryPage from './PasswordRecoveryPage/PasswordRecoveryPage.duck';
|
||||
|
||||
export {
|
||||
CheckoutPage,
|
||||
|
|
@ -25,4 +26,5 @@ export {
|
|||
ProfileSettingsPage,
|
||||
SalePage,
|
||||
SearchPage,
|
||||
PasswordRecoveryPage,
|
||||
};
|
||||
|
|
|
|||
|
|
@ -42,9 +42,9 @@ import * as EditListingPhotosForm
|
|||
import * as EditListingPricingForm
|
||||
from './containers/EditListingPricingForm/EditListingPricingForm.example';
|
||||
import * as LoginForm from './containers/LoginForm/LoginForm.example';
|
||||
import * as PasswordForgottenForm
|
||||
from './containers/PasswordForgottenForm/PasswordForgottenForm.example';
|
||||
import * as PasswordResetForm from './containers/PasswordResetForm/PasswordResetForm.example';
|
||||
import * as PasswordRecoveryForm
|
||||
from './containers/PasswordRecoveryForm/PasswordRecoveryForm.example';
|
||||
import * as PayoutDetailsForm from './containers/PayoutDetailsForm/PayoutDetailsForm.example';
|
||||
import * as SignupForm from './containers/SignupForm/SignupForm.example';
|
||||
import * as StripePaymentForm from './containers/StripePaymentForm/StripePaymentForm.example';
|
||||
|
|
@ -78,8 +78,8 @@ export {
|
|||
ModalInMobile,
|
||||
NamedLink,
|
||||
PaginationLinks,
|
||||
PasswordForgottenForm,
|
||||
PasswordResetForm,
|
||||
PasswordRecoveryForm,
|
||||
PayoutDetailsForm,
|
||||
ResponsiveImage,
|
||||
SelectField,
|
||||
|
|
|
|||
|
|
@ -11,8 +11,8 @@ import {
|
|||
ManageListingsPage,
|
||||
NotFoundPage,
|
||||
OrderPage,
|
||||
PasswordForgottenPage,
|
||||
PasswordResetPage,
|
||||
PasswordRecoveryPage,
|
||||
PayoutPreferencesPage,
|
||||
ProfilePage,
|
||||
ProfileSettingsPage,
|
||||
|
|
@ -145,16 +145,10 @@ const routesConfiguration = [
|
|||
component: props => <AuthenticationPage {...props} tab="signup" />,
|
||||
},
|
||||
{
|
||||
path: '/password',
|
||||
path: '/recover-password',
|
||||
exact: true,
|
||||
name: 'PasswordPage',
|
||||
component: props => <PasswordForgottenPage {...props} />,
|
||||
},
|
||||
{
|
||||
path: '/password/forgotten',
|
||||
exact: true,
|
||||
name: 'PasswordForgottenPage',
|
||||
component: props => <PasswordForgottenPage {...props} />,
|
||||
name: 'PasswordRecoveryPage',
|
||||
component: props => <PasswordRecoveryPage {...props} />,
|
||||
},
|
||||
{
|
||||
path: '/inbox',
|
||||
|
|
|
|||
|
|
@ -166,7 +166,9 @@
|
|||
"LoginForm.emailLabel": "Email",
|
||||
"LoginForm.emailPlaceholder": "john.doe@example.com",
|
||||
"LoginForm.emailRequired": "This field is required",
|
||||
"LoginForm.forgotPassword": "Forgot your password?",
|
||||
"LoginForm.logIn": "Log in",
|
||||
"LoginForm.forgotPasswordHelp": "No problem.",
|
||||
"LoginForm.passwordLabel": "Password",
|
||||
"LoginForm.passwordPlaceholder": "Enter your password…",
|
||||
"LoginForm.passwordRequired": "This field is required",
|
||||
|
|
@ -223,6 +225,28 @@
|
|||
"PasswordResetPage.passwordChangedHelpText": "Your password has been changed successfully.",
|
||||
"PasswordResetPage.resetFailed": "Reset failed. Please try again.",
|
||||
"PasswordResetPage.title": "Reset password",
|
||||
"PasswordRecoveryForm.emailLabel": "Email",
|
||||
"PasswordRecoveryForm.emailNotFound": "Hmm. We didn't find an account with that email. Please double-check your email and try again.",
|
||||
"PasswordRecoveryForm.emailPlaceholder": "john.doe@example.com",
|
||||
"PasswordRecoveryForm.emailRequired": "This field is required",
|
||||
"PasswordRecoveryForm.loginLinkText": "Go log in.",
|
||||
"PasswordRecoveryForm.loginLinkInfo": "Suddenly remembered your password? {loginLink}",
|
||||
"PasswordRecoveryForm.sendInstructions": "Send instructions",
|
||||
"PasswordRecoveryPage.actionFailedTitle": "Whoops!",
|
||||
"PasswordRecoveryPage.actionFailedMessage": "Something went wrong. Please refresh the page and try again.",
|
||||
"PasswordRecoveryPage.emailNotVerifiedTitle": "We have bad news",
|
||||
"PasswordRecoveryPage.emailNotVerifiedMessage": "Unfortunately, we cannot recover your password, because your email {initialEmailText} hasn't been verified.",
|
||||
"PasswordRecoveryPage.emailNotVerifiedContactAdmin": "To resolve the issue, please contact Saunatime administrators.",
|
||||
"PasswordRecoveryPage.emailSubmittedTitle": "Check your inbox",
|
||||
"PasswordRecoveryPage.emailSubmittedMessage": "We have sent the instructions for resetting your password to {submittedEmailText}.",
|
||||
"PasswordRecoveryPage.fixEmailLinkText": "Fix it.",
|
||||
"PasswordRecoveryPage.fixEmailInfo": "Whoops, typo in your email? {fixEmailLink}",
|
||||
"PasswordRecoveryPage.forgotPasswordTitle": "Forgot your password?",
|
||||
"PasswordRecoveryPage.forgotPasswordMessage": "No worries! Please enter the email address you used when signing up and we'll send you instructions on how to set a new password.",
|
||||
"PasswordRecoveryPage.resendEmailLinkText": "Send another email.",
|
||||
"PasswordRecoveryPage.resendEmailInfo": "Didn't get the email? {resendEmailLink}",
|
||||
"PasswordRecoveryPage.resendingEmailInfo": "Resending instructions...",
|
||||
"PasswordRecoveryPage.title": "Request a new password",
|
||||
"PayoutDetailsForm.addressTitle": "Address",
|
||||
"PayoutDetailsForm.bankDetails": "Bank details",
|
||||
"PayoutDetailsForm.birthdayDatePlaceholder": "dd",
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue