mirror of
https://github.com/kingomarnajjar/flex-template-web.git
synced 2026-07-27 09:13:14 +10:00
Merge pull request #433 from sharetribe/change-email-errors
Change email errors
This commit is contained in:
commit
48ebdc2efb
12 changed files with 141 additions and 25 deletions
|
|
@ -21,9 +21,6 @@
|
|||
flex-direction: column;
|
||||
width: 100%;
|
||||
|
||||
/* Hide Topbar's box-shadow when side nav is stacked below Topbar as a new bar */
|
||||
z-index: calc(var(--zIndexTopbar));
|
||||
|
||||
@media (--viewportLarge) {
|
||||
flex-direction: row;
|
||||
justify-content: center;
|
||||
|
|
|
|||
|
|
@ -154,7 +154,9 @@ class TopbarComponent extends Component {
|
|||
const {
|
||||
className,
|
||||
rootClassName,
|
||||
desktopClassName,
|
||||
mobileRootClassName,
|
||||
mobileClassName,
|
||||
isAuthenticated,
|
||||
authInProgress,
|
||||
currentUser,
|
||||
|
|
@ -233,7 +235,7 @@ class TopbarComponent extends Component {
|
|||
|
||||
return (
|
||||
<div className={classes}>
|
||||
<div className={classNames(mobileRootClassName || css.container)}>
|
||||
<div className={classNames(mobileRootClassName || css.container, mobileClassName)}>
|
||||
<Button rootClassName={css.menu} onClick={this.handleMobileMenuOpen}>
|
||||
<MenuIcon
|
||||
className={css.menuIcon}
|
||||
|
|
@ -253,6 +255,7 @@ class TopbarComponent extends Component {
|
|||
</div>
|
||||
<div className={css.desktop}>
|
||||
<TopbarDesktop
|
||||
className={desktopClassName}
|
||||
currentUserHasListings={currentUserHasListings}
|
||||
currentUser={currentUser}
|
||||
currentPage={currentPage}
|
||||
|
|
@ -336,7 +339,9 @@ class TopbarComponent extends Component {
|
|||
TopbarComponent.defaultProps = {
|
||||
className: null,
|
||||
rootClassName: null,
|
||||
desktopClassName: null,
|
||||
mobileRootClassName: null,
|
||||
mobileClassName: null,
|
||||
notificationCount: 0,
|
||||
currentUser: null,
|
||||
currentUserHasOrders: null,
|
||||
|
|
@ -349,7 +354,9 @@ const { arrayOf, bool, func, instanceOf, number, shape, string } = PropTypes;
|
|||
TopbarComponent.propTypes = {
|
||||
className: string,
|
||||
rootClassName: string,
|
||||
desktopClassName: string,
|
||||
mobileRootClassName: string,
|
||||
mobileClassName: string,
|
||||
isAuthenticated: bool.isRequired,
|
||||
authInProgress: bool.isRequired,
|
||||
currentUser: propTypes.currentUser,
|
||||
|
|
|
|||
|
|
@ -32,8 +32,7 @@ const TopbarDesktop = props => {
|
|||
initialSearchFormValues,
|
||||
} = props;
|
||||
|
||||
const rootClass = rootClassName || css.root;
|
||||
const classes = classNames(rootClass, className);
|
||||
const classes = classNames(rootClassName || css.root, className);
|
||||
|
||||
const search = (
|
||||
<TopbarSearchForm
|
||||
|
|
|
|||
|
|
@ -78,12 +78,22 @@
|
|||
line-height: 20px;
|
||||
margin: 0;
|
||||
cursor: pointer;
|
||||
opacity: 1;
|
||||
transition: opacity var(--transitionStyleButton);
|
||||
|
||||
@media (--viewportMedium) {
|
||||
margin: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.resendClicked {
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
.tooMany {
|
||||
color: var(--matterColorAnti);
|
||||
}
|
||||
|
||||
.confirmChangesTitle {
|
||||
/* Font */
|
||||
color: var(--matterColorAnti);
|
||||
|
|
|
|||
|
|
@ -5,6 +5,10 @@ import { reduxForm, propTypes as formPropTypes } from 'redux-form';
|
|||
import classNames from 'classnames';
|
||||
import * as validators from '../../util/validators';
|
||||
import { ensureCurrentUser } from '../../util/data';
|
||||
import {
|
||||
isForbiddenChangeEmailError,
|
||||
isTooManyEmailVerificationRequestsError,
|
||||
} from '../../util/errors';
|
||||
import { PrimaryButton, TextInputField } from '../../components';
|
||||
|
||||
import css from './ContactDetailsForm.css';
|
||||
|
|
@ -13,6 +17,7 @@ const ContactDetailsFormComponent = props => {
|
|||
const {
|
||||
rootClassName,
|
||||
className,
|
||||
changeEmailError,
|
||||
currentUser,
|
||||
form,
|
||||
handleSubmit,
|
||||
|
|
@ -22,6 +27,9 @@ const ContactDetailsFormComponent = props => {
|
|||
invalid,
|
||||
onResendVerificationEmail,
|
||||
pristine,
|
||||
ready,
|
||||
sendVerificationEmailError,
|
||||
sendVerificationEmailInProgress,
|
||||
} = props;
|
||||
|
||||
const user = ensureCurrentUser(currentUser);
|
||||
|
|
@ -55,12 +63,22 @@ const ContactDetailsFormComponent = props => {
|
|||
);
|
||||
} else if (!emailVerified && !pendingEmail) {
|
||||
// Current email is unverified. This is the email given in sign up form
|
||||
/* eslint-disable jsx-a11y/no-static-element-interactions */
|
||||
const resendEmailLink = (
|
||||
<span className={css.helperLink} onClick={onResendVerificationEmail} role="button">
|
||||
<FormattedMessage id="ContactDetailsForm.resendEmailVerificationText" />
|
||||
</span>
|
||||
|
||||
const tooManyVerificationRequests = isTooManyEmailVerificationRequestsError(
|
||||
sendVerificationEmailError
|
||||
);
|
||||
const resendClasses = classNames(css.helperLink, {
|
||||
[css.resendClicked]: sendVerificationEmailInProgress,
|
||||
});
|
||||
|
||||
/* eslint-disable jsx-a11y/no-static-element-interactions */
|
||||
const resendEmailLink = tooManyVerificationRequests
|
||||
? <span className={css.tooMany}>
|
||||
<FormattedMessage id="ContactDetailsForm.tooManyVerificationRequests" />
|
||||
</span>
|
||||
: <span className={resendClasses} onClick={onResendVerificationEmail} role="button">
|
||||
<FormattedMessage id="ContactDetailsForm.resendEmailVerificationText" />
|
||||
</span>;
|
||||
/* eslint-enable jsx-a11y/no-static-element-interactions */
|
||||
|
||||
emailVerifiedInfo = (
|
||||
|
|
@ -74,16 +92,27 @@ const ContactDetailsFormComponent = props => {
|
|||
const pendingEmailStyled = <span className={css.emailStyle}>{pendingEmail}</span>;
|
||||
const pendingEmailCheckInbox = (
|
||||
<span className={css.checkInbox}>
|
||||
<FormattedMessage id="ContactDetailsForm.pendingEmailCheckInbox" values={{ pendingEmail: pendingEmailStyled}} />
|
||||
<FormattedMessage
|
||||
id="ContactDetailsForm.pendingEmailCheckInbox"
|
||||
values={{ pendingEmail: pendingEmailStyled }}
|
||||
/>
|
||||
</span>
|
||||
);
|
||||
|
||||
/* eslint-disable jsx-a11y/no-static-element-interactions */
|
||||
const resendPendingEmailLink = (
|
||||
<span className={css.helperLink} onClick={onResendVerificationEmail} role="button">
|
||||
<FormattedMessage id="ContactDetailsForm.resendEmailVerificationText" />
|
||||
</span>
|
||||
const tooManyVerificationRequests = isTooManyEmailVerificationRequestsError(
|
||||
sendVerificationEmailError
|
||||
);
|
||||
const resendClasses = classNames(css.helperLink, {
|
||||
[css.resendClicked]: sendVerificationEmailInProgress,
|
||||
});
|
||||
/* eslint-disable jsx-a11y/no-static-element-interactions */
|
||||
const resendPendingEmailLink = tooManyVerificationRequests
|
||||
? <span className={css.tooMany}>
|
||||
<FormattedMessage id="ContactDetailsForm.tooManyVerificationRequests" />
|
||||
</span>
|
||||
: <span className={resendClasses} onClick={onResendVerificationEmail} role="button">
|
||||
<FormattedMessage id="ContactDetailsForm.resendEmailVerificationText" />
|
||||
</span>;
|
||||
/* eslint-enable jsx-a11y/no-static-element-interactions */
|
||||
|
||||
emailVerifiedInfo = (
|
||||
|
|
@ -106,8 +135,16 @@ const ContactDetailsFormComponent = props => {
|
|||
const passwordRequiredMessage = intl.formatMessage({
|
||||
id: 'ContactDetailsForm.passwordRequired',
|
||||
});
|
||||
|
||||
const passwordRequired = validators.required(passwordRequiredMessage);
|
||||
|
||||
const passwordFailedMessage = intl.formatMessage({
|
||||
id: 'ContactDetailsForm.passwordFailed',
|
||||
});
|
||||
const passwordErrorText = isForbiddenChangeEmailError(changeEmailError)
|
||||
? passwordFailedMessage
|
||||
: null;
|
||||
|
||||
const classes = classNames(rootClassName || css.root, className);
|
||||
const submitDisabled = invalid || submitting || inProgress;
|
||||
|
||||
|
|
@ -141,10 +178,17 @@ const ContactDetailsFormComponent = props => {
|
|||
label={passwordLabel}
|
||||
placeholder={passwordPlaceholder}
|
||||
validate={passwordRequired}
|
||||
customErrorText={passwordErrorText}
|
||||
/>
|
||||
</div>
|
||||
<div className={css.bottomWrapper}>
|
||||
<PrimaryButton className={css.submitButton} type="submit" disabled={submitDisabled}>
|
||||
<PrimaryButton
|
||||
className={css.submitButton}
|
||||
type="submit"
|
||||
inProgress={inProgress}
|
||||
ready={ready}
|
||||
disabled={submitDisabled}
|
||||
>
|
||||
<FormattedMessage id="ContactDetailsForm.saveChanges" />
|
||||
</PrimaryButton>
|
||||
</div>
|
||||
|
|
@ -155,18 +199,25 @@ const ContactDetailsFormComponent = props => {
|
|||
ContactDetailsFormComponent.defaultProps = {
|
||||
rootClassName: null,
|
||||
className: null,
|
||||
changeEmailError: null,
|
||||
inProgress: false,
|
||||
sendVerificationEmailError: null,
|
||||
sendVerificationEmailInProgress: false,
|
||||
};
|
||||
|
||||
const { bool, func, string } = PropTypes;
|
||||
const { bool, func, instanceOf, string } = PropTypes;
|
||||
|
||||
ContactDetailsFormComponent.propTypes = {
|
||||
...formPropTypes,
|
||||
rootClassName: string,
|
||||
className: string,
|
||||
changeEmailError: instanceOf(Error),
|
||||
inProgress: bool,
|
||||
intl: intlShape.isRequired,
|
||||
onResendVerificationEmail: func.isRequired,
|
||||
ready: bool.isRequired,
|
||||
sendVerificationEmailError: instanceOf(Error),
|
||||
sendVerificationEmailInProgress: bool,
|
||||
};
|
||||
|
||||
const defaultFormName = 'ContactDetailsForm';
|
||||
|
|
|
|||
|
|
@ -13,6 +13,11 @@
|
|||
|
||||
}
|
||||
|
||||
.desktopTopbar,
|
||||
.mobileTopbar {
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
.tabs {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
|
|
|
|||
|
|
@ -6,23 +6,34 @@ export const CHANGE_EMAIL_REQUEST = 'app/ContactDetailsPage/CHANGE_EMAIL_REQUEST
|
|||
export const CHANGE_EMAIL_SUCCESS = 'app/ContactDetailsPage/CHANGE_EMAIL_SUCCESS';
|
||||
export const CHANGE_EMAIL_ERROR = 'app/ContactDetailsPage/CHANGE_EMAIL_ERROR';
|
||||
|
||||
export const CHANGE_EMAIL_CLEAR = 'app/ContactDetailsPage/CHANGE_EMAIL_CLEAR';
|
||||
|
||||
// ================ Reducer ================ //
|
||||
|
||||
const initialState = {
|
||||
changeEmailError: null,
|
||||
changeEmailInProgress: false,
|
||||
emailChanged: false,
|
||||
};
|
||||
|
||||
export default function reducer(state = initialState, action = {}) {
|
||||
const { type, payload } = action;
|
||||
switch (type) {
|
||||
case CHANGE_EMAIL_REQUEST:
|
||||
return { ...state, changeEmailInProgress: true, changeEmailError: null };
|
||||
return { ...state, changeEmailInProgress: true, changeEmailError: null, emailChanged: false };
|
||||
case CHANGE_EMAIL_SUCCESS:
|
||||
return { ...state, changeEmailInProgress: false };
|
||||
return { ...state, changeEmailInProgress: false, emailChanged: true };
|
||||
case CHANGE_EMAIL_ERROR:
|
||||
return { ...state, changeEmailInProgress: false, changeEmailError: payload };
|
||||
|
||||
case CHANGE_EMAIL_CLEAR:
|
||||
return {
|
||||
...state,
|
||||
changeEmailInProgress: false,
|
||||
changeEmailError: null,
|
||||
emailChanged: false,
|
||||
};
|
||||
|
||||
default:
|
||||
return state;
|
||||
}
|
||||
|
|
@ -38,6 +49,8 @@ export const changeEmailError = error => ({
|
|||
error: true,
|
||||
});
|
||||
|
||||
export const changeEmailClear = () => ({ type: CHANGE_EMAIL_CLEAR });
|
||||
|
||||
// ================ Thunks ================ //
|
||||
|
||||
export const changeEmail = params =>
|
||||
|
|
|
|||
|
|
@ -20,21 +20,25 @@ import {
|
|||
} from '../../components';
|
||||
import { ContactDetailsForm } from '../../containers';
|
||||
|
||||
import { changeEmail } from './ContactDetailsPage.duck';
|
||||
import { changeEmail, changeEmailClear } from './ContactDetailsPage.duck';
|
||||
import css from './ContactDetailsPage.css';
|
||||
|
||||
export const ContactDetailsPageComponent = props => {
|
||||
const {
|
||||
authInfoError,
|
||||
authInProgress,
|
||||
changeEmailError,
|
||||
changeEmailInProgress,
|
||||
currentUser,
|
||||
currentUserHasListings,
|
||||
currentUserHasOrders,
|
||||
emailChanged,
|
||||
history,
|
||||
isAuthenticated,
|
||||
location,
|
||||
logoutError,
|
||||
notificationCount,
|
||||
onChange,
|
||||
onLogout,
|
||||
onManageDisableScrolling,
|
||||
sendVerificationEmailInProgress,
|
||||
|
|
@ -66,10 +70,15 @@ export const ContactDetailsPageComponent = props => {
|
|||
? <ContactDetailsForm
|
||||
className={css.form}
|
||||
initialValues={{ email }}
|
||||
changeEmailError={changeEmailError}
|
||||
currentUser={currentUser}
|
||||
onResendVerificationEmail={onResendVerificationEmail}
|
||||
onSubmit={onSubmitChangeEmail}
|
||||
inProgress={authInProgress}
|
||||
onChange={onChange}
|
||||
inProgress={changeEmailInProgress}
|
||||
ready={emailChanged}
|
||||
sendVerificationEmailInProgress={sendVerificationEmailInProgress}
|
||||
sendVerificationEmailError={sendVerificationEmailError}
|
||||
/>
|
||||
: null;
|
||||
|
||||
|
|
@ -83,9 +92,11 @@ export const ContactDetailsPageComponent = props => {
|
|||
currentUser={currentUser}
|
||||
currentUserHasListings={currentUserHasListings}
|
||||
currentUserHasOrders={currentUserHasOrders}
|
||||
desktopClassName={css.desktopTopbar}
|
||||
history={history}
|
||||
isAuthenticated={isAuthenticated}
|
||||
location={location}
|
||||
mobileClassName={css.mobileTopbar}
|
||||
notificationCount={notificationCount}
|
||||
onLogout={onLogout}
|
||||
onManageDisableScrolling={onManageDisableScrolling}
|
||||
|
|
@ -113,6 +124,7 @@ export const ContactDetailsPageComponent = props => {
|
|||
|
||||
ContactDetailsPageComponent.defaultProps = {
|
||||
authInfoError: null,
|
||||
changeEmailError: null,
|
||||
currentUser: null,
|
||||
currentUserHasOrders: null,
|
||||
logoutError: null,
|
||||
|
|
@ -125,12 +137,16 @@ const { bool, func, instanceOf, number, object, shape } = PropTypes;
|
|||
ContactDetailsPageComponent.propTypes = {
|
||||
authInfoError: instanceOf(Error),
|
||||
authInProgress: bool.isRequired,
|
||||
changeEmailError: instanceOf(Error),
|
||||
changeEmailInProgress: bool.isRequired,
|
||||
currentUser: propTypes.currentUser,
|
||||
currentUserHasListings: bool.isRequired,
|
||||
currentUserHasOrders: bool,
|
||||
emailChanged: bool.isRequired,
|
||||
isAuthenticated: bool.isRequired,
|
||||
logoutError: instanceOf(Error),
|
||||
notificationCount: number,
|
||||
onChange: func.isRequired,
|
||||
onLogout: func.isRequired,
|
||||
onManageDisableScrolling: func.isRequired,
|
||||
onSubmitChangeEmail: func.isRequired,
|
||||
|
|
@ -157,12 +173,16 @@ const mapStateToProps = state => {
|
|||
sendVerificationEmailInProgress,
|
||||
sendVerificationEmailError,
|
||||
} = state.user;
|
||||
const { changeEmailError, changeEmailInProgress, emailChanged } = state.ContactDetailsPage;
|
||||
return {
|
||||
authInfoError,
|
||||
authInProgress: authenticationInProgress(state),
|
||||
changeEmailError,
|
||||
changeEmailInProgress,
|
||||
currentUser,
|
||||
currentUserHasListings,
|
||||
currentUserHasOrders,
|
||||
emailChanged,
|
||||
notificationCount,
|
||||
isAuthenticated,
|
||||
logoutError,
|
||||
|
|
@ -173,6 +193,7 @@ const mapStateToProps = state => {
|
|||
};
|
||||
|
||||
const mapDispatchToProps = dispatch => ({
|
||||
onChange: () => dispatch(changeEmailClear()),
|
||||
onLogout: historyPush => dispatch(logout(historyPush)),
|
||||
onManageDisableScrolling: (componentId, disableScrolling) =>
|
||||
dispatch(manageDisableScrolling(componentId, disableScrolling)),
|
||||
|
|
|
|||
|
|
@ -15,11 +15,14 @@ describe('ContactDetailsPage', () => {
|
|||
authInProgress={false}
|
||||
currentUserHasListings={false}
|
||||
isAuthenticated={false}
|
||||
onChange={noop}
|
||||
onLogout={noop}
|
||||
onManageDisableScrolling={noop}
|
||||
sendVerificationEmailInProgress={false}
|
||||
onResendVerificationEmail={noop}
|
||||
onSubmitChangeEmail={noop}
|
||||
changeEmailInProgress={false}
|
||||
emailChanged={false}
|
||||
/>
|
||||
);
|
||||
expect(tree).toMatchSnapshot();
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@
|
|||
* https://github.com/erikras/ducks-modular-redux
|
||||
*/
|
||||
import CheckoutPage from './CheckoutPage/CheckoutPage.duck';
|
||||
import ContactDetailsPage from './ContactDetailsPage/ContactDetailsPage.duck';
|
||||
import EditListingPage from './EditListingPage/EditListingPage.duck';
|
||||
import InboxPage from './InboxPage/InboxPage.duck';
|
||||
import ListingPage from './ListingPage/ListingPage.duck';
|
||||
|
|
@ -17,6 +18,7 @@ import PasswordRecoveryPage from './PasswordRecoveryPage/PasswordRecoveryPage.du
|
|||
|
||||
export {
|
||||
CheckoutPage,
|
||||
ContactDetailsPage,
|
||||
EditListingPage,
|
||||
InboxPage,
|
||||
ListingPage,
|
||||
|
|
|
|||
|
|
@ -51,16 +51,18 @@
|
|||
"ContactDetailsForm.confirmChangesInfo": "To change your email, you need to enter your current password.",
|
||||
"ContactDetailsForm.confirmChangesTitle": "Confirm your changes",
|
||||
"ContactDetailsForm.emailLabel": "Your email",
|
||||
"ContactDetailsForm.emailRequired": "This field is required",
|
||||
"ContactDetailsForm.emailRequired": "Email is required",
|
||||
"ContactDetailsForm.emailUnverified": "You haven't verified your email yet. {resendEmailLink}",
|
||||
"ContactDetailsForm.emailVerified": "Your email is verified.",
|
||||
"ContactDetailsForm.passwordFailed": "You entered wrong password",
|
||||
"ContactDetailsForm.passwordLabel": "Current password",
|
||||
"ContactDetailsForm.passwordPlaceholder": "Enter your current password…",
|
||||
"ContactDetailsForm.passwordRequired": "This field is required",
|
||||
"ContactDetailsForm.passwordRequired": "Password is required",
|
||||
"ContactDetailsForm.pendingEmailCheckInbox": "Check your inbox to verify {pendingEmail} now.",
|
||||
"ContactDetailsForm.pendingEmailUnverified": "You have changed your email address but haven’t verified the new address yet. {pendingEmailCheckInbox} {resendPendingEmailLink}",
|
||||
"ContactDetailsForm.resendEmailVerificationText": "Resend verification email.",
|
||||
"ContactDetailsForm.saveChanges": "Save changes",
|
||||
"ContactDetailsForm.tooManyVerificationRequests": "Too many email verification requests sent.",
|
||||
"ContactDetailsPage.emailTabTitle": "Email",
|
||||
"ContactDetailsPage.passwordTabTitle": "Password",
|
||||
"ContactDetailsPage.title": "Email settings",
|
||||
|
|
|
|||
|
|
@ -88,3 +88,9 @@ export const isTransactionInitiateListingNotFoundError = apiError => {
|
|||
return error.code === ERROR_CODE_TRANSITION_PARAMETER_VALIDATION_FAILED && notfound;
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Check if the given API error (from `sdk.currentUser.changeEmail(params)`)
|
||||
* is due to giving wrong password.
|
||||
*/
|
||||
export const isForbiddenChangeEmailError = apiError => apiError && apiError.status === 403;
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue