@ta-interaktiv/react-municipality-search 1.5.4 → 1.7.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.
@@ -9,6 +9,7 @@ import { animated, config as springConfig, Transition } from 'react-spring'
9
9
  import IntlMessageFormat from 'intl-messageformat'
10
10
  import de from './locales/de.yml'
11
11
  import fr from './locales/fr.yml'
12
+ import Cookies from 'js-cookie'
12
13
 
13
14
  type MessageListContent = string | MessageList
14
15
 
@@ -30,6 +31,7 @@ export interface Municipality {
30
31
  readonly KTKZ: string
31
32
  readonly NORMORTSNAME: string
32
33
  readonly NORMGEMEINDE: string
34
+ timestamp?: number
33
35
  }
34
36
 
35
37
  export interface Props {
@@ -76,6 +78,7 @@ interface State {
76
78
 
77
79
  /** All localised strings for the component */
78
80
  const MESSAGES = { de, fr }
81
+ const localStorageItemName = 'selectedMunicipalities'
79
82
 
80
83
  /**
81
84
  * Create a translation Service function, using the provided MessageList and
@@ -103,7 +106,16 @@ const translationServiceFactory = (
103
106
  )
104
107
  }
105
108
  }
106
-
109
+ function getDomain():string {
110
+ let url = window.location.hostname
111
+ url = url.replace(/(https?:\/\/)?(www.)?/i, '')
112
+ const urlArray = url.split('.')
113
+ url = urlArray.slice(urlArray.length - 2).join('.')
114
+ if (url.indexOf('/') !== -1) {
115
+ return url.split('/')[0]
116
+ }
117
+ return url;
118
+ }
107
119
  class MunicipalityDownloadError extends Error {}
108
120
 
109
121
  /** RegEx to look whether a string starts with a number */
@@ -130,7 +142,7 @@ export class MunicipalitySearch extends Component<Props, State> {
130
142
  public static defaultProps = {
131
143
  municipalityData: '2021v3',
132
144
  locale: 'de',
133
- numberOfLastSelectedMunicipalities: 1
145
+ numberOfLastSelectedMunicipalities: 1,
134
146
  }
135
147
 
136
148
  public state = {
@@ -138,14 +150,14 @@ export class MunicipalitySearch extends Component<Props, State> {
138
150
  results: [],
139
151
  value: '',
140
152
  error: null,
141
- municipalities: []
153
+ municipalities: [],
142
154
  }
143
155
 
144
156
  private resetComponent = () => {
145
157
  this.setState({
146
158
  isLoading: false,
147
159
  results: [],
148
- value: ''
160
+ value: '',
149
161
  })
150
162
  }
151
163
 
@@ -202,7 +214,7 @@ export class MunicipalitySearch extends Component<Props, State> {
202
214
  window.dataLayer.push({
203
215
  event: 'Interactions',
204
216
  event_action: 'input',
205
- event_label: `search:for_${searchTerm}:results_${results.length}`
217
+ event_label: `search:for_${searchTerm}:results_${results.length}`,
206
218
  })
207
219
  }
208
220
 
@@ -214,7 +226,7 @@ export class MunicipalitySearch extends Component<Props, State> {
214
226
  searchTerm: string
215
227
  ): Municipality[] {
216
228
  const map = new Map()
217
- arr.forEach(v => {
229
+ arr.forEach((v) => {
218
230
  if (this.props.dedupe) {
219
231
  map.set(v.ORTNAME + v.GDENR, v)
220
232
  } else {
@@ -273,57 +285,130 @@ export class MunicipalitySearch extends Component<Props, State> {
273
285
  .fetch(url, {
274
286
  method: 'GET',
275
287
  mode: 'no-cors',
276
- credentials: 'include'
288
+ credentials: 'include',
277
289
  })
278
- .then(response => {
290
+ .then((response) => {
279
291
  console.log(response)
280
292
  })
281
- .catch(error => {
293
+ .catch((error) => {
282
294
  console.log(error)
283
295
  })
284
296
  }
285
297
 
286
- private pushToLocalStorageArray = (newMuni: Municipality) => {
287
- let prevSelectedMunicipalities
288
- let newArray: Municipality[] = []
289
- const localStorageItemName = 'selectedMunicipalities'
298
+ /**
299
+ * Get the history of the last selected municipalities from localStorage and cookies
300
+ */
301
+ private getStoredMunicipalities = (): Municipality[] | [] => {
302
+ let munisFromLocalStorage: Municipality[] = []
303
+ let munisFromCookies: Municipality[] = []
290
304
  try {
291
- prevSelectedMunicipalities =
292
- localStorage.getItem(localStorageItemName) ?? JSON.stringify([])
293
- newArray = JSON.parse(prevSelectedMunicipalities)
294
- } catch {
295
- console.warn('🗄 localStorage is not available')
305
+ const munisFromLocalStorageString =
306
+ localStorage.getItem('selectedMunicipalities') ?? '[]'
307
+ munisFromLocalStorage = JSON.parse(munisFromLocalStorageString)
308
+ } catch (e) {
309
+ console.log(e)
310
+ console.warn('🗄 localStorage of selectedMunicipalities is not available')
311
+ }
312
+ try {
313
+ const munisFromCookiesString =
314
+ Cookies.get('selectedMunicipalities') ?? '[]'
315
+ munisFromCookies = JSON.parse(munisFromCookiesString)
316
+ } catch (e) {
317
+ console.log(e)
318
+ console.warn('🍪 cookies of selectedMunicipalities is not available')
296
319
  }
320
+ const ordered = [...munisFromLocalStorage, ...munisFromCookies].sort(
321
+ (a, b) => {
322
+ if (b.timestamp && a.timestamp) {
323
+ return b.timestamp - a.timestamp
324
+ }
325
+ if (b.timestamp) {
326
+ return 1
327
+ }
328
+ if (a.timestamp) {
329
+ return -1
330
+ }
331
+ return 0
332
+ }
333
+ )
334
+ return this.deduplicateMunicipalityArray(ordered)
335
+ }
336
+
337
+ /**
338
+ * Save the history of the last selected municipalities
339
+ * They will be saved in localStorage and in cookies
340
+ * Cookies are used for exchange between subdomains eg. interaktiv.tagesanzeiger.ch and tagesanzeiger.ch
341
+ */
342
+ private saveToSelectedMunicipalities = (newMuni: Municipality) => {
343
+ const prevSelectedMunicipalities = this.getStoredMunicipalities()
297
344
  // limit the number of stored municipalities
298
- const slicedArray = newArray.slice(0, 15)
345
+ const slicedArray = prevSelectedMunicipalities.slice(
346
+ 0,
347
+ 15
348
+ ) as Municipality[]
349
+ newMuni.timestamp = Date.now()
299
350
  slicedArray.unshift(newMuni)
351
+ console.log('domain:', getDomain())
352
+
300
353
  try {
301
- // deduplicate the array and stringify it for localStorage
302
- localStorage.setItem(
303
- localStorageItemName,
304
- JSON.stringify([
305
- ...new Map(
306
- slicedArray.map((muni: Municipality) => [
307
- muni.GDENR + muni.ORTNAME + muni.PLZ4,
308
- muni
309
- ])
310
- ).values()
311
- ])
354
+ // deduplicate the array, sort it by timestamp and stringify it for localStorage and cookies
355
+ const newMuniArray = JSON.stringify(
356
+ this.deduplicateMunicipalityArray(slicedArray)
312
357
  )
358
+ localStorage.setItem(localStorageItemName, newMuniArray)
359
+ Cookies.set(localStorageItemName, newMuniArray, {
360
+ expires: 365,
361
+ domain: getDomain(),
362
+ })
313
363
  } catch {
314
364
  console.warn('🗄 localStorage is not writeable')
315
365
  }
316
366
  }
317
367
 
368
+ /**
369
+ * Deduplicate the array of municipalities
370
+ * @param munis Municipalities array to deduplicate
371
+ * @returns Municipalities array deduplicated by ORTNAME, GDENR and PLZ4
372
+ */
373
+
374
+ private deduplicateMunicipalityArray = (
375
+ munis: Municipality[]
376
+ ): Municipality[] => {
377
+ return [
378
+ ...new Map(
379
+ munis
380
+ .slice()
381
+ .reverse()
382
+ .map((muni: Municipality) => [
383
+ muni.ORTNAME + muni.GDENR + (this.props.dedupe ? '' : muni.PLZ4),
384
+ muni,
385
+ ])
386
+ ).values(),
387
+ ]
388
+ .reverse()
389
+ .sort((a, b) => {
390
+ if (b.timestamp && a.timestamp) {
391
+ return b.timestamp - a.timestamp
392
+ }
393
+ if (b.timestamp) {
394
+ return 1
395
+ }
396
+ if (a.timestamp) {
397
+ return -1
398
+ }
399
+ return 0
400
+ })
401
+ }
402
+
318
403
  private getMunicipalitiesData = () => {
319
404
  this.setState({
320
- isLoading: true
405
+ isLoading: true,
321
406
  })
322
407
 
323
408
  return fetch(
324
409
  `https://interaktiv.tagesanzeiger.ch/static/gemeindesuche/${this.props.municipalityData}.json`
325
410
  )
326
- .then(res => {
411
+ .then((res) => {
327
412
  if (!res.ok) {
328
413
  throw new MunicipalityDownloadError(
329
414
  `Download error: ${res.status}: ${res.statusText}.`
@@ -331,14 +416,14 @@ export class MunicipalitySearch extends Component<Props, State> {
331
416
  }
332
417
  return res.json()
333
418
  })
334
- .then(data => {
419
+ .then((data) => {
335
420
  this.setState({
336
421
  isLoading: false,
337
- municipalities: data
422
+ municipalities: data,
338
423
  })
339
424
  return Promise.resolve(data)
340
425
  })
341
- .catch(error => {
426
+ .catch((error) => {
342
427
  if (error instanceof MunicipalityDownloadError) {
343
428
  console.log(error)
344
429
  console.info(
@@ -349,7 +434,7 @@ export class MunicipalitySearch extends Component<Props, State> {
349
434
  )
350
435
  this.setState({
351
436
  isLoading: false,
352
- error: 'error.municipalitiesNotDownloaded'
437
+ error: 'error.municipalitiesNotDownloaded',
353
438
  })
354
439
  } else {
355
440
  throw error
@@ -369,7 +454,7 @@ export class MunicipalitySearch extends Component<Props, State> {
369
454
  /** Component mounting */
370
455
  public componentDidMount() {
371
456
  // Download municipalities list
372
- this.getMunicipalitiesData().then(data => {
457
+ this.getMunicipalitiesData().then((data) => {
373
458
  // set the municipalities that have the same GDENR, ORTNAME and PLZ4 as the ones in localStorage as results
374
459
  let locallyStoredMunicipalitiesArray
375
460
  try {
@@ -387,30 +472,34 @@ export class MunicipalitySearch extends Component<Props, State> {
387
472
  locallyStoredMunicipalitiesArray
388
473
  ) as Municipality[]
389
474
  const allBfsIdsAndNames = parsed.map(
390
- muni => muni.GDENR + muni.ORTNAME + muni.PLZ4
475
+ (muni) => muni.GDENR + muni.ORTNAME + muni.PLZ4
391
476
  )
392
477
  const filteredFromFetchedData: Municipality[] = data.filter(
393
478
  (muni: Municipality) =>
394
479
  allBfsIdsAndNames.includes(muni.GDENR + muni.ORTNAME + muni.PLZ4)
395
480
  )
396
481
  // sort before deduplication
397
- filteredFromFetchedData.sort((a, b) =>{
398
- return allBfsIdsAndNames.indexOf((a.GDENR + a.ORTNAME + a.PLZ4)) - allBfsIdsAndNames.indexOf((b.GDENR + b.ORTNAME + b.PLZ4))
399
- } )
400
- // always dedupe bc. sometimes there is duplicate data in the jsons
401
- const dedupedData = [ ...new Map(
402
- filteredFromFetchedData.slice().reverse().map((muni: Municipality) => [
403
- muni.GDENR + muni.ORTNAME + (this.props.dedupe ? "" : muni.PLZ4),
404
- muni,
405
- ])
406
- ).values()].reverse()
482
+ filteredFromFetchedData.sort((a, b) => {
483
+ return (
484
+ allBfsIdsAndNames.indexOf(a.GDENR + a.ORTNAME + a.PLZ4) -
485
+ allBfsIdsAndNames.indexOf(b.GDENR + b.ORTNAME + b.PLZ4)
486
+ )
487
+ })
488
+ // always dedupe bc. sometimes there is duplicate data in the jsons
489
+ const dedupedData = this.deduplicateMunicipalityArray(
490
+ filteredFromFetchedData
491
+ )
492
+
407
493
  // sort the dedupedData in the same order as in the localStorage
408
- dedupedData.sort((a, b) =>{
409
- return allBfsIdsAndNames.indexOf((a.GDENR + a.ORTNAME + a.PLZ4)) - allBfsIdsAndNames.indexOf((b.GDENR + b.ORTNAME + b.PLZ4))
410
- } )
411
- const muniCount = this.props.numberOfLastSelectedMunicipalities
494
+ dedupedData.sort((a, b) => {
495
+ return (
496
+ allBfsIdsAndNames.indexOf(a.GDENR + a.ORTNAME + a.PLZ4) -
497
+ allBfsIdsAndNames.indexOf(b.GDENR + b.ORTNAME + b.PLZ4)
498
+ )
499
+ })
500
+ const muniCount = this.props.numberOfLastSelectedMunicipalities
412
501
  this.setState({
413
- results: dedupedData.slice(0, muniCount)
502
+ results: dedupedData.slice(0, muniCount),
414
503
  })
415
504
  }
416
505
  })
@@ -488,7 +577,7 @@ export class MunicipalitySearch extends Component<Props, State> {
488
577
  this.state.error ? 'red' : 'basic grey'
489
578
  } ${labelDirection} label`}
490
579
  >
491
- {labelContent()}
580
+ {String(labelContent())}
492
581
  </label>
493
582
  </div>
494
583
  )}
@@ -513,13 +602,13 @@ export class MunicipalitySearch extends Component<Props, State> {
513
602
  this.props.onSelectionHandler(result)
514
603
  // this.sendToDataPipeline(result.PLZ4)
515
604
  if (this.props.showLastSelectedMunicipalities) {
516
- this.pushToLocalStorageArray(result)
605
+ this.saveToSelectedMunicipalities(result)
517
606
  }
518
607
  if (this.props.resetOnSelect) {
519
608
  this.resetComponent()
520
609
  }
521
610
  }}
522
- onKeyUp={e => {
611
+ onKeyUp={(e) => {
523
612
  if (e.key === 'Enter' || e.key === 'Space') {
524
613
  this.props.onSelectionHandler(result)
525
614
  // this.sendToDataPipeline(result.PLZ4)
@@ -537,7 +626,9 @@ export class MunicipalitySearch extends Component<Props, State> {
537
626
  {result.ORTNAME}
538
627
  </ResultHeader>
539
628
  <ResultMeta className='resultMeta'>
540
- {t('list.municipalityPrefix')} {result.GDENAMK}
629
+ <>
630
+ {t('list.municipalityPrefix')} {result.GDENAMK}
631
+ </>
541
632
  </ResultMeta>
542
633
  </animated.div>
543
634
  )}