<repo_name>cmilfont/pwa2<file_sep>/src/api/domain/fetch-entities.js
import { fromJS, List } from 'immutable';

function getCoords(item) {
  const { lat, lon } = (item.type === 'experience')? 
    item.destination.location: item.location;
  return [lat, lon];
}

export default (state, action) => {

  const isArray = action.payload.length;
  const coords = isArray ? 
    action.payload.map(item => getCoords(item)):
    [getCoords(action.payload)];

  const newState = state.setIn(['map', 'bounds'], List(coords))
                        .setIn(['entities', action.meta.entity], fromJS(action.payload))


  return isArray ?
      newState:
      newState.setIn(['map', 'center'], fromJS(getCoords(action.payload)))
              .setIn(['map', 'zoom'], 16);
}<file_sep>/src/api/sagas/index.js
import { all, fork } from 'redux-saga/effects';
import watchFetchEntities from './fetch-entities';

function* sagas() {
  yield all([
    fork(watchFetchEntities),
  ]);
}

export default sagas;<file_sep>/src/components/places/place/place.js
import React from 'react';
import { Link } from 'react-router-dom';
import PropTypes from 'prop-types';

const Place = ({
  highlights,
  content,
  name,
  slug,
  cover: { file: { url } },
  destination: { name: destinationName }
}) => {

  const highlightedContent = highlights.reduce((resultado, text) => {
    return resultado.replace(text, `<span class="high">${text}</span>`);
  }, content);

  return (
  <li>
    <div>
      <img src={url} />
      <div className="name">
        <Link to={`/places/${slug}`}>
          {name}
        </Link>
      </div>
      <div>{destinationName}</div>
      <div
        dangerouslySetInnerHTML={{ __html: highlightedContent }}
      />
    </div>
  </li>
  )
};

export default Place;
<file_sep>/src/components/map/index.js
import React from 'react';
import { connect } from 'react-redux';
import { Map, LayerGroup } from 'react-leaflet';
import { GoogleApiLoader, GoogleMutant } from 'react-leaflet-googlemutant';
import Marker from './markers/marker';

class BeerswarmMap extends React.Component {
  render() {
    const {bounds, center, zoom} = this.props;
    const options = {
      maxZoom: 18,
      minZoom: 0,
      zoomControl: false,
      doubleClickZoom: true,
      keyboard: false,
    };
    const viewport = { center, zoom };
    if (bounds.length) {
      options.bounds = bounds;
    }
    const places = bounds.map(item => (
      <Marker coords={item} />
    ));
    return (
      <div>
        <GoogleApiLoader apiKey="<KEY>">
          <Map
            attributionControl={false}
            viewport={viewport}
            {...options}
          >
            <GoogleMutant type="roadmap" />
            <LayerGroup>
              {places}
            </LayerGroup>
          </Map>
        </GoogleApiLoader>
        Mapa
      </div>
    )
  }
}

export default connect(
  state => {
    const {bounds, center, zoom } = state.get('map').toJS();
    return {
      bounds, center, zoom
    }
  }
)(BeerswarmMap);<file_sep>/src/components/routes.js
import Places from './places';
import Place from './places/place/index.js';
import Experiences from './experiences';

export default [
  {
    path: '/',
    component: Places,
    exact: true,
  },
  {
    path: '/places',
    component: Places,
    exact: true,
  },
  {
    path: '/places/:placeSlug?',
    component: Place,
  },
  {
    path: '/experiences',
    component: Experiences, 
  }
];<file_sep>/src/components/theme/index.js
import React from 'react';
import Places from '../places';
import { renderRoutes } from 'react-router-config';
import { Link } from 'react-router-dom';
import routes from '../routes';
import Map from '../map';

export default () => (
  <div>
    <div className="toolbar">
      <Link to="/experiences"> Experiences </Link>
      <Link to="/places"> Places </Link>
      <Link to="/destinations"> Desetinations </Link>
    </div>
    <Map />
    <div className="content">
      {renderRoutes(routes)}
    </div>
  </div>
);<file_sep>/src/api/sagas/fetch-entities.js
import pluralize from 'pluralize';
import { takeLatest, put } from 'redux-saga/effects';
import { FETCH_ENTITY_ERROR, FETCH_ENTITY_SUCCESS } from '../constants';
import { LOCATION_CHANGE } from 'react-router-redux';
import axios from 'axios';

function* verify(action) {
  try {
    const [, entityUrl, slug] = action.payload.pathname.split('/');
    const entity = slug ? pluralize.singular(entityUrl) : entityUrl;
    if (entity) {
      const res = yield axios(`/api/${action.payload.pathname}`);
      const payload = slug ? res.data[0] : res.data;
      yield put({
        type: FETCH_ENTITY_SUCCESS,
        payload,
        meta: {
          entity
        }
      })
    }

  } catch (error) {
    put({
      type: FETCH_ENTITY_ERROR,
      payload: error.message,
    })
  }
}

export default function* watch() {
  yield takeLatest(LOCATION_CHANGE, verify);
}<file_sep>/src/components/highlight/index.js
import React from 'react';
import { connect } from 'react-redux';
import PropTypes from 'prop-types';

class Highlight extends React.Component {

  static childContextTypes = {
    highlight: PropTypes.func,
    highlights: PropTypes.array,
  }

  getChildContext() {
    return {
      highlight: this.highlight,
      highlights: this.props.highlights,
    }
  }

  componentDidMount() {
    window.onmouseup = this.highlight;
  }

  highlight = () => {
    const selection = window.getSelection();
    this.props.addHighlight(selection.toString());
  }

  render() {
    return this.props.children;
  }
}

export default connect(
  state => {
    return ({
      highlights: state.get('highlights').toJS()
    })
  },
  dispatch => ({
    addHighlight: payload => dispatch({
      type: 'SELECT_TEXT',
      payload,
    })
  })
)(Highlight);