mirror of
https://github.com/kingomarnajjar/flex-template-web.git
synced 2026-07-28 20:53:24 +10:00
ProfileSettingsForm
This commit is contained in:
parent
a069d7f0db
commit
cec8ce3cb6
4 changed files with 502 additions and 0 deletions
259
src/containers/ProfileSettingsForm/ProfileSettingsForm.css
Normal file
259
src/containers/ProfileSettingsForm/ProfileSettingsForm.css
Normal file
|
|
@ -0,0 +1,259 @@
|
|||
@import '../../marketplace.css';
|
||||
|
||||
:root {
|
||||
--avatarSize: 96px;
|
||||
--avatarSizeDesktop: 240px;
|
||||
}
|
||||
|
||||
.root {
|
||||
margin-top: 23px;
|
||||
|
||||
@media (--viewportMedium) {
|
||||
margin-top: 35px;
|
||||
}
|
||||
}
|
||||
|
||||
.sectionContainer {
|
||||
padding: 0;
|
||||
margin-bottom: 34px;
|
||||
|
||||
@media (--viewportMedium) {
|
||||
padding: 0;
|
||||
margin-bottom: 54px;
|
||||
}
|
||||
}
|
||||
|
||||
.sectionTitle {
|
||||
/* Font */
|
||||
color: var(--matterColorAnti);
|
||||
|
||||
margin-top: 0;
|
||||
margin-bottom: 13px;
|
||||
@media (--viewportMedium) {
|
||||
margin-top: 0;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
}
|
||||
|
||||
.lastSection {
|
||||
margin-bottom: 66px;
|
||||
@media (--viewportMedium) {
|
||||
margin-bottom: 111px;
|
||||
}
|
||||
}
|
||||
|
||||
.uploadAvatarInput {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.uploadAvatarWrapper {
|
||||
margin-top: 19px;
|
||||
margin-bottom: 18px;
|
||||
|
||||
@media (--viewportMedium) {
|
||||
margin-top: 44px;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
}
|
||||
|
||||
.label {
|
||||
width: var(--avatarSize);
|
||||
@media (--viewportMedium) {
|
||||
width: var(--avatarSizeDesktop);
|
||||
}
|
||||
}
|
||||
|
||||
.avatarPlaceholder,
|
||||
.avatarContainer {
|
||||
/* Dimension */
|
||||
position: relative;
|
||||
width: var(--avatarSize);
|
||||
height: var(--avatarSize);
|
||||
|
||||
/* Center content */
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
|
||||
/* Initial coloring */
|
||||
background-color: var(--matterColorBright);
|
||||
border-radius: calc(var(--avatarSize) / 2);
|
||||
cursor: pointer;
|
||||
|
||||
@media (--viewportMedium) {
|
||||
width: var(--avatarSizeDesktop);
|
||||
height: var(--avatarSizeDesktop);
|
||||
border-radius: calc(var(--avatarSizeDesktop) / 2);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
.avatarPlaceholder {
|
||||
/* Placeholder border */
|
||||
border-style: dashed;
|
||||
border-color: var(--matterColorNegative);
|
||||
border-width: 2px;
|
||||
|
||||
&:hover {
|
||||
border-color: var(--matterColorAnti);
|
||||
}
|
||||
}
|
||||
|
||||
.avatarPlaceholderTextMobile {
|
||||
@media (--viewportMedium) {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
.avatarPlaceholderText {
|
||||
display: none;
|
||||
|
||||
@media (--viewportMedium) {
|
||||
max-width: 130px;
|
||||
text-align: center;
|
||||
}
|
||||
}
|
||||
|
||||
.avatarUploadError {
|
||||
/* Placeholder border */
|
||||
border-style: dashed;
|
||||
border-color: var(--failColor);
|
||||
border-width: 2px;
|
||||
}
|
||||
|
||||
.error {
|
||||
/* Font */
|
||||
@apply --marketplaceH4FontStyles;
|
||||
color: var(--failColor);
|
||||
margin-top: 18px;
|
||||
margin-bottom: 0;
|
||||
|
||||
@media (--viewportMedium) {
|
||||
margin-top: 22px;
|
||||
margin-bottom: 2px;
|
||||
}
|
||||
}
|
||||
|
||||
.changeAvatar {
|
||||
/* Font */
|
||||
@apply --marketplaceH5FontStyles;
|
||||
|
||||
/* Positioning: right */
|
||||
position: absolute;
|
||||
bottom: 27px;
|
||||
right: -129px;
|
||||
/* Dimensions */
|
||||
width: 105px;
|
||||
height: 41px;
|
||||
padding: 9px 10px 9px 35px;
|
||||
|
||||
/* Look and feel (buttonish) */
|
||||
background-color: var(--matterColorLight);
|
||||
background-image: url('data:image/svg+xml;utf8,<svg width="14" height="14" viewBox="0 0 14 14" xmlns="http://www.w3.org/2000/svg"><g stroke="#4A4A4A" fill="none" fill-rule="evenodd" stroke-linecap="round" stroke-linejoin="round"><path d="M5.307 11.155L1 13l1.846-4.308L10.54 1 13 3.46zM11 5L9 3M5 11L3 9"/></g></svg>');
|
||||
background-repeat: no-repeat;
|
||||
background-position: 15px center;
|
||||
border: solid 1px var(--matterColorNegative);
|
||||
|
||||
margin-top: 0;
|
||||
margin-bottom: 0;
|
||||
|
||||
@media (--viewportMedium) {
|
||||
/* Position: under */
|
||||
bottom: -8px;
|
||||
right: auto;
|
||||
margin-top: 0;
|
||||
margin-bottom: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.uploadingImage {
|
||||
/* Dimensions */
|
||||
width: var(--avatarSize);
|
||||
height: var(--avatarSize);
|
||||
|
||||
/* Image fitted to container */
|
||||
object-fit: cover;
|
||||
background-color: var(--matterColorNegative); /* Loading BG color */
|
||||
border-radius: calc(var(--avatarSize) / 2);
|
||||
overflow: hidden;
|
||||
|
||||
display: block;
|
||||
position: relative;
|
||||
margin: 0;
|
||||
|
||||
@media (--viewportMedium) {
|
||||
width: var(--avatarSizeDesktop);
|
||||
height: var(--avatarSizeDesktop);
|
||||
border-radius: calc(var(--avatarSizeDesktop) / 2);
|
||||
}
|
||||
}
|
||||
|
||||
.uploadingImageOverlay {
|
||||
/* Cover everything (overlay) */
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
|
||||
/* Overlay style */
|
||||
background-color: var(--matterColorLight);
|
||||
opacity: 0.8;
|
||||
|
||||
/* Center content */
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
/* Avatar has square aspect ratio */
|
||||
/* Default is 3:2 */
|
||||
.squareAspectRatio {
|
||||
padding-bottom: 100%;
|
||||
}
|
||||
|
||||
.tip {
|
||||
@apply --marketplaceDefaultFontStyles;
|
||||
color: var(--matterColorAnti);
|
||||
margin-top: 0;
|
||||
margin-bottom: 13px;
|
||||
|
||||
@media (--viewportMedium) {
|
||||
margin-top: 0;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
.fileInfo {
|
||||
@apply --marketplaceH4FontStyles;
|
||||
color: var(--matterColorAnti);
|
||||
margin-top: 0;
|
||||
margin-bottom: 0;
|
||||
|
||||
@media (--viewportMedium) {
|
||||
margin-top: 0;
|
||||
margin-bottom: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.nameContainer {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
margin-top: 14px;
|
||||
|
||||
@media (--viewportMedium) {
|
||||
margin-top: 26px;
|
||||
}
|
||||
}
|
||||
|
||||
.firstName {
|
||||
width: calc(34% - 9px);
|
||||
}
|
||||
|
||||
.lastName {
|
||||
width: calc(66% - 9px);
|
||||
}
|
||||
|
||||
.submitButton {
|
||||
margin-top: 24px;
|
||||
}
|
||||
225
src/containers/ProfileSettingsForm/ProfileSettingsForm.js
Normal file
225
src/containers/ProfileSettingsForm/ProfileSettingsForm.js
Normal file
|
|
@ -0,0 +1,225 @@
|
|||
import React, { PropTypes } from 'react';
|
||||
import { compose } from 'redux';
|
||||
import { FormattedMessage, injectIntl, intlShape } from 'react-intl';
|
||||
import { Field, reduxForm, propTypes as formPropTypes } from 'redux-form';
|
||||
import classNames from 'classnames';
|
||||
import { ensureCurrentUser } from '../../util/data';
|
||||
import * as validators from '../../util/validators';
|
||||
import { Avatar, Button, ImageFromFile, SpinnerIcon, TextInputField } from '../../components';
|
||||
|
||||
import css from './ProfileSettingsForm.css';
|
||||
|
||||
const ACCEPT_IMAGES = 'image/*';
|
||||
|
||||
const RenderAvatar = props => {
|
||||
const { accept, id, input, label, type, disabled, uploadImageError } = props;
|
||||
const { name, onChange } = input;
|
||||
const error = uploadImageError
|
||||
? <div className={css.error}>
|
||||
<FormattedMessage id="ProfileSettingsForm.imageUploadFailed" />
|
||||
</div>
|
||||
: null;
|
||||
|
||||
return (
|
||||
<div className={css.uploadAvatarWrapper}>
|
||||
<label className={css.label} htmlFor={id}>{label}</label>
|
||||
<input
|
||||
accept={accept}
|
||||
className={css.uploadAvatarInput}
|
||||
disabled={disabled}
|
||||
id={id}
|
||||
name={name}
|
||||
onChange={event => {
|
||||
const file = event.target.files[0];
|
||||
const tempId = `${file.name}_${Date.now()}`;
|
||||
onChange({ id: tempId, file });
|
||||
}}
|
||||
type={type}
|
||||
/>
|
||||
{error}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
RenderAvatar.defaultProps = { uploadImageError: null };
|
||||
const { bool, func, instanceOf, node, object, shape, string } = PropTypes;
|
||||
|
||||
RenderAvatar.propTypes = {
|
||||
accept: string.isRequired,
|
||||
disabled: bool.isRequired,
|
||||
id: string.isRequired,
|
||||
input: shape({
|
||||
value: object,
|
||||
onChange: func.isRequired,
|
||||
name: string.isRequired,
|
||||
}).isRequired,
|
||||
label: node.isRequired,
|
||||
type: string.isRequired,
|
||||
uploadImageError: instanceOf(Error),
|
||||
};
|
||||
|
||||
const ProfileSettingsFormComponent = props => {
|
||||
const {
|
||||
className,
|
||||
currentUser,
|
||||
form,
|
||||
handleSubmit,
|
||||
intl,
|
||||
invalid,
|
||||
onImageUpload,
|
||||
profileImage,
|
||||
rootClassName,
|
||||
submitting,
|
||||
updateInProgress,
|
||||
updateProfileError,
|
||||
uploadImageError,
|
||||
uploadInProgress,
|
||||
} = props;
|
||||
|
||||
const user = ensureCurrentUser(currentUser);
|
||||
|
||||
// First name
|
||||
const firstNameLabel = intl.formatMessage({
|
||||
id: 'ProfileSettingsForm.firstNameLabel',
|
||||
});
|
||||
const firstNamePlaceholder = intl.formatMessage({
|
||||
id: 'ProfileSettingsForm.firstNamePlaceholder',
|
||||
});
|
||||
const firstNameRequiredMessage = intl.formatMessage({
|
||||
id: 'ProfileSettingsForm.firstNameRequired',
|
||||
});
|
||||
const firstNameRequired = validators.required(firstNameRequiredMessage);
|
||||
|
||||
// Last name
|
||||
const lastNameLabel = intl.formatMessage({
|
||||
id: 'ProfileSettingsForm.lastNameLabel',
|
||||
});
|
||||
const lastNamePlaceholder = intl.formatMessage({
|
||||
id: 'ProfileSettingsForm.lastNamePlaceholder',
|
||||
});
|
||||
const lastNameRequiredMessage = intl.formatMessage({
|
||||
id: 'ProfileSettingsForm.lastNameRequired',
|
||||
});
|
||||
const lastNameRequired = validators.required(lastNameRequiredMessage);
|
||||
|
||||
const uploadingOverlay = uploadInProgress
|
||||
? <div className={css.uploadingImageOverlay}><SpinnerIcon /></div>
|
||||
: null;
|
||||
|
||||
const hasUploadError = !!uploadImageError && !uploadInProgress;
|
||||
const errorClasses = classNames({ [css.avatarUploadError]: hasUploadError });
|
||||
const avatarImage = uploadInProgress && profileImage.file
|
||||
? <ImageFromFile
|
||||
id={profileImage.id}
|
||||
className={errorClasses}
|
||||
rootClassName={css.uploadingImage}
|
||||
aspectRatioClassName={css.squareAspectRatio}
|
||||
file={profileImage.file}
|
||||
>
|
||||
{uploadingOverlay}
|
||||
</ImageFromFile>
|
||||
: <Avatar className={errorClasses} user={user} />;
|
||||
|
||||
const chooseAvatarLabel = profileImage.imageId || (uploadInProgress && profileImage.file)
|
||||
? <div className={css.avatarContainer}>
|
||||
{avatarImage}
|
||||
<div className={css.changeAvatar}>
|
||||
<FormattedMessage id="ProfileSettingsForm.changeAvatar" />
|
||||
</div>
|
||||
</div>
|
||||
: <div className={css.avatarPlaceholder}>
|
||||
<div className={css.avatarPlaceholderText}>
|
||||
<FormattedMessage id="ProfileSettingsForm.addYourProfilePicture" />
|
||||
</div>
|
||||
<div className={css.avatarPlaceholderTextMobile}>
|
||||
<FormattedMessage id="ProfileSettingsForm.addYourProfilePictureMobile" />
|
||||
</div>
|
||||
</div>;
|
||||
|
||||
const submitError = updateProfileError
|
||||
? <div className={css.error}>
|
||||
<FormattedMessage id="ProfileSettingsForm.updateProfileFailed" />
|
||||
</div>
|
||||
: null;
|
||||
|
||||
const classes = classNames(rootClassName || css.root, className);
|
||||
const submitDisabled = invalid || submitting || uploadInProgress || updateInProgress;
|
||||
|
||||
return (
|
||||
<form className={classes} onSubmit={handleSubmit}>
|
||||
<div className={css.sectionContainer}>
|
||||
<h3 className={css.sectionTitle}>
|
||||
<FormattedMessage id="ProfileSettingsForm.yourProfilePicture" />
|
||||
</h3>
|
||||
<Field
|
||||
accept={ACCEPT_IMAGES}
|
||||
component={RenderAvatar}
|
||||
disabled={uploadInProgress}
|
||||
id="ProfileSettingsForm.changeAvatar"
|
||||
label={chooseAvatarLabel}
|
||||
name="profileImage"
|
||||
onChange={onImageUpload}
|
||||
type="file"
|
||||
uploadImageError={uploadImageError}
|
||||
/>
|
||||
<div className={css.tip}><FormattedMessage id="ProfileSettingsForm.tip" /></div>
|
||||
<div className={css.fileInfo}><FormattedMessage id="ProfileSettingsForm.fileInfo" /></div>
|
||||
</div>
|
||||
<div className={classNames(css.sectionContainer, css.lastSection)}>
|
||||
<h3 className={css.sectionTitle}>
|
||||
<FormattedMessage id="ProfileSettingsForm.yourName" />
|
||||
</h3>
|
||||
<div className={css.nameContainer}>
|
||||
<TextInputField
|
||||
className={css.firstName}
|
||||
type="text"
|
||||
name="firstName"
|
||||
id={`${form}.firstName`}
|
||||
label={firstNameLabel}
|
||||
placeholder={firstNamePlaceholder}
|
||||
validate={firstNameRequired}
|
||||
/>
|
||||
<TextInputField
|
||||
className={css.lastName}
|
||||
type="text"
|
||||
name="lastName"
|
||||
id={`${form}.lastName`}
|
||||
label={lastNameLabel}
|
||||
placeholder={lastNamePlaceholder}
|
||||
validate={lastNameRequired}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
{submitError}
|
||||
<Button className={css.submitButton} type="submit" disabled={submitDisabled}>
|
||||
<FormattedMessage id="ProfileSettingsForm.saveChanges" />
|
||||
</Button>
|
||||
</form>
|
||||
);
|
||||
};
|
||||
|
||||
ProfileSettingsFormComponent.defaultProps = {
|
||||
rootClassName: null,
|
||||
className: null,
|
||||
uploadImageError: null,
|
||||
updateProfileError: null,
|
||||
};
|
||||
|
||||
ProfileSettingsFormComponent.propTypes = {
|
||||
...formPropTypes,
|
||||
rootClassName: string,
|
||||
className: string,
|
||||
uploadImageError: instanceOf(Error),
|
||||
uploadInProgress: bool.isRequired,
|
||||
updateInProgress: bool.isRequired,
|
||||
updateProfileError: instanceOf(Error),
|
||||
intl: intlShape.isRequired,
|
||||
};
|
||||
|
||||
const defaultFormName = 'ProfileSettingsForm';
|
||||
|
||||
const ProfileSettingsForm = compose(reduxForm({ form: defaultFormName }), injectIntl)(
|
||||
ProfileSettingsFormComponent
|
||||
);
|
||||
|
||||
export default ProfileSettingsForm;
|
||||
|
|
@ -25,6 +25,7 @@ import PasswordForgottenPage from './PasswordForgottenPage/PasswordForgottenPage
|
|||
import PayoutDetailsForm from './PayoutDetailsForm/PayoutDetailsForm';
|
||||
import PayoutPreferencesPage from './PayoutPreferencesPage/PayoutPreferencesPage';
|
||||
import ProfilePage from './ProfilePage/ProfilePage';
|
||||
import ProfileSettingsForm from './ProfileSettingsForm/ProfileSettingsForm';
|
||||
import ProfileSettingsPage from './ProfileSettingsPage/ProfileSettingsPage';
|
||||
import SalePage from './SalePage/SalePage';
|
||||
import SearchPage from './SearchPage/SearchPage';
|
||||
|
|
@ -62,6 +63,7 @@ export {
|
|||
PayoutDetailsForm,
|
||||
PayoutPreferencesPage,
|
||||
ProfilePage,
|
||||
ProfileSettingsForm,
|
||||
ProfileSettingsPage,
|
||||
SalePage,
|
||||
SearchPage,
|
||||
|
|
|
|||
|
|
@ -251,6 +251,22 @@
|
|||
"PayoutDetailsForm.streetAddressRequired": "This field is required",
|
||||
"PayoutDetailsForm.submitButtonText": "Save details & publish listing",
|
||||
"PayoutDetailsForm.title": "One more thing: payout preferences",
|
||||
"ProfileSettingsForm.addYourProfilePicture": "+ Add your profile picture…",
|
||||
"ProfileSettingsForm.addYourProfilePictureMobile": "+ Add",
|
||||
"ProfileSettingsForm.changeAvatar": "Change",
|
||||
"ProfileSettingsForm.fileInfo": ".JPG, .GIF or .PNG max. 10 MB",
|
||||
"ProfileSettingsForm.firstNameLabel": "First name",
|
||||
"ProfileSettingsForm.firstNamePlaceholder": "John",
|
||||
"ProfileSettingsForm.firstNameRequired": "This field is required",
|
||||
"ProfileSettingsForm.imageUploadFailed": "Whoopsie, something went wrong - please try again.",
|
||||
"ProfileSettingsForm.lastNameLabel": "Last name",
|
||||
"ProfileSettingsForm.lastNamePlaceholder": "Doe",
|
||||
"ProfileSettingsForm.lastNameRequired": "This field is required",
|
||||
"ProfileSettingsForm.saveChanges": "Save changes",
|
||||
"ProfileSettingsForm.tip": "Tip: Choose an image where your face is recognizable.",
|
||||
"ProfileSettingsForm.updateProfileFailed": "Whoopsie, something went wrong - please try again.",
|
||||
"ProfileSettingsForm.yourName": "Your name",
|
||||
"ProfileSettingsForm.yourProfilePicture": "Your profile picture",
|
||||
"ResponsiveImage.noImage": "No image",
|
||||
"SaleDetailsPanel.bookingBreakdownTitle": "Booking breakdown",
|
||||
"SaleDetailsPanel.listingAcceptedTitle": "Woohoo! You accepted a request from {customerName} for {listingLink}",
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue