implement profiles tab

This commit is contained in:
Bart Akeley 2018-08-19 20:02:43 -05:00
parent 770ce4f71e
commit 0e1f45f638
8 changed files with 209 additions and 55 deletions

View file

@ -6,7 +6,7 @@ import rxjsconfig from 'recompose/rxjsObservableConfig';
import setObservableConfig from 'recompose/setObservableConfig';
import TopToolbar from './components/TopToolbar';
import { NativeRouter, Route, Redirect, AndroidBackButton, Switch } from 'react-router-native';
import List from './pages/List';
import Nav from './pages/Nav';
import FoodItemDetail from './pages/FoodItemDetail';
import PlaceDetail from './pages/PlaceDetail';
import CreateFoodItem from './pages/CreateFoodItem';
@ -33,10 +33,11 @@ export default class App extends Component {
<TopToolbar />
<Switch>
<Route path="/landing" component={LandingPage} />
<Route path="/list/:type" component={List} />
<Route path="/list/:type" component={Nav} />
<Route path="/foodItem/:id" component={FoodItemDetail} />
<Route path="/place/:id" component={PlaceDetail} />
<Route path="/login" component={LoginPage} />
<Route path="/logout" component={LoginPage} />
<Route path="/zipcode" component={ZipcodePage} />
<Route
path="/createFoodItem"

View file

@ -37,12 +37,8 @@ export const getPositionFromZip = async (zip: string) => {
return pos;
};
export const getLocation = async (zipcode: string) => {
if (zipcode) {
AsyncStorage.setItem('zipcode', zipcode);
}
const currentZip = zipcode || (await AsyncStorage.getItem('zipcode'));
export const getLocation = async () => {
const currentZip = await AsyncStorage.getItem('zipcode');
if (currentZip && currentZip !== 'usegps') {
getPositionFromZip(currentZip);

View file

@ -1,5 +1,5 @@
// @flow
import { curry, pipe, pathOr, prop } from 'ramda';
import { curry, pipe, pathOr } from 'ramda';
import queryString from 'query-string';
type RouteTo = {
@ -26,8 +26,3 @@ export const getSearch = pipe(
pathOr('', ['router', 'route', 'location', 'search']),
queryString.parse
);
export const getZipcode = pipe(
getSearch,
prop('zipcode')
);

View file

@ -44,12 +44,7 @@ const LandingPage = ({ skipIfAlreadyChosen, loading }: Props) => {
We need to use your location to bring you the best experience possible.
</Text>
<View style={{ width: 275, marginBottom: 30 }}>
<RouterButton
to="/list/food?positionBy=location"
replace
title="OK, fine"
color={theme.palette.primaryColor}
/>
<RouterButton to="/list/food" replace title="OK, fine" color={theme.palette.primaryColor} />
</View>
<Link to="/zipcode" replace>
<Text style={{ color: theme.palette.primaryColor, marginBottom: 20, fontSize: 16 }}>

View file

@ -13,6 +13,9 @@ type Props = {
error: string,
setError: (err: string) => void,
router: {
location: {
pathname: string,
},
history: {
replace: (url: string) => void,
location: {
@ -20,28 +23,35 @@ type Props = {
},
},
},
deauthUser: () => void,
};
const LoginPageComponent = ({ authUser, error }: Props) => {
return (
<View style={{ flexDirection: 'column', alignItems: 'center' }}>
<Text style={theme.loginPage.titleStyle}>Sign In</Text>
<View style={theme.loginPage.buttonStyle}>
<Button
title="Sign in with Facebook"
onPress={authUser('facebook')}
color={theme.palette.facebook}
/>
const LoginPageComponent = ({ authUser, deauthUser, error, router }: Props) => {
debugger;
if (/logout/.test(router.route.location.pathname)) {
deauthUser();
return null;
} else {
return (
<View style={{ flexDirection: 'column', alignItems: 'center' }}>
<Text style={theme.loginPage.titleStyle}>Sign In</Text>
<View style={theme.loginPage.buttonStyle}>
<Button
title="Sign in with Facebook"
onPress={authUser('facebook')}
color={theme.palette.facebook}
/>
</View>
<View style={theme.loginPage.buttonStyle}>
<Button
title="Sign in with Google"
onPress={authUser('google')}
color={theme.palette.google}
/>
</View>
{error && <Text>Login Failed! {error}</Text>}
</View>
<View style={theme.loginPage.buttonStyle}>
<Button
title="Sign in with Google"
onPress={authUser('google')}
color={theme.palette.google}
/>
</View>
{error && <Text>Login Failed! {error}</Text>}
</View>
);
);
}
};
const LoginPage = compose(
@ -51,7 +61,7 @@ const LoginPage = compose(
authUser: ({ router: { history }, setError }: Props) => (
provider: AUTH_PROVIDER
) => async () => {
const { returnto = '/' } = queryString.parse(history.location.search);
const { returnto = '/list/food' } = queryString.parse(history.location.search);
try {
if (!AuthManager.isLoggedIn()) {
await AuthManager.authenticate(provider);
@ -65,6 +75,11 @@ const LoginPage = compose(
}
}
},
deauthUser: ({ router: { history } }) => () => {
const { returnto = '/list/food' } = queryString.parse(history.location.search);
AuthManager.deauthenticate();
history.replace(returnto);
},
})
)(LoginPageComponent);

View file

@ -4,13 +4,14 @@ import { View } from 'react-native';
import Food from './Food';
import Places from './Places';
import { BottomNavigation } from 'react-native-material-ui';
import { getZipcode } from '../helpers/RouteHelpers';
import { getLocation } from '../apis/PositionApi';
import { compose, pure, withHandlers, withProps, lifecycle } from 'recompose';
import { withRouterContext, type routerContext } from '../enhancers/routeEnhancers';
import { pathOr } from 'ramda';
import uiTheme from '../ui-theme';
import ProfilePage from './ProfilePage';
import AuthManager from '../AuthManager';
type Props = {
activeTab: string,
@ -18,12 +19,13 @@ type Props = {
setRoute: (route: string) => () => void,
};
const List = ({ activeTab, setRoute }: Props) => {
const Nav = ({ activeTab, setRoute }: Props) => {
return (
<View style={uiTheme.listView}>
<View style={{ flex: 1 }}>
{activeTab === 'food' && <Food onRefresh={getLocation} />}
{activeTab === 'places' && <Places onRefresh={getLocation} />}
{activeTab === 'profile' && <ProfilePage isLoggedIn={AuthManager.isLoggedIn()} />}
</View>
<BottomNavigation active={activeTab}>
<BottomNavigation.Action
@ -39,7 +41,12 @@ const List = ({ activeTab, setRoute }: Props) => {
onPress={setRoute('/list/places')}
/>
<BottomNavigation.Action key="favorites" icon="favorite" label="Favorites" />
<BottomNavigation.Action key="profile" icon="person" label="Profile" />
<BottomNavigation.Action
key="profile"
icon="person"
label="Profile"
onPress={setRoute('/list/profile')}
/>
</BottomNavigation>
</View>
);
@ -59,7 +66,7 @@ export default compose(
}),
lifecycle({
componentDidMount() {
getLocation(getZipcode(this.props));
getLocation();
},
})
)(List);
)(Nav);

132
js/pages/ProfilePage.js Normal file
View file

@ -0,0 +1,132 @@
// @flow
import React from 'react';
import { View, Text, Switch, TextInput, AsyncStorage } from 'react-native';
import IconButton from '../components/IconButton';
import { palette } from '../ui-theme';
import { compose, withState, withHandlers, lifecycle } from 'recompose';
import { withRouterContext } from '../enhancers/routeEnhancers';
import AuthManager from '../AuthManager';
import { getLocation } from '../apis/PositionApi';
type Props = {
zipcode: ?string,
loading: boolean,
setLoading: (l: boolean) => void,
setZip: (zip: string | null) => void,
saveZip: (zip: number) => Promise<void>,
error: string,
setError: (e: string) => void,
toggleGPS: (a: boolean) => Promise<void>,
router: {
history: {
replace: (route: string) => void,
},
},
isLoggedIn: boolean,
};
export const ProfilePage = (props: Props) => {
const {
zipcode,
loading,
setZip,
saveZip,
error,
toggleGPS,
router: { history },
isLoggedIn,
} = props;
const usingGPS = zipcode === 'usegps';
return (
<View style={{ flex: 1, flexDirection: 'column', padding: 15 }}>
<Text style={{ fontSize: 32, marginBottom: 20, color: '#555555' }}>Profile</Text>
<View style={{ flexDirection: 'row', justifyContent: 'space-between' }}>
<Text style={{ fontSize: 18 }}>Use My Location</Text>
<Switch value={usingGPS} onValueChange={toggleGPS} />
</View>
{!usingGPS && (
<View style={{ flexDirection: 'row' }}>
<View
style={{
flex: 1,
flexDirection: 'column',
justifyContent: 'flex-end',
borderBottomColor: palette.disabledColor,
borderBottomWidth: 1,
height: 60,
}}>
<TextInput
editable={!loading}
value={zipcode}
placeholder="zipcode"
onChangeText={setZip}
onEndEditing={saveZip}
keyboardType="numeric"
style={{
fontSize: 18,
}}
/>
</View>
</View>
)}
{!!error && <Text style={{ fontSize: 16, color: 'red' }}>{error}</Text>}
<View style={{ marginTop: 20 }}>
{isLoggedIn && (
<IconButton
glyph="exit-to-app"
text="Logout"
onPress={() => history.replace('/logout?returnto=/list/food')}
color={palette.accentColor}
textStyle={{ color: palette.accentColor }}
/>
)}
{!isLoggedIn && (
<IconButton
glyph="exit-to-app"
text="Login"
onPress={() => history.replace('/login?returnto=/list/food')}
color={palette.accentColor}
textStyle={{ color: palette.accentColor }}
/>
)}
</View>
</View>
);
};
export default compose(
withRouterContext,
withState('loading', 'setLoading', false),
withState('zipcode', 'setZip', null),
withState('error', 'setError', ''),
lifecycle({
componentDidMount() {
this.props.setLoading(true);
AsyncStorage.getItem('zipcode').then(zip => {
this.props.setZip(zip);
this.props.setLoading(false);
});
},
}),
withHandlers({
saveZip: (props: Props) => async () => {
props.setError('');
if (!props.zipcode || props.zipcode.length !== 5) {
props.setError('Zipcode must be five digits');
return;
}
props.setLoading(true);
await AsyncStorage.setItem('zipcode', props.zipcode);
props.setLoading(false);
getLocation();
},
toggleGPS: (props: Props) => async (useGPS: boolean) => {
props.setLoading(true);
const zipcode = useGPS ? 'usegps' : '';
props.setZip(zipcode);
await AsyncStorage.setItem('zipcode', 'usegps');
props.setLoading(false);
getLocation();
},
})
)(ProfilePage);

View file

@ -1,18 +1,23 @@
// @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 { View, Text, TextInput, Button, AsyncStorage } from 'react-native';
import { compose, withState, withHandlers } from 'recompose';
import { withRouterContext } from '../enhancers/routeEnhancers';
import theme from '../ui-theme';
type Props = {
zipcode: string,
setZipcode: (zip: string) => void,
router: {
history: {
replace: (route: string) => void,
},
},
saveZip: (z: string) => Promise<void>,
};
const ZipcodePage = ({ zipcode, setZipcode }: Props) => {
const ZipcodePage = ({ zipcode, setZipcode, saveZip }: Props) => {
return (
<View style={{ flex: 1, justifyContent: 'center', padding: 30 }}>
<Text style={{ fontSize: 18 }}>
@ -28,15 +33,23 @@ const ZipcodePage = ({ zipcode, setZipcode }: Props) => {
maxLength={5}
autoFocus
/>
<RouterButton
to={`/list/food?zipcode=${zipcode}`}
<Button
onPress={saveZip}
title="Save"
color={theme.palette.primaryColor}
disabled={!(zipcode && zipcode.length === 5)}
replace
/>
</View>
);
};
export default compose(withState('zipcode', 'setZipcode', ''))(ZipcodePage);
export default compose(
withState('zipcode', 'setZipcode', ''),
withRouterContext,
withHandlers({
saveZip: (props: Props) => async () => {
await AsyncStorage.setItem('zipcode', props.zipcode);
props.router.history.replace('/list/food');
},
})
)(ZipcodePage);