mirror of
https://github.com/kingomarnajjar/flex-template-web.git
synced 2026-07-28 20:53:24 +10:00
Merge pull request #74 from sharetribe/new-listing-location
Add location field to new listing form
This commit is contained in:
commit
17cfe340d8
9 changed files with 108 additions and 37 deletions
|
|
@ -8,6 +8,7 @@
|
|||
|
||||
.predictions {
|
||||
position: absolute;
|
||||
margin: 0;
|
||||
top: 30px;
|
||||
width: 100%;
|
||||
background-color: #fff;
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ import FilterPanel from './FilterPanel/FilterPanel';
|
|||
import HeroSection from './HeroSection/HeroSection';
|
||||
import ListingCard from './ListingCard/ListingCard';
|
||||
import ListingCardSmall from './ListingCardSmall/ListingCardSmall';
|
||||
import LocationAutocompleteInput from './LocationAutocompleteInput/LocationAutocompleteInput';
|
||||
import MapPanel from './MapPanel/MapPanel';
|
||||
import Menu from './Menu/Menu';
|
||||
import NamedLink from './NamedLink/NamedLink';
|
||||
|
|
@ -24,6 +25,7 @@ export {
|
|||
HeroSection,
|
||||
ListingCard,
|
||||
ListingCardSmall,
|
||||
LocationAutocompleteInput,
|
||||
MapPanel,
|
||||
Menu,
|
||||
NamedLink,
|
||||
|
|
|
|||
|
|
@ -3,43 +3,66 @@ import { Field, reduxForm, propTypes as formPropTypes } from 'redux-form';
|
|||
import { intlShape, injectIntl } from 'react-intl';
|
||||
import { isEqual } from 'lodash';
|
||||
import { arrayMove } from 'react-sortable-hoc';
|
||||
import { noEmptyArray, maxLength, required } from '../../util/validators';
|
||||
import { AddImages } from '../../components';
|
||||
import {
|
||||
noEmptyArray,
|
||||
maxLength,
|
||||
required,
|
||||
autocompleteSearchRequired,
|
||||
autocompletePlaceSelected,
|
||||
} from '../../util/validators';
|
||||
import { AddImages, LocationAutocompleteInput } from '../../components';
|
||||
import css from './EditListingForm.css';
|
||||
|
||||
const ACCEPT_IMAGES = 'image/*';
|
||||
const TITLE_MAX_LENGTH = 60;
|
||||
|
||||
// Custom inputs with validator messages
|
||||
const RenderField = ({ input, label, type, meta }) => {
|
||||
const { touched, error } = meta;
|
||||
const component = type === 'textarea'
|
||||
? <textarea {...input} placeholder={label} />
|
||||
: <input {...input} placeholder={label} type={type} />;
|
||||
return (
|
||||
<div>
|
||||
<label htmlFor={input.name}>{label}</label>
|
||||
const { bool, func, object, shape, string } = PropTypes;
|
||||
|
||||
/**
|
||||
* Hoc to convert a component used within a Field to one that renders
|
||||
* a label and possible errors.
|
||||
*
|
||||
* @param {Component|String} Comp component or a String that is used
|
||||
* to create an input element
|
||||
*
|
||||
* @return {Component} new component that wraps the given one with a
|
||||
* label and possible errors
|
||||
*/
|
||||
const enhancedField = Comp => {
|
||||
const EnhancedField = props => {
|
||||
const { input, label, meta } = props;
|
||||
const { touched, error } = meta;
|
||||
let component = null;
|
||||
if (typeof Comp !== 'string') {
|
||||
component = <Comp {...props} />;
|
||||
} else if (Comp === 'textarea') {
|
||||
component = <textarea {...input} placeholder={label} />;
|
||||
} else {
|
||||
component = <input {...input} placeholder={label} type={Comp} />;
|
||||
}
|
||||
return (
|
||||
<div>
|
||||
<label htmlFor={input.name}>{label}</label>
|
||||
{component}
|
||||
{touched && error ? <span className={css.error}>{error}</span> : null}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
);
|
||||
};
|
||||
EnhancedField.displayName = `EnhancedField(${Comp.displayName || Comp.name})`;
|
||||
|
||||
const { bool, func, object, shape, string } = PropTypes;
|
||||
EnhancedField.propTypes = {
|
||||
input: shape({
|
||||
onChange: func.isRequired,
|
||||
name: string.isRequired,
|
||||
}).isRequired,
|
||||
label: string.isRequired,
|
||||
meta: shape({
|
||||
touched: bool,
|
||||
error: string,
|
||||
}).isRequired,
|
||||
};
|
||||
|
||||
RenderField.propTypes = {
|
||||
input: shape({
|
||||
onChange: func.isRequired,
|
||||
name: string.isRequired,
|
||||
}).isRequired,
|
||||
label: string.isRequired,
|
||||
type: string.isRequired,
|
||||
meta: shape({
|
||||
touched: bool,
|
||||
error: string,
|
||||
}).isRequired,
|
||||
return EnhancedField;
|
||||
};
|
||||
|
||||
// Add image wrapper. Label is the only visible element, file input is hidden.
|
||||
|
|
@ -120,14 +143,19 @@ class EditListingForm extends Component {
|
|||
);
|
||||
const maxLength60 = maxLength(maxLengthStr, TITLE_MAX_LENGTH);
|
||||
const imageRequiredStr = intl.formatMessage({ id: 'EditListingForm.imageRequired' });
|
||||
const locationRequiredMessage = intl.formatMessage({
|
||||
id: 'EditListingForm.locationRequired',
|
||||
});
|
||||
const locationNotRecognizedMessage = intl.formatMessage({
|
||||
id: 'EditListingForm.locationNotRecognized',
|
||||
});
|
||||
|
||||
return (
|
||||
<form onSubmit={handleSubmit}>
|
||||
<Field
|
||||
name="title"
|
||||
label="Title"
|
||||
component={RenderField}
|
||||
type="text"
|
||||
component={enhancedField('text')}
|
||||
validate={[required(requiredStr), maxLength60]}
|
||||
/>
|
||||
|
||||
|
|
@ -160,11 +188,21 @@ class EditListingForm extends Component {
|
|||
/>
|
||||
</AddImages>
|
||||
|
||||
<Field
|
||||
name="location"
|
||||
label="Location"
|
||||
format={null}
|
||||
component={enhancedField(LocationAutocompleteInput)}
|
||||
validate={[
|
||||
autocompleteSearchRequired(locationRequiredMessage),
|
||||
autocompletePlaceSelected(locationNotRecognizedMessage),
|
||||
]}
|
||||
/>
|
||||
|
||||
<Field
|
||||
name="description"
|
||||
label="Description"
|
||||
component={RenderField}
|
||||
type="textarea"
|
||||
component={enhancedField('textarea')}
|
||||
validate={[required(requiredStr)]}
|
||||
/>
|
||||
<button type="submit" disabled={pristine || submitting || disabled}>{saveActionMsg}</button>
|
||||
|
|
|
|||
|
|
@ -14,14 +14,13 @@ import {
|
|||
} from './EditListingPage.duck';
|
||||
|
||||
const formatRequestData = values => {
|
||||
const { description, images, title } = values;
|
||||
const { description, images, title, location } = values;
|
||||
const { selectedPlace: { address, origin } } = location;
|
||||
|
||||
return {
|
||||
address: 'Bulevardi 14, 00200 Helsinki, Finland',
|
||||
address,
|
||||
description,
|
||||
geolocation: {
|
||||
lat: 40.6,
|
||||
lng: 73.9,
|
||||
},
|
||||
geolocation: origin,
|
||||
images: images.map(i => i.imageId),
|
||||
title,
|
||||
};
|
||||
|
|
|
|||
|
|
@ -30,7 +30,8 @@
|
|||
height: 100%;
|
||||
}
|
||||
|
||||
.description {
|
||||
.description,
|
||||
.address {
|
||||
margin: 2rem;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -56,6 +56,8 @@ export class ListingPageComponent extends Component {
|
|||
|
||||
const title = currentListing ? currentListing.attributes.title : '';
|
||||
const description = currentListing ? currentListing.attributes.description : '';
|
||||
const address = currentListing ? currentListing.attributes.address : '';
|
||||
const geolocation = currentListing ? currentListing.attributes.geolocation : null;
|
||||
|
||||
// TODO Responsive image-objects need to be thought through when final image sizes are know
|
||||
const images = currentListing && currentListing.images
|
||||
|
|
@ -88,6 +90,9 @@ export class ListingPageComponent extends Component {
|
|||
{/* eslint-disable react/no-danger */}
|
||||
<div className={css.description} dangerouslySetInnerHTML={{ __html: description }} />
|
||||
{/* eslint-enable react/no-danger */}
|
||||
<div className={css.address}>
|
||||
{address} {geolocation ? `(${geolocation.lat}, ${geolocation.lng})` : null}
|
||||
</div>
|
||||
<div className={css.filterSection}>
|
||||
<h1>Here will be filters (or dragons)</h1>
|
||||
<h2>Studio type</h2>
|
||||
|
|
|
|||
|
|
@ -7,6 +7,11 @@ exports[`ListingPage matches snapshot 1`] = `
|
|||
"__html": "listing1 description",
|
||||
}
|
||||
} />
|
||||
<div>
|
||||
listing1 address
|
||||
|
||||
(40, 60)
|
||||
</div>
|
||||
<div>
|
||||
<h1>
|
||||
Here will be filters (or dragons)
|
||||
|
|
|
|||
|
|
@ -8,6 +8,8 @@
|
|||
"EditListingForm.required": "Required",
|
||||
"EditListingForm.imageRequired": "You need to add at least one image.",
|
||||
"EditListingForm.maxLength": "Must be {maxLength} characters or less",
|
||||
"EditListingForm.locationRequired": "You need to provide a location",
|
||||
"EditListingForm.locationNotRecognized": "We didn't recognize this location. Please try another location.",
|
||||
"ListingPage.loadingListingData": "Loading listing data",
|
||||
"ListingPage.noListingData": "Could not find listing data",
|
||||
"PageLayout.authInfoFailed": "Could not get authentication information.",
|
||||
|
|
|
|||
|
|
@ -1,3 +1,7 @@
|
|||
import { types } from 'sharetribe-sdk';
|
||||
|
||||
const { LatLng } = types;
|
||||
|
||||
/**
|
||||
* Validator functions and helpers for Redux Forms
|
||||
*/
|
||||
|
|
@ -19,3 +23,17 @@ export const noEmptyArray = message =>
|
|||
value => {
|
||||
return value && Array.isArray(value) && value.length > 0 ? VALID : message;
|
||||
};
|
||||
|
||||
export const autocompleteSearchRequired = message =>
|
||||
value => {
|
||||
return value && value.search ? VALID : message;
|
||||
};
|
||||
|
||||
export const autocompletePlaceSelected = message =>
|
||||
value => {
|
||||
const selectedPlaceIsValid = value &&
|
||||
value.selectedPlace &&
|
||||
value.selectedPlace.address &&
|
||||
value.selectedPlace.origin instanceof LatLng;
|
||||
return selectedPlaceIsValid ? VALID : message;
|
||||
};
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue