diff --git a/js/apis/PlaceDetailsApi.js b/js/apis/PlaceDetailsApi.js new file mode 100644 index 0000000..3bce083 --- /dev/null +++ b/js/apis/PlaceDetailsApi.js @@ -0,0 +1,18 @@ +// @flow +const apiKey = 'AIzaSyBfMm1y6JayCbXrQmgAG1R3ka4ZOJno_5E'; +const placesUrl = `https://maps.googleapis.com/maps/api/place/details/json?key=${apiKey}`; + +export const getPlaceDetails = async (placeid: string) => { + if (!placeid || typeof placeid !== 'string') { + throw new Error('placeid looks wrong'); + } + + const res = await fetch(`${placesUrl}&placeid=${placeid}`); + + const { error_message, result } = await res.json(); + + if (error_message) { + throw new Error(error_message); + } + return result; +}; diff --git a/js/components/ItemTile.js b/js/components/ItemTile.js index 191b8c2..d9d1d7d 100644 --- a/js/components/ItemTile.js +++ b/js/components/ItemTile.js @@ -60,10 +60,10 @@ export const FoodItemTile = pure(({ foodItem = {}, place = {} }: { foodItem: Foo - {foodItem.name} + {foodItem.name || ''} - {`${place.name} - ${foodItem.distance} mi`} + {`${place.name || ''} - ${foodItem.distance} mi`} diff --git a/js/components/PlaceTile.js b/js/components/PlaceTile.js index 4e6bc9b..cb0a347 100644 --- a/js/components/PlaceTile.js +++ b/js/components/PlaceTile.js @@ -2,7 +2,7 @@ import { View } from 'react-native'; import React from 'react'; import { pure } from 'recompose'; -import { type List } from 'immutable'; +import { List } from 'immutable'; import typeof PlaceRecord from '../records/PlaceRecord'; import { Link } from 'react-router-native'; import { Thumbnail, StrongText, SubText } from './ItemTile'; @@ -11,7 +11,7 @@ import { routeWithTitle } from '../helpers/RouteHelpers'; import theme from '../ui-theme'; type PlaceTileProps = { place: PlaceRecord, distance: number, categories: List }; -export default pure(({ place, distance, categories }: PlaceTileProps) => { +export default pure(({ place = {}, distance = 999, categories = new List() }: PlaceTileProps) => { return ( diff --git a/js/helpers/ImmutableHelpers.js b/js/helpers/ImmutableHelpers.js index e9107e3..a0bc4dd 100644 --- a/js/helpers/ImmutableHelpers.js +++ b/js/helpers/ImmutableHelpers.js @@ -3,4 +3,9 @@ import { type List, type Map } from 'immutable'; export const pushInto = (list: List, item: T): List => list.push(item); -export const setById = (map: Map, item: { id: string }): Map => map.set(item.id, item); +export const setById = (map: Map, item: { id: string }): Map => { + if (!item.id) { + return map; + } + return map.set(item.id, item); +}; diff --git a/js/pages/CreateFoodItem.js b/js/pages/CreateFoodItem.js index 3953b50..ccbfe45 100644 --- a/js/pages/CreateFoodItem.js +++ b/js/pages/CreateFoodItem.js @@ -119,6 +119,8 @@ export default compose( longitude, name, address, + phoneNumber, + website, }) => { setPlace( new PlaceRecord({ @@ -127,6 +129,8 @@ export default compose( address, latitude, longitude, + phoneNumber, + website, }) ); diff --git a/js/records/PlaceRecord.js b/js/records/PlaceRecord.js index cc554da..db8034f 100644 --- a/js/records/PlaceRecord.js +++ b/js/records/PlaceRecord.js @@ -1,20 +1,102 @@ // @flow -import { Record } from 'immutable'; +import { List, Record, fromJS } from 'immutable'; + +export type GooglePlaceObj = { + place_id: string, + name: string, + formatted_address: string, + geometry: { + location: { + lat: number, + lng: number, + }, + viewport: { + northeast: { + lat: number, + lng: number, + }, + southwest: { + lat: number, + lng: number, + }, + }, + }, + formatted_phone_number: string, + url: string, + photos: Array, + icon: string, + opening_hours: { + open_now: boolean, + periods: Array<{ + close: { + day: number, + time: string, + }, + open: { + day: number, + time: string, + }, + }>, + weekday_text: Array, + }, +}; export type Place = { - id: ?string, + id: string, name: string, address: string, - latitude: ?number, - longitude: ?number, + latitude: number, + longitude: number, + phoneNumber: string, + googleMapsUrl: string, + photos: List, + icon: string, + hours: List, + openNow: boolean, }; const FoodRecordDefaults: Place = { - id: null, + id: '', name: '', address: '', - latitude: null, - longitude: null, + latitude: 0, + longitude: 0, + phoneNumber: '', + googleMapsUrl: '', + photos: new List(), + icon: '', + hours: new List(), + openNow: false, }; -export default Record(FoodRecordDefaults, 'PlaceRecord'); +const PlaceRecord = Record(FoodRecordDefaults, 'PlaceRecord'); + +export const buildPlaceRecord = (place: GooglePlaceObj) => { + const { + place_id, + name, + formatted_address, + geometry: { location = {} } = {}, + formatted_phone_number, + url, + photos, + icon, + opening_hours = {}, + } = place; + + return new PlaceRecord({ + id: place_id, + name: name, + address: formatted_address, + latitude: location.lat, + longitude: location.lng, + phoneNumber: formatted_phone_number, + googleMapsUrl: url, + photos: fromJS(photos), + icon: icon, + hours: fromJS(opening_hours.weekday_text), + openNow: opening_hours.open_now, + }); +}; + +export default PlaceRecord; diff --git a/js/streams/FoodItemsStream.js b/js/streams/FoodItemsStream.js index 6459fc1..691a75c 100644 --- a/js/streams/FoodItemsStream.js +++ b/js/streams/FoodItemsStream.js @@ -108,4 +108,6 @@ const DUMMY_DATA = [ }, ]; -export default Observable.from(DUMMY_DATA).map(FoodItemRecord).scan(setById, Map()); +export const foodItemsRaw$ = Observable.from(DUMMY_DATA); + +export default foodItemsRaw$.map(FoodItemRecord).scan(setById, Map()); diff --git a/js/streams/PlacesStream.js b/js/streams/PlacesStream.js index feb51a3..cf19efb 100644 --- a/js/streams/PlacesStream.js +++ b/js/streams/PlacesStream.js @@ -1,46 +1,26 @@ // @flow -import PlaceRecord from '../records/PlaceRecord'; -import { Observable } from 'rxjs'; +import { buildPlaceRecord } from '../records/PlaceRecord'; import { Map } from 'immutable'; -import { setById } from '../helpers/ImmutableHelpers'; +import { foodItemsRaw$ } from './FoodItemsStream'; +import { getPlaceDetails } from '../apis/PlaceDetailsApi'; +import { memoize } from 'ramda'; -// TODO open a websoket and create observable from it -const DUMMY_DATA = [ - { - id: 'ChIJf5QJFBK1RIYRjjfxZz9Z0O0', - name: 'Whole Foods', - address: '525 N. Lamar Blvd, Austin', - latitude: 30.270667599999996, - longitude: -97.7532464, - }, - { - id: 'ChIJm4_R2BG1RIYRQcnsPEmzGQY', - name: "Trader Joe's", - address: '211 Walter Seaholm Dr, Ste 100, Austin', - latitude: 30.267681999999994, - longitude: -97.7527494, - }, - { - id: 'ChIJG44vBQi1RIYRvWGHdYUolZY', - name: 'Royal Blue Grocery', - address: '301 Brazos St Suite 110, Austin, TX 78701, USA', - latitude: 30.265019100000004, - longitude: -97.7419085, - }, - { - id: 'ChIJ72if-Qe1RIYRCzMucGEEdBA', - name: 'Second Street Market', - address: '200 San Jacinto Blvd, Austin', - latitude: 30.263963300000004, - longitude: -97.742308, - }, - { - id: 'ChIJr3szW6a1RIYRkM7LRpnBIO0', - name: 'Lonestar Souvenir and Food', - address: '502 E. 6th St, Austin', - latitude: 30.266898599999998, - longitude: -97.73798459999999, - }, -]; +/** + * return a promise of a place details object + * if already requested return existing promise + * swallow exceptions so as not to break the stream + */ +const safeGetPlaceDetails = memoize(placeId => { + return getPlaceDetails(placeId).catch(error => { + console.log(error); // eslint-disable-line no-console + return {}; + }); +}); -export default Observable.from(DUMMY_DATA).map(PlaceRecord).scan(setById, new Map()); +const uniquePlaceIds$ = foodItemsRaw$.map(({ placeId }) => placeId).distinct(); + +const placeRecords$ = uniquePlaceIds$.mergeMap(safeGetPlaceDetails).map(buildPlaceRecord); + +export default placeRecords$.scan((accMap, place) => { + return accMap.set(place.id, place); +}, new Map()); diff --git a/package.json b/package.json index b63987a..8dd7f60 100644 --- a/package.json +++ b/package.json @@ -24,7 +24,7 @@ "react-native-vector-icons": "^4.0.0", "react-router-native": "^4.0.0", "recompose": "^0.23.4", - "rxjs": "^5.2.0" + "rxjs": "^5.4.2" }, "devDependencies": { "babel-eslint": "^7.1.1", diff --git a/yarn.lock b/yarn.lock index 435b1a0..a9719cc 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3973,9 +3973,9 @@ rx-lite@^3.1.2: version "3.1.2" resolved "https://registry.yarnpkg.com/rx-lite/-/rx-lite-3.1.2.tgz#19ce502ca572665f3b647b10939f97fd1615f102" -rxjs@^5.2.0: - version "5.4.0" - resolved "https://registry.yarnpkg.com/rxjs/-/rxjs-5.4.0.tgz#a7db14ab157f9d7aac6a56e655e7a3860d39bf26" +rxjs@^5.4.2: + version "5.4.2" + resolved "https://registry.yarnpkg.com/rxjs/-/rxjs-5.4.2.tgz#2a3236fcbf03df57bae06fd6972fd99e5c08fcf7" dependencies: symbol-observable "^1.0.1"