From 7cca8ab38ac4eca46872cb569246b8599a354c83 Mon Sep 17 00:00:00 2001 From: Bart Akeley Date: Sat, 19 May 2018 11:17:08 -0500 Subject: [PATCH 1/6] prompt for zipcode and remember choic --- js/App.js | 9 +++++- js/apis/GoogleMapsApi.js | 35 ++++++++++++++++++++++++ js/apis/PlaceDetailsApi.js | 6 ++-- js/apis/PositionApi.js | 35 ++++++++++++++++++------ js/constants/AppConstants.js | 2 ++ js/pages/LandingPage.js | 53 ++++++++++++++++++++++++++++++------ js/pages/List.js | 22 ++++----------- js/pages/ZipcodePage.js | 42 ++++++++++++++++++++++++++++ 8 files changed, 167 insertions(+), 37 deletions(-) create mode 100644 js/apis/GoogleMapsApi.js create mode 100644 js/pages/ZipcodePage.js diff --git a/js/App.js b/js/App.js index c95b18a..9ddb184 100644 --- a/js/App.js +++ b/js/App.js @@ -1,6 +1,6 @@ //@flow import React, { Component } from 'react'; -import { View, StatusBar } from 'react-native'; +import { View, StatusBar, AsyncStorage } from 'react-native'; // eslint-disable-line import { ThemeProvider } from 'react-native-material-ui'; import theme from './ui-theme'; import DrawerMenu from './pages/DrawerMenu'; @@ -16,6 +16,7 @@ import CreateFoodItem from './pages/CreateFoodItem'; import AuthManager from './AuthManager'; import LoginPage from './pages/LoginPage'; import LandingPage from './pages/LandingPage'; +import ZipcodePage from './pages/ZipcodePage'; setObservableConfig(rxjsconfig); @@ -32,6 +33,11 @@ export default class App extends Component { this._drawer.open(); }; + componentDidMount() { + // uncomment this to force show the landing/zipcode screens + // AsyncStorage.removeItem('zipcode'); + } + render() { return ( @@ -53,6 +59,7 @@ export default class App extends Component { + { diff --git a/js/apis/GoogleMapsApi.js b/js/apis/GoogleMapsApi.js new file mode 100644 index 0000000..7e55545 --- /dev/null +++ b/js/apis/GoogleMapsApi.js @@ -0,0 +1,35 @@ +// @flow +import { GoogleAPIKey } from '../constants/AppConstants'; +import { path } from 'ramda'; + +const url = 'https://maps.googleapis.com/maps/api/geocode/json'; + +type Location = { + lat: number, + lng: number, +}; + +type AddressComponent = { + long_name: string, + short_name: string, + types: Array, +}; + +type GeocodeResult = { + results: Array<{ + address_components: Array, + formatted_address: string, + geometry: { + location: Location, + }, + place_id: string, + types: Array, + }>, +}; + +export const getCoordsFromZip = async (zip: string): Promise => { + const res: GeocodeResult = await (await fetch( + `${url}?key=${GoogleAPIKey}&address=${zip}` + )).json(); + return path(['results', 0, 'geometry', 'location'], res) || { lat: 0, lng: 0 }; +}; diff --git a/js/apis/PlaceDetailsApi.js b/js/apis/PlaceDetailsApi.js index 2ed30da..8d849c4 100644 --- a/js/apis/PlaceDetailsApi.js +++ b/js/apis/PlaceDetailsApi.js @@ -1,9 +1,9 @@ // @flow import { type GooglePlaceObj } from '../records/PlaceRecord'; +import { GoogleAPIKey } from '../constants/AppConstants'; -const apiKey = 'AIzaSyBfMm1y6JayCbXrQmgAG1R3ka4ZOJno_5E'; -const placesUrl = `https://maps.googleapis.com/maps/api/place/details/json?key=${apiKey}`; -const photosUrl = `https://maps.googleapis.com/maps/api/place/photo?key=${apiKey}`; +const placesUrl = `https://maps.googleapis.com/maps/api/place/details/json?key=${GoogleAPIKey}`; +const photosUrl = `https://maps.googleapis.com/maps/api/place/photo?key=${GoogleAPIKey}`; type GooglePlaceDetailsResponse = { error_message: ?string, result: GooglePlaceObj }; diff --git a/js/apis/PositionApi.js b/js/apis/PositionApi.js index 89a9957..8e2cf62 100644 --- a/js/apis/PositionApi.js +++ b/js/apis/PositionApi.js @@ -1,5 +1,7 @@ // @flow import { emitter } from '../streams/LocationStream'; +import { getCoordsFromZip } from './GoogleMapsApi'; +import { AsyncStorage } from 'react-native'; export const getCurrentPosition = () => { return new Promise((resolve, reject) => { @@ -20,15 +22,32 @@ export const getCurrentPosition = () => { }; // TODO actually implement geolocation for zipcode into lat/lng -export const getPositionFromZip = () => { - const dummyPos: any = { +export const getPositionFromZip = async (zip: string) => { + const { lat: latitude, lng: longitude } = await getCoordsFromZip(zip); + + const pos: any = { coords: { - latitude: 30.267, - longitude: -97.7485, + latitude, + longitude, }, }; - return new Promise(resolve => { - emitter(dummyPos); - resolve(dummyPos); - }); + + emitter(pos); + + return pos; +}; + +export const getLocation = async (zipcode: string) => { + if (zipcode) { + AsyncStorage.setItem('zipcode', zipcode); + } + + const currentZip = zipcode || (await AsyncStorage.getItem('zipcode')); + + if (currentZip && currentZip !== 'usegps') { + getPositionFromZip(currentZip); + } else { + AsyncStorage.setItem('zipcode', 'usegps'); + getCurrentPosition(); + } }; diff --git a/js/constants/AppConstants.js b/js/constants/AppConstants.js index 10d824a..a58403a 100644 --- a/js/constants/AppConstants.js +++ b/js/constants/AppConstants.js @@ -8,3 +8,5 @@ export const BASE_URL = 'aretherecookies.herokuapp.com'; // @stouthaus // export const BASE_URL = '192.168.1.169:3000'; + +export const GoogleAPIKey = 'AIzaSyBfMm1y6JayCbXrQmgAG1R3ka4ZOJno_5E'; diff --git a/js/pages/LandingPage.js b/js/pages/LandingPage.js index db58238..5af891c 100644 --- a/js/pages/LandingPage.js +++ b/js/pages/LandingPage.js @@ -1,13 +1,31 @@ // @flow import React from 'react'; -import { View, Text, Image } from 'react-native'; +import { View, Text, Image, AsyncStorage } from 'react-native'; import atcCookieImage from '../../static/atc-cookie-logo.png'; import { Link } from 'react-router-native'; import RouterButton from 'react-router-native-button'; +import { compose, withHandlers, withState } from 'recompose'; +import { withRouterContext } from '../enhancers/routeEnhancers'; import theme from '../ui-theme'; -const LandingPage = () => { +type Props = { + skipIfAlreadyChosen: () => void, + setLoading: (val: boolean) => void, + loading: boolean, + router: { + history: { + replace: (route: string) => void, + }, + }, +}; + +const LandingPage = ({ skipIfAlreadyChosen, loading }: Props) => { + if (loading) { + skipIfAlreadyChosen(); + return null; + } + return ( { flex: 1, height: '100%', }}> - - + + We need to use your location to bring you the best experience possible. - + { color={theme.palette.primaryColor} /> - - I'd rather not. + + + I'd rather not. + ); }; -export default LandingPage; +export default compose( + withRouterContext, + withState('loading', 'setLoading', true), + withHandlers({ + skipIfAlreadyChosen: (props: Props) => async () => { + const zipcode = await AsyncStorage.getItem('zipcode'); + if (zipcode) { + props.router.history.replace('/list/food'); + } else { + props.setLoading(false); + } + }, + }) +)(LandingPage); diff --git a/js/pages/List.js b/js/pages/List.js index 59c8ff6..9e98b4e 100644 --- a/js/pages/List.js +++ b/js/pages/List.js @@ -6,7 +6,7 @@ import ScrollableTabView from 'react-native-scrollable-tab-view'; import theme from '../ui-theme.js'; import { type RoutingContextFlowTypes, routingContextPropTypes } from '../routes'; import { getSearch } from '../helpers/RouteHelpers'; -import { getCurrentPosition, getPositionFromZip } from '../apis/PositionApi'; +import { getLocation } from '../apis/PositionApi'; const tabs = ['food', 'places']; @@ -37,22 +37,10 @@ class List extends Component { // TODO convert this component to SFC using recompose // also, figure out a less lifecyle-y way to do this request componentDidMount() { - this.getLocation(); + const { zipcode } = getSearch(this.context); + getLocation(zipcode); } - getLocation = () => { - const { positionBy = 'zip' } = getSearch(this.context); - try { - if (positionBy === 'location') { - return getCurrentPosition(); - } else { - return getPositionFromZip(); - } - } catch (error) { - console.log(error); // eslint-disable-line - } - }; - updateTabRoute = ({ i }: { i: number }) => { const currentTab = getTabIndex(this.props); @@ -94,8 +82,8 @@ class List extends Component { prerenderingSiblingsNumber={Infinity} onChangeTab={this.updateTabRoute} initialPage={getTabIndex(this.props)}> - - + + ); } diff --git a/js/pages/ZipcodePage.js b/js/pages/ZipcodePage.js new file mode 100644 index 0000000..b525483 --- /dev/null +++ b/js/pages/ZipcodePage.js @@ -0,0 +1,42 @@ +// @flow + +import React from 'react'; +import { View, Text, TextInput } from 'react-native'; +import { compose, withState } from 'recompose'; +import RouterButton from 'react-router-native-button'; + +import theme from '../ui-theme'; + +type Props = { + zipcode: string, + setZipcode: (zip: string) => void, +}; + +const ZipcodePage = ({ zipcode, setZipcode }: Props) => { + return ( + + + OK, we won't detect your location. Instead, please enter a zipcode for us to use as your + default location. + + + + + ); +}; + +export default compose(withState('zipcode', 'setZipcode', ''))(ZipcodePage); From a25988d4df9a1ff52d808d2581e4956bb6731cdd Mon Sep 17 00:00:00 2001 From: Bart Akeley Date: Sat, 19 May 2018 12:25:00 -0500 Subject: [PATCH 2/6] versionCode 10 --- android/app/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/android/app/build.gradle b/android/app/build.gradle index c487751..39fd9f3 100644 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -91,7 +91,7 @@ android { applicationId "com.aretherecookies" minSdkVersion 16 targetSdkVersion 22 - versionCode 9 + versionCode 10 versionName "1.0" ndk { abiFilters "armeabi-v7a", "x86" From 9ddb7341ef1cea4de4002a1bf9c4f2b13e82eb33 Mon Sep 17 00:00:00 2001 From: Bart Akeley Date: Sat, 19 May 2018 12:25:25 -0500 Subject: [PATCH 3/6] simple error text on login page --- js/pages/LoginPage.js | 26 ++++++++++++++++++++++---- 1 file changed, 22 insertions(+), 4 deletions(-) diff --git a/js/pages/LoginPage.js b/js/pages/LoginPage.js index f1aa27d..45aa646 100644 --- a/js/pages/LoginPage.js +++ b/js/pages/LoginPage.js @@ -1,7 +1,7 @@ //@flow import React from 'react'; import { View, Text, Button } from 'react-native'; -import { withHandlers, compose } from 'recompose'; +import { withHandlers, compose, withState } from 'recompose'; import { withRouterContext } from '../enhancers/routeEnhancers'; import AuthManager, { type AUTH_PROVIDER } from '../AuthManager'; import queryString from 'query-string'; @@ -10,8 +10,18 @@ import theme from '../ui-theme'; type Props = { authUser: (provider: string) => () => Promise, + error: string, + setError: (err: string) => void, + router: { + history: { + replace: (url: string) => void, + location: { + search: string, + }, + }, + }, }; -const LoginPageComponent = ({ authUser }: Props) => { +const LoginPageComponent = ({ authUser, error }: Props) => { return ( Sign In @@ -29,14 +39,18 @@ const LoginPageComponent = ({ authUser }: Props) => { color={theme.palette.google} /> + {error && Login Failed! {error}} ); }; const LoginPage = compose( withRouterContext, + withState('error', 'setError', null), withHandlers({ - authUser: ({ router: { history } }) => (provider: AUTH_PROVIDER) => async () => { + authUser: ({ router: { history }, setError }: Props) => ( + provider: AUTH_PROVIDER + ) => async () => { const { returnto = '/' } = queryString.parse(history.location.search); try { if (!AuthManager.isLoggedIn()) { @@ -44,7 +58,11 @@ const LoginPage = compose( } history.replace(returnto); } catch (error) { - console.log(error); //eslint-disable-line + if (error && error.message) { + setError(error.message); + } else if (typeof error === 'string') { + setError(error); + } } }, }) From a788bc89819e66f2d8c16ea944cb29c97826f1f5 Mon Sep 17 00:00:00 2001 From: Bart Akeley Date: Sat, 19 May 2018 18:32:21 -0500 Subject: [PATCH 4/6] versionCode 10 --- android/app/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/android/app/build.gradle b/android/app/build.gradle index 39fd9f3..d2e6b75 100644 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -91,7 +91,7 @@ android { applicationId "com.aretherecookies" minSdkVersion 16 targetSdkVersion 22 - versionCode 10 + versionCode 11 versionName "1.0" ndk { abiFilters "armeabi-v7a", "x86" From 645e0887c536c6c5be5d81f80aae29ec6cc39a6e Mon Sep 17 00:00:00 2001 From: Bart Akeley Date: Sun, 27 May 2018 08:51:40 -0500 Subject: [PATCH 5/6] switch from web to android google api key --- android/app/google-services.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/android/app/google-services.json b/android/app/google-services.json index 73a5596..6697f3b 100644 --- a/android/app/google-services.json +++ b/android/app/google-services.json @@ -1 +1 @@ -{"web":{"client_id":"648700523612-jbif6e356rc13pbcmrmc7gk212eelgcp.apps.googleusercontent.com","project_id":"august-copilot-171122","auth_uri":"https://accounts.google.com/o/oauth2/auth","token_uri":"https://accounts.google.com/o/oauth2/token","auth_provider_x509_cert_url":"https://www.googleapis.com/oauth2/v1/certs","client_secret":"U44sJzIyL0yCreGpJ1v1saNZ"}} \ No newline at end of file +{"web":{"client_id":"648700523612-lm35m5d7m7k0sdutqmatbfhq2qsnd5if.apps.googleusercontent.com","project_id":"august-copilot-171122","auth_uri":"https://accounts.google.com/o/oauth2/auth","token_uri":"https://accounts.google.com/o/oauth2/token","auth_provider_x509_cert_url":"https://www.googleapis.com/oauth2/v1/certs","client_secret":"U44sJzIyL0yCreGpJ1v1saNZ"}} \ No newline at end of file From c4db0f09fcde1bb2944ee952d925b6f0aacc3ada Mon Sep 17 00:00:00 2001 From: Bart Akeley Date: Sun, 27 May 2018 08:52:01 -0500 Subject: [PATCH 6/6] versionCode 12 --- android/app/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/android/app/build.gradle b/android/app/build.gradle index d2e6b75..c9c2f58 100644 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -91,7 +91,7 @@ android { applicationId "com.aretherecookies" minSdkVersion 16 targetSdkVersion 22 - versionCode 11 + versionCode 12 versionName "1.0" ndk { abiFilters "armeabi-v7a", "x86"