use google places api for all places components

This commit is contained in:
Bart Akeley 2017-07-22 16:28:05 -05:00
parent b6b5497a72
commit e23f279b65
11 changed files with 152 additions and 61 deletions

View file

@ -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;
};

View file

@ -60,10 +60,10 @@ export const FoodItemTile = pure(({ foodItem = {}, place = {} }: { foodItem: Foo
<Thumbnail thumb={foodItem.thumbImage} />
<View style={{ paddingTop: 15 }}>
<StrongText>
{foodItem.name}
{foodItem.name || ''}
</StrongText>
<SubText>
{`${place.name} - ${foodItem.distance} mi`}
{`${place.name || ''} - ${foodItem.distance} mi`}
</SubText>
</View>
</TileBox>

View file

@ -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<string> };
export default pure(({ place, distance, categories }: PlaceTileProps) => {
export default pure(({ place = {}, distance = 999, categories = new List() }: PlaceTileProps) => {
return (
<Link
to={routeWithTitle(`/place/${place.id || ''}`, place.name)}

View file

@ -6,7 +6,7 @@ import { path } from 'ramda';
import mapPropsStream from 'recompose/mapPropsStream';
import FoodItems$ from '../streams/FoodItemsStream';
import typeof FoodItemRecord from '../records/FoodItemRecord';
import { Set, Map } from 'immutable';
import { Map } from 'immutable';
import { getCategoryText } from '../helpers/CategoryHelpers';
export const withFoodItems = mapPropsStream(props$ =>

View file

@ -3,4 +3,9 @@ import { type List, type Map } from 'immutable';
export const pushInto = <T>(list: List<T>, item: T): List<T> => list.push(item);
export const setById = (map: Map<string, any>, item: { id: string }): Map<string, any> => map.set(item.id, item);
export const setById = (map: Map<string, any>, item: { id: string }): Map<string, any> => {
if (!item.id) {
return map;
}
return map.set(item.id, item);
};

View file

@ -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,
})
);

View file

@ -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<string>,
icon: string,
opening_hours: {
open_now: boolean,
periods: Array<{
close: {
day: number,
time: string,
},
open: {
day: number,
time: string,
},
}>,
weekday_text: Array<string>,
},
};
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<string>,
icon: string,
hours: List<string>,
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;

View file

@ -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());

View file

@ -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());

View file

@ -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",

View file

@ -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"