mirror of
https://github.com/kingomarnajjar/flex-template-web.git
synced 2026-07-28 20:53:24 +10:00
Email verification page
This commit is contained in:
parent
d6c6827270
commit
7b5d5d5fab
10 changed files with 414 additions and 1 deletions
|
|
@ -80,6 +80,7 @@ describe('Application', () => {
|
|||
'/account/contact-details': defaultAuthPath,
|
||||
'/account/payout-preferences': defaultAuthPath,
|
||||
'/account/security': defaultAuthPath,
|
||||
'/email_verification': defaultAuthPath,
|
||||
};
|
||||
forEach(urlRedirects, (redirectPath, url) => {
|
||||
const context = {};
|
||||
|
|
|
|||
|
|
@ -0,0 +1,25 @@
|
|||
@import '../../marketplace.css';
|
||||
|
||||
.root {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
flex: 1;
|
||||
justify-content: space-between;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.button {
|
||||
@apply --marketplaceButtonStyles;
|
||||
|
||||
margin-top: 24px;
|
||||
align-self: stretch;
|
||||
|
||||
@media (--viewportMedium) {
|
||||
margin-top: 72px;
|
||||
}
|
||||
}
|
||||
|
||||
.error {
|
||||
margin-top: 24px;
|
||||
color: var(--failColor);
|
||||
}
|
||||
|
|
@ -0,0 +1,99 @@
|
|||
import React, { PropTypes } from 'react';
|
||||
import { compose } from 'redux';
|
||||
import { FormattedMessage, injectIntl } from 'react-intl';
|
||||
import { reduxForm, Field, propTypes as formPropTypes } from 'redux-form';
|
||||
import { Button, NamedLink } from '../../components';
|
||||
|
||||
import * as propTypes from '../../util/propTypes';
|
||||
import css from './EmailVerificationForm.css';
|
||||
const EmailVerificationFormComponent = props => {
|
||||
const {
|
||||
currentUser,
|
||||
submitting,
|
||||
inProgress,
|
||||
handleSubmit,
|
||||
verificationError,
|
||||
} = props;
|
||||
|
||||
const email = <strong>{currentUser.attributes.email}</strong>;
|
||||
|
||||
const errorMessage = (
|
||||
<div className={css.error}>
|
||||
<FormattedMessage id="EmailVerificationForm.verificationFailed" />
|
||||
</div>
|
||||
);
|
||||
|
||||
const verifyEmail = (
|
||||
<div className={css.root}>
|
||||
<div>
|
||||
<h2>
|
||||
<FormattedMessage id="EmailVerificationForm.verifyEmailAddress" />
|
||||
</h2>
|
||||
|
||||
<p>
|
||||
<FormattedMessage id="EmailVerificationForm.finishAccountSetup" values={{ email }} />
|
||||
</p>
|
||||
|
||||
{verificationError ? errorMessage : null}
|
||||
|
||||
</div>
|
||||
|
||||
<form onSubmit={handleSubmit}>
|
||||
<Field component="input" type="hidden" name="verificationToken" />
|
||||
|
||||
<Button className={css.button} type="submit" disabled={submitting || inProgress}>
|
||||
|
||||
{submitting || inProgress
|
||||
? <FormattedMessage id="EmailVerificationForm.verifying" />
|
||||
: <FormattedMessage id="EmailVerificationForm.verify" />}
|
||||
</Button>
|
||||
</form>
|
||||
</div>
|
||||
);
|
||||
|
||||
const alreadyVerified = (
|
||||
<div className={css.root}>
|
||||
<div>
|
||||
<h2>
|
||||
<FormattedMessage id="EmailVerificationForm.emailVerified" />
|
||||
</h2>
|
||||
|
||||
<p>
|
||||
<FormattedMessage
|
||||
id="EmailVerificationForm.yourEmailAddressIsVerified"
|
||||
values={{ email }}
|
||||
/>
|
||||
</p>
|
||||
|
||||
</div>
|
||||
|
||||
<NamedLink className={css.button} name="LandingPage">
|
||||
<FormattedMessage id="EmailVerificationForm.okHomepage" />
|
||||
</NamedLink>
|
||||
</div>
|
||||
);
|
||||
|
||||
return currentUser.attributes.emailVerified ? alreadyVerified : verifyEmail;
|
||||
};
|
||||
EmailVerificationFormComponent.defaultProps = {
|
||||
currentUser: null,
|
||||
inProgress: false,
|
||||
verificationError: null,
|
||||
};
|
||||
const {
|
||||
instanceOf,
|
||||
bool,
|
||||
} = PropTypes;
|
||||
|
||||
EmailVerificationFormComponent.propTypes = {
|
||||
...formPropTypes,
|
||||
inProgress: bool,
|
||||
currentUser: propTypes.currentUser,
|
||||
verificationError: instanceOf(Error),
|
||||
};
|
||||
const defaultFormName = 'EmailVerificationForm';
|
||||
|
||||
const EmailVerificationForm = compose(reduxForm({ form: defaultFormName }), injectIntl)(
|
||||
EmailVerificationFormComponent
|
||||
);
|
||||
export default EmailVerificationForm;
|
||||
|
|
@ -0,0 +1,40 @@
|
|||
@import '../../marketplaceFonts.css';
|
||||
@import '../../marketplace.css';
|
||||
|
||||
.root {
|
||||
@apply --backgroundImage;
|
||||
|
||||
/* PageLayout is using flex: EmailVerificationPage's .root takes all available space */
|
||||
flex-grow: 1;
|
||||
|
||||
/* EmailVerificationPage's root uses flexbox */
|
||||
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 {
|
||||
margin-top: 24px;
|
||||
color: var(--failColor);
|
||||
}
|
||||
142
src/containers/EmailVerificationPage/EmailVerificationPage.js
Normal file
142
src/containers/EmailVerificationPage/EmailVerificationPage.js
Normal file
|
|
@ -0,0 +1,142 @@
|
|||
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 { PageLayout, Topbar } from '../../components';
|
||||
import { EmailVerificationForm } from '../../containers';
|
||||
import { authenticationInProgress } from '../../ducks/Auth.duck';
|
||||
import { verify, verificationInProgress } from '../../ducks/EmailVerification.duck';
|
||||
import { manageDisableScrolling, isScrollingDisabled } from '../../ducks/UI.duck';
|
||||
import { parse } from '../../util/urlHelpers';
|
||||
import css from './EmailVerificationPage.css';
|
||||
export const EmailVerificationPageComponent = props => {
|
||||
const {
|
||||
authInfoError,
|
||||
authInProgress,
|
||||
currentUser,
|
||||
currentUserHasListings,
|
||||
intl,
|
||||
isAuthenticated,
|
||||
logoutError,
|
||||
notificationCount,
|
||||
onLogout,
|
||||
onManageDisableScrolling,
|
||||
scrollingDisabled,
|
||||
submitVerification,
|
||||
verifInProgress,
|
||||
verificationError,
|
||||
} = props;
|
||||
const title = intl.formatMessage({
|
||||
id: 'EmailVerificationPage.title',
|
||||
});
|
||||
|
||||
const urlParams = parse(location.search);
|
||||
|
||||
const initialValues = { verificationToken: urlParams.t };
|
||||
|
||||
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}>
|
||||
<div className={css.content}>
|
||||
|
||||
{currentUser
|
||||
? <EmailVerificationForm
|
||||
initialValues={initialValues}
|
||||
onSubmit={submitVerification}
|
||||
currentUser={currentUser}
|
||||
inProgress={verifInProgress}
|
||||
verificationError={verificationError}
|
||||
/>
|
||||
: <FormattedMessage id="EmailVerificationPage.loadingUserInformation" />}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</PageLayout>
|
||||
);
|
||||
};
|
||||
|
||||
EmailVerificationPageComponent.defaultProps = {
|
||||
authInfoError: null,
|
||||
currentUser: null,
|
||||
logoutError: null,
|
||||
notificationCount: 0,
|
||||
verificationError: null,
|
||||
};
|
||||
const { bool, func, instanceOf, number } = PropTypes;
|
||||
EmailVerificationPageComponent.propTypes = {
|
||||
authInfoError: instanceOf(Error),
|
||||
authInProgress: bool.isRequired,
|
||||
currentUser: propTypes.currentUser,
|
||||
currentUserHasListings: bool.isRequired,
|
||||
isAuthenticated: bool.isRequired,
|
||||
logoutError: instanceOf(Error),
|
||||
notificationCount: number,
|
||||
onLogout: func.isRequired,
|
||||
onManageDisableScrolling: func.isRequired,
|
||||
scrollingDisabled: bool.isRequired,
|
||||
submitVerification: func.isRequired,
|
||||
verifInProgress: bool.isRequired,
|
||||
verificationError: instanceOf(Error),
|
||||
intl: intlShape.isRequired,
|
||||
};
|
||||
const mapStateToProps = state => {
|
||||
const {
|
||||
authInfoError,
|
||||
isAuthenticated,
|
||||
logoutError,
|
||||
} = state.Auth;
|
||||
const {
|
||||
currentUser,
|
||||
currentUserHasListings,
|
||||
currentUserNotificationCount: notificationCount,
|
||||
} = state.user;
|
||||
const {
|
||||
verificationError,
|
||||
} = state.EmailVerification;
|
||||
return {
|
||||
authInfoError,
|
||||
authInProgress: authenticationInProgress(state),
|
||||
verifInProgress: verificationInProgress(state),
|
||||
verificationError,
|
||||
currentUser,
|
||||
currentUserHasListings,
|
||||
isAuthenticated,
|
||||
logoutError,
|
||||
notificationCount,
|
||||
scrollingDisabled: isScrollingDisabled(state),
|
||||
};
|
||||
};
|
||||
|
||||
const mapDispatchToProps = dispatch => ({
|
||||
onManageDisableScrolling: (componentId, disableScrolling) =>
|
||||
dispatch(manageDisableScrolling(componentId, disableScrolling)),
|
||||
submitVerification: ({ verificationToken }) => {
|
||||
return dispatch(verify(verificationToken));
|
||||
},
|
||||
});
|
||||
|
||||
const EmailVerificationPage = compose(
|
||||
connect(mapStateToProps, mapDispatchToProps),
|
||||
withRouter,
|
||||
injectIntl
|
||||
)(EmailVerificationPageComponent);
|
||||
|
||||
export default EmailVerificationPage;
|
||||
|
|
@ -10,6 +10,8 @@ import EditListingPage from './EditListingPage/EditListingPage';
|
|||
import EditListingPhotosForm from './EditListingPhotosForm/EditListingPhotosForm';
|
||||
import EditListingPricingForm from './EditListingPricingForm/EditListingPricingForm';
|
||||
import EditProfilePage from './EditProfilePage/EditProfilePage';
|
||||
import EmailVerificationPage from './EmailVerificationPage/EmailVerificationPage';
|
||||
import EmailVerificationForm from './EmailVerificationForm/EmailVerificationForm';
|
||||
import InboxPage from './InboxPage/InboxPage';
|
||||
import LandingPage from './LandingPage/LandingPage';
|
||||
import ListingPage from './ListingPage/ListingPage';
|
||||
|
|
@ -45,6 +47,8 @@ export {
|
|||
EditListingPhotosForm,
|
||||
EditListingPricingForm,
|
||||
EditProfilePage,
|
||||
EmailVerificationPage,
|
||||
EmailVerificationForm,
|
||||
InboxPage,
|
||||
LandingPage,
|
||||
ListingPage,
|
||||
|
|
|
|||
70
src/ducks/EmailVerification.duck.js
Normal file
70
src/ducks/EmailVerification.duck.js
Normal file
|
|
@ -0,0 +1,70 @@
|
|||
import { fetchCurrentUser } from './user.duck';
|
||||
|
||||
// ================ Action types ================ //
|
||||
|
||||
export const VERIFICATION_REQUEST = 'app/EmailVerification/VERIFICATION_REQUEST';
|
||||
export const VERIFICATION_SUCCESS = 'app/EmailVerification/VERIFICATION_SUCCESS';
|
||||
export const VERIFICATION_ERROR = 'app/EmailVerification/VERIFICATION_ERROR';
|
||||
|
||||
// ================ Reducer ================ //
|
||||
|
||||
const initialState = {
|
||||
isVerified: false,
|
||||
|
||||
// verification
|
||||
verificationError: null,
|
||||
verificationInProgress: false,
|
||||
};
|
||||
|
||||
export default function reducer(state = initialState, action = {}) {
|
||||
const { type, payload } = action;
|
||||
switch (type) {
|
||||
case VERIFICATION_REQUEST:
|
||||
return {
|
||||
...state,
|
||||
verificationInProgress: true,
|
||||
verificationError: null,
|
||||
};
|
||||
case VERIFICATION_SUCCESS:
|
||||
return { ...state, verificationInProgress: false, isVerified: true };
|
||||
case VERIFICATION_ERROR:
|
||||
return { ...state, verificationInProgress: false, verificationError: payload };
|
||||
default:
|
||||
return state;
|
||||
}
|
||||
}
|
||||
|
||||
// ================ Selectors ================ //
|
||||
|
||||
export const verificationInProgress = state => {
|
||||
return state.EmailVerification.verificationInProgress;
|
||||
};
|
||||
|
||||
// ================ Action creators ================ //
|
||||
|
||||
export const verificationRequest = () => ({ type: VERIFICATION_REQUEST });
|
||||
export const verificationSuccess = () => ({ type: VERIFICATION_SUCCESS });
|
||||
export const verificationError = error => ({
|
||||
type: VERIFICATION_ERROR,
|
||||
payload: error,
|
||||
error: true,
|
||||
});
|
||||
|
||||
// ================ Thunks ================ //
|
||||
|
||||
export const verify = verificationToken =>
|
||||
(dispatch, getState, sdk) => {
|
||||
if (verificationInProgress(getState())) {
|
||||
return Promise.reject(new Error('Email verification already in progress'));
|
||||
}
|
||||
dispatch(verificationRequest());
|
||||
|
||||
// Note that the thunk does not reject when the verification fails, it
|
||||
// just dispatches the login error action.
|
||||
const verifyParams = { verificationToken: `${verificationToken}` };
|
||||
return sdk.users
|
||||
.verifyEmail(verifyParams)
|
||||
.then(() => dispatch(verificationSuccess()))
|
||||
.then(() => dispatch(fetchCurrentUser()))
|
||||
.catch(e => dispatch(verificationError(e)));
|
||||
};
|
||||
|
|
@ -11,5 +11,15 @@ import LocationFilter from './LocationFilter.duck';
|
|||
import UI from './UI.duck';
|
||||
import marketplaceData from './marketplaceData.duck';
|
||||
import user from './user.duck';
|
||||
import EmailVerification from './EmailVerification.duck';
|
||||
|
||||
export { form, Auth, FlashNotification, LocationFilter, UI, marketplaceData, user };
|
||||
export {
|
||||
form,
|
||||
Auth,
|
||||
FlashNotification,
|
||||
LocationFilter,
|
||||
UI,
|
||||
marketplaceData,
|
||||
user,
|
||||
EmailVerification,
|
||||
};
|
||||
|
|
|
|||
|
|
@ -20,6 +20,7 @@ import {
|
|||
SearchPage,
|
||||
SecurityPage,
|
||||
StyleguidePage,
|
||||
EmailVerificationPage,
|
||||
} from './containers';
|
||||
|
||||
const RedirectToLandingPage = () => <NamedRedirect name="LandingPage" />;
|
||||
|
|
@ -319,6 +320,17 @@ const routesConfiguration = [
|
|||
name: 'NotFoundPage',
|
||||
component: props => <NotFoundPage {...props} />,
|
||||
},
|
||||
|
||||
// Do not change this path!
|
||||
//
|
||||
// The API expects that the Starter App implements /email_verification endpoint
|
||||
{
|
||||
path: '/email_verification',
|
||||
auth: true,
|
||||
exact: true,
|
||||
name: 'EmailVerificationPage',
|
||||
component: props => <EmailVerificationPage {...props} />,
|
||||
},
|
||||
];
|
||||
|
||||
export default routesConfiguration;
|
||||
|
|
|
|||
|
|
@ -78,6 +78,16 @@
|
|||
"EditListingPricingForm.pricePerNight": "Price per night in euros",
|
||||
"EditListingPricingForm.priceRequired": "You need to add a valid price.",
|
||||
"EditListingPricingPanel.title": "How much does it cost?",
|
||||
"EmailVerificationPage.title": "Verify your email address",
|
||||
"EmailVerificationPage.loadingUserInformation": "Loading user information...",
|
||||
"EmailVerificationForm.verifyEmailAddress": "Verify your email address",
|
||||
"EmailVerificationForm.finishAccountSetup": "To finish setting up your Saunatime account, we sent an email to {email} to make sure this email is yours.",
|
||||
"EmailVerificationForm.verify": "Verify my email",
|
||||
"EmailVerificationForm.verifying": "Verifying...",
|
||||
"EmailVerificationForm.emailVerified": "Your email address is verified",
|
||||
"EmailVerificationForm.yourEmailAddressIsVerified": "Your email address {email} is verified.",
|
||||
"EmailVerificationForm.okHomepage": "Ok, got it. Take me to the homepage",
|
||||
"EmailVerificationForm.verificationFailed": "Could not verify the email address.",
|
||||
"HeroSection.mobileSearchButtonText": "Search saunas",
|
||||
"HeroSection.subTitle": "The largest online community to rent saunas in Finland.",
|
||||
"HeroSection.title": "Book saunas everywhere.",
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue