@woosmap/ui 2.7.0 → 2.11.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@woosmap/ui",
3
- "version": "2.7.0",
3
+ "version": "2.11.0",
4
4
  "repository": {
5
5
  "type": "git",
6
6
  "url": "git+https://github.com/WebGeoServices/ui.git"
@@ -186,7 +186,7 @@ export default class AddressDemo extends Component {
186
186
 
187
187
  const useCaseChoice = {
188
188
  key: 'usecase',
189
- label: tr('Usecase'),
189
+ label: tr('Use case'),
190
190
  component: (
191
191
  <ButtonGroup isLight key="usecase">
192
192
  <Button
@@ -0,0 +1,370 @@
1
+ /* eslint-disable jsx-a11y/click-events-have-key-events */
2
+ import React, { Component } from 'react';
3
+ import axios from 'axios';
4
+ import PropTypes from 'prop-types';
5
+ import GoogleMapReact from 'google-map-react';
6
+ import Demo from './SkeletonDemo';
7
+ import AddressAutocomplete from '../Woosmap/AddressAutocomplete';
8
+ import Marker from '../Map/Marker';
9
+ import Constants from '../../Constants';
10
+ import { viewpoint } from '../Map/drawOnMap';
11
+ import CountrySelect from '../Select/CountrySelect';
12
+ import { tr } from '../utils/locale';
13
+ import Input from '../Input/Input';
14
+
15
+ export default class LocalitiesAddressDemo extends Component {
16
+ constructor(props) {
17
+ super(props);
18
+ this.state = {
19
+ response: null,
20
+ error: null,
21
+ input: '',
22
+ countries: [],
23
+ geometry: false,
24
+ showDetails: false,
25
+ };
26
+ this.map = null;
27
+ this.marker = null;
28
+ this.mounted = true;
29
+ this.viewport = null;
30
+ this.requestUrl = 'https://api.woosmap.com/localities/autocomplete';
31
+ this.requestDetailsUrl = 'https://api.woosmap.com/localities/details';
32
+ this.inputRef = React.createRef();
33
+ this.addressAutocompleteRef = React.createRef();
34
+ }
35
+
36
+ componentWillUnmount() {
37
+ this.mounted = false;
38
+ }
39
+
40
+ onAutocompleteInputChangeCb = (input) => {
41
+ this.setState(
42
+ {
43
+ error: null,
44
+ input,
45
+ },
46
+ this.requestAddress
47
+ );
48
+ };
49
+
50
+ requestAddress = () => {
51
+ axios
52
+ .get(this.requestUrl, {
53
+ params: this.getRequestparams(),
54
+ })
55
+ .then((response) => {
56
+ if (this.mounted) {
57
+ const state = {
58
+ showDetails: false,
59
+ response: response.data,
60
+ error: null,
61
+ };
62
+ this.setState(state);
63
+ }
64
+ })
65
+ .catch((error) => {
66
+ const errorResult = (error && error.response && error.response.data) || 'Unhandled error';
67
+ this.setState({ error: errorResult });
68
+ });
69
+ };
70
+
71
+ requestAddressDetails = (result) => {
72
+ const { geometry } = this.state;
73
+ const params = {
74
+ public_id: result.id,
75
+ key: Constants.woosmapKey,
76
+ };
77
+ if (geometry) {
78
+ params.fields = 'geometry';
79
+ }
80
+ axios
81
+ .get(this.requestDetailsUrl, {
82
+ params,
83
+ })
84
+ .then((response) => {
85
+ if (this.mounted) {
86
+ this.setState({
87
+ error: null,
88
+ showDetails: true,
89
+ response: response.data.result,
90
+ selectedLocality: response.data.result,
91
+ });
92
+ }
93
+ })
94
+ .catch((error) => {
95
+ const errorResult = (error && error.response && error.response.data) || 'Unhandled error';
96
+ this.setState({ error: errorResult });
97
+ });
98
+ };
99
+
100
+ getRequestparams = () => {
101
+ const { input, selectedLocality, countries, showDetails, geometry } = this.state;
102
+ if (showDetails) {
103
+ const detailsParam = {
104
+ key: Constants.woosmapKey,
105
+ public_id: selectedLocality.public_id,
106
+ };
107
+ if (geometry) {
108
+ detailsParam.fields = 'geometry';
109
+ }
110
+ return detailsParam;
111
+ }
112
+ const params = {
113
+ key: Constants.woosmapKey,
114
+ };
115
+ if (countries && countries.length > 0) {
116
+ params.components = countries.map((item) => `country:${item.value}`).join('|');
117
+ }
118
+
119
+ params.input = input;
120
+ if (geometry) {
121
+ params.fields = 'geometry';
122
+ }
123
+ return params;
124
+ };
125
+
126
+ displayOnMap = () => {
127
+ const { selectedAddress } = this.state;
128
+ if (this.viewport) {
129
+ this.viewport.setMap(null);
130
+ }
131
+ if (this.map && selectedAddress) {
132
+ if (selectedAddress.viewpoint) {
133
+ this.viewport = viewpoint(this.map, selectedAddress.viewpoint);
134
+ this.map.fitBounds(selectedAddress.viewpoint.bounds, 10);
135
+ } else {
136
+ this.map.setZoom(10);
137
+ }
138
+ }
139
+ };
140
+
141
+ selectAddress = (address) => {
142
+ this.setState(
143
+ {
144
+ error: null,
145
+ selectedLocality: address,
146
+ },
147
+ this.requestAddress
148
+ );
149
+ };
150
+
151
+ renderHeaderFilters = () => {
152
+ const { countries } = this.state;
153
+ const { usecaseFilter } = this.props;
154
+ let components = null;
155
+ if (countries && countries.length > 0) {
156
+ components = countries.map((item) => `country:${item.value}`).join('|');
157
+ }
158
+ const inputFilter = {
159
+ label: tr('Type in an address '),
160
+ component: (
161
+ <AddressAutocomplete
162
+ inputRef={this.inputRef}
163
+ key="addressAutocomplete"
164
+ ref={this.addressAutocompleteRef}
165
+ apiOrder={['localities']}
166
+ woosmapKey={Constants.woosmapKey}
167
+ placeholder="Start to type in an address"
168
+ defaultLocality={this.defaultOrigin}
169
+ componentsLocalities={components}
170
+ callback={this.requestAddressDetails}
171
+ inputChangeCb={this.onAutocompleteInputChangeCb}
172
+ />
173
+ ),
174
+ };
175
+ return [usecaseFilter, inputFilter];
176
+ };
177
+
178
+ renderFooterFilters = () => {
179
+ const { geometry } = this.state;
180
+ const filterTypes = {
181
+ label: tr('Filter by'),
182
+ component: (
183
+ <div className="demo__input-container--inline" key="country">
184
+ <CountrySelect
185
+ isMulti
186
+ placeholder={tr('Country')}
187
+ filter={['fr', 'gb']}
188
+ onChange={(items) => this.setState({ countries: items }, this.requestAddress)}
189
+ />
190
+ </div>
191
+ ),
192
+ };
193
+ const filterGeometry = {
194
+ label: tr('Geometry only'),
195
+ component: (
196
+ <Input
197
+ type="checkbox"
198
+ key="geometry"
199
+ checked={geometry}
200
+ onChange={(e) => {
201
+ this.setState({ geometry: e.target.checked });
202
+ }}
203
+ />
204
+ ),
205
+ };
206
+ return [filterGeometry, filterTypes];
207
+ };
208
+
209
+ renderAddress = () => {
210
+ const { selectedLocality, geometry } = this.state;
211
+ if (geometry) {
212
+ return null;
213
+ }
214
+ let addressComponents = [];
215
+ const hasComponents = selectedLocality && selectedLocality.address_components;
216
+ if (hasComponents) {
217
+ addressComponents = selectedLocality.address_components;
218
+ }
219
+ let shippingAddress = '';
220
+ let shippingAddress2 = '';
221
+ let postcode = '';
222
+ let locality = '';
223
+ let state = '';
224
+ let country = '';
225
+ addressComponents.forEach((component) => {
226
+ const [componentType] = component.types;
227
+ switch (componentType) {
228
+ case 'street_number': {
229
+ shippingAddress = `${component.long_name} ${shippingAddress}`;
230
+ break;
231
+ }
232
+ case 'route': {
233
+ shippingAddress += component.short_name;
234
+ break;
235
+ }
236
+ case 'postal_codes': {
237
+ postcode = `${component.long_name}${postcode}`;
238
+ break;
239
+ }
240
+ case 'postal_code_suffix': {
241
+ postcode = `${postcode}-${component.long_name}`;
242
+ break;
243
+ }
244
+ case 'locality':
245
+ locality = component.long_name;
246
+ break;
247
+
248
+ case 'state': {
249
+ state = component.long_name;
250
+ break;
251
+ }
252
+ case 'administrative_area_level_1': {
253
+ state = component.long_name;
254
+ break;
255
+ }
256
+ case 'country': {
257
+ country = component.long_name;
258
+ break;
259
+ }
260
+ case 'premise': {
261
+ shippingAddress2 = component.long_name;
262
+ break;
263
+ }
264
+ default:
265
+ break;
266
+ }
267
+ });
268
+
269
+ return (
270
+ <div className="demo--localitiesaddress__formcontainer">
271
+ {!hasComponents && (
272
+ // eslint-disable-next-line jsx-a11y/interactive-supports-focus
273
+ <div role="button" className="overlay" />
274
+ )}
275
+ <Input
276
+ label="Apartment, unit, suite, or floor #"
277
+ placeholder="Apartment, unit, suite, or floor #"
278
+ value={shippingAddress2}
279
+ onChange={() => {}}
280
+ />
281
+ <Input
282
+ label="Street number, street name"
283
+ placeholder="Street number, street name"
284
+ value={shippingAddress}
285
+ onChange={() => {}}
286
+ />
287
+ <Input label="City" value={locality} placeholder="City" onChange={() => {}} />
288
+ <Input
289
+ className="fullw"
290
+ label="State/Province"
291
+ placeholder="State/Province"
292
+ value={state}
293
+ onChange={() => {}}
294
+ />
295
+ <Input
296
+ className="fullw"
297
+ label="Postal code"
298
+ placeholder="Postal code"
299
+ value={postcode}
300
+ onChange={() => {}}
301
+ />
302
+ <Input label="Country/Region" placeholder="Country/Region" value={country} onChange={() => {}} />
303
+ </div>
304
+ );
305
+ };
306
+
307
+ renderMap = () => {
308
+ const { selectedLocality } = this.state;
309
+
310
+ const location =
311
+ selectedLocality && selectedLocality.geometry
312
+ ? selectedLocality.geometry.location
313
+ : { lat: 48.863350992156306, lng: 2.3484066674237796 };
314
+ const marker =
315
+ selectedLocality && selectedLocality.geometry ? <Marker lat={location.lat} lng={location.lng} /> : null;
316
+ return (
317
+ <GoogleMapReact
318
+ defaultZoom={10}
319
+ center={[location.lat, location.lng]}
320
+ bootstrapURLKeys={{ key: Constants.gmapKey, libraries: ['geometry', 'places'] }}
321
+ yesIWantToUseGoogleMapApiInternals
322
+ onGoogleApiLoaded={({ map }) => {
323
+ this.map = map;
324
+ this.displayOnMap();
325
+ }}
326
+ >
327
+ {marker}
328
+ </GoogleMapReact>
329
+ );
330
+ };
331
+
332
+ render() {
333
+ const { response, error, showDetails } = this.state;
334
+ const { noheader, headerLabels, subHeader } = this.props;
335
+ let request = this.requestUrl;
336
+ if (showDetails) {
337
+ request = this.requestDetailsUrl;
338
+ }
339
+ return (
340
+ <Demo
341
+ noheader={noheader}
342
+ header={headerLabels}
343
+ subHeader={subHeader}
344
+ id="localities-demo"
345
+ className="demo--localitiesaddress"
346
+ footerFilters={this.renderFooterFilters()}
347
+ headerFilters={this.renderHeaderFilters()}
348
+ request={request}
349
+ params={this.getRequestparams()}
350
+ referer="http://localhost/"
351
+ response={error || response}
352
+ result={this.renderAddress()}
353
+ map={this.renderMap()}
354
+ />
355
+ );
356
+ }
357
+ }
358
+
359
+ LocalitiesAddressDemo.defaultProps = {
360
+ noheader: false,
361
+ headerLabels: null,
362
+ usecaseFilter: null,
363
+ subHeader: null,
364
+ };
365
+ LocalitiesAddressDemo.propTypes = {
366
+ noheader: PropTypes.bool,
367
+ headerLabels: PropTypes.object,
368
+ usecaseFilter: PropTypes.object,
369
+ subHeader: PropTypes.object,
370
+ };
@@ -0,0 +1,12 @@
1
+ import React from 'react';
2
+ import LocalitiesAddressDemo from './LocalitiesAddressDemo';
3
+
4
+ const Story = {
5
+ title: 'demo/LocalitiesAddress',
6
+ component: LocalitiesAddressDemo,
7
+ };
8
+
9
+ export default Story;
10
+
11
+ const TemplateLocalitiesAddress = () => <LocalitiesAddressDemo />;
12
+ export const LocalitiesAddress = TemplateLocalitiesAddress.bind({});
@@ -0,0 +1,83 @@
1
+ /* eslint-disable jsx-a11y/click-events-have-key-events */
2
+ import React, { Component } from 'react';
3
+ import LocalitiesDemo from './LocalitiesDemo';
4
+ import Button from '../Button/Button';
5
+ import ButtonGroup from '../Button/ButtonGroup';
6
+ import LocalitiesAddressDemo from './LocalitiesAddressDemo';
7
+ import { tr } from '../utils/locale';
8
+
9
+ export default class LocalitiesAllDemo extends Component {
10
+ constructor(props) {
11
+ super(props);
12
+ this.state = { usecase: 'localities' };
13
+ }
14
+
15
+ renderSubHeader = () => (
16
+ <p>
17
+ {tr('You can select bewteen 2 use cases:')}
18
+ <ul className="demo__desc__list">
19
+ <li>{tr('Store Locator integration: autocomplete on city names, suburb names, postcodes...')}</li>
20
+ <li>
21
+ {tr(
22
+ 'Checkout integration: on full addresses, renders the right part of the address in the right form fields'
23
+ )}
24
+ </li>
25
+ </ul>
26
+ </p>
27
+ );
28
+
29
+ renderUsecaseFilter = () => {
30
+ const { usecase } = this.state;
31
+ return {
32
+ key: 'usecase',
33
+ label: tr('Use case'),
34
+ component: (
35
+ <ButtonGroup isLight key="usecase">
36
+ <Button
37
+ type="group"
38
+ key="localities"
39
+ active={usecase === 'localities'}
40
+ onClick={() => {
41
+ this.setState({ usecase: 'localities' });
42
+ }}
43
+ label={tr('Store Locator')}
44
+ />
45
+ <Button
46
+ type="group"
47
+ key="addresses"
48
+ active={usecase === 'addresses'}
49
+ onClick={() => {
50
+ this.setState({ usecase: 'addresses' });
51
+ }}
52
+ label={tr('Checkout')}
53
+ />
54
+ </ButtonGroup>
55
+ ),
56
+ };
57
+ };
58
+
59
+ render() {
60
+ const { usecase } = this.state;
61
+ const headerLabels = {
62
+ title: tr('Localities API'),
63
+ desc: tr(
64
+ 'Woosmap Localities sorts and displays the 5 most relevant results, with priority to the most likely results, recognises common abbreviations and apostrophes, with high tolerance for typos.'
65
+ ),
66
+ };
67
+ return usecase === 'localities' ? (
68
+ <LocalitiesDemo
69
+ noheader={false}
70
+ headerLabels={headerLabels}
71
+ usecaseFilter={this.renderUsecaseFilter()}
72
+ subHeader={this.renderSubHeader()}
73
+ />
74
+ ) : (
75
+ <LocalitiesAddressDemo
76
+ noheader={false}
77
+ headerLabels={headerLabels}
78
+ usecaseFilter={this.renderUsecaseFilter()}
79
+ subHeader={this.renderSubHeader()}
80
+ />
81
+ );
82
+ }
83
+ }
@@ -0,0 +1,12 @@
1
+ import React from 'react';
2
+ import LocalitiesAllDemo from './LocalitiesAllDemo';
3
+
4
+ const Story = {
5
+ title: 'demo/LocalitiesAll',
6
+ component: LocalitiesAllDemo,
7
+ };
8
+
9
+ export default Story;
10
+
11
+ const TemplateLocalitiesAll = () => <LocalitiesAllDemo />;
12
+ export const Localities = TemplateLocalitiesAll.bind({});
@@ -194,6 +194,7 @@ export default class LocalitiesDemo extends Component {
194
194
 
195
195
  renderHeaderFilters = () => {
196
196
  const { input } = this.state;
197
+ const { usecaseFilter } = this.props;
197
198
  const inputFilter = {
198
199
  label: tr('Type in '),
199
200
  component: (
@@ -205,7 +206,7 @@ export default class LocalitiesDemo extends Component {
205
206
  />
206
207
  ),
207
208
  };
208
- return [inputFilter];
209
+ return [usecaseFilter, inputFilter];
209
210
  };
210
211
 
211
212
  renderFooterFilters = () => {
@@ -256,17 +257,12 @@ export default class LocalitiesDemo extends Component {
256
257
 
257
258
  render() {
258
259
  const { localities, error } = this.state;
259
- const { noheader } = this.props;
260
- const headerLabels = {
261
- title: tr('Localities API'),
262
- desc: tr(
263
- 'Offer autocomplete on city names, suburb names, postcodes and full addresses in some countries. Accelerate customer journey by searching on full words as well as substrings, with high flexibility on typos.'
264
- ),
265
- };
260
+ const { noheader, headerLabels, subHeader } = this.props;
266
261
  return (
267
262
  <Demo
268
263
  id="localities-demo"
269
264
  header={headerLabels}
265
+ subHeader={subHeader}
270
266
  docLink="https://developers.woosmap.com/products/localities/get-started/"
271
267
  className="demo--localities"
272
268
  footerFilters={this.renderFooterFilters()}
@@ -285,7 +281,13 @@ export default class LocalitiesDemo extends Component {
285
281
 
286
282
  LocalitiesDemo.defaultProps = {
287
283
  noheader: false,
284
+ headerLabels: {},
285
+ usecaseFilter: null,
286
+ subHeader: null,
288
287
  };
289
288
  LocalitiesDemo.propTypes = {
290
289
  noheader: PropTypes.bool,
290
+ headerLabels: PropTypes.object,
291
+ usecaseFilter: PropTypes.object,
292
+ subHeader: PropTypes.object,
291
293
  };
@@ -42,7 +42,7 @@ export default class MapDemo extends Component {
42
42
  styleColor = `
43
43
  {featureType: "all", stylers:[{"saturation": -100, "lightness": -30}]},`;
44
44
  }
45
- if (style === 'lighgrey') {
45
+ if (style === 'lightgrey') {
46
46
  styleColor = `
47
47
  {featureType: "all", stylers:[{"saturation": -100}]},`;
48
48
  }
@@ -145,7 +145,7 @@ storesOverlay.setMap(map);
145
145
  renderFooterFilters = () => {
146
146
  const { hasStores, hasPois } = this.state;
147
147
  const filterPois = {
148
- label: tr('Display Map Pois'),
148
+ label: tr('Display Map POIs'),
149
149
  component: (
150
150
  <Input
151
151
  type="checkbox"
@@ -234,12 +234,10 @@ export default class MerchantDemo extends Component {
234
234
  if (!transaction) {
235
235
  return JSON.stringify(
236
236
  {
237
- merchants: [
238
- request.merchants.map((item) => ({
239
- dirty_name: item.dirty_name,
240
- country: item.country,
241
- })),
242
- ],
237
+ merchants: request.merchants.map((item) => ({
238
+ dirty_name: item.dirty_name,
239
+ country: item.country,
240
+ })),
243
241
  },
244
242
  null,
245
243
  2
@@ -314,7 +314,7 @@ export default class SearchDemo extends Component {
314
314
  const { data, error } = this.state;
315
315
  const { noheader } = this.props;
316
316
  const headerLabels = {
317
- title: tr('Search API'),
317
+ title: tr('Store Search API'),
318
318
  desc: tr(
319
319
  'Search among your own Points of Interest. Find stores by geography, by attributes or by autocomplete on store names.'
320
320
  ),
@@ -1,8 +1,9 @@
1
1
  import React, { Component } from 'react';
2
2
  import DistanceDemo from './DistanceDemo';
3
3
  import GeolocationDemo from './GeolocationDemo';
4
- import LocalitiesDemo from './LocalitiesDemo';
4
+ import LocalitiesDemo from './LocalitiesAllDemo';
5
5
  import MerchantDemo from './MerchantDemo';
6
+ import MapDemo from './MapDemo';
6
7
  import SearchDemo from './SearchDemo';
7
8
  import { changeLanguage } from '../utils/locale';
8
9
  import TrackEventContext from '../utils/TrackEventContext';
@@ -35,6 +36,7 @@ class IframeCommunication extends Component {
35
36
  'geolocation-demo': document.getElementById('geolocation-demo').offsetTop,
36
37
  'search-demo': document.getElementById('search-demo').offsetTop,
37
38
  'distance-demo': document.getElementById('distance-demo').offsetTop,
39
+ 'map-demo': document.getElementById('map-demo').offsetTop,
38
40
  },
39
41
  '*'
40
42
  );
@@ -62,9 +64,10 @@ const Template = () => {
62
64
  <div>
63
65
  <TrackEventContext.Provider value={trackEvent}>
64
66
  <LocalitiesDemo />
67
+ <SearchDemo />
65
68
  <MerchantDemo />
69
+ <MapDemo />
66
70
  <DistanceDemo />
67
- <SearchDemo />
68
71
  <GeolocationDemo />
69
72
  <IframeCommunication />
70
73
  </TrackEventContext.Provider>
@@ -79,9 +82,10 @@ const TemplateEn = () => {
79
82
  <div>
80
83
  <TrackEventContext.Provider value={trackEvent}>
81
84
  <LocalitiesDemo />
85
+ <SearchDemo />
82
86
  <MerchantDemo />
87
+ <MapDemo />
83
88
  <DistanceDemo />
84
- <SearchDemo />
85
89
  <GeolocationDemo />
86
90
  <IframeCommunication />
87
91
  </TrackEventContext.Provider>