diff --git a/src/components/Promised/Promised.js b/src/components/Promised/Promised.js new file mode 100644 index 00000000..92c7b89f --- /dev/null +++ b/src/components/Promised/Promised.js @@ -0,0 +1,50 @@ +/** + * Promised component makes it easier to render content that + * depends on resolution of a Promise. + * + * How to use: + * {v}} renderRejected={v => v} /> + */ + +import { Component, PropTypes } from 'react'; + +class Promised extends Component { + constructor(props) { + super(props); + + // success value is string to be more useful when rendering texts. + this.state = { + value: '', + error: null, + }; + } + + componentDidMount() { + this.props.promise + .then(value => { + this.setState({ value }); + }) + .catch(error => { + this.setState({ error }); + }); + } + + render() { + const { renderFulfilled, renderRejected } = this.props; + return this.state.error ? renderRejected(this.state.error) : renderFulfilled(this.state.value); + } +} + +Promised.defaultProps = { renderRejected: e => e }; + +const { func, shape } = PropTypes; + +Promised.propTypes = { + promise: shape({ + then: func.isRequired, // usually promises are detected from this single function alone + }).isRequired, + renderFulfilled: func.isRequired, + renderRejected: func.isRequired, +}; + +export default Promised; diff --git a/src/components/index.js b/src/components/index.js index b450a3a4..940f786c 100644 --- a/src/components/index.js +++ b/src/components/index.js @@ -11,6 +11,7 @@ import NamedRedirect from './NamedRedirect/NamedRedirect'; import OrderDetailsPanel from './OrderDetailsPanel/OrderDetailsPanel'; import OrderDiscussionPanel from './OrderDiscussionPanel/OrderDiscussionPanel'; import PageLayout from './PageLayout/PageLayout'; +import Promised from './Promised/Promised'; import RoutesProvider from './RoutesProvider/RoutesProvider'; import SearchResultsPanel from './SearchResultsPanel/SearchResultsPanel'; @@ -28,6 +29,7 @@ export { OrderDetailsPanel, OrderDiscussionPanel, PageLayout, + Promised, RoutesProvider, SearchResultsPanel, }; diff --git a/src/containers/EditListingForm/EditListingForm.css b/src/containers/EditListingForm/EditListingForm.css index ee7dba23..dcbb04ed 100644 --- a/src/containers/EditListingForm/EditListingForm.css +++ b/src/containers/EditListingForm/EditListingForm.css @@ -1,3 +1,61 @@ .error { color: red; } + +.imagesContainer { + width: 100%; + min-height: 150px; +} + +.addImageWrapper { + float: left; + width: 110px; + height: 110px; + margin: 5px; + + &::after { + content: "."; + visibility: hidden; + display: block; + height: 0; + clear: both; + } +} + +.addImage { + width: 110px; + height: 110px; + background-color: lightgrey; + display: flex; + justify-content: center; + align-items: center; + cursor: pointer; + margin-top: 0; +} + +.thumbnail { + float: left; + position: relative; + width: 110px; + height: 110px; + margin: 5px; + overflow: hidden; +} + +.thumbnailImage { + width: 100%; + height: 100%; + object-fit: cover; +} + +.thumbnailLoading { + position: absolute; + top: 0; + right: 0; + bottom: 0; + left: 0; + background-color: rgba(211, 211, 211, 0.8); + display: flex; + justify-content: center; + align-items: center; +} diff --git a/src/containers/EditListingForm/EditListingForm.example.js b/src/containers/EditListingForm/EditListingForm.example.js index b6300b72..91d193de 100644 --- a/src/containers/EditListingForm/EditListingForm.example.js +++ b/src/containers/EditListingForm/EditListingForm.example.js @@ -4,8 +4,12 @@ import EditListingForm from './EditListingForm'; export const Empty = { component: EditListingForm, props: { + images: [], + onImageUpload(values) { + console.log(`onImageUpload with id (${values.id}) and file name (${values.file.name})`); + }, onSubmit(values) { - console.log('submit new password form values:', values); + console.log('Submit EditListingForm with (unformatted) values:', values); }, }, }; diff --git a/src/containers/EditListingForm/EditListingForm.js b/src/containers/EditListingForm/EditListingForm.js index e9e1b741..33bb443a 100644 --- a/src/containers/EditListingForm/EditListingForm.js +++ b/src/containers/EditListingForm/EditListingForm.js @@ -1,12 +1,30 @@ import React, { Component, PropTypes } from 'react'; import { Field, reduxForm, propTypes as formPropTypes } from 'redux-form'; import { intlShape, injectIntl } from 'react-intl'; +import { isEqual } from 'lodash'; import { maxLength, required } from '../../util/validators'; +import { Promised } from '../../components'; import css from './EditListingForm.css'; -const MAX_LENGTH = 60; +const ACCEPT_IMAGES = 'image/*'; +const TITLE_MAX_LENGTH = 60; -const RenderField = ({ input, label, type, meta: { touched, error } }) => { +// readImage returns a promise which is resolved +// when FileReader has loaded given file as dataURL +const readImage = file => new Promise((resolve, reject) => { + const reader = new FileReader(); + reader.onload = e => resolve(e.target.result); + reader.onerror = e => { + // eslint-disable-next-line + console.error(`Error ${e} happened while reading ${file.name}: ${e.target.result}`); + reject(new Error(`Error reading ${file.name}: ${e.target.result}`)); + }; + reader.readAsDataURL(file); +}); + +// Custom inputs with validator messages +const RenderField = ({ input, label, type, meta }) => { + const { touched, error } = meta; const component = type === 'textarea' ?