mirror of
https://gitlab.com/wheres-the-tp/ui-mobile.git
synced 2026-01-25 06:14:55 -06:00
rename food to product
This commit is contained in:
parent
a11150e960
commit
96e24881fc
46 changed files with 622 additions and 646 deletions
|
|
@ -8,9 +8,9 @@ import { StatusBar } from 'react-native';
|
|||
import { ThemeContext, getTheme } from 'react-native-material-ui';
|
||||
import { NativeRouter, Route, Redirect, AndroidBackButton, Switch } from 'react-router-native';
|
||||
import Nav from './pages/Nav';
|
||||
import FoodItemDetail from './pages/FoodItemDetail';
|
||||
import ProductDetail from './pages/ProductDetail';
|
||||
import PlaceDetail from './pages/PlaceDetail';
|
||||
import CreateFoodItem from './pages/CreateFoodItem';
|
||||
import CreateProduct from './pages/CreateProduct';
|
||||
import LoginPage from './pages/LoginPage';
|
||||
import LandingPage from './pages/LandingPage';
|
||||
import ZipcodePage from './pages/ZipcodePage';
|
||||
|
|
@ -34,12 +34,12 @@ export default class App extends Component {
|
|||
<Switch>
|
||||
<Route path="/landing" component={LandingPage} />
|
||||
<Route path="/list/:type" component={Nav} />
|
||||
<Route path="/foodItem/:id" component={FoodItemDetail} />
|
||||
<Route path="/product/:id" component={ProductDetail} />
|
||||
<Route path="/place/:id" component={PlaceDetail} />
|
||||
<Route path="/login" component={LoginPage} />
|
||||
<Route path="/logout" component={LoginPage} />
|
||||
<Route path="/zipcode" component={ZipcodePage} />
|
||||
<Route path="/createFoodItem" component={CreateFoodItem} />
|
||||
<Route path="/createProduct" component={CreateProduct} />
|
||||
</Switch>
|
||||
</AppContainer>
|
||||
</ThemeContext.Provider>
|
||||
|
|
|
|||
|
|
@ -21,24 +21,24 @@ export const fetchFaves = withLoggedInEmail(async email => {
|
|||
});
|
||||
});
|
||||
|
||||
export const putFaves = withLoggedInEmail(async (email, foodItemIds) => {
|
||||
export const putFaves = withLoggedInEmail(async (email, productIds) => {
|
||||
return fetchRequest({
|
||||
endpoint: '/fave',
|
||||
method: 'PUT',
|
||||
body: {
|
||||
email,
|
||||
foodItemIds,
|
||||
productIds,
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
export const deleteFaves = withLoggedInEmail(async (email, foodItemIds) => {
|
||||
export const deleteFaves = withLoggedInEmail(async (email, productIds) => {
|
||||
return fetchRequest({
|
||||
endpoint: '/fave',
|
||||
method: 'DELETE',
|
||||
body: {
|
||||
email,
|
||||
foodItemIds,
|
||||
productIds,
|
||||
},
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -2,19 +2,19 @@
|
|||
import type { ImageRaw } from '../records/ImageRecord';
|
||||
import { fetchRequest, fetchRequestBinary } from './FetchApi';
|
||||
|
||||
export const getImages = (foodItemId: string): Promise<Array<ImageRaw>> => {
|
||||
export const getImages = (productId: string): Promise<Array<ImageRaw>> => {
|
||||
return fetchRequest({
|
||||
endpoint: `/images/${foodItemId}`,
|
||||
endpoint: `/images/${productId}`,
|
||||
method: 'GET',
|
||||
});
|
||||
};
|
||||
|
||||
export const addImage = async ({
|
||||
foodItemId,
|
||||
productId,
|
||||
username,
|
||||
imageUri,
|
||||
}: {
|
||||
foodItemId: string,
|
||||
productId: string,
|
||||
username: string,
|
||||
imageUri: string,
|
||||
}) => {
|
||||
|
|
@ -30,7 +30,7 @@ export const addImage = async ({
|
|||
body.append('username', username);
|
||||
|
||||
const res = await fetchRequestBinary({
|
||||
endpoint: `/images/${foodItemId}`,
|
||||
endpoint: `/images/${productId}`,
|
||||
body,
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -1,16 +1,15 @@
|
|||
import { memoizeWith, pathOr, map, nth } from 'ramda';
|
||||
import FilterRecord from '../records/FilterRecord';
|
||||
import FoodItemRecord from '../records/FoodItemRecord';
|
||||
import { pathOr, map, nth } from 'ramda';
|
||||
import ProductRecord from '../records/ProductRecord';
|
||||
import AuthManager from '../AuthManager';
|
||||
import { addImage } from './ImagesApi';
|
||||
import { fetchRequest } from './FetchApi';
|
||||
import debounce from '../helpers/debounce';
|
||||
|
||||
export type FoodItemsFilter = {
|
||||
export type ProductsFilter = {
|
||||
radius?: number,
|
||||
};
|
||||
|
||||
export type RawFoodItem = {
|
||||
export type RawProduct = {
|
||||
id: string,
|
||||
name: string,
|
||||
placeid: string,
|
||||
|
|
@ -23,30 +22,30 @@ export type RawFoodItem = {
|
|||
lastupdated: number,
|
||||
};
|
||||
|
||||
export type FoodItemsForLocation = {
|
||||
export type ProductsForLocation = {
|
||||
orderby: string,
|
||||
filter: FoodItemsFilter,
|
||||
fooditems: ?Array<RawFoodItem>,
|
||||
filter: ProductsFilter,
|
||||
products: ?Array<RawProduct>,
|
||||
};
|
||||
|
||||
export const getFoodItems = debounce(async ({ filter }) => {
|
||||
export const getProducts = debounce(async ({ filter }) => {
|
||||
const { search } = filter;
|
||||
|
||||
if (!search) {
|
||||
return {
|
||||
fooditems: [],
|
||||
products: [],
|
||||
loading: false,
|
||||
error: null,
|
||||
};
|
||||
}
|
||||
|
||||
try {
|
||||
const { fooditems } = await fetchRequest({
|
||||
endpoint: `/fooditems/${encodeURIComponent(search)}`,
|
||||
const { products } = await fetchRequest({
|
||||
endpoint: `/products/${encodeURIComponent(search)}`,
|
||||
});
|
||||
|
||||
return {
|
||||
fooditems,
|
||||
products,
|
||||
loading: false,
|
||||
error: null,
|
||||
};
|
||||
|
|
@ -55,14 +54,14 @@ export const getFoodItems = debounce(async ({ filter }) => {
|
|||
return {
|
||||
orderby: 'distance',
|
||||
filter: {},
|
||||
fooditems: [],
|
||||
products: [],
|
||||
loading: false,
|
||||
error: error,
|
||||
};
|
||||
}
|
||||
}, 500);
|
||||
|
||||
export const createFoodItem = async (foodItem: FoodItemRecord) => {
|
||||
export const createProduct = async (product: ProductRecord) => {
|
||||
if (!AuthManager.user) {
|
||||
throw new Error('You must be logged in to create food items');
|
||||
}
|
||||
|
|
@ -70,14 +69,14 @@ export const createFoodItem = async (foodItem: FoodItemRecord) => {
|
|||
const username = AuthManager.user.name;
|
||||
|
||||
const res = await fetchRequest({
|
||||
endpoint: '/addfooditem',
|
||||
endpoint: '/addproduct',
|
||||
method: 'POST',
|
||||
body: foodItem,
|
||||
body: product,
|
||||
});
|
||||
|
||||
const addImageUri = (imageUri: string) => addImage({ foodItemId: res.id, imageUri, username });
|
||||
const addImageUri = (imageUri: string) => addImage({ productId: res.id, imageUri, username });
|
||||
|
||||
const images = await Promise.all(map(addImageUri, foodItem.images.toArray()));
|
||||
const images = await Promise.all(map(addImageUri, product.images.toArray()));
|
||||
|
||||
const thumbimage = pathOr('', ['url'], nth(0, images));
|
||||
|
||||
|
|
@ -4,17 +4,17 @@ import type { QuantityResponse } from '../constants/QuantityConstants';
|
|||
import { fetchRequest } from './FetchApi';
|
||||
|
||||
export const setQuantity = ({
|
||||
foodItemId,
|
||||
productId,
|
||||
quantity,
|
||||
}: {
|
||||
foodItemId: string,
|
||||
productId: string,
|
||||
quantity: Quantity,
|
||||
}): Promise<QuantityResponse> => {
|
||||
return fetchRequest({
|
||||
method: 'POST',
|
||||
endpoint: '/quantity',
|
||||
body: {
|
||||
foodItemId,
|
||||
productId,
|
||||
quantity,
|
||||
},
|
||||
});
|
||||
|
|
|
|||
|
|
@ -1,56 +0,0 @@
|
|||
import React from 'react';
|
||||
import { View } from 'react-native';
|
||||
import { pure } from 'recompose';
|
||||
import FoodItemRecord from '../records/FoodItemRecord';
|
||||
import typeof PlaceRecord from '../records/PlaceRecord';
|
||||
import theme from '../ui-theme';
|
||||
import { Link } from 'react-router-native';
|
||||
import { routeWithTitle } from '../helpers/RouteHelpers';
|
||||
import { TileBox, StrongText, SubText, Thumbnail, QuantityLine } from './ItemTile';
|
||||
import { withPlace } from '../enhancers/placeEnhancers';
|
||||
|
||||
const PlaceNameAndDistance = withPlace(
|
||||
({ place, distance = 999.9 }: { place: ?PlaceRecord, distance: number }) => {
|
||||
return (
|
||||
<SubText>{`${(place && place.name) || 'Loading...'} - ${parseFloat(distance).toFixed(
|
||||
1
|
||||
)} mi`}</SubText>
|
||||
);
|
||||
}
|
||||
);
|
||||
|
||||
const EmptyFoodItemTile = () => {
|
||||
return (
|
||||
<View>
|
||||
<TileBox>
|
||||
<Thumbnail thumb={null} />
|
||||
<View>
|
||||
<StrongText>Loading...</StrongText>
|
||||
</View>
|
||||
</TileBox>
|
||||
</View>
|
||||
);
|
||||
};
|
||||
|
||||
export default pure(({ foodItem }: { foodItem: FoodItemRecord }) => {
|
||||
if (!foodItem) {
|
||||
return <EmptyFoodItemTile />;
|
||||
}
|
||||
|
||||
return (
|
||||
<Link
|
||||
to={routeWithTitle(`/foodItem/${foodItem.id || ''}`, foodItem.name)}
|
||||
underlayColor={theme.itemTile.pressHighlightColor}>
|
||||
<View>
|
||||
<TileBox>
|
||||
<Thumbnail thumb={foodItem.thumbImage} />
|
||||
<View>
|
||||
<StrongText>{foodItem.name || ''}</StrongText>
|
||||
<PlaceNameAndDistance placeId={foodItem.placeId} distance={foodItem.distance} />
|
||||
<QuantityLine quantity={foodItem.quantity} lastupdated={foodItem.lastupdated} />
|
||||
</View>
|
||||
</TileBox>
|
||||
</View>
|
||||
</Link>
|
||||
);
|
||||
});
|
||||
|
|
@ -1,7 +1,7 @@
|
|||
import { View } from 'react-native';
|
||||
import React from 'react';
|
||||
import { pure } from 'recompose';
|
||||
import FoodItemRecord from '../records/FoodItemRecord';
|
||||
import ProductRecord from '../records/ProductRecord';
|
||||
import typeof PlaceRecord from '../records/PlaceRecord';
|
||||
import { Link } from 'react-router-native';
|
||||
import { TileBox, Thumbnail, StrongText, SubText } from './ItemTile';
|
||||
|
|
@ -13,8 +13,8 @@ import { type Map } from 'immutable';
|
|||
|
||||
const getHoursText = (place: PlaceRecord) => (place.openNow ? 'Open now' : 'Closed');
|
||||
|
||||
// const getCategoriesText = (foodItems: Map<string, FoodItemRecord>) => {
|
||||
// const categories = getCategories(foodItems);
|
||||
// const getCategoriesText = (products: Map<string, ProductRecord>) => {
|
||||
// const categories = getCategories(products);
|
||||
// if (categories.size < 1) {
|
||||
// return 'Nothing listed yet';
|
||||
// }
|
||||
|
|
@ -25,7 +25,7 @@ import theme from '../ui-theme';
|
|||
|
||||
type PlaceTileProps = {
|
||||
place: PlaceRecord,
|
||||
foodItems: Map<string, FoodItemRecord>,
|
||||
products: Map<string, ProductRecord>,
|
||||
};
|
||||
|
||||
export default pure(({ place }: PlaceTileProps) => {
|
||||
|
|
@ -38,7 +38,7 @@ export default pure(({ place }: PlaceTileProps) => {
|
|||
<Thumbnail thumb={place.thumb} />
|
||||
<View>
|
||||
<StrongText>{place.name}</StrongText>
|
||||
{/* <SubText>{getCategoriesText(foodItems)}</SubText> */}
|
||||
{/* <SubText>{getCategoriesText(products)}</SubText> */}
|
||||
<SubText>
|
||||
{getHoursText(place)} - {parseFloat(place.distance).toFixed(1)} mi
|
||||
</SubText>
|
||||
|
|
|
|||
|
|
@ -1,20 +1,20 @@
|
|||
// @flow
|
||||
import { withCreateFoodItemState } from '../enhancers/createFoodItemEnhancers';
|
||||
import { withCreateProductState } from '../enhancers/createProductEnhancers';
|
||||
import { withReplaceRoute } from '../enhancers/routeEnhancers';
|
||||
import { compose, onlyUpdateForKeys, withHandlers } from 'recompose';
|
||||
import { routeWithTitle } from '../helpers/RouteHelpers';
|
||||
import TopSaveButton from './TopSaveButton';
|
||||
|
||||
export default compose(
|
||||
withCreateFoodItemState,
|
||||
withCreateProductState,
|
||||
withReplaceRoute,
|
||||
withHandlers({
|
||||
onSave: ({ saveFoodItem, setLoading, setError, replaceRoute }) => async () => {
|
||||
onSave: ({ saveProduct, setLoading, setError, replaceRoute }) => async () => {
|
||||
setError(null);
|
||||
setLoading(true);
|
||||
try {
|
||||
const { id, name } = await saveFoodItem();
|
||||
replaceRoute(routeWithTitle(`/foodItem/${id || ''}`, name));
|
||||
const { id, name } = await saveProduct();
|
||||
replaceRoute(routeWithTitle(`/product/${id || ''}`, name));
|
||||
} catch (error) {
|
||||
setError(error);
|
||||
} finally {
|
||||
54
js/components/ProductTile.js
Normal file
54
js/components/ProductTile.js
Normal file
|
|
@ -0,0 +1,54 @@
|
|||
import React from 'react';
|
||||
import { View } from 'react-native';
|
||||
import { pure } from 'recompose';
|
||||
import ProductRecord from '../records/ProductRecord';
|
||||
import typeof PlaceRecord from '../records/PlaceRecord';
|
||||
import theme from '../ui-theme';
|
||||
import { Link } from 'react-router-native';
|
||||
import { routeWithTitle } from '../helpers/RouteHelpers';
|
||||
import { TileBox, StrongText, SubText, Thumbnail, QuantityLine } from './ItemTile';
|
||||
import { withPlace } from '../enhancers/placeEnhancers';
|
||||
|
||||
const PlaceNameAndDistance = withPlace(({ place }) => {
|
||||
return (
|
||||
<SubText>{`${(place && place.name) || 'Loading...'} - ${parseFloat(place.distance).toFixed(
|
||||
1
|
||||
)} mi`}</SubText>
|
||||
);
|
||||
});
|
||||
|
||||
const EmptyProductTile = () => {
|
||||
return (
|
||||
<View>
|
||||
<TileBox>
|
||||
<Thumbnail thumb={null} />
|
||||
<View>
|
||||
<StrongText>Loading...</StrongText>
|
||||
</View>
|
||||
</TileBox>
|
||||
</View>
|
||||
);
|
||||
};
|
||||
|
||||
export default pure(({ product }: { product: ProductRecord }) => {
|
||||
if (!product) {
|
||||
return <EmptyProductTile />;
|
||||
}
|
||||
|
||||
return (
|
||||
<Link
|
||||
to={routeWithTitle(`/product/${product.id || ''}`, product.name)}
|
||||
underlayColor={theme.itemTile.pressHighlightColor}>
|
||||
<View>
|
||||
<TileBox>
|
||||
<Thumbnail thumb={product.thumbImage} />
|
||||
<View>
|
||||
<StrongText>{product.name || ''}</StrongText>
|
||||
<PlaceNameAndDistance placeId={product.placeId} placeType={product.placeType} />
|
||||
<QuantityLine quantity={product.quantity} lastupdated={product.lastupdated} />
|
||||
</View>
|
||||
</TileBox>
|
||||
</View>
|
||||
</Link>
|
||||
);
|
||||
});
|
||||
|
|
@ -2,7 +2,7 @@
|
|||
import React, { Component } from 'react';
|
||||
import { View } from 'react-native';
|
||||
import { type SetSeq } from 'immutable';
|
||||
import FoodItemRecord from '../records/FoodItemRecord';
|
||||
import ProductRecord from '../records/ProductRecord';
|
||||
import { compose, pure } from 'recompose';
|
||||
import { pipe } from 'ramda';
|
||||
import { withFilter } from '../enhancers/filterEnhancers';
|
||||
|
|
@ -14,20 +14,20 @@ type homomorph<T> = T => T;
|
|||
|
||||
type Props = {
|
||||
filter: FilterRecord,
|
||||
foodItemsSeq: SetSeq<FoodItemRecord>,
|
||||
children: (foodItem: FoodItemRecord) => Component<*, *, *>,
|
||||
productsSeq: SetSeq<ProductRecord>,
|
||||
children: (product: ProductRecord) => Component<*, *, *>,
|
||||
};
|
||||
|
||||
const sortByDistance = (foodItemsSeq: SetSeq<FoodItemRecord>): SetSeq<FoodItemRecord> => {
|
||||
return foodItemsSeq.sort((left, right) => left.distance - right.distance);
|
||||
const sortByDistance = (productsSeq: SetSeq<ProductRecord>): SetSeq<ProductRecord> => {
|
||||
return productsSeq.sort((left, right) => left.distance - right.distance);
|
||||
};
|
||||
|
||||
const sortByLastUpdated = (foodItemsSeq: SetSeq<FoodItemRecord>): SetSeq<FoodItemRecord> => {
|
||||
return foodItemsSeq.sort((left, right) => right.lastupdated - left.lastupdated);
|
||||
const sortByLastUpdated = (productsSeq: SetSeq<ProductRecord>): SetSeq<ProductRecord> => {
|
||||
return productsSeq.sort((left, right) => right.lastupdated - left.lastupdated);
|
||||
};
|
||||
|
||||
const sortByQuantity = (foodItemsSeq: SetSeq<FoodItemRecord>): SetSeq<FoodItemRecord> => {
|
||||
return foodItemsSeq.sort((left, right) => {
|
||||
const sortByQuantity = (productsSeq: SetSeq<ProductRecord>): SetSeq<ProductRecord> => {
|
||||
return productsSeq.sort((left, right) => {
|
||||
const quantityCompare = compareQuantity(left.quantity, right.quantity);
|
||||
return quantityCompare === 0 ? left.distance - right.distance : quantityCompare;
|
||||
});
|
||||
|
|
@ -44,20 +44,20 @@ const getSortBy = (filter: FilterRecord): homomorph<*> => {
|
|||
}
|
||||
};
|
||||
|
||||
const intoArray = (foodItemsSeq: SetSeq<FoodItemRecord>): Array<FoodItemRecord> =>
|
||||
foodItemsSeq ? foodItemsSeq.toArray() : [];
|
||||
const intoArray = (productsSeq: SetSeq<ProductRecord>): Array<ProductRecord> =>
|
||||
productsSeq ? productsSeq.toArray() : [];
|
||||
|
||||
const FoodItemList = (props: Props) => {
|
||||
const { filter, foodItemsSeq, children } = props;
|
||||
const ProductList = (props: Props) => {
|
||||
const { filter, productsSeq, children } = props;
|
||||
|
||||
if (!foodItemsSeq) {
|
||||
if (!productsSeq) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const items = pipe(
|
||||
getSortBy(filter),
|
||||
intoArray
|
||||
)(foodItemsSeq);
|
||||
)(productsSeq);
|
||||
|
||||
return <View style={{ flexShrink: 2 }}>{items.map(children)}</View>;
|
||||
};
|
||||
|
|
@ -65,4 +65,4 @@ const FoodItemList = (props: Props) => {
|
|||
export default compose(
|
||||
pure,
|
||||
withFilter
|
||||
)(FoodItemList);
|
||||
)(ProductList);
|
||||
|
|
@ -5,7 +5,7 @@ import { Toolbar, Icon } from 'react-native-material-ui';
|
|||
import { path, cond, test } from 'ramda';
|
||||
import queryString from 'query-string';
|
||||
import { getSearch, getViewMode } from '../helpers/RouteHelpers';
|
||||
import FoodItemSaveBtn from '../components/FoodItemSaveBtn';
|
||||
import ProductSaveBtn from '../components/ProductSaveBtn';
|
||||
import { withFilter } from '../enhancers/filterEnhancers';
|
||||
import FilterRecord from '../records/FilterRecord';
|
||||
import { palette } from '../ui-theme';
|
||||
|
|
@ -56,7 +56,7 @@ class TopToolbar extends Component {
|
|||
setFilter(filter.set('search', ''));
|
||||
};
|
||||
|
||||
onChangeText = (search) => {
|
||||
onChangeText = search => {
|
||||
const { setFilter, filter } = this.props;
|
||||
setFilter(filter.set('search', search));
|
||||
};
|
||||
|
|
@ -64,8 +64,8 @@ class TopToolbar extends Component {
|
|||
getRightElement = () => {
|
||||
const route = path(['router', 'route', 'location', 'pathname'], this.props);
|
||||
|
||||
if (/^\/createFoodItem/.test(route)) {
|
||||
return <FoodItemSaveBtn />;
|
||||
if (/^\/createProduct/.test(route)) {
|
||||
return <ProductSaveBtn />;
|
||||
}
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -1,34 +1,34 @@
|
|||
import mapPropsStream from 'recompose/mapPropsStream';
|
||||
import CreateFoodItem$, { emitter as emitCreateItemState } from '../streams/CreateFoodItemStream';
|
||||
import { emitter as emitFoodItemsState } from '../streams/FoodItemsStream';
|
||||
import { createFoodItem } from '../apis/FoodItemsApi';
|
||||
import FoodItemRecord, { createFoodItem as buildFoodItem } from '../records/FoodItemRecord';
|
||||
import CreateProduct$, { emitter as emitCreateItemState } from '../streams/CreateProductStream';
|
||||
import { emitter as emitProductsState } from '../streams/ProductsStream';
|
||||
import { createProduct } from '../apis/ProductsApi';
|
||||
import ProductRecord, { createProduct as buildProduct } from '../records/ProductRecord';
|
||||
import Snackbar from 'react-native-snackbar';
|
||||
|
||||
export const withCreateFoodItemState = mapPropsStream((props$) => {
|
||||
return props$.combineLatest(CreateFoodItem$, (props, state) => {
|
||||
const { foodItem, loading, error } = state;
|
||||
export const withCreateProductState = mapPropsStream(props$ => {
|
||||
return props$.combineLatest(CreateProduct$, (props, state) => {
|
||||
const { product, loading, error } = state;
|
||||
|
||||
const setFoodItem = (foodItem: FoodItemRecord) => emitCreateItemState({ ...state, foodItem });
|
||||
const setProduct = (product: ProductRecord) => emitCreateItemState({ ...state, product });
|
||||
const setLoading = (loading: boolean) => emitCreateItemState({ ...state, loading });
|
||||
const setError = (error: Error) => emitCreateItemState({ ...state, error });
|
||||
|
||||
const saveFoodItem = async () => {
|
||||
const saveProduct = async () => {
|
||||
try {
|
||||
// insert new item into db and cast it into FoodItemRecord
|
||||
const newItem = buildFoodItem(await createFoodItem(foodItem));
|
||||
// insert new item into db and cast it into ProductRecord
|
||||
const newItem = buildProduct(await createProduct(product));
|
||||
|
||||
Snackbar.show({
|
||||
title: foodItem.name + ' added',
|
||||
title: product.name + ' added',
|
||||
backgroundColor: 'black',
|
||||
color: 'white',
|
||||
});
|
||||
|
||||
// notify food items state of new item
|
||||
emitFoodItemsState(newItem);
|
||||
emitProductsState(newItem);
|
||||
|
||||
// clear the create item form to default empty record
|
||||
setFoodItem(new FoodItemRecord());
|
||||
setProduct(new ProductRecord());
|
||||
|
||||
// allow caller to see what was created
|
||||
return newItem;
|
||||
|
|
@ -48,11 +48,11 @@ export const withCreateFoodItemState = mapPropsStream((props$) => {
|
|||
|
||||
return {
|
||||
...props,
|
||||
foodItem,
|
||||
product,
|
||||
loading,
|
||||
error,
|
||||
setFoodItem,
|
||||
saveFoodItem,
|
||||
setProduct,
|
||||
saveProduct,
|
||||
setLoading,
|
||||
setError,
|
||||
};
|
||||
|
|
@ -9,9 +9,9 @@ const fetchAndEmitFaves = async () => {
|
|||
emitFaves(faves);
|
||||
};
|
||||
|
||||
const buildTempFave = foodItemId =>
|
||||
const buildTempFave = productId =>
|
||||
fromJS({
|
||||
food_item_id: foodItemId,
|
||||
food_item_id: productId,
|
||||
date: Date.now(),
|
||||
});
|
||||
|
||||
|
|
@ -27,19 +27,19 @@ export const withFaves = mapPropsStream(props$ =>
|
|||
console.log(err); // eslint-disable-line no-console
|
||||
}
|
||||
},
|
||||
addFave: async foodItemId => {
|
||||
addFave: async productId => {
|
||||
try {
|
||||
await putFaves([foodItemId]);
|
||||
emitFave(buildTempFave(foodItemId));
|
||||
await putFaves([productId]);
|
||||
emitFave(buildTempFave(productId));
|
||||
} catch (err) {
|
||||
console.log(err); // eslint-disable-line no-console
|
||||
}
|
||||
},
|
||||
deleteFave: async foodItemId => {
|
||||
deleteFave: async productId => {
|
||||
try {
|
||||
await deleteFaves([foodItemId]);
|
||||
await deleteFaves([productId]);
|
||||
|
||||
const idx = faves.findIndex(fave => get(fave, 'id') === foodItemId);
|
||||
const idx = faves.findIndex(fave => get(fave, 'id') === productId);
|
||||
if (idx >= 0) {
|
||||
emitFaves(faves.delete(idx));
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,87 +0,0 @@
|
|||
// @flow
|
||||
import withProps from 'recompose/withProps';
|
||||
import compose from 'recompose/compose';
|
||||
import mapPropsStream from 'recompose/mapPropsStream';
|
||||
import FoodItems$ from '../streams/FoodItemsStream';
|
||||
import typeof FoodItemRecord from '../records/FoodItemRecord';
|
||||
import { Map, Set } from 'immutable';
|
||||
import { pipe, path, toLower, equals } from 'ramda';
|
||||
|
||||
type FindPredicate<A> = (left: A) => (right: A) => Boolean;
|
||||
|
||||
const getName: (f: FoodItemRecord) => String = pipe(path(['name']), toLower);
|
||||
|
||||
const matchesName: FindPredicate<FoodItemRecord> = left => right => {
|
||||
return equals(getName(left), getName(right));
|
||||
};
|
||||
|
||||
const addIfNotExisting = (predicate: FindPredicate<FoodItemRecord>) => (
|
||||
set: Set<FoodItemRecord>,
|
||||
item: FoodItemRecord
|
||||
) => {
|
||||
return !set.find(predicate(item)) ? set.add(item) : set;
|
||||
};
|
||||
|
||||
const intoSet = reducer => items => {
|
||||
return items ? items.reduce(reducer, new Set()) : new Set();
|
||||
};
|
||||
|
||||
export const withFoodItems = mapPropsStream(props$ =>
|
||||
props$.combineLatest(FoodItems$, (props, foodItems) => {
|
||||
return {
|
||||
...props,
|
||||
foodItemsMap: foodItems,
|
||||
};
|
||||
})
|
||||
);
|
||||
|
||||
export const withFoodItemsAsSeq = mapPropsStream(props$ =>
|
||||
props$.combineLatest(FoodItems$, (props, foodItems) => {
|
||||
return {
|
||||
...props,
|
||||
foodItemsSeq: foodItems && foodItems.valueSeq(),
|
||||
};
|
||||
})
|
||||
);
|
||||
|
||||
export const withFoodItemIdFromRoute = withProps((props: { match: { params: { id: string } } }) => {
|
||||
const id: string = path(['match', 'params', 'id'], props) || '';
|
||||
return { foodItemId: id };
|
||||
});
|
||||
|
||||
export const withFoodItem = compose(
|
||||
withFoodItems,
|
||||
withFoodItemIdFromRoute,
|
||||
withProps((props: { foodItemsMap: ?Map<number, FoodItemRecord>, foodItemId: number }) => {
|
||||
const { foodItemsMap, foodItemId } = props;
|
||||
return { foodItem: foodItemsMap && foodItemsMap.get(foodItemId) };
|
||||
})
|
||||
);
|
||||
|
||||
export const withFoodItemPlaceId = withProps((props: { foodItem: FoodItemRecord }) => {
|
||||
const { foodItem } = props;
|
||||
return {
|
||||
placeId: foodItem && foodItem.placeId,
|
||||
};
|
||||
});
|
||||
|
||||
export const withFoodItemsGroupedByPlace = compose(
|
||||
withFoodItems,
|
||||
withProps((props: { foodItemsMap: ?Map<number, FoodItemRecord> }) => {
|
||||
if (!props.foodItemsMap) {
|
||||
return {};
|
||||
}
|
||||
return {
|
||||
foodItemsByPlace: props.foodItemsMap.groupBy(foodItem => foodItem.placeId),
|
||||
};
|
||||
})
|
||||
);
|
||||
|
||||
export const withUniqueFoodItems = compose(
|
||||
withFoodItemsAsSeq,
|
||||
withProps(({ foodItemsSeq }) => {
|
||||
return {
|
||||
foodItemsSeq: intoSet(addIfNotExisting(matchesName))(foodItemsSeq),
|
||||
};
|
||||
})
|
||||
);
|
||||
|
|
@ -1,39 +1,39 @@
|
|||
// @flow
|
||||
import { withProps } from "recompose";
|
||||
import { emit } from "../streams/ImagesStream";
|
||||
import { addImage, getImages } from "../apis/ImagesApi";
|
||||
import AuthManager from "../AuthManager";
|
||||
import { map } from "ramda";
|
||||
import { withProps } from 'recompose';
|
||||
import { emit } from '../streams/ImagesStream';
|
||||
import { addImage, getImages } from '../apis/ImagesApi';
|
||||
import AuthManager from '../AuthManager';
|
||||
import { map } from 'ramda';
|
||||
|
||||
export const withImages = withProps({
|
||||
addImage: async ({ foodItemId, imageUri }) => {
|
||||
addImage: async ({ productId, imageUri }) => {
|
||||
try {
|
||||
if (!AuthManager.user) {
|
||||
throw new Error("You need to be logged in to add images");
|
||||
throw new Error('You need to be logged in to add images');
|
||||
}
|
||||
|
||||
const username = AuthManager.user.name;
|
||||
|
||||
const { url, date } = await addImage({ foodItemId, username, imageUri });
|
||||
const { url, date } = await addImage({ productId, username, imageUri });
|
||||
|
||||
emit({
|
||||
url,
|
||||
username,
|
||||
date,
|
||||
food_item_id: foodItemId
|
||||
food_item_id: productId,
|
||||
});
|
||||
} catch (error) {
|
||||
// TODO error handling in the UI
|
||||
console.error(error); // eslint-disable-line
|
||||
}
|
||||
},
|
||||
getImages: async ({ foodItemId }) => {
|
||||
getImages: async ({ productId }) => {
|
||||
try {
|
||||
const images = await getImages(foodItemId);
|
||||
const images = await getImages(productId);
|
||||
map(emit, images);
|
||||
} catch (error) {
|
||||
// TODO error handling in the UI
|
||||
console.error(error); // eslint-disable-line
|
||||
}
|
||||
}
|
||||
},
|
||||
});
|
||||
|
|
|
|||
|
|
@ -1,24 +1,16 @@
|
|||
// @flow
|
||||
import withProps from 'recompose/withProps';
|
||||
import mapPropsStream from 'recompose/mapPropsStream';
|
||||
import compose from 'recompose/compose';
|
||||
import Places$, { emitter as emitPlace } from '../streams/PlacesStream';
|
||||
import FoodItems$ from '../streams/FoodItemsStream';
|
||||
import Products$ from '../streams/ProductsStream';
|
||||
import { path } from 'ramda';
|
||||
import { List } from 'immutable';
|
||||
import { List, getIn } from 'immutable';
|
||||
import { buildPlaceRecord } from '../records/PlaceRecord';
|
||||
import { getPlaceDetails } from '../apis/GooglePlacesApi';
|
||||
import { getSearch } from '../helpers/RouteHelpers';
|
||||
import { withRouterContext } from './routeEnhancers';
|
||||
import { find } from 'rxjs/operator/find';
|
||||
|
||||
export const fetchPlaceDetails = async ({
|
||||
distance,
|
||||
placeId,
|
||||
}: {
|
||||
distance: number,
|
||||
placeId: ?string,
|
||||
}) => {
|
||||
export const fetchPlaceDetails = async ({ distance, placeId }) => {
|
||||
const place = await getPlaceDetails({
|
||||
distance,
|
||||
placeId,
|
||||
|
|
@ -41,7 +33,6 @@ export const withPlaceIdFromRoute = withProps((props: { match: { params: { id: s
|
|||
});
|
||||
|
||||
export const withPlaceId = compose(
|
||||
withPlaces,
|
||||
withRouterContext,
|
||||
withProps(props => {
|
||||
const placeId = props.placeId || getSearch(props).placeId;
|
||||
|
|
@ -59,40 +50,34 @@ export const withPlaceActions = withProps(() => {
|
|||
export const withPlace = compose(
|
||||
withPlaceId,
|
||||
withPlaces,
|
||||
withProps(({ placeId, places }) => {
|
||||
if (!placeId) {
|
||||
return {
|
||||
place: null,
|
||||
fetchPlaceDetails,
|
||||
};
|
||||
}
|
||||
return {
|
||||
place: places && places.get(placeId),
|
||||
fetchPlaceDetails,
|
||||
};
|
||||
withProps(({ placeId, places, placeType }) => {
|
||||
const place =
|
||||
placeId && placeType && getIn(places, [placeType], []).find(place => place.id === placeId);
|
||||
|
||||
return { place, fetchPlaceDetails };
|
||||
})
|
||||
);
|
||||
|
||||
export const withPlaceForFoodItem = compose(
|
||||
export const withPlaceForProduct = compose(
|
||||
withPlaces,
|
||||
withProps(({ places, foodItem }) => {
|
||||
if (!foodItem || !foodItem.placeType) {
|
||||
withProps(({ places, product }) => {
|
||||
if (!product || !product.placeType) {
|
||||
return { place: null };
|
||||
}
|
||||
const place = places.find(place => place.placeType === foodItem.placeType);
|
||||
const place = getIn(places, [product.placeType, 0]);
|
||||
return { place };
|
||||
})
|
||||
);
|
||||
|
||||
export const withFoodItemsForPlace = mapPropsStream(props$ =>
|
||||
props$.combineLatest(FoodItems$, (props, foodItemsMap) => {
|
||||
export const withProductsForPlace = mapPropsStream(props$ =>
|
||||
props$.combineLatest(Products$, (props, productsMap) => {
|
||||
const placeId = props.placeId;
|
||||
const foodItems = foodItemsMap
|
||||
? foodItemsMap.toList().filter(foodItem => placeId === foodItem.placeId)
|
||||
const products = productsMap
|
||||
? productsMap.toList().filter(product => placeId === product.placeId)
|
||||
: List();
|
||||
return {
|
||||
...props,
|
||||
foodItems,
|
||||
products,
|
||||
};
|
||||
})
|
||||
);
|
||||
|
|
|
|||
90
js/enhancers/productsEnhancers.js
Normal file
90
js/enhancers/productsEnhancers.js
Normal file
|
|
@ -0,0 +1,90 @@
|
|||
// @flow
|
||||
import withProps from 'recompose/withProps';
|
||||
import compose from 'recompose/compose';
|
||||
import mapPropsStream from 'recompose/mapPropsStream';
|
||||
import Products$ from '../streams/ProductsStream';
|
||||
import typeof ProductRecord from '../records/ProductRecord';
|
||||
import { Map, Set } from 'immutable';
|
||||
import { pipe, path, toLower, equals } from 'ramda';
|
||||
|
||||
type FindPredicate<A> = (left: A) => (right: A) => Boolean;
|
||||
|
||||
const getName: (f: ProductRecord) => String = pipe(
|
||||
path(['name']),
|
||||
toLower
|
||||
);
|
||||
|
||||
const matchesName: FindPredicate<ProductRecord> = left => right => {
|
||||
return equals(getName(left), getName(right));
|
||||
};
|
||||
|
||||
const addIfNotExisting = (predicate: FindPredicate<ProductRecord>) => (
|
||||
set: Set<ProductRecord>,
|
||||
item: ProductRecord
|
||||
) => {
|
||||
return !set.find(predicate(item)) ? set.add(item) : set;
|
||||
};
|
||||
|
||||
const intoSet = reducer => items => {
|
||||
return items ? items.reduce(reducer, new Set()) : new Set();
|
||||
};
|
||||
|
||||
export const withProducts = mapPropsStream(props$ =>
|
||||
props$.combineLatest(Products$, (props, products) => {
|
||||
return {
|
||||
...props,
|
||||
productsMap: products,
|
||||
};
|
||||
})
|
||||
);
|
||||
|
||||
export const withProductsAsSeq = mapPropsStream(props$ =>
|
||||
props$.combineLatest(Products$, (props, products) => {
|
||||
return {
|
||||
...props,
|
||||
productsSeq: products && products.valueSeq(),
|
||||
};
|
||||
})
|
||||
);
|
||||
|
||||
export const withProductIdFromRoute = withProps((props: { match: { params: { id: string } } }) => {
|
||||
const id: string = path(['match', 'params', 'id'], props) || '';
|
||||
return { productId: id };
|
||||
});
|
||||
|
||||
export const withProduct = compose(
|
||||
withProducts,
|
||||
withProductIdFromRoute,
|
||||
withProps((props: { productsMap: ?Map<number, ProductRecord>, productId: number }) => {
|
||||
const { productsMap, productId } = props;
|
||||
return { product: productsMap && productsMap.get(productId) };
|
||||
})
|
||||
);
|
||||
|
||||
export const withProductPlaceId = withProps((props: { product: ProductRecord }) => {
|
||||
const { product } = props;
|
||||
return {
|
||||
placeId: product && product.placeId,
|
||||
};
|
||||
});
|
||||
|
||||
export const withProductsGroupedByPlace = compose(
|
||||
withProducts,
|
||||
withProps((props: { productsMap: ?Map<number, ProductRecord> }) => {
|
||||
if (!props.productsMap) {
|
||||
return {};
|
||||
}
|
||||
return {
|
||||
productsByPlace: props.productsMap.groupBy(product => product.placeId),
|
||||
};
|
||||
})
|
||||
);
|
||||
|
||||
export const withUniqueProducts = compose(
|
||||
withProductsAsSeq,
|
||||
withProps(({ productsSeq }) => {
|
||||
return {
|
||||
productsSeq: intoSet(addIfNotExisting(matchesName))(productsSeq),
|
||||
};
|
||||
})
|
||||
);
|
||||
|
|
@ -1,18 +1,18 @@
|
|||
//@flow
|
||||
import { withProps } from "recompose";
|
||||
import { setQuantity } from "../apis/QuantityApi";
|
||||
import { emit as emitQuantity } from "../streams/QuantityStream";
|
||||
import type { Quantity, QuantityResponse } from "../constants/QuantityConstants";
|
||||
import { nth } from "ramda";
|
||||
import { withProps } from 'recompose';
|
||||
import { setQuantity } from '../apis/QuantityApi';
|
||||
import { emit as emitQuantity } from '../streams/QuantityStream';
|
||||
import type { Quantity, QuantityResponse } from '../constants/QuantityConstants';
|
||||
import { nth } from 'ramda';
|
||||
|
||||
export const withUpdateQuantity = withProps({
|
||||
updateQuantity: async ({ foodItemId, quantity }: { foodItemId: string, quantity: Quantity }) => {
|
||||
updateQuantity: async ({ productId, quantity }: { productId: string, quantity: Quantity }) => {
|
||||
try {
|
||||
const newQuantity: QuantityResponse = nth(0, await setQuantity({ foodItemId, quantity }));
|
||||
const newQuantity: QuantityResponse = nth(0, await setQuantity({ productId, quantity }));
|
||||
emitQuantity(newQuantity);
|
||||
} catch (error) {
|
||||
// todo - error states in food item detail page
|
||||
console.error(error); // eslint-disable-line
|
||||
}
|
||||
}
|
||||
},
|
||||
});
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
// @flow
|
||||
import FoodItemRecord from '../records/FoodItemRecord';
|
||||
import ProductRecord from '../records/ProductRecord';
|
||||
import {
|
||||
type Category,
|
||||
CATEGORY_BEVERAGES,
|
||||
|
|
@ -21,9 +21,9 @@ export const getCategoryText = (category: Category) => {
|
|||
}
|
||||
};
|
||||
|
||||
export const getCategories = (foodItems: Map<string, FoodItemRecord>) => {
|
||||
return foodItems
|
||||
.map(foodItem => foodItem.get('category'))
|
||||
export const getCategories = (products: Map<string, ProductRecord>) => {
|
||||
return products
|
||||
.map(product => product.get('category'))
|
||||
.toSet()
|
||||
.map(getCategoryText)
|
||||
.toList();
|
||||
|
|
|
|||
|
|
@ -1,28 +1,32 @@
|
|||
import { memoizeWith, identity } from "ramda";
|
||||
import { memoizeWith, identity } from 'ramda';
|
||||
|
||||
export const getZoomBox = memoizeWith(identity, (foodItemsMap, coords) =>
|
||||
foodItemsMap.reduce(
|
||||
(prev, foodItem) => {
|
||||
const minLat = !prev.minLat || prev.minLat > foodItem.latitude ? foodItem.latitude : prev.minLat;
|
||||
export const getZoomBox = memoizeWith(identity, (productsMap, coords) =>
|
||||
productsMap.reduce(
|
||||
(prev, product) => {
|
||||
const minLat =
|
||||
!prev.minLat || prev.minLat > product.latitude ? product.latitude : prev.minLat;
|
||||
|
||||
const maxLat = !prev.maxLat || prev.maxLat < foodItem.latitude ? foodItem.latitude : prev.maxLat;
|
||||
const maxLat =
|
||||
!prev.maxLat || prev.maxLat < product.latitude ? product.latitude : prev.maxLat;
|
||||
|
||||
const minLng = !prev.minLng || prev.minLng > foodItem.longitude ? foodItem.longitude : prev.minLng;
|
||||
const minLng =
|
||||
!prev.minLng || prev.minLng > product.longitude ? product.longitude : prev.minLng;
|
||||
|
||||
const maxLng = !prev.maxLng || prev.maxLng < foodItem.longitude ? foodItem.longitude : prev.maxLng;
|
||||
const maxLng =
|
||||
!prev.maxLng || prev.maxLng < product.longitude ? product.longitude : prev.maxLng;
|
||||
|
||||
return {
|
||||
minLat,
|
||||
maxLat,
|
||||
minLng,
|
||||
maxLng
|
||||
maxLng,
|
||||
};
|
||||
},
|
||||
{
|
||||
minLat: coords.latitude,
|
||||
maxLat: coords.latitude,
|
||||
minLng: coords.longitude,
|
||||
maxLng: coords.longitude
|
||||
maxLng: coords.longitude,
|
||||
}
|
||||
)
|
||||
);
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
// @flow
|
||||
import React, { PureComponent } from 'react';
|
||||
import typeof FoodItemRecord from '../records/FoodItemRecord';
|
||||
import typeof ProductRecord from '../records/ProductRecord';
|
||||
import { CATEGORIES } from '../constants/CategoryConstants';
|
||||
import { getCategoryText } from '../helpers/CategoryHelpers';
|
||||
import FullScreenModal from './FullScreenModal';
|
||||
|
|
@ -10,7 +10,7 @@ import PickerItemRow from '../components/PickerItemRow';
|
|||
type Props = {
|
||||
onClose: () => void,
|
||||
onUpdate: (c: Category) => void,
|
||||
foodItem: FoodItemRecord,
|
||||
product: ProductRecord,
|
||||
};
|
||||
|
||||
class CategoryModal extends PureComponent {
|
||||
|
|
@ -24,13 +24,13 @@ class CategoryModal extends PureComponent {
|
|||
};
|
||||
|
||||
render() {
|
||||
const { onClose, foodItem } = this.props;
|
||||
const { onClose, product } = this.props;
|
||||
return (
|
||||
<FullScreenModal title="Category" onClose={onClose}>
|
||||
{CATEGORIES.map(category => (
|
||||
<PickerItemRow
|
||||
key={category}
|
||||
isSelected={foodItem && foodItem.category === category}
|
||||
isSelected={product && product.category === category}
|
||||
text={getCategoryText(category)}
|
||||
onPress={() => this.updateAndClose(category)}
|
||||
/>
|
||||
|
|
|
|||
|
|
@ -50,7 +50,7 @@ export const wrapModalComponent = ({ component, onCloseProp, onUpdateProp }) =>
|
|||
pure,
|
||||
mapProps((props: Props) => {
|
||||
return {
|
||||
foodItem: props.foodItem,
|
||||
product: props.product,
|
||||
onClose: props[onCloseProp],
|
||||
onUpdate: props[onUpdateProp],
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1,20 +1,19 @@
|
|||
// @flow
|
||||
import React, { Component } from 'react';
|
||||
import { TouchableOpacity, ScrollView, SafeAreaView } from 'react-native';
|
||||
import { TextButton } from './Modal';
|
||||
import FoodItemList from '../components/FoodItemList';
|
||||
import FoodItemRecord from '../records/FoodItemRecord';
|
||||
import ProductList from '../components/ProductsList';
|
||||
import ProductRecord from '../records/ProductRecord';
|
||||
import { StrongText } from '../components/ItemTile';
|
||||
import { Toolbar } from 'react-native-material-ui';
|
||||
import FilterRecord from '../records/FilterRecord';
|
||||
import { compose, pure } from 'recompose';
|
||||
import { withUniqueFoodItems } from '../enhancers/foodItemEnhancers';
|
||||
import { withUniqueProducts } from '../enhancers/productsEnhancers';
|
||||
import { Set } from 'immutable';
|
||||
|
||||
type Props = {
|
||||
onClose: () => void,
|
||||
onUpdate: (name: string) => void,
|
||||
foodItemsSeq: ?Set<FoodItemRecord>,
|
||||
productsSeq: ?Set<ProductRecord>,
|
||||
};
|
||||
|
||||
class NameModal extends Component {
|
||||
|
|
@ -45,7 +44,7 @@ class NameModal extends Component {
|
|||
};
|
||||
|
||||
render() {
|
||||
const { onClose, foodItemsSeq } = this.props;
|
||||
const { onClose, productsSeq } = this.props;
|
||||
const { filter } = this.state;
|
||||
return (
|
||||
<SafeAreaView
|
||||
|
|
@ -80,17 +79,15 @@ class NameModal extends Component {
|
|||
/>
|
||||
)}
|
||||
<ScrollView style={{ paddingLeft: 78 }}>
|
||||
<FoodItemList filter={filter} limit={10} foodItemsSeq={foodItemsSeq}>
|
||||
{(foodItem: FoodItemRecord) => (
|
||||
<TouchableOpacity
|
||||
key={foodItem.id}
|
||||
onPress={() => this.updateAndClose(foodItem.name)}>
|
||||
<ProductList filter={filter} limit={10} productsSeq={productsSeq}>
|
||||
{(product: ProductRecord) => (
|
||||
<TouchableOpacity key={product.id} onPress={() => this.updateAndClose(product.name)}>
|
||||
<StrongText style={{ paddingTop: 20, paddingBottom: 20 }}>
|
||||
{foodItem.name}
|
||||
{product.name}
|
||||
</StrongText>
|
||||
</TouchableOpacity>
|
||||
)}
|
||||
</FoodItemList>
|
||||
</ProductList>
|
||||
</ScrollView>
|
||||
</SafeAreaView>
|
||||
);
|
||||
|
|
@ -99,5 +96,5 @@ class NameModal extends Component {
|
|||
|
||||
export default compose(
|
||||
pure,
|
||||
withUniqueFoodItems
|
||||
withUniqueProducts
|
||||
)(NameModal);
|
||||
|
|
@ -3,17 +3,17 @@ import React from 'react';
|
|||
import { getQuantityDropdownText } from '../helpers/QuantityHelpers';
|
||||
import { type Quantity, QUANTITIES } from '../constants/QuantityConstants';
|
||||
import FullScreenModal from './FullScreenModal';
|
||||
import { typeof FoodItem } from '../records/FoodItemRecord';
|
||||
import { typeof Product } from '../records/ProductRecord';
|
||||
import PickerItemRow from '../components/PickerItemRow';
|
||||
|
||||
type Props = {
|
||||
onClose: () => void,
|
||||
foodItem: FoodItem,
|
||||
product: Product,
|
||||
onUpdate: (q: Quantity) => void,
|
||||
};
|
||||
|
||||
const QuantityModal = (props: Props) => {
|
||||
const { foodItem, onUpdate, onClose } = props;
|
||||
const { product, onUpdate, onClose } = props;
|
||||
|
||||
const onPress = (q: Quantity) => () => {
|
||||
onUpdate(q);
|
||||
|
|
@ -26,7 +26,7 @@ const QuantityModal = (props: Props) => {
|
|||
<PickerItemRow
|
||||
key={quantity}
|
||||
text={getQuantityDropdownText(quantity)}
|
||||
isSelected={quantity === foodItem.quantity}
|
||||
isSelected={quantity === product.quantity}
|
||||
onPress={onPress(quantity)}
|
||||
/>
|
||||
))}
|
||||
|
|
|
|||
|
|
@ -1,11 +1,10 @@
|
|||
// @flow
|
||||
import React from 'react';
|
||||
import { View, Text, TouchableOpacity } from 'react-native';
|
||||
import theme from '../ui-theme';
|
||||
import { Divider } from 'react-native-material-ui';
|
||||
import FoodItemRecord from '../records/FoodItemRecord';
|
||||
import ProductRecord from '../records/ProductRecord';
|
||||
import PlaceRecord from '../records/PlaceRecord';
|
||||
import NameModal from '../modals/FoodItemNameModal';
|
||||
import NameModal from '../modals/ProductNameModal';
|
||||
import CategoryModal from '../modals/CategoryModal';
|
||||
import ImagePreviewModal from '../modals/ImagePreviewModal';
|
||||
import {
|
||||
|
|
@ -19,8 +18,8 @@ import {
|
|||
} from 'recompose';
|
||||
import RNGooglePlaces from 'react-native-google-places';
|
||||
import { ImageThumb, ImagePicker } from '../components/ImagePicker';
|
||||
import { withCreateFoodItemState } from '../enhancers/createFoodItemEnhancers';
|
||||
import { withPlaceForFoodItem, withPlaceId, withPlaceActions } from '../enhancers/placeEnhancers';
|
||||
import { withCreateProductState } from '../enhancers/createProductEnhancers';
|
||||
import { withPlaceForProduct, withPlaceId, withPlaceActions } from '../enhancers/placeEnhancers';
|
||||
import Spinner from 'react-native-loading-spinner-overlay';
|
||||
import { openImagePicker } from '../helpers/ImagePickerHelpers';
|
||||
import { IndexedSeq } from 'immutable';
|
||||
|
|
@ -32,10 +31,10 @@ import { wrapModalComponent } from '../modals/FullScreenModal';
|
|||
import { withAuthRedirect } from '../enhancers/authEnhancers';
|
||||
|
||||
type Props = {
|
||||
foodItem: typeof FoodItemRecord,
|
||||
setPropOfFoodItem: Function,
|
||||
product: typeof ProductRecord,
|
||||
setPropOfProduct: Function,
|
||||
toggleNameModal: Function,
|
||||
setFoodItem: Function,
|
||||
setProduct: Function,
|
||||
setModalsVisible: Function,
|
||||
place: ?PlaceRecord,
|
||||
updatePlace: (place: GooglePlaceObject) => void,
|
||||
|
|
@ -44,7 +43,7 @@ type Props = {
|
|||
loading: boolean,
|
||||
setLoading: (arg: boolean) => void,
|
||||
emitPlace: (place: Object) => void,
|
||||
withFoodItemsAsSeq: IndexedSeq<FoodItemRecord>,
|
||||
withProductsAsSeq: IndexedSeq<ProductRecord>,
|
||||
toggleCategoryModal: () => void,
|
||||
toggleQuantityModal: () => void,
|
||||
};
|
||||
|
|
@ -89,9 +88,9 @@ const openPlaceModal = (onChoosePlace: (place: GooglePlaceObject) => void) => ()
|
|||
RNGooglePlaces.openAutocompleteModal({ type: 'establishment' }).then(onChoosePlace);
|
||||
};
|
||||
|
||||
const CreateFoodItem = (props: Props) => {
|
||||
const CreateProduct = (props: Props) => {
|
||||
const {
|
||||
foodItem,
|
||||
product,
|
||||
toggleNameModal,
|
||||
updatePlace,
|
||||
addImage,
|
||||
|
|
@ -105,20 +104,20 @@ const CreateFoodItem = (props: Props) => {
|
|||
return (
|
||||
<View style={{ ...theme.page.container, backgroundColor: 'white', padding: 10 }}>
|
||||
<Spinner visible={loading} />
|
||||
<Field text={foodItem.name} onPress={toggleNameModal} placeholder="Name" />
|
||||
<Field text={product.name} onPress={toggleNameModal} placeholder="Name" />
|
||||
<Field text={place && place.name} onPress={openPlaceModal(updatePlace)} placeholder="Place" />
|
||||
<Field
|
||||
text={foodItem.category && getCategoryText(foodItem.category)}
|
||||
text={product.category && getCategoryText(product.category)}
|
||||
onPress={toggleCategoryModal}
|
||||
placeholder="Category"
|
||||
/>
|
||||
<Field
|
||||
text={foodItem.quantity && getQuantityDropdownText(foodItem.quantity)}
|
||||
text={product.quantity && getQuantityDropdownText(product.quantity)}
|
||||
onPress={toggleQuantityModal}
|
||||
placeholder="Quantity"
|
||||
/>
|
||||
<ImagePicker onCreateNew={addImage}>
|
||||
{foodItem.images.map((imageURI: string) => (
|
||||
{product.images.map((imageURI: string) => (
|
||||
<ImageThumb key={imageURI} uri={imageURI} onPress={() => setImagePreview(imageURI)} />
|
||||
))}
|
||||
</ImagePicker>
|
||||
|
|
@ -128,31 +127,31 @@ const CreateFoodItem = (props: Props) => {
|
|||
|
||||
const ImagePreviewComp = compose(
|
||||
renderComponent,
|
||||
mapProps(({ foodItem, setFoodItem, setImagePreview, imagePreview }) => {
|
||||
mapProps(({ product, setProduct, setImagePreview, imagePreview }) => {
|
||||
return {
|
||||
onClose: () => setImagePreview(-1),
|
||||
onDelete: () => {
|
||||
setFoodItem(foodItem.deleteIn(['images', imagePreview]));
|
||||
setProduct(product.deleteIn(['images', imagePreview]));
|
||||
setImagePreview(null);
|
||||
},
|
||||
imageSrc: foodItem.images.get(imagePreview),
|
||||
imageSrc: product.images.get(imagePreview),
|
||||
};
|
||||
})
|
||||
)(ImagePreviewModal);
|
||||
|
||||
const setPropOfFoodItem = ({ foodItem, setFoodItem }: Props) => (prop: string) => (value: any) => {
|
||||
setFoodItem(foodItem.set(prop, value));
|
||||
const setPropOfProduct = ({ product, setProduct }: Props) => (prop: string) => (value: any) => {
|
||||
setProduct(product.set(prop, value));
|
||||
};
|
||||
|
||||
const addImage = ({ foodItem, setFoodItem }: Props) => async () => {
|
||||
const addImage = ({ product, setProduct }: Props) => async () => {
|
||||
const { path } = await openImagePicker();
|
||||
Snackbar.show({
|
||||
title: 'Photo added.',
|
||||
});
|
||||
setFoodItem(foodItem.update('images', images => images.add(path)));
|
||||
setProduct(product.update('images', images => images.add(path)));
|
||||
};
|
||||
|
||||
const updatePlace = ({ foodItem, setFoodItem, emitPlace }: Props) => placeDetails => {
|
||||
const updatePlace = ({ product, setProduct, emitPlace }: Props) => placeDetails => {
|
||||
const { placeID, latitude, longitude } = placeDetails;
|
||||
|
||||
emitPlace(
|
||||
|
|
@ -162,8 +161,8 @@ const updatePlace = ({ foodItem, setFoodItem, emitPlace }: Props) => placeDetail
|
|||
})
|
||||
);
|
||||
|
||||
setFoodItem(
|
||||
foodItem.merge({
|
||||
setProduct(
|
||||
product.merge({
|
||||
placeId: placeID,
|
||||
latitude,
|
||||
longitude,
|
||||
|
|
@ -173,16 +172,16 @@ const updatePlace = ({ foodItem, setFoodItem, emitPlace }: Props) => placeDetail
|
|||
|
||||
export default compose(
|
||||
withAuthRedirect,
|
||||
withCreateFoodItemState,
|
||||
withCreateProductState,
|
||||
withPlaceId,
|
||||
withPlaceForFoodItem,
|
||||
withPlaceForProduct,
|
||||
withPlaceActions,
|
||||
withState('nameModalOpen', 'setNameModalOpen', false),
|
||||
withState('imagePreview', 'setImagePreview', null),
|
||||
withState('categoryModalOpen', 'setCategoryModalOpen', false),
|
||||
withState('quantityModalOpen', 'setQuantityModalOpen', false),
|
||||
withHandlers({
|
||||
setPropOfFoodItem,
|
||||
setPropOfProduct,
|
||||
addImage,
|
||||
updatePlace,
|
||||
toggleNameModal: ({ nameModalOpen, setNameModalOpen }) => () => {
|
||||
|
|
@ -196,14 +195,14 @@ export default compose(
|
|||
},
|
||||
}),
|
||||
withHandlers({
|
||||
setQuantity: ({ setPropOfFoodItem }) => setPropOfFoodItem('quantity'),
|
||||
setCategory: ({ setPropOfFoodItem }) => setPropOfFoodItem('category'),
|
||||
setName: ({ setPropOfFoodItem }) => setPropOfFoodItem('name'),
|
||||
setQuantity: ({ setPropOfProduct }) => setPropOfProduct('quantity'),
|
||||
setCategory: ({ setPropOfProduct }) => setPropOfProduct('category'),
|
||||
setName: ({ setPropOfProduct }) => setPropOfProduct('name'),
|
||||
}),
|
||||
lifecycle({
|
||||
componentDidMount() {
|
||||
const { placeId, setFoodItem } = this.props;
|
||||
setFoodItem(new FoodItemRecord({ placeId }));
|
||||
const { placeId, setProduct } = this.props;
|
||||
setProduct(new ProductRecord({ placeId }));
|
||||
},
|
||||
}),
|
||||
branch(
|
||||
|
|
@ -231,4 +230,4 @@ export default compose(
|
|||
onUpdateProp: 'setQuantity',
|
||||
})
|
||||
)
|
||||
)(CreateFoodItem);
|
||||
)(CreateProduct);
|
||||
|
|
@ -2,12 +2,12 @@
|
|||
import React from 'react';
|
||||
import { View, ScrollView, RefreshControl } from 'react-native';
|
||||
import { compose, pure, withState, withHandlers, lifecycle } from 'recompose';
|
||||
import { withFoodItems } from '../enhancers/foodItemEnhancers';
|
||||
import { withProducts } from '../enhancers/productsEnhancers';
|
||||
import { Map, get } from 'immutable';
|
||||
import typeof FoodItemRecord from '../records/FoodItemRecord';
|
||||
import typeof ProductRecord from '../records/ProductRecord';
|
||||
import { withFaves } from '../enhancers/favesEnhancer';
|
||||
import type { Faves, Fave } from '../streams/FavesStream';
|
||||
import FoodItemTile from '../components/FoodItemTile';
|
||||
import ProductTile from '../components/ProductTile';
|
||||
import { withAuthRedirect } from '../enhancers/authEnhancers';
|
||||
|
||||
type Props = {
|
||||
|
|
@ -26,7 +26,7 @@ const FavesList = ({ faves, isRefreshing, onPulldown }: Props) => {
|
|||
refreshControl={<RefreshControl refreshing={refreshing} onRefresh={onPulldown} />}>
|
||||
{faves &&
|
||||
faves.map((fave: Faves, index: number) => {
|
||||
return <FoodItemTile key={fave.id || index} foodItem={fave} />;
|
||||
return <ProductTile key={fave.id || index} product={fave} />;
|
||||
})}
|
||||
</ScrollView>
|
||||
</View>
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ import { compose, renderComponent, withState, withProps } from 'recompose';
|
|||
import { Map, get } from 'immutable';
|
||||
import MapView from 'react-native-maps';
|
||||
import { routeWithTitle } from '../helpers/RouteHelpers';
|
||||
import FoodItemTile from '../components/FoodItemTile';
|
||||
import ProductTile from '../components/ProductTile';
|
||||
import { getZoomBox } from '../helpers/CoordinatesHelpers';
|
||||
import { withFaves } from '../enhancers/favesEnhancer';
|
||||
import { type Faves } from '../streams/FavesStream';
|
||||
|
|
@ -47,9 +47,9 @@ const FavesMap = ({ faves, region, onRegionChange, pushRoute, initialRegion }: P
|
|||
}}>
|
||||
<MapView.Callout
|
||||
onPress={() => {
|
||||
pushRoute(routeWithTitle(`/foodItem/${get(fave, 'id', '')}`, get(fave, 'name')));
|
||||
pushRoute(routeWithTitle(`/product/${get(fave, 'id', '')}`, get(fave, 'name')));
|
||||
}}>
|
||||
<FoodItemTile foodItem={fave} />
|
||||
<ProductTile product={fave} />
|
||||
</MapView.Callout>
|
||||
</MapView.Marker>
|
||||
))}
|
||||
|
|
@ -58,8 +58,8 @@ const FavesMap = ({ faves, region, onRegionChange, pushRoute, initialRegion }: P
|
|||
);
|
||||
};
|
||||
|
||||
const withInitialRegionProp = withProps(({ foodItemsMap = Map(), location }) => {
|
||||
const zoomBox = getZoomBox(foodItemsMap, location.coords);
|
||||
const withInitialRegionProp = withProps(({ productsMap = Map(), location }) => {
|
||||
const zoomBox = getZoomBox(productsMap, location.coords);
|
||||
|
||||
return {
|
||||
initialRegion: {
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
// @flow
|
||||
import React from 'react';
|
||||
import { View, SafeAreaView } from 'react-native';
|
||||
import Food from './Food';
|
||||
import Food from './Products';
|
||||
import Places from './Places';
|
||||
import { BottomNavigation } from 'react-native-material-ui';
|
||||
import { getLocation } from '../apis/PositionApi';
|
||||
|
|
@ -60,7 +60,7 @@ const Nav = (props: Props) => {
|
|||
key="add"
|
||||
icon="add-circle"
|
||||
label="Add"
|
||||
onPress={pushRoute(`/createFoodItem?backto=${location.pathname}`)}
|
||||
onPress={pushRoute(`/createProduct?backto=${location.pathname}`)}
|
||||
style={{
|
||||
container: {
|
||||
minWidth: 40,
|
||||
|
|
|
|||
|
|
@ -4,18 +4,14 @@ import { View, Text, Image, ScrollView } from 'react-native';
|
|||
import theme, { palette } from '../ui-theme';
|
||||
import { compose, pure, withState, withHandlers, lifecycle } from 'recompose';
|
||||
import typeof PlaceRecord from '../records/PlaceRecord';
|
||||
import {
|
||||
withPlace,
|
||||
withPlaceIdFromRoute,
|
||||
withFoodItemsForPlace,
|
||||
} from '../enhancers/placeEnhancers';
|
||||
import { withPlace, withPlaceIdFromRoute, withProductsForPlace } from '../enhancers/placeEnhancers';
|
||||
import Carousel from 'react-native-looped-carousel';
|
||||
import CountBadge from '../components/CountBadge';
|
||||
import { StrongText } from '../components/ItemTile';
|
||||
import IconButton from '../components/IconButton';
|
||||
import { type List } from 'immutable';
|
||||
import typeof FoodItemRecord from '../records/FoodItemRecord';
|
||||
import FoodItemTile from '../components/FoodItemTile';
|
||||
import typeof ProductRecord from '../records/ProductRecord';
|
||||
import ProductTile from '../components/ProductTile';
|
||||
import { openUrl } from '../helpers/linkHelpers';
|
||||
import { routeWithQuery } from '../helpers/RouteHelpers';
|
||||
import { Link } from 'react-router-native';
|
||||
|
|
@ -32,7 +28,7 @@ const contentTileStyle = {
|
|||
|
||||
type Props = {
|
||||
place: ?PlaceRecord,
|
||||
foodItems: ?List<FoodItemRecord>,
|
||||
products: ?List<ProductRecord>,
|
||||
currentImage: number,
|
||||
setCurrentImage: (idx: number) => void,
|
||||
viewOnMap: () => void,
|
||||
|
|
@ -41,7 +37,7 @@ type Props = {
|
|||
};
|
||||
|
||||
const PlaceDetail = (props: Props) => {
|
||||
const { place, foodItems, currentImage, setCurrentImage, viewOnMap, phoneCall } = props;
|
||||
const { place, products, currentImage, setCurrentImage, viewOnMap, phoneCall } = props;
|
||||
|
||||
if (!place) {
|
||||
return <View />;
|
||||
|
|
@ -56,7 +52,7 @@ const PlaceDetail = (props: Props) => {
|
|||
{photos.size === 1 && <Image style={stretchedStyle} source={{ uri: photos.first() }} />}
|
||||
{photos.size > 1 && (
|
||||
<Carousel autoplay={false} onAnimateNextPage={setCurrentImage} style={stretchedStyle}>
|
||||
{photos.map((uri) => (
|
||||
{photos.map(uri => (
|
||||
<Image key={uri} style={{ flex: 1, resizeMode: 'stretch' }} source={{ uri }} />
|
||||
))}
|
||||
</Carousel>
|
||||
|
|
@ -84,14 +80,14 @@ const PlaceDetail = (props: Props) => {
|
|||
</View>
|
||||
<View style={{ padding: 15, ...contentTileStyle }}>
|
||||
<StrongText>Products</StrongText>
|
||||
{!!foodItems &&
|
||||
foodItems.map((foodItem) => (
|
||||
<FoodItemTile key={foodItem.id} foodItem={foodItem} place={place} />
|
||||
{!!products &&
|
||||
products.map(product => (
|
||||
<ProductTile key={product.id} product={product} place={place} />
|
||||
))}
|
||||
{!foodItems ||
|
||||
(!foodItems.size && (
|
||||
{!products ||
|
||||
(!products.size && (
|
||||
<Link
|
||||
to={routeWithQuery('/createFoodItem', {
|
||||
to={routeWithQuery('/createProduct', {
|
||||
routeTitle: 'Add a Food Item',
|
||||
placeId: place.id,
|
||||
})}>
|
||||
|
|
@ -116,7 +112,7 @@ export default compose(
|
|||
pure,
|
||||
withPlaceIdFromRoute,
|
||||
withPlace,
|
||||
withFoodItemsForPlace,
|
||||
withProductsForPlace,
|
||||
withState('currentImage', 'setCurrentImage', 0),
|
||||
withHandlers({
|
||||
viewOnMap: (props: Props) => () => {
|
||||
|
|
|
|||
|
|
@ -3,15 +3,15 @@ import React from 'react';
|
|||
import { View, ScrollView, RefreshControl } from 'react-native';
|
||||
import PlaceTile from '../components/PlaceTile';
|
||||
import { compose, pure, withState, withHandlers, lifecycle } from 'recompose';
|
||||
// import { withFoodItemsGroupedByPlace } from '../enhancers/foodItemEnhancers';
|
||||
// import { withProductsGroupedByPlace } from '../enhancers/productEnhancers';
|
||||
import { withPlaces } from '../enhancers/placeEnhancers';
|
||||
import { Map, List } from 'immutable';
|
||||
import typeof FoodItemRecord from '../records/FoodItemRecord';
|
||||
import typeof ProductRecord from '../records/ProductRecord';
|
||||
import typeof PlaceRecord from '../records/PlaceRecord';
|
||||
import { withRouterContext } from '../enhancers/routeEnhancers';
|
||||
|
||||
type Props = {
|
||||
foodItemsByPlace: Map<string, Map<string, FoodItemRecord>>,
|
||||
productsByPlace: Map<string, Map<string, ProductRecord>>,
|
||||
places: ?Map<string, PlaceRecord>,
|
||||
onRefresh: () => Promise<any>,
|
||||
onPulldown: () => {},
|
||||
|
|
@ -21,7 +21,7 @@ type Props = {
|
|||
|
||||
const byDistance = (left: PlaceRecord, right: PlaceRecord) => left.distance - right.distance;
|
||||
|
||||
const PlacesList = ({ foodItemsByPlace = Map(), places, isRefreshing, onPulldown }: Props) => {
|
||||
const PlacesList = ({ productsByPlace = Map(), places, isRefreshing, onPulldown }: Props) => {
|
||||
const refreshing = isRefreshing || !places;
|
||||
return (
|
||||
<View>
|
||||
|
|
@ -31,8 +31,8 @@ const PlacesList = ({ foodItemsByPlace = Map(), places, isRefreshing, onPulldown
|
|||
places
|
||||
.sort(byDistance)
|
||||
.map((place: PlaceRecord, placeId: string) => {
|
||||
// const foodItems = foodItemsByPlace.get(placeId, new List());
|
||||
return <PlaceTile key={placeId} place={place} foodItems={[]} />;
|
||||
// const products = productsByPlace.get(placeId, new List());
|
||||
return <PlaceTile key={placeId} place={place} products={[]} />;
|
||||
})
|
||||
.toList()}
|
||||
</ScrollView>
|
||||
|
|
@ -42,7 +42,7 @@ const PlacesList = ({ foodItemsByPlace = Map(), places, isRefreshing, onPulldown
|
|||
|
||||
export default compose(
|
||||
pure,
|
||||
// withFoodItemsGroupedByPlace,
|
||||
// withProductsGroupedByPlace,
|
||||
withPlaces,
|
||||
withRouterContext,
|
||||
withState('isRefreshing', 'setRefreshing', false),
|
||||
|
|
|
|||
|
|
@ -1,14 +1,14 @@
|
|||
// @flow
|
||||
import React from 'react';
|
||||
import { View } from 'react-native';
|
||||
import typeof FoodItemRecord from '../records/FoodItemRecord';
|
||||
import typeof ProductRecord from '../records/ProductRecord';
|
||||
import { compose, renderComponent } from 'recompose';
|
||||
import { Map } from 'immutable';
|
||||
import MapView from 'react-native-maps';
|
||||
import { routeWithTitle } from '../helpers/RouteHelpers';
|
||||
import { withPlaces } from '../enhancers/placeEnhancers';
|
||||
import PlaceTile from '../components/PlaceTile';
|
||||
import { withFoodItemsGroupedByPlace } from '../enhancers/foodItemEnhancers';
|
||||
import { withProductsGroupedByPlace } from '../enhancers/productsEnhancers';
|
||||
import { withRegionState } from '../enhancers/mapViewEnhancers';
|
||||
import typeof PlaceRecord from '../records/PlaceRecord';
|
||||
|
||||
|
|
@ -21,7 +21,7 @@ type Region = {
|
|||
|
||||
type Props = {
|
||||
places: Map<string, PlaceRecord>,
|
||||
foodItemsByPlace: Map<string, Map<string, FoodItemRecord>>,
|
||||
productsByPlace: Map<string, Map<string, ProductRecord>>,
|
||||
location: Location,
|
||||
region: Region,
|
||||
onRegionChange: Region => void,
|
||||
|
|
@ -30,7 +30,7 @@ type Props = {
|
|||
|
||||
const PlacesMap = ({
|
||||
places = Map(),
|
||||
foodItemsByPlace = Map(),
|
||||
productsByPlace = Map(),
|
||||
region,
|
||||
onRegionChange,
|
||||
pushRoute,
|
||||
|
|
@ -44,10 +44,10 @@ const PlacesMap = ({
|
|||
onRegionChange={onRegionChange}>
|
||||
{places
|
||||
.map((place: PlaceRecord, placeId: string) => {
|
||||
const foodItems = foodItemsByPlace.get(placeId, new Map());
|
||||
const firstFoodItem = foodItems.first();
|
||||
const products = productsByPlace.get(placeId, new Map());
|
||||
const firstProduct = products.first();
|
||||
|
||||
if (!firstFoodItem) {
|
||||
if (!firstProduct) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
@ -56,14 +56,14 @@ const PlacesMap = ({
|
|||
key={placeId}
|
||||
title={place.name}
|
||||
coordinate={{
|
||||
latitude: firstFoodItem.latitude,
|
||||
longitude: firstFoodItem.longitude,
|
||||
latitude: firstProduct.latitude,
|
||||
longitude: firstProduct.longitude,
|
||||
}}>
|
||||
<MapView.Callout
|
||||
onPress={() => {
|
||||
pushRoute(routeWithTitle(`/place/${placeId || ''}`, place.name));
|
||||
}}>
|
||||
<PlaceTile place={place} foodItems={foodItems} />
|
||||
<PlaceTile place={place} products={products} />
|
||||
</MapView.Callout>
|
||||
</MapView.Marker>
|
||||
);
|
||||
|
|
@ -76,7 +76,7 @@ const PlacesMap = ({
|
|||
|
||||
export default compose(
|
||||
renderComponent,
|
||||
withFoodItemsGroupedByPlace,
|
||||
withProductsGroupedByPlace,
|
||||
withPlaces,
|
||||
withRegionState
|
||||
)(PlacesMap);
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@ import React from 'react';
|
|||
import { Button, Image, Text, View } from 'react-native';
|
||||
import theme from '../ui-theme';
|
||||
import { StrongText, SubText } from '../components/ItemTile';
|
||||
import typeof FoodItemRecord from '../records/FoodItemRecord';
|
||||
import typeof ProductRecord from '../records/ProductRecord';
|
||||
import { type Quantity } from '../constants/QuantityConstants';
|
||||
import typeof PlaceRecord from '../records/PlaceRecord';
|
||||
import {
|
||||
|
|
@ -15,8 +15,8 @@ import {
|
|||
lifecycle,
|
||||
} from 'recompose';
|
||||
import IconButton from '../components/IconButton';
|
||||
import { withFoodItem } from '../enhancers/foodItemEnhancers';
|
||||
import { withPlaceForFoodItem } from '../enhancers/placeEnhancers';
|
||||
import { withProduct } from '../enhancers/productsEnhancers';
|
||||
import { withPlaceForProduct } from '../enhancers/placeEnhancers';
|
||||
import Carousel from 'react-native-looped-carousel';
|
||||
import CountBadge from '../components/CountBadge';
|
||||
import { routeWithTitle, loginWithBackto } from '../helpers/RouteHelpers';
|
||||
|
|
@ -36,11 +36,11 @@ import { withFaves } from '../enhancers/favesEnhancer';
|
|||
import debounce from '../helpers/debounce';
|
||||
import { withCurrentPath, withReplaceRoute } from '../enhancers/routeEnhancers';
|
||||
|
||||
const { foodItemDetails: style } = theme;
|
||||
const { productDetails: style } = theme;
|
||||
|
||||
const stretchedStyle = { position: 'absolute', top: 0, left: 0, right: 0, bottom: 0 };
|
||||
|
||||
const FoodItemImages = ({
|
||||
const ProductImages = ({
|
||||
currentImage,
|
||||
visibleImages,
|
||||
changeCurrentImage,
|
||||
|
|
@ -90,26 +90,26 @@ const contentTileStyle = {
|
|||
};
|
||||
|
||||
type Props = {
|
||||
foodItem: ?FoodItemRecord,
|
||||
foodItemId: string,
|
||||
product: ?ProductRecord,
|
||||
productId: string,
|
||||
place: PlaceRecord,
|
||||
currentImage: number,
|
||||
quantityModalOpen: boolean,
|
||||
isAuthed: boolean,
|
||||
changeCurrentImage: (index: number) => void,
|
||||
updateAmount: (quantity: Quantity) => void,
|
||||
updateQuantity: (arg: { foodItemId: string, quantity: Quantity }) => void,
|
||||
updateQuantity: (arg: { productId: string, quantity: Quantity }) => void,
|
||||
toggleQuantityModal: () => void,
|
||||
addPhoto: () => void,
|
||||
addImage: (arg: { foodItemId: string, imageUri: string }) => Promise<void>,
|
||||
addImage: (arg: { productId: string, imageUri: string }) => Promise<void>,
|
||||
addToFaves: () => void,
|
||||
isFave: boolean,
|
||||
deleteFromFaves: () => void,
|
||||
};
|
||||
|
||||
export const FoodItemDetail = (props: Props) => {
|
||||
export const ProductDetail = (props: Props) => {
|
||||
const {
|
||||
foodItem,
|
||||
product,
|
||||
place,
|
||||
currentImage,
|
||||
changeCurrentImage,
|
||||
|
|
@ -123,15 +123,15 @@ export const FoodItemDetail = (props: Props) => {
|
|||
deleteFromFaves,
|
||||
} = props;
|
||||
|
||||
if (!foodItem || !place) {
|
||||
if (!product || !place) {
|
||||
return <Spinner />;
|
||||
}
|
||||
|
||||
return (
|
||||
<View style={{ ...theme.page.container }}>
|
||||
<FoodItemImages
|
||||
<ProductImages
|
||||
currentImage={currentImage}
|
||||
visibleImages={foodItem.images.filter(identity)}
|
||||
visibleImages={product.images.filter(identity)}
|
||||
changeCurrentImage={changeCurrentImage}
|
||||
/>
|
||||
<View style={{ ...contentTileStyle }}>
|
||||
|
|
@ -153,10 +153,10 @@ export const FoodItemDetail = (props: Props) => {
|
|||
justifyContent: 'space-between',
|
||||
}}>
|
||||
<Text style={{ fontSize: 42, color: 'black' }}>
|
||||
{getQuantityLabelText(foodItem.quantity)}
|
||||
{getQuantityLabelText(product.quantity)}
|
||||
</Text>
|
||||
<SubText>
|
||||
Last updated at {moment(foodItem.lastupdated).format('h:mm A on MMM D, YYYY')}
|
||||
Last updated at {moment(product.lastupdated).format('h:mm A on MMM D, YYYY')}
|
||||
</SubText>
|
||||
{isAuthed && (
|
||||
<Button
|
||||
|
|
@ -168,7 +168,7 @@ export const FoodItemDetail = (props: Props) => {
|
|||
{!isAuthed && (
|
||||
<RouterButton
|
||||
title="Log in to update quantity"
|
||||
to={loginWithBackto(`/foodItem/${foodItem.id}?loginAction=open-quantity-modal`)}
|
||||
to={loginWithBackto(`/product/${product.id}?loginAction=open-quantity-modal`)}
|
||||
color={theme.actionButton.speedDialActionIcon.backgroundColor}
|
||||
/>
|
||||
)}
|
||||
|
|
@ -204,15 +204,15 @@ export const FoodItemDetail = (props: Props) => {
|
|||
)}
|
||||
</View>
|
||||
{quantityModalOpen && (
|
||||
<QuantityModal foodItem={foodItem} onUpdate={updateAmount} onClose={toggleQuantityModal} />
|
||||
<QuantityModal product={product} onUpdate={updateAmount} onClose={toggleQuantityModal} />
|
||||
)}
|
||||
</View>
|
||||
);
|
||||
};
|
||||
|
||||
export default compose(
|
||||
withFoodItem,
|
||||
withPlaceForFoodItem,
|
||||
withProduct,
|
||||
withPlaceForProduct,
|
||||
withUpdateQuantity,
|
||||
withAuthed,
|
||||
withFaves,
|
||||
|
|
@ -221,33 +221,33 @@ export default compose(
|
|||
withState('currentImage', 'changeCurrentImage', 0),
|
||||
withState('quantityModalOpen', 'setQuantityModalOpen', false),
|
||||
withHandlers({
|
||||
updateAmount: ({ updateQuantity, foodItem }: Props) => async (quantity: Quantity) => {
|
||||
if (!foodItem) {
|
||||
updateAmount: ({ updateQuantity, product }: Props) => async (quantity: Quantity) => {
|
||||
if (!product) {
|
||||
return;
|
||||
}
|
||||
await updateQuantity({ foodItemId: foodItem.id, quantity });
|
||||
Snackbar.show({ title: 'Food updated.', backgroundColor: 'black', color: 'white' });
|
||||
await updateQuantity({ productId: product.id, quantity });
|
||||
Snackbar.show({ title: 'Product updated.', backgroundColor: 'black', color: 'white' });
|
||||
},
|
||||
toggleQuantityModal: ({ quantityModalOpen, setQuantityModalOpen }) =>
|
||||
debounce(() => {
|
||||
setQuantityModalOpen(!quantityModalOpen);
|
||||
}, 500),
|
||||
addToFaves: ({ addFave, foodItemId, isAuthed, replaceRoute }) =>
|
||||
addToFaves: ({ addFave, productId, isAuthed, replaceRoute }) =>
|
||||
debounce(() => {
|
||||
if (!isAuthed) {
|
||||
replaceRoute(loginWithBackto(`/foodItem/${foodItemId}?loginAction=add-to-faves`));
|
||||
replaceRoute(loginWithBackto(`/product/${productId}?loginAction=add-to-faves`));
|
||||
} else {
|
||||
addFave(foodItemId);
|
||||
addFave(productId);
|
||||
}
|
||||
}, 500),
|
||||
deleteFromFaves: ({ deleteFave, foodItemId }) => () => deleteFave(foodItemId),
|
||||
deleteFromFaves: ({ deleteFave, productId }) => () => deleteFave(productId),
|
||||
}),
|
||||
withProps(props => ({
|
||||
isFave: props.faves && !!props.faves.find(fave => get(fave, 'id') === props.foodItemId),
|
||||
isFave: props.faves && !!props.faves.find(fave => get(fave, 'id') === props.productId),
|
||||
loginAction: pathOr('', [1], /loginAction=(.+)(&?.*$)/.exec(props.currentPath)),
|
||||
})),
|
||||
onlyUpdateForKeys([
|
||||
'foodItem',
|
||||
'product',
|
||||
'place',
|
||||
'quantityModalOpen',
|
||||
// 'imagesLoading',
|
||||
|
|
@ -267,4 +267,4 @@ export default compose(
|
|||
}
|
||||
},
|
||||
})
|
||||
)(FoodItemDetail);
|
||||
)(ProductDetail);
|
||||
|
|
@ -1,30 +1,19 @@
|
|||
// @flow
|
||||
import React from 'react';
|
||||
import { View, Text, ScrollView, RefreshControl } from 'react-native';
|
||||
import FoodItemTile from '../components/FoodItemTile';
|
||||
import FoodItemList from '../components/FoodItemList';
|
||||
import typeof FoodItemRecord from '../records/FoodItemRecord';
|
||||
import { withFoodItemsAsSeq } from '../enhancers/foodItemEnhancers';
|
||||
import { type SetSeq } from 'immutable';
|
||||
import ProductTile from '../components/ProductTile';
|
||||
import ProductsList from '../components/ProductsList';
|
||||
import { withProductsAsSeq } from '../enhancers/productsEnhancers';
|
||||
import { compose, pure, withState, withHandlers, lifecycle } from 'recompose';
|
||||
import { withFilter } from '../enhancers/filterEnhancers';
|
||||
import { withRouterContext } from '../enhancers/routeEnhancers';
|
||||
import FullScreenMessage from '../components/FullScreenMessage';
|
||||
import { withFaves } from '../enhancers/favesEnhancer';
|
||||
|
||||
type Props = {
|
||||
foodItemsSeq: SetSeq<FoodItemRecord>,
|
||||
isRefreshing: boolean,
|
||||
onRefresh: () => Promise<any>,
|
||||
onPulldown: () => {},
|
||||
isFilterDirty: boolean,
|
||||
};
|
||||
const ProductList = props => {
|
||||
const { productsSeq, isRefreshing, onPulldown, isFilterDirty } = props;
|
||||
|
||||
const FoodList = (props: Props) => {
|
||||
const { foodItemsSeq, isRefreshing, onPulldown, isFilterDirty } = props;
|
||||
|
||||
const refreshing = isRefreshing || !foodItemsSeq;
|
||||
const showNoResults = !isRefreshing && isFilterDirty && foodItemsSeq && !foodItemsSeq.size;
|
||||
const refreshing = isRefreshing || !productsSeq;
|
||||
const showNoResults = !isRefreshing && isFilterDirty && productsSeq && !productsSeq.size;
|
||||
|
||||
return (
|
||||
<View style={{ flex: 1 }}>
|
||||
|
|
@ -38,9 +27,9 @@ const FoodList = (props: Props) => {
|
|||
<ScrollView
|
||||
style={{ flex: 1 }}
|
||||
refreshControl={<RefreshControl refreshing={refreshing} onRefresh={onPulldown} />}>
|
||||
<FoodItemList foodItemsSeq={foodItemsSeq}>
|
||||
{(foodItem: FoodItemRecord) => <FoodItemTile key={foodItem.id} foodItem={foodItem} />}
|
||||
</FoodItemList>
|
||||
<ProductsList productsSeq={productsSeq}>
|
||||
{product => <ProductTile key={product.id} product={product} />}
|
||||
</ProductsList>
|
||||
</ScrollView>
|
||||
)}
|
||||
</View>
|
||||
|
|
@ -50,7 +39,7 @@ const FoodList = (props: Props) => {
|
|||
export default compose(
|
||||
pure,
|
||||
withRouterContext,
|
||||
withFoodItemsAsSeq,
|
||||
withProductsAsSeq,
|
||||
withFilter,
|
||||
withFaves,
|
||||
withState('isRefreshing', 'setRefreshing', false),
|
||||
|
|
@ -72,4 +61,4 @@ export default compose(
|
|||
this.props.onPulldown();
|
||||
},
|
||||
})
|
||||
)(FoodList);
|
||||
)(ProductList);
|
||||
|
|
@ -1,7 +1,6 @@
|
|||
// @flow
|
||||
import { compose, branch } from 'recompose';
|
||||
import FoodList from './FoodList';
|
||||
import FoodMap from './FoodMap';
|
||||
import ProductList from './ProductList';
|
||||
import ProductMap from './ProductsMap';
|
||||
import { withRouterContext, withViewMode, withPushRoute } from '../enhancers/routeEnhancers';
|
||||
|
||||
export default compose(
|
||||
|
|
@ -10,5 +9,5 @@ export default compose(
|
|||
withPushRoute,
|
||||
branch(({ viewMode }) => {
|
||||
return viewMode === 'map';
|
||||
}, FoodMap)
|
||||
)(FoodList);
|
||||
}, ProductMap)
|
||||
)(ProductList);
|
||||
|
|
@ -1,42 +1,24 @@
|
|||
// @flow
|
||||
import React from 'react';
|
||||
import { View, Image } from 'react-native';
|
||||
import typeof FoodItemRecord from '../records/FoodItemRecord';
|
||||
import { withFoodItems } from '../enhancers/foodItemEnhancers';
|
||||
import { withProducts } from '../enhancers/productsEnhancers';
|
||||
import { withLocation } from '../enhancers/locationEnhancers';
|
||||
import { compose, renderComponent, withState, withProps } from 'recompose';
|
||||
import { Map } from 'immutable';
|
||||
import MapView, { Marker } from 'react-native-maps';
|
||||
import { routeWithTitle } from '../helpers/RouteHelpers';
|
||||
import FoodItemTile from '../components/FoodItemTile';
|
||||
import ProductTile from '../components/ProductTile';
|
||||
import { getZoomBox } from '../helpers/CoordinatesHelpers';
|
||||
import LOCATION_DOT from '../../static/location-dot.png';
|
||||
|
||||
type Region = {
|
||||
latitude: number,
|
||||
longitude: number,
|
||||
latitudeDelta: number,
|
||||
longitudeDelta: number,
|
||||
};
|
||||
|
||||
type Props = {
|
||||
foodItemsMap: Map<string, FoodItemRecord>,
|
||||
location: Location,
|
||||
initialRegion: Region,
|
||||
region: Region,
|
||||
onRegionChange: (Region) => void,
|
||||
pushRoute: () => {},
|
||||
};
|
||||
|
||||
const FoodMap = ({
|
||||
foodItemsMap,
|
||||
const ProductsMap = ({
|
||||
productsMap,
|
||||
region,
|
||||
onRegionChange,
|
||||
pushRoute,
|
||||
initialRegion,
|
||||
location,
|
||||
}: Props) => {
|
||||
if (!foodItemsMap) {
|
||||
}) => {
|
||||
if (!productsMap) {
|
||||
return null;
|
||||
}
|
||||
|
||||
|
|
@ -46,21 +28,21 @@ const FoodMap = ({
|
|||
region={region || initialRegion}
|
||||
style={{ flex: 1 }}
|
||||
onRegionChangeComplete={onRegionChange}>
|
||||
{foodItemsMap
|
||||
.map((foodItem, id) => {
|
||||
{productsMap
|
||||
.map((product, id) => {
|
||||
return (
|
||||
<MapView.Marker
|
||||
key={id}
|
||||
title={foodItem.name}
|
||||
title={product.name}
|
||||
coordinate={{
|
||||
latitude: foodItem.latitude || 0,
|
||||
longitude: foodItem.longitude || 0,
|
||||
latitude: product.latitude || 0,
|
||||
longitude: product.longitude || 0,
|
||||
}}>
|
||||
<MapView.Callout
|
||||
onPress={() => {
|
||||
pushRoute(routeWithTitle(`/foodItem/${foodItem.id || ''}`, foodItem.name));
|
||||
pushRoute(routeWithTitle(`/product/${product.id || ''}`, product.name));
|
||||
}}>
|
||||
<FoodItemTile foodItem={foodItem} />
|
||||
<ProductTile product={product} />
|
||||
</MapView.Callout>
|
||||
</MapView.Marker>
|
||||
);
|
||||
|
|
@ -76,8 +58,8 @@ const FoodMap = ({
|
|||
);
|
||||
};
|
||||
|
||||
const withInitialRegionProp = withProps(({ foodItemsMap = Map(), location }) => {
|
||||
const zoomBox = getZoomBox(foodItemsMap, location.coords);
|
||||
const withInitialRegionProp = withProps(({ productsMap = Map(), location }) => {
|
||||
const zoomBox = getZoomBox(productsMap, location.coords);
|
||||
|
||||
return {
|
||||
initialRegion: {
|
||||
|
|
@ -91,8 +73,8 @@ const withInitialRegionProp = withProps(({ foodItemsMap = Map(), location }) =>
|
|||
|
||||
export default compose(
|
||||
renderComponent,
|
||||
withFoodItems,
|
||||
withProducts,
|
||||
withLocation,
|
||||
withState('region', 'onRegionChange', null),
|
||||
withInitialRegionProp
|
||||
)(FoodMap);
|
||||
)(ProductsMap);
|
||||
|
|
@ -11,7 +11,7 @@ const ImageRecord = Record({
|
|||
url: '',
|
||||
username: '',
|
||||
date: Date.now(),
|
||||
foodItemId: '',
|
||||
productId: '',
|
||||
});
|
||||
|
||||
export type ImageFragment = { id: string, images: OrderedSet<typeof ImageRecord> };
|
||||
|
|
@ -19,7 +19,7 @@ export type ImageFragment = { id: string, images: OrderedSet<typeof ImageRecord>
|
|||
export const buildImageRecord = (imageRaw: ImageRaw) => {
|
||||
return new ImageRecord({
|
||||
...imageRaw,
|
||||
foodItemId: imageRaw.food_item_id,
|
||||
productId: imageRaw.food_item_id,
|
||||
});
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -1,11 +1,10 @@
|
|||
import { Set, Record } from 'immutable';
|
||||
import { type RawFoodItem } from '../apis/FoodItemsApi';
|
||||
import { type RawProduct } from '../apis/ProductsApi';
|
||||
import { type Category } from '../constants/CategoryConstants';
|
||||
import { type Quantity } from '../constants/QuantityConstants';
|
||||
import ImageRecord, { buildImageRecord } from '../records/ImageRecord';
|
||||
import { map, pathOr } from 'ramda';
|
||||
import ImageRecord, { buildImageRecord } from './ImageRecord';
|
||||
|
||||
export type FoodItem = {
|
||||
export type Product = {
|
||||
id: ?string,
|
||||
name: string,
|
||||
placeId: ?number,
|
||||
|
|
@ -20,7 +19,7 @@ export type FoodItem = {
|
|||
lastupdated: number,
|
||||
};
|
||||
|
||||
const FoodRecordDefaults: FoodItem = {
|
||||
const FoodRecordDefaults: Product = {
|
||||
id: '',
|
||||
name: '',
|
||||
placeId: null,
|
||||
|
|
@ -36,19 +35,19 @@ const FoodRecordDefaults: FoodItem = {
|
|||
lastupdated: 0,
|
||||
};
|
||||
|
||||
const FoodItemRecord = Record(FoodRecordDefaults, 'FoodItemRecord');
|
||||
const ProductRecord = Record(FoodRecordDefaults, 'ProductRecord');
|
||||
|
||||
export const createFoodItem = (foodItemRaw: ?RawFoodItem) => {
|
||||
if (!foodItemRaw) {
|
||||
return foodItemRaw;
|
||||
export const createProduct = (productRaw: ?RawProduct) => {
|
||||
if (!productRaw) {
|
||||
return productRaw;
|
||||
}
|
||||
return new FoodItemRecord({
|
||||
...foodItemRaw,
|
||||
placeType: foodItemRaw.placeType,
|
||||
thumbImage: foodItemRaw.thumbimage,
|
||||
// images: new Set(map(buildImageRecord, pathOr([], ['images'], foodItemRaw))),
|
||||
images: new Set([buildImageRecord({ url: foodItemRaw.thumbimage })]),
|
||||
return new ProductRecord({
|
||||
...productRaw,
|
||||
placeType: productRaw.placeType,
|
||||
thumbImage: productRaw.thumbimage,
|
||||
// images: new Set(map(buildImageRecord, pathOr([], ['images'], productRaw))),
|
||||
images: new Set([buildImageRecord({ url: productRaw.thumbimage })]),
|
||||
});
|
||||
};
|
||||
|
||||
export default FoodItemRecord;
|
||||
export default ProductRecord;
|
||||
|
|
@ -1,19 +0,0 @@
|
|||
// @flow
|
||||
import { ReplaySubject } from 'rxjs';
|
||||
import FoodItemRecord from '../records/FoodItemRecord';
|
||||
|
||||
type CreateFoodItemState = {
|
||||
foodItem: FoodItemRecord,
|
||||
loading: boolean,
|
||||
error?: ?Error,
|
||||
};
|
||||
|
||||
const multicaster: ReplaySubject<CreateFoodItemState> = new ReplaySubject();
|
||||
|
||||
export function emitter(val: CreateFoodItemState) {
|
||||
multicaster.next(val);
|
||||
}
|
||||
|
||||
emitter({ foodItem: new FoodItemRecord(), loading: false, error: null });
|
||||
|
||||
export default multicaster;
|
||||
19
js/streams/CreateProductStream.js
Normal file
19
js/streams/CreateProductStream.js
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
// @flow
|
||||
import { ReplaySubject } from 'rxjs';
|
||||
import ProductRecord from '../records/ProductRecord';
|
||||
|
||||
type CreateProductState = {
|
||||
product: ProductRecord,
|
||||
loading: boolean,
|
||||
error?: ?Error,
|
||||
};
|
||||
|
||||
const multicaster: ReplaySubject<CreateProductState> = new ReplaySubject();
|
||||
|
||||
export function emitter(val: CreateProductState) {
|
||||
multicaster.next(val);
|
||||
}
|
||||
|
||||
emitter({ product: new ProductRecord(), loading: false, error: null });
|
||||
|
||||
export default multicaster;
|
||||
|
|
@ -2,7 +2,7 @@
|
|||
import { BehaviorSubject } from 'rxjs';
|
||||
import { Record, List, get } from 'immutable';
|
||||
import filter$ from './FilterStream';
|
||||
import foodItems$ from './FoodItemsStream';
|
||||
import products$ from './ProductsStream';
|
||||
import { test } from 'ramda';
|
||||
|
||||
export type Fave = {
|
||||
|
|
@ -22,20 +22,20 @@ export function emitOne(fave: ?Fave) {
|
|||
}
|
||||
|
||||
export default observable
|
||||
.combineLatest(foodItems$)
|
||||
.map(([faves, foodItems]) => {
|
||||
return faves && faves.map(fave => get(foodItems, get(fave, 'food_item_id')));
|
||||
.combineLatest(products$)
|
||||
.map(([faves, products]) => {
|
||||
return faves && faves.map(fave => get(products, get(fave, 'food_item_id')));
|
||||
})
|
||||
.combineLatest(filter$)
|
||||
.map(([faveFoodItems, filter]) => {
|
||||
.map(([faveProducts, filter]) => {
|
||||
const filterTest = filter.search && test(new RegExp(filter.search, 'i'));
|
||||
return (
|
||||
faveFoodItems &&
|
||||
faveFoodItems.filter(faveFoodItem => {
|
||||
if (!faveFoodItem) {
|
||||
faveProducts &&
|
||||
faveProducts.filter(faveProduct => {
|
||||
if (!faveProduct) {
|
||||
return false;
|
||||
}
|
||||
return !filterTest || filterTest(faveFoodItem.name);
|
||||
return !filterTest || filterTest(faveProduct.name);
|
||||
})
|
||||
);
|
||||
});
|
||||
|
|
|
|||
|
|
@ -1,68 +0,0 @@
|
|||
//@flow
|
||||
import { BehaviorSubject, Observable } from 'rxjs';
|
||||
import FoodItemRecord, { createFoodItem, type FoodItem } from '../records/FoodItemRecord';
|
||||
import { setById } from '../helpers/ImmutableHelpers';
|
||||
import { Map, type Record } from 'immutable';
|
||||
import location$ from './LocationStream';
|
||||
import { getFoodItems, type FoodItemsForLocation } from '../apis/FoodItemsApi';
|
||||
import Filter$ from './FilterStream';
|
||||
import Quantity$ from './QuantityStream';
|
||||
import type { QuantityFragment } from '../constants/QuantityConstants';
|
||||
import { type ImageFragment } from '../records/ImageRecord';
|
||||
import Image$ from './ImagesStream';
|
||||
|
||||
const foodItemSubject: BehaviorSubject<FoodItemRecord> = new BehaviorSubject();
|
||||
|
||||
export function emitter(val?: ?FoodItemRecord) {
|
||||
foodItemSubject.next(val);
|
||||
}
|
||||
|
||||
emitter(null);
|
||||
|
||||
const manualUpdate$ = foodItemSubject.scan(
|
||||
(foodItemMap: Map<string, FoodItemRecord>, foodItem: FoodItemRecord) => {
|
||||
return foodItem ? foodItemMap.set(foodItem.id, foodItem) : foodItemMap;
|
||||
},
|
||||
Map()
|
||||
);
|
||||
|
||||
const fetchedFoodItems$ = Filter$.combineLatest(location$)
|
||||
.debounceTime(200)
|
||||
.mergeMap(([filter, loc]) => {
|
||||
if (!loc) {
|
||||
return Promise.resolve({});
|
||||
}
|
||||
return getFoodItems({ filter, loc });
|
||||
})
|
||||
.map(({ fooditems }: FoodItemsForLocation) => {
|
||||
if (fooditems) {
|
||||
return fooditems.map(createFoodItem).reduce(setById, new Map());
|
||||
}
|
||||
return null;
|
||||
});
|
||||
|
||||
export default fetchedFoodItems$
|
||||
.combineLatest(manualUpdate$, (foodItemMap: Map<string, FoodItemRecord>, manualUpdates) => {
|
||||
if (foodItemMap) {
|
||||
return foodItemMap.mergeDeep(manualUpdates);
|
||||
}
|
||||
})
|
||||
.combineLatest(
|
||||
Quantity$,
|
||||
(
|
||||
foodItems: ?Map<string, FoodItemRecord>,
|
||||
quantitiesFromStream: Map<string, QuantityFragment>
|
||||
) => {
|
||||
if (foodItems) {
|
||||
return foodItems.mergeDeep(quantitiesFromStream);
|
||||
}
|
||||
}
|
||||
)
|
||||
.combineLatest(
|
||||
Image$,
|
||||
(foodItems: ?Map<string, FoodItemRecord>, latestFromImages$: Map<String, ImageFragment>) => {
|
||||
if (foodItems) {
|
||||
return foodItems.mergeDeep(latestFromImages$);
|
||||
}
|
||||
}
|
||||
);
|
||||
|
|
@ -13,18 +13,15 @@ export function emit(val: ?ImageRaw) {
|
|||
// force our observable to emit an initial empty map so that food items will load
|
||||
emit(null);
|
||||
|
||||
export default observable.scan(
|
||||
(imagesByFoodItemId: Map<string, ImageFragment>, image: ImageRaw) => {
|
||||
if (!image || !image.food_item_id) {
|
||||
return imagesByFoodItemId;
|
||||
}
|
||||
export default observable.scan((imagesByProductId: Map<string, ImageFragment>, image: ImageRaw) => {
|
||||
if (!image || !image.food_item_id) {
|
||||
return imagesByProductId;
|
||||
}
|
||||
|
||||
return imagesByFoodItemId.update(image.food_item_id, ({ images = OrderedSet() } = {}) => {
|
||||
return {
|
||||
id: image.food_item_id,
|
||||
images: images.add(buildImageRecord(image)),
|
||||
};
|
||||
});
|
||||
},
|
||||
new Map()
|
||||
);
|
||||
return imagesByProductId.update(image.food_item_id, ({ images = OrderedSet() } = {}) => {
|
||||
return {
|
||||
id: image.food_item_id,
|
||||
images: images.add(buildImageRecord(image)),
|
||||
};
|
||||
});
|
||||
}, new Map());
|
||||
|
|
|
|||
|
|
@ -1,14 +1,14 @@
|
|||
import { ReplaySubject, Observable } from 'rxjs';
|
||||
import { ReplaySubject } from 'rxjs';
|
||||
import { buildPlaceRecord } from '../records/PlaceRecord';
|
||||
import { Map } from 'immutable';
|
||||
import { findNearbyPlaces, getPlaceDetails } from '../apis/GooglePlacesApi';
|
||||
import { findNearbyPlaces } from '../apis/GooglePlacesApi';
|
||||
import { path } from 'ramda';
|
||||
import { type GooglePlaceObj } from '../records/PlaceRecord';
|
||||
import { setById } from '../helpers/ImmutableHelpers';
|
||||
import location$ from './LocationStream';
|
||||
import filter$ from './FilterStream';
|
||||
import FilterRecord from '../records/FilterRecord';
|
||||
import foodItems$ from './FoodItemsStream';
|
||||
// import products$ from './ProductsStream';
|
||||
import PlaceRecord from '../records/PlaceRecord';
|
||||
import geodist from 'geodist';
|
||||
|
||||
|
|
@ -22,9 +22,9 @@ export function emitter(val?: ?PlaceRecord) {
|
|||
|
||||
filter$.subscribe(() => emitter(null));
|
||||
|
||||
// foodItems$
|
||||
// .mergeMap((foodItems = Map()) => Observable.from(foodItems.toArray()))
|
||||
// .mergeMap(([foodItemId, foodItem]) => getPlaceDetails(foodItem))
|
||||
// products$
|
||||
// .mergeMap((products = Map()) => Observable.from(products.toArray()))
|
||||
// .mergeMap(([productId, product]) => getPlaceDetails(product))
|
||||
// .map(buildPlaceRecord)
|
||||
// .subscribe(emitter);
|
||||
|
||||
|
|
@ -63,9 +63,22 @@ location$
|
|||
})
|
||||
.subscribe(places => places && places.map(emitter));
|
||||
|
||||
export default placesSubject.scan((places, place) => {
|
||||
if (!place) {
|
||||
return null;
|
||||
}
|
||||
return setById(places || new Map(), place);
|
||||
});
|
||||
export default placesSubject.scan(
|
||||
(typeToPlace, place) => {
|
||||
if (!place) {
|
||||
return typeToPlace;
|
||||
}
|
||||
|
||||
const { placeType } = place;
|
||||
|
||||
if (Object.keys(typeToPlace).includes(placeType)) {
|
||||
return {
|
||||
...typeToPlace,
|
||||
[placeType]: [...typeToPlace[placeType], place],
|
||||
};
|
||||
}
|
||||
|
||||
return typeToPlace;
|
||||
},
|
||||
{ HEB: [], WholeFoods: [] }
|
||||
);
|
||||
|
|
|
|||
81
js/streams/ProductsStream.js
Normal file
81
js/streams/ProductsStream.js
Normal file
|
|
@ -0,0 +1,81 @@
|
|||
//@flow
|
||||
import { BehaviorSubject } from 'rxjs';
|
||||
import ProductRecord, { createProduct } from '../records/ProductRecord';
|
||||
import { setById } from '../helpers/ImmutableHelpers';
|
||||
import { Map, set, getIn } from 'immutable';
|
||||
import location$ from './LocationStream';
|
||||
import { getProducts, type ProductsForLocation } from '../apis/ProductsApi';
|
||||
import Filter$ from './FilterStream';
|
||||
import Quantity$ from './QuantityStream';
|
||||
import type { QuantityFragment } from '../constants/QuantityConstants';
|
||||
// import { type ImageFragment } from '../records/ImageRecord';
|
||||
// import Image$ from './ImagesStream';
|
||||
import places$ from './PlacesStream';
|
||||
|
||||
const productSubject: BehaviorSubject<ProductRecord> = new BehaviorSubject();
|
||||
|
||||
export function emitter(val?: ?ProductRecord) {
|
||||
productSubject.next(val);
|
||||
}
|
||||
|
||||
emitter(null);
|
||||
|
||||
const manualUpdate$ = productSubject.scan(
|
||||
(productMap: Map<string, ProductRecord>, product: ProductRecord) => {
|
||||
return product ? productMap.set(product.id, product) : productMap;
|
||||
},
|
||||
Map()
|
||||
);
|
||||
|
||||
const fetchedProducts$ = Filter$.combineLatest(location$)
|
||||
.debounceTime(200)
|
||||
.mergeMap(([filter, loc]) => {
|
||||
if (!loc) {
|
||||
return Promise.resolve({});
|
||||
}
|
||||
return getProducts({ filter, loc });
|
||||
})
|
||||
.map(({ products }: ProductsForLocation) => {
|
||||
if (products) {
|
||||
return products.map(createProduct).reduce(setById, new Map());
|
||||
}
|
||||
return null;
|
||||
});
|
||||
|
||||
export default fetchedProducts$
|
||||
.combineLatest(manualUpdate$, (productMap: Map<string, ProductRecord>, manualUpdates) => {
|
||||
if (productMap) {
|
||||
return productMap.mergeDeep(manualUpdates);
|
||||
}
|
||||
})
|
||||
.combineLatest(
|
||||
Quantity$,
|
||||
(
|
||||
products: ?Map<string, ProductRecord>,
|
||||
quantitiesFromStream: Map<string, QuantityFragment>
|
||||
) => {
|
||||
if (products) {
|
||||
return products.mergeDeep(quantitiesFromStream);
|
||||
}
|
||||
}
|
||||
)
|
||||
.combineLatest(places$, (products, places) => {
|
||||
if (!places || !products) {
|
||||
return products;
|
||||
}
|
||||
|
||||
const getPlaceIdForNearest = placeType => getIn(places, [placeType, 0, 'id'], '');
|
||||
|
||||
return products.map(product =>
|
||||
set(product, 'placeId', getPlaceIdForNearest(product.placeType))
|
||||
);
|
||||
});
|
||||
// )
|
||||
// .combineLatest(
|
||||
// Image$,
|
||||
// (products: ?Map<string, ProductRecord>, latestFromImages$: Map<String, ImageFragment>) => {
|
||||
// if (products) {
|
||||
// return products.mergeDeep(latestFromImages$);
|
||||
// }
|
||||
// }
|
||||
// );
|
||||
|
|
@ -12,13 +12,16 @@ export function emit(val: ?QuantityResponse) {
|
|||
// force our observable to emit an initial empty map so that food items will load
|
||||
emit(null);
|
||||
|
||||
export default observable.scan((quantitiesByFoodItemId: Map<string, QuantityFragment>, quantity?: QuantityResponse) => {
|
||||
if (!quantity) {
|
||||
return quantitiesByFoodItemId;
|
||||
}
|
||||
export default observable.scan(
|
||||
(quantitiesByProductId: Map<string, QuantityFragment>, quantity?: QuantityResponse) => {
|
||||
if (!quantity) {
|
||||
return quantitiesByProductId;
|
||||
}
|
||||
|
||||
return quantitiesByFoodItemId.set(quantity.food_item_id, {
|
||||
quantity: quantity.quantity,
|
||||
lastupdated: quantity.date,
|
||||
});
|
||||
}, new Map());
|
||||
return quantitiesByProductId.set(quantity.food_item_id, {
|
||||
quantity: quantity.quantity,
|
||||
lastupdated: quantity.date,
|
||||
});
|
||||
},
|
||||
new Map()
|
||||
);
|
||||
|
|
|
|||
|
|
@ -96,7 +96,7 @@ export default {
|
|||
defaultColor: '#000000',
|
||||
selectedColor: '#017C9A',
|
||||
},
|
||||
foodItemDetails: {
|
||||
productDetails: {
|
||||
actionIconColor: '#017C9A',
|
||||
},
|
||||
placeDetails: {
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue