Fix creating meal list and adding products

This commit is contained in:
= 2021-01-16 22:53:26 +01:00
parent bf835d2a89
commit 1a611fbea1
26 changed files with 600 additions and 176 deletions

View File

@ -0,0 +1,14 @@
import productFormModel from './productFormModel';
const {
formField: {
quantity,
unit,
}
} = productFormModel;
const formInitialValues = {
[quantity.name]: 1,
[unit.name]: 'portion',
};
export default formInitialValues

View File

@ -0,0 +1,17 @@
const productFormModel = {
formId: 'addProductsToMealForm',
formField: {
quantity: {
name: 'quantity',
label: 'Quantity*',
requiredErrorMsg: 'Quantity is required'
},
unit: {
name: 'unit',
label: 'Unit*',
requiredErrorMsg: 'Unit is required'
},
}
};
export default productFormModel

View File

@ -0,0 +1,15 @@
import * as Yup from 'yup';
import productFormModel from './productFormModel';
const {
formField: {
unit,
quantity,
}
} = productFormModel;
const validationSchema =
Yup.object().shape({
[unit.name]: Yup.string().required(`${unit.requiredErrorMsg}`),
[quantity.name]: Yup.number().required(`${quantity.requiredErrorMsg}`),
})
export default validationSchema

View File

@ -0,0 +1,111 @@
import {Button, Dialog, DialogActions, Typography, FormControl, DialogContent, DialogTitle} from "@material-ui/core";
import React from "react";
import {Form, Formik} from "formik";
import { useDispatch, useSelector } from 'react-redux';
import { createStructuredSelector } from 'reselect'
import PropTypes from "prop-types";
import SelectField from "components/SelectField";
import InputField from "components/InputField";
import formInitialValues from "../../FormModel/formInitialValues";
import validationSchema from "../../FormModel/validationSchema";
import productFormModel from "../../FormModel/productFormModel";
import {createMealAction, addProductsToMealAction} from "pages/Home/actions";
import { makeSelectMealId, makeSelectMealLabel } from 'pages/Home/selectors'
import {format} from "date-fns";
const stateSelector = createStructuredSelector({
mealId: makeSelectMealId(),
mealLabel: makeSelectMealLabel()
})
const { formId, formField } = productFormModel;
const ProductDetailsForm = ({
isOpen,
handleClose,
id,
label,
unit,
servingCapacity,
macronutrients
}) => {
const dispatch = useDispatch()
const { mealId, mealLabel } = useSelector(stateSelector)
const unitTypes = [
{ label: 'None', value: undefined },
{ label: 'portion', value: 'portion' },
{ label: unit, value: unit },
]
const today = format(new Date(), "yyyy-MM-dd");
const onSubmit = (values, actions) => {
const products = [{
...values,
product: id,
}]
if (mealId) {
dispatch(addProductsToMealAction({ id: mealId, products }))
} else {
dispatch(createMealAction({ date: today, label: mealLabel, products }))
}
actions.setSubmitting(false);
handleClose();
}
return (
<Dialog
open={isOpen}
>
<Formik
initialValues={formInitialValues}
validationSchema={validationSchema}
onSubmit={onSubmit}
>
{({ isSubmitting }) => (
<Form id={formId}>
<DialogTitle>{label}</DialogTitle>
<DialogContent>
<Typography variant="body2">1 portion = {servingCapacity}${unit}</Typography>
<FormControl>
<InputField label={formField.quantity.label} name={formField.quantity.name} type="number" min="1" />
</FormControl>
<FormControl >
<SelectField label={formField.unit.label} name={formField.unit.name} data={unitTypes} />
</FormControl>
</DialogContent>
<DialogActions>
<Button onClick={handleClose}>
cancel
</Button>
<Button type="submit" color="primary" disabled={isSubmitting}>
Ok
</Button>
</DialogActions>
</Form>
)}
</Formik>
</Dialog>
)
}
ProductDetailsForm.propTypes = {
isOpen: PropTypes.bool.isRequired,
handleClose: PropTypes.func.isRequired,
id: PropTypes.string.isRequired,
label: PropTypes.string.isRequired,
unit: PropTypes.string.isRequired,
servingCapacity: PropTypes.number.isRequired,
macronutrients: PropTypes.shape({
salt: PropTypes.number.isRequired,
calories: PropTypes.number.isRequired,
fat: PropTypes.number.isRequired,
carbohydrates: PropTypes.number.isRequired,
protein: PropTypes.number.isRequired,
}).isRequired
}
export default ProductDetailsForm

View File

@ -1,25 +1,55 @@
import React from 'react'; import React, { useState } from 'react';
import PropTypes from 'prop-types' import PropTypes from 'prop-types'
import {Checkbox, ListItem, ListItemSecondaryAction, ListItemText} from "@material-ui/core"; import {Checkbox, ListItem, Typography, ListItemSecondaryAction, ListItemText} from "@material-ui/core";
import ProductLabel from 'components/ProductLabel' import ProductLabel from 'components/ProductLabel'
import ProductDetailsForm from './Forms/ProductDetailsForm'
const ProductItem = ({ id, verified, eco, label, unit, servingCapacity, macronutrients }) => { const ProductItem = ({ id, verified, eco, label, unit, servingCapacity, macronutrients }) => {
const [isProductDetailsDialogOpen, setIsProductDetailsDialogOpen] = useState(false);
const { calories } = macronutrients
const handleOpenDialog = () => {
setIsProductDetailsDialogOpen(true)
}
const handleCloseDialog = () => {
setIsProductDetailsDialogOpen(false)
}
return ( return (
<ListItem button> <React.Fragment>
<ListItem button onClick={handleOpenDialog}>
<ListItemText <ListItemText
primary={<ProductLabel text={label} verified={verified} eco={eco} />} primary={<ProductLabel text={label} verified={verified} eco={eco} />}
secondary={
<Typography variant="body2">1 portion = {Math.floor((servingCapacity / 100) * calories)}kcal</Typography>
}
disableTypography disableTypography
/> />
<ListItemSecondaryAction> <ListItemSecondaryAction>
<Checkbox <Checkbox
value={id}
name="product"
edge="end" edge="end"
/> />
</ListItemSecondaryAction> </ListItemSecondaryAction>
</ListItem> </ListItem>
<ProductDetailsForm
isOpen={isProductDetailsDialogOpen}
id={id}
handleClose={handleCloseDialog}
verified={verified}
eco={eco}
label={label}
unit={unit}
servingCapacity={servingCapacity}
macronutrients={macronutrients}
/>
</React.Fragment>
); );
}; };
ProductItem.propTypes = { ProductItem.propTypes = {
selected: PropTypes.bool.isRequired,
id: PropTypes.string.isRequired, id: PropTypes.string.isRequired,
verified: PropTypes.bool.isRequired, verified: PropTypes.bool.isRequired,
eco: PropTypes.bool.isRequired, eco: PropTypes.bool.isRequired,

View File

@ -10,22 +10,26 @@ import {
Dialog, Dialog,
List, List,
} from '@material-ui/core'; } from '@material-ui/core';
import {Add as AddIcon, Delete as DeleteIcon} from "@material-ui/icons"; import { useDispatch } from 'react-redux'
import {Add as AddIcon} from "@material-ui/icons";
import SearchInput from 'components/SearchInput' import SearchInput from 'components/SearchInput'
import BarcodeScanner from 'components/BarcodeScanner' import BarcodeScanner from 'components/BarcodeScanner'
import { createStructuredSelector } from 'reselect' import { createStructuredSelector } from 'reselect'
import { makeSelectProducts, makeSelectProduct } from 'pages/Home/selectors'
import { useSelector } from 'react-redux' import { useSelector } from 'react-redux'
import { makeSelectProducts } from 'pages/Home/selectors'
import ProductItem from './ProductItem' import ProductItem from './ProductItem'
import { setSelectedMealAction } from 'pages/Home/actions'
const stateSelector = createStructuredSelector({ const stateSelector = createStructuredSelector({
products: makeSelectProducts(), products: makeSelectProducts(),
product: makeSelectProduct(),
}) })
const AddProductToMealDialog = ({ mealLabel }) => { const AddProductToMealDialog = ({ mealId, mealLabel }) => {
const [isOpen, setIsOpen] = useState(false); const [isOpen, setIsOpen] = useState(false);
const { products, product } = useSelector(stateSelector) const { products } = useSelector(stateSelector)
const dispatch = useDispatch()
const selectMeal = () => dispatch(setSelectedMealAction({ label: mealLabel, id: mealId }))
const handleClose = (event) => { const handleClose = (event) => {
event.stopPropagation(); event.stopPropagation();
@ -35,8 +39,8 @@ const AddProductToMealDialog = ({ mealLabel }) => {
const handleOpen = (event) => { const handleOpen = (event) => {
event.stopPropagation(); event.stopPropagation();
setIsOpen(true) setIsOpen(true)
selectMeal();
} }
return ( return (
<React.Fragment> <React.Fragment>
<IconButton onClick={handleOpen} onFocus={(event) => event.stopPropagation()}> <IconButton onClick={handleOpen} onFocus={(event) => event.stopPropagation()}>
@ -61,6 +65,7 @@ const AddProductToMealDialog = ({ mealLabel }) => {
<List> <List>
{products.map(({ id, verified, eco, salt, brand, servingCapacity, barcode, label, unit, calories, fat, carbohydrates, protein }) => ( {products.map(({ id, verified, eco, salt, brand, servingCapacity, barcode, label, unit, calories, fat, carbohydrates, protein }) => (
<ProductItem <ProductItem
selected={false}
key={id} key={id}
id={id} id={id}
verified={verified} verified={verified}

View File

@ -7,26 +7,20 @@ import {
Typography Typography
} from "@material-ui/core"; } from "@material-ui/core";
import React from "react"; import React from "react";
import PropTypes from 'prop-types'
import {MACRONUTRIENTS, MEALS_LIST} from "utils/mock"; import {MACRONUTRIENTS, MEALS_LIST} from "utils/mock";
import MacronutrientsChart from "components/MacronutrientsChart"; import MacronutrientsChart from "components/MacronutrientsChart";
import Dish from "components/ProductCard"; import ProductCard from "components/ProductCard";
import AddProductToMealDialog from 'components/AddProductToMealDialog'; import AddProductToMealDialog from 'components/AddProductToMealDialog';
const MealCard = ({ label, products }) => {
const calcMealMacronutrients = (dishes) => {
const mealMacronutrients = dishes
.flatMap(({ macronutrients }) => macronutrients)
.reduce((acc, { label, value, unit }) => ({
...acc,
[label]: {
label,
unit,
value: acc[label] ? acc[label].value + value : value
}
}), {})
return Object.entries(mealMacronutrients).map(([_, macronutrients]) => macronutrients) // {/*{*/}
} // {/* calcMealMacronutrients(products).map(({ unit, value, label }, index) => (*/}
// {/* <MacronutrientsChart current={value} unit={unit} max={5000} label={label} key={index} />*/}
// {/* ))*/}
// {/*}*/}
const MealCard = ({ id, label, products }) => {
return ( return (
<Accordion> <Accordion>
@ -36,25 +30,22 @@ const MealCard = ({ label, products }) => {
<Typography variant="h5"> <Typography variant="h5">
{label} {label}
</Typography> </Typography>
<AddProductToMealDialog mealLabel={label} /> <AddProductToMealDialog mealId={id} mealLabel={label} />
</Grid> </Grid>
<Grid container alignItems="center"> <Grid container alignItems="center">
{
calcMealMacronutrients(products).map(({ unit, value, label }, index) => (
<MacronutrientsChart current={value} unit={unit} max={5000} label={label} key={index} />
))
}
</Grid> </Grid>
</Grid> </Grid>
</AccordionSummary> </AccordionSummary>
<AccordionDetails> <AccordionDetails>
<Grid item xs={12}> <Grid item xs={12}>
<List> <List>
{MEALS_LIST.map(({ label, macronutrients }, index) => ( {products.map(({ _id, product, quantity, unit }) => (
<Dish <ProductCard
label={label} id={_id}
macronutrients={macronutrients} product={product}
key={index} quantity={quantity}
unit={unit}
key={_id}
/> />
))} ))}
</List> </List>
@ -64,4 +55,13 @@ const MealCard = ({ label, products }) => {
); );
}; };
MealCard.propTypes = {
id: PropTypes.string,
label: PropTypes.string.isRequired,
products: PropTypes.oneOfType([
PropTypes.array,
PropTypes.arrayOf(PropTypes.object),
]).isRequired,
}
export default MealCard; export default MealCard;

View File

@ -4,19 +4,28 @@ import React from "react";
import MacronutrientsDetails from "components/MacronutrientsDetails"; import MacronutrientsDetails from "components/MacronutrientsDetails";
import EditProductDialog from "components/EditProductDialog"; import EditProductDialog from "components/EditProductDialog";
import {Delete as DeleteIcon} from "@material-ui/icons"; import {Delete as DeleteIcon} from "@material-ui/icons";
import ProductLabel from 'components/ProductLabel'
const ProductCard = ({ label, macronutrients }) => { const ProductCard = ({ id, product, quantity, unit }) => {
const { label, calories, eco, verified } = product
const calculateCalories = () => {
const isUnitPortion = unit === 'portion'
if (isUnitPortion) {
return calories * quantity
}
return (calories / 100) * quantity
}
return ( return (
<ListItem> <ListItem>
<ListItemText <ListItemText
primary={label} primary={<ProductLabel eco={eco} verified={verified} text={label}/>}
disableTypography disableTypography
secondary={ secondary={`calories: ${calculateCalories()}`}
<MacronutrientsDetails
macronutrients={macronutrients}
/>
}
/> />
<ListItemSecondaryAction> <ListItemSecondaryAction>
<EditProductDialog /> <EditProductDialog />
@ -29,11 +38,10 @@ const ProductCard = ({ label, macronutrients }) => {
} }
ProductCard.propTypes = { ProductCard.propTypes = {
label: PropTypes.string.isRequired, id: PropTypes.string.isRequired,
macronutrients: PropTypes.arrayOf(PropTypes.shape({ product: PropTypes.object.isRequired,
value: PropTypes.number.isRequired, quantity: PropTypes.number.isRequired,
unit: PropTypes.string.isRequired, unit: PropTypes.string.isRequired,
})).isRequired,
} }
export default ProductCard export default ProductCard

View File

@ -1,31 +0,0 @@
import React from 'react';
import { useFormikContext } from 'formik';
import {Typography, Grid, Box} from '@material-ui/core';
import useStyles from './styles';
const ReviewProfile = () => {
const classes = useStyles();
const { values: formValues } = useFormikContext();
console.log('TODO: ReviewProfile')
return (
<React.Fragment>
<Grid container direction="column">
<Typography variant="h6" gutterBottom className={classes.title}>
Payment details
</Typography>
<Grid container alignContent="space-between">
{Object.entries(formValues).map(([label, value], index) => (
<Grid key={index} item xs={12}>
<Typography gutterBottom>{label}</Typography>
<Typography gutterBottom>{value}</Typography>
</Grid>
))}
</Grid>
</Grid>
</React.Fragment>
);
}
export default ReviewProfile

View File

@ -1,15 +0,0 @@
import { makeStyles } from '@material-ui/core/styles';
const useStyles = makeStyles((theme) => ({
listItem: {
padding: theme.spacing(1, 0),
},
total: {
fontWeight: 700,
},
title: {
marginTop: theme.spacing(2),
},
}));
export default useStyles

View File

@ -1,13 +1,13 @@
export const GET_PROFILE_REQUEST = 'app/ProfilePage/GET_PROFILE_REQUEST'; export const GET_PROFILE_REQUEST = 'app/CreateProfilePage/GET_PROFILE_REQUEST';
export const GET_PROFILE_SUCCESS = 'app/ProfilePage/GET_PROFILE_SUCCESS'; export const GET_PROFILE_SUCCESS = 'app/CreateProfilePage/GET_PROFILE_SUCCESS';
export const GET_PROFILE_ERROR = 'app/ProfilePage/GET_PROFILE_ERROR'; export const GET_PROFILE_ERROR = 'app/CreateProfilePage/GET_PROFILE_ERROR';
export const CREATE_PROFILE_REQUEST = 'app/ProfilePage/CREATE_PROFILE_REQUEST'; export const CREATE_PROFILE_REQUEST = 'app/CreateProfilePage/CREATE_PROFILE_REQUEST';
export const CREATE_PROFILE_SUCCESS = 'app/ProfilePage/CREATE_PROFILE_SUCCESS'; export const CREATE_PROFILE_SUCCESS = 'app/CreateProfilePage/CREATE_PROFILE_SUCCESS';
export const CREATE_PROFILE_ERROR = 'app/ProfilePage/CREATE_PROFILE_ERROR'; export const CREATE_PROFILE_ERROR = 'app/CreateProfilePage/CREATE_PROFILE_ERROR';
export const UPDATE_PROFILE_REQUEST = 'app/ProfilePage/UPDATE_PROFILE_REQUEST'; export const UPDATE_PROFILE_REQUEST = 'app/CreateProfilePage/UPDATE_PROFILE_REQUEST';
export const UPDATE_PROFILE_SUCCESS = 'app/ProfilePage/UPDATE_PROFILE_SUCCESS'; export const UPDATE_PROFILE_SUCCESS = 'app/CreateProfilePage/UPDATE_PROFILE_SUCCESS';
export const UPDATE_PROFILE_ERROR = 'app/ProfilePage/UPDATE_PROFILE_ERROR'; export const UPDATE_PROFILE_ERROR = 'app/CreateProfilePage/UPDATE_PROFILE_ERROR';
export const PROFILE_INPUT_CHANGE = 'app/ProfilePage/PROFILE_INPUT_CHANGE'; export const PROFILE_INPUT_CHANGE = 'app/CreateProfilePage/PROFILE_INPUT_CHANGE';

View File

@ -5,7 +5,6 @@ import { useDispatch } from 'react-redux';
import { Formik, Form } from 'formik'; import { Formik, Form } from 'formik';
import GoalForm from './Forms/GoalForm'; import GoalForm from './Forms/GoalForm';
import PersonalDetailsForm from './Forms/PersonalDetailsForm'; import PersonalDetailsForm from './Forms/PersonalDetailsForm';
import ReviewProfile from './ReviewProfile';
import reducer from "./reducer"; import reducer from "./reducer";
import saga from "./saga"; import saga from "./saga";
import { createProfileAction } from './actions' import { createProfileAction } from './actions'
@ -14,7 +13,7 @@ import validationSchema from './FormModel/validationSchema'
import formInitialValues from './FormModel/formInitialValues' import formInitialValues from './FormModel/formInitialValues'
import profileFormModel from './FormModel/profileFormModel' import profileFormModel from './FormModel/profileFormModel'
const steps = ['Your goal', 'Personal details', 'Review your profile']; const steps = ['Your goal', 'Personal details'];
const { formId, formField } = profileFormModel; const { formId, formField } = profileFormModel;
const renderStepContent = (step) => { const renderStepContent = (step) => {
@ -23,8 +22,6 @@ const renderStepContent = (step) => {
return <GoalForm formField={formField} />; return <GoalForm formField={formField} />;
case 1: case 1:
return <PersonalDetailsForm formField={formField} />; return <PersonalDetailsForm formField={formField} />;
case 2:
return <ReviewProfile />;
default: default:
throw new Error('Unknown step'); throw new Error('Unknown step');
} }
@ -42,7 +39,6 @@ const ProfilePage = () => {
const dispatch = useDispatch(); const dispatch = useDispatch();
const submitForm = (values, actions) => { const submitForm = (values, actions) => {
console.log(values)
dispatch(createProfileAction(values)) dispatch(createProfileAction(values))
actions.setSubmitting(false); actions.setSubmitting(false);
setActiveStep(activeStep + 1); setActiveStep(activeStep + 1);

View File

@ -16,9 +16,16 @@ import {
SEARCH_PRODUCT_BY_BARCODE_SUCCESS, SEARCH_PRODUCT_BY_BARCODE_SUCCESS,
SEARCH_PRODUCT_BY_BARCODE_ERROR, SEARCH_PRODUCT_BY_BARCODE_ERROR,
SEARCH_PRODUCT_BY_LABEL_SUCCESS, SEARCH_PRODUCT_BY_LABEL_SUCCESS,
SEARCH_PRODUCT_BY_LABEL_ERROR SEARCH_PRODUCT_BY_LABEL_ERROR,
SET_SELECTED_MEAL,
} from './constants'; } from './constants';
export const setSelectedMealAction = ({ label, id }) => ({
type: SET_SELECTED_MEAL,
label,
id,
})
export const searchProductByLabelAction = ({ label }) => ({ export const searchProductByLabelAction = ({ label }) => ({
type: SEARCH_PRODUCT_BY_LABEL_REQUEST, type: SEARCH_PRODUCT_BY_LABEL_REQUEST,
label, label,
@ -68,11 +75,9 @@ export const updateMealAction = () => ({
type: UPDATE_MEAL_REQUEST, type: UPDATE_MEAL_REQUEST,
}) })
export const updateMealSuccessAction = ({ label, products, date }) => ({ export const updateMealSuccessAction = ({ meal }) => ({
type: UPDATE_MEAL_SUCCESS, type: UPDATE_MEAL_SUCCESS,
label, meal
products,
date,
}) })
export const updateMealErrorAction = ({error}) => ({ export const updateMealErrorAction = ({error}) => ({
@ -87,8 +92,9 @@ export const createMealAction = ({ label, products, date }) => ({
date, date,
}) })
export const createMealSuccessAction = ({ label, products, date }) => ({ export const createMealSuccessAction = ({ id, label, products, date }) => ({
type: CREATE_MEAL_SUCCESS, type: CREATE_MEAL_SUCCESS,
id,
label, label,
products, products,
date, date,
@ -99,9 +105,10 @@ export const createMealErrorAction = ({error}) => ({
error, error,
}) })
export const addProductsToMealAction = ({ products }) => ({ export const addProductsToMealAction = ({ id, products }) => ({
type: ADD_PRODUCTS_TO_MEAL_REQUEST, type: ADD_PRODUCTS_TO_MEAL_REQUEST,
products, products,
id,
}) })
export const addProductsToSuccessAction = ({ meal }) => ({ export const addProductsToSuccessAction = ({ meal }) => ({
@ -113,4 +120,3 @@ export const addProductsToMealErrorAction = ({error}) => ({
type: ADD_PRODUCTS_TO_MEAL_ERROR, type: ADD_PRODUCTS_TO_MEAL_ERROR,
error, error,
}) })

View File

@ -21,3 +21,5 @@ export const SEARCH_PRODUCT_BY_LABEL_ERROR = 'app/HomePage/SEARCH_PRODUCT_BY_LAB
export const SEARCH_PRODUCT_BY_BARCODE_REQUEST = 'app/HomePage/SEARCH_PRODUCT_BY_BARCODE_REQUEST' export const SEARCH_PRODUCT_BY_BARCODE_REQUEST = 'app/HomePage/SEARCH_PRODUCT_BY_BARCODE_REQUEST'
export const SEARCH_PRODUCT_BY_BARCODE_SUCCESS = 'app/HomePage/SEARCH_PRODUCT_BY_BARCODE_SUCCESS'; export const SEARCH_PRODUCT_BY_BARCODE_SUCCESS = 'app/HomePage/SEARCH_PRODUCT_BY_BARCODE_SUCCESS';
export const SEARCH_PRODUCT_BY_BARCODE_ERROR = 'app/HomePage/SEARCH_PRODUCT_BY_BARCODE_ERROR'; export const SEARCH_PRODUCT_BY_BARCODE_ERROR = 'app/HomePage/SEARCH_PRODUCT_BY_BARCODE_ERROR';
export const SET_SELECTED_MEAL = 'app/HomePage/SET_SELECTED_MEAL'

View File

@ -11,8 +11,6 @@ import { getMealsAction } from './actions'
import MealCard from "components/MealCard"; import MealCard from "components/MealCard";
import { createStructuredSelector } from 'reselect' import { createStructuredSelector } from 'reselect'
const TODAY = format(new Date(), "yyyy-MM-dd")
const stateSelector = createStructuredSelector({ const stateSelector = createStructuredSelector({
meals: makeSelectMeals() meals: makeSelectMeals()
}) })
@ -26,13 +24,14 @@ const HomePage = () => {
const dispatch = useDispatch(); const dispatch = useDispatch();
useEffect(() => { useEffect(() => {
dispatch(getMealsAction({ date: '2020-12-29' })) const today = format(new Date(), "yyyy-MM-dd");
dispatch(getMealsAction({ date: today }));
}, []); }, []);
return ( return (
<Container> <Container>
{meals.map(({ id, products, label }) => ( {meals.map(({ id, products, label }, index) => (
<MealCard label={label} products={products} key={id} /> <MealCard id={id} label={label} products={products} key={index} />
))} ))}
</Container> </Container>
); );

View File

@ -18,42 +18,43 @@ import {
SEARCH_PRODUCT_BY_BARCODE_ERROR, SEARCH_PRODUCT_BY_BARCODE_ERROR,
SEARCH_PRODUCT_BY_LABEL_REQUEST, SEARCH_PRODUCT_BY_LABEL_REQUEST,
SEARCH_PRODUCT_BY_LABEL_SUCCESS, SEARCH_PRODUCT_BY_LABEL_SUCCESS,
SEARCH_PRODUCT_BY_LABEL_ERROR SEARCH_PRODUCT_BY_LABEL_ERROR,
SET_SELECTED_MEAL,
} from './constants'; } from './constants';
const defaultMeals = [ const defaultMeals = [
{ {
id: 1, id: null,
label: 'Breakfast', label: 'Breakfast',
products: [], products: [],
}, },
{ {
id: 2, id: null,
label: 'Snack I', label: 'Snack I',
products: [], products: [],
}, },
{ {
id: 3, id: null,
label: 'Lunch', label: 'Lunch',
products: [], products: [],
}, },
{ {
id: 4, id: null,
label: 'Snack II', label: 'Snack II',
products: [], products: [],
}, },
{ {
id: 5, id: null,
label: 'Dinner', label: 'Dinner',
products: [], products: [],
}, },
{ {
id: 6, id: null,
label: 'Snack III', label: 'Snack III',
products: [], products: [],
}, },
{ {
id: 7, id: null,
label: 'Supper', label: 'Supper',
products: [], products: [],
}, },
@ -68,14 +69,35 @@ export const initialState = {
barcode: '', barcode: '',
product: {}, product: {},
date: '', date: '',
mealId: '', form: {
id: '',
label: '',
products: [],
date: null,
},
meals: defaultMeals meals: defaultMeals
}; };
const homePageReducer = produce((draft, action) => { const homePageReducer = produce((draft, action) => {
switch(action.type) { switch(action.type) {
case SET_SELECTED_MEAL:
draft.form.id = action.id;
draft.form.label = action.label.toLowerCase();
break;
case CREATE_MEAL_REQUEST:
draft.isLoading = true;
draft.form.label = action.label.toLowerCase();
draft.form.products = action.products;
draft.form.date = action.date;
break;
case GET_MEALS_SUCCESS: case GET_MEALS_SUCCESS:
draft.meals = action.meals.length || defaultMeals; draft.meals =
action.meals.length > 0
? action.meals
: defaultMeals;
draft.isLoading = false; draft.isLoading = false;
break; break;
@ -105,7 +127,7 @@ const homePageReducer = produce((draft, action) => {
break; break;
case UPDATE_MEAL_SUCCESS: case UPDATE_MEAL_SUCCESS:
console.log(UPDATE_MEAL_SUCCESS, 'UPDATE_MEAL_SUCCESS')
draft.isLoading = false; draft.isLoading = false;
break; break;
@ -119,13 +141,19 @@ const homePageReducer = produce((draft, action) => {
break; break;
case CREATE_MEAL_SUCCESS: case CREATE_MEAL_SUCCESS:
draft.meals.push(action.meal); const { id, label, products, date } = action
draft.meals.push({ id, label, products, date });
draft.isLoading = false; draft.isLoading = false;
break; break;
case CREATE_MEAL_REQUEST:
case UPDATE_MEAL_REQUEST:
case ADD_PRODUCTS_TO_MEAL_REQUEST: case ADD_PRODUCTS_TO_MEAL_REQUEST:
draft.form.id = action.id;
draft.form.products = action.products;
draft.isLoading = true;
break;
case UPDATE_MEAL_REQUEST:
draft.isLoading = true; draft.isLoading = true;
break; break;

View File

@ -1,8 +1,8 @@
import { takeLatest, call, put, select } from 'redux-saga/effects'; import { takeLatest, call, put, select } from 'redux-saga/effects';
import {api, request, routes} from 'utils'; import {api, request, routes} from 'utils';
import { GET_MEALS_REQUEST, UPDATE_MEAL_REQUEST, SEARCH_PRODUCT_BY_BARCODE_REQUEST, SEARCH_PRODUCT_BY_LABEL_REQUEST, CREATE_MEAL_REQUEST, ADD_PRODUCTS_TO_MEAL_REQUEST } from './constants'; import { GET_MEALS_REQUEST, UPDATE_MEAL_REQUEST, SEARCH_PRODUCT_BY_BARCODE_REQUEST, SEARCH_PRODUCT_BY_LABEL_REQUEST, CREATE_MEAL_REQUEST, ADD_PRODUCTS_TO_MEAL_REQUEST } from './constants';
import { searchProductByLabelErrorAction, searchProductByLabelSuccessAction, createMealSuccessAction, createMealErrorAction, searchProductByBarcodeSuccessAction, searchProductByBarcodeErrorAction, updateMealErrorAction, updateMealSuccessAction, getMealsSuccessAction, getMealsErrorAction } from './actions'; import { searchProductByLabelErrorAction, addProductsToSuccessAction, addProductsToMealErrorAction, searchProductByLabelSuccessAction, createMealSuccessAction, createMealErrorAction, searchProductByBarcodeSuccessAction, searchProductByBarcodeErrorAction, updateMealErrorAction, updateMealSuccessAction, getMealsSuccessAction, getMealsErrorAction } from './actions';
import { makeSelectLabel, makeSelectMealId, makeSelectBarcode, makeSelectProducts, makeSelectDate, } from './selectors' import { makeSelectLabel, makeSelectFormProducts, makeSelectMealLabel, makeSelectMealId, makeSelectBarcode, makeSelectDate, } from './selectors'
import { makeSelectTokens } from 'containers/App/selectors' import { makeSelectTokens } from 'containers/App/selectors'
import {push} from "connected-react-router"; import {push} from "connected-react-router";
@ -29,8 +29,8 @@ export function* getMeals() {
export function* updateMeal() { export function* updateMeal() {
const { access } = yield select(makeSelectTokens()); const { access } = yield select(makeSelectTokens());
const label = yield select(makeSelectLabel()); const products = yield select(makeSelectFormProducts());
const products = yield select(makeSelectProducts()); const label = yield select(makeSelectMealLabel());
const date = yield select(makeSelectDate()); const date = yield select(makeSelectDate());
const mealId = yield select(makeSelectMealId()); const mealId = yield select(makeSelectMealId());
@ -43,8 +43,9 @@ export function* updateMeal() {
}; };
try { try {
const { birthday, gender, height, currentWeight, goalWeight, rateOfChange, activity } = yield call(request, requestURL, requestParameters); const meal = yield call(request, requestURL, requestParameters);
yield put(updateMealSuccessAction({birthday, gender, height, currentWeight, goalWeight, rateOfChange, activity})); console.log('pages/home/saga/UPDATE_MEAL', meal)
// yield put(updateMealSuccessAction({ meal }));
} catch (error) { } catch (error) {
yield put(updateMealErrorAction({error: error.message})); yield put(updateMealErrorAction({error: error.message}));
} }
@ -52,12 +53,14 @@ export function* updateMeal() {
export function* createMeal() { export function* createMeal() {
const { access } = yield select(makeSelectTokens()); const { access } = yield select(makeSelectTokens());
const label = yield select(makeSelectLabel()); const label = yield select(makeSelectMealLabel());
const products = yield select(makeSelectProducts()); const products = yield select(makeSelectFormProducts());
const date = yield select(makeSelectDate()); const date = yield select(makeSelectDate());
const requestURL = api.meals; const requestURL = api.meals;
console.log(products)
const requestParameters = { const requestParameters = {
method: 'POST', method: 'POST',
headers: { Accept: 'application/json', 'Content-Type': 'application/json', Authorization: `Bearer ${access.token}`, }, headers: { Accept: 'application/json', 'Content-Type': 'application/json', Authorization: `Bearer ${access.token}`, },
@ -65,8 +68,8 @@ export function* createMeal() {
}; };
try { try {
const { id, label, products, date } = yield call(request, requestURL, requestParameters); const meal = yield call(request, requestURL, requestParameters);
yield put(createMealSuccessAction({ id, label, products, date })); yield put(createMealSuccessAction(meal));
yield put(push(routes.dashboard.path)); yield put(push(routes.dashboard.path));
} catch (error) { } catch (error) {
yield put(createMealErrorAction({error: error.message})); yield put(createMealErrorAction({error: error.message}));
@ -75,23 +78,23 @@ export function* createMeal() {
export function* addProductsToMeal() { export function* addProductsToMeal() {
const { access } = yield select(makeSelectTokens()); const { access } = yield select(makeSelectTokens());
const products = yield select(makeSelectProducts()); const products = yield select(makeSelectFormProducts());
const mealId = yield select(makeSelectMealId()); const mealId = yield select(makeSelectMealId());
const requestURL = `${api.meals}/${mealId}`; const requestURL = `${api.meals}/${mealId}`;
const requestParameters = { const requestParameters = {
method: 'POST', method: 'PATCH',
headers: { Accept: 'application/json', 'Content-Type': 'application/json', Authorization: `Bearer ${access.token}`, }, headers: { Accept: 'application/json', 'Content-Type': 'application/json', Authorization: `Bearer ${access.token}`, },
body: JSON.stringify({ products }), body: JSON.stringify({ products }),
}; };
try { try {
const { meal } = yield call(request, requestURL, requestParameters); const meal = yield call(request, requestURL, requestParameters);
yield put(createMealSuccessAction({ meal })); yield put(addProductsToSuccessAction({ meal }));
yield put(push(routes.dashboard.path)); yield put(push(routes.dashboard.path));
} catch (error) { } catch (error) {
yield put(createMealErrorAction({error: error.message})); yield put(addProductsToMealErrorAction({error: error.message}));
} }
} }

View File

@ -12,9 +12,6 @@ const makeSelectIsLoading = () =>
const makeSelectMeals = () => const makeSelectMeals = () =>
createSelector(selectHomePageDomain, (substate) => substate.meals); createSelector(selectHomePageDomain, (substate) => substate.meals);
const makeSelectMealId = () =>
createSelector(selectHomePageDomain, (substate) => substate.mealId);
const makeSelectDate = () => const makeSelectDate = () =>
createSelector(selectHomePageDomain, (substate) => substate.date); createSelector(selectHomePageDomain, (substate) => substate.date);
@ -30,8 +27,19 @@ const makeSelectProduct = () =>
const makeSelectBarcode = () => const makeSelectBarcode = () =>
createSelector(selectHomePageDomain, (substate) => substate.barocde); createSelector(selectHomePageDomain, (substate) => substate.barocde);
const makeSelectMealLabel = () =>
createSelector(selectHomePageDomain, (substate) => substate.form.label);
const makeSelectMealId = () =>
createSelector(selectHomePageDomain, (substate) => substate.form.id);
const makeSelectFormProducts = () =>
createSelector(selectHomePageDomain, (substate) => substate.form.products);
export { export {
selectHomePageDomain, selectHomePageDomain,
makeSelectFormProducts,
makeSelectMealLabel,
makeSelectMealId, makeSelectMealId,
makeSelectDate, makeSelectDate,
makeSelectLabel, makeSelectLabel,

View File

@ -18,7 +18,6 @@ import useStyles from './styles'
import validationSchema from './FormModel/validationSchema' import validationSchema from './FormModel/validationSchema'
import formInitialValues from './FormModel/formInitialValues' import formInitialValues from './FormModel/formInitialValues'
import loginFormModel from './FormModel/loginFormModel' import loginFormModel from './FormModel/loginFormModel'
import reducer from "./reducer";
import saga from "./saga"; import saga from "./saga";
const { const {

View File

@ -0,0 +1,29 @@
import {
GET_PROFILE_REQUEST,
GET_PROFILE_SUCCESS,
GET_PROFILE_ERROR,
} from './constants';
export const getProfileAction = () => ({
type: GET_PROFILE_REQUEST,
})
export const getProfileSuccessAction = ({ weeksToGoal, currentWeight, goal, dailyCalories, birthday, gender, height, weight, goalWeight, rateOfChange, activity}) => ({
type: GET_PROFILE_SUCCESS,
birthday,
weeksToGoal,
dailyCalories,
gender,
height,
weight,
goal,
currentWeight,
goalWeight,
rateOfChange,
activity
})
export const getProfileErrorAction = ({error}) => ({
type: GET_PROFILE_ERROR,
error,
})

View File

@ -0,0 +1,3 @@
export const GET_PROFILE_REQUEST = 'app/ProfilePage/GET_PROFILE_REQUEST';
export const GET_PROFILE_SUCCESS = 'app/ProfilePage/GET_PROFILE_SUCCESS';
export const GET_PROFILE_ERROR = 'app/ProfilePage/GET_PROFILE_ERROR';

View File

@ -1,10 +1,59 @@
import React from 'react'; import React, { useEffect } from 'react';
import {useInjectSaga, useInjectReducer} from "redux-injectors";
import { useDispatch, useSelector } from 'react-redux'
import { Grid, List, ListItem, ListItemText } from '@material-ui/core'
import { startCase } from 'lodash'
import saga from "./saga";
import reducer from "./reducer";
import {getProfileAction} from './actions'
import { createStructuredSelector } from 'reselect'
import {
makeSelectBirthday,
makeSelectHeight,
makeSelectCurrentWeight,
makeSelectGoalWeight,
makeSelectRateOfChange,
makeSelectActivity,
makeSelectGoal,
makeSelectGender,
makeSelectDailyCalories,
makeSelectWeeksToGoal,
} from './selectors'
const stateSelector = createStructuredSelector({
birthday: makeSelectBirthday(),
height: makeSelectHeight(),
currentWeight: makeSelectCurrentWeight(),
goalWeight: makeSelectGoalWeight(),
rateOfChange: makeSelectRateOfChange(),
activity: makeSelectActivity(),
goal: makeSelectGoal(),
gender: makeSelectGender(),
dailyCalories: makeSelectDailyCalories(),
weeksToGoal: makeSelectWeeksToGoal(),
})
const key = 'profilePage'
const ProfilePage = () => { const ProfilePage = () => {
useInjectSaga({ key, saga });
useInjectReducer({ key, reducer })
const dispatch = useDispatch()
const userProfileDetails = useSelector(stateSelector)
useEffect(() => {
dispatch(getProfileAction())
}, [])
return ( return (
<div> <Grid container>
ProfilePage <List>
</div> {Object.keys(userProfileDetails).map((key, index) => (
<ListItem key={index}>
<ListItemText primary={`${startCase(key)}: ${userProfileDetails[key]}`} />
</ListItem>
))}
</List>
</Grid>
); );
}; };

View File

@ -0,0 +1,51 @@
import produce from 'immer';
import {
GET_PROFILE_REQUEST,
GET_PROFILE_SUCCESS,
GET_PROFILE_ERROR,
} from './constants';
import {format} from "date-fns";
export const initialState = {
isLoading: false,
error: {},
gender: '',
goal: 0,
birthday: '',
height: 0,
currentWeight: 0,
goalWeight: 0,
rateOfChange: 0,
activity: 0,
dailyCalories: 0,
weeksToGoal: 0,
};
const loginPageReducer = produce((draft, action) => {
switch(action.type) {
case GET_PROFILE_SUCCESS:
draft.birthday = action.birthday;
draft.gender = action.gender;
draft.goal = action.goal;
draft.height = action.height;
draft.currentWeight = action.currentWeight;
draft.goalWeight = action.goalWeight;
draft.rateOfChange = action.rateOfChange;
draft.activity = action.activity;
draft.weeksToGoal = action.weeksToGoal;
draft.dailyCalories = action.dailyCalories;
draft.isLoading = false;
break;
case GET_PROFILE_REQUEST:
draft.isLoading = true;
break;
case GET_PROFILE_ERROR:
draft.isLoading = false;
draft.error = action.error;
break;
}
}, initialState);
export default loginPageReducer;

40
src/pages/Profile/saga.js Normal file
View File

@ -0,0 +1,40 @@
import { takeLatest, call, put, select } from 'redux-saga/effects';
import {api, request} from 'utils';
import { GET_PROFILE_REQUEST } from './constants';
import { getProfileErrorAction, getProfileSuccessAction } from './actions';
import { makeSelectTokens } from 'containers/App/selectors'
export function* getProfile() {
const { access } = yield select(makeSelectTokens());
const requestURL = api.profile;
const requestParameters = {
method: 'GET',
headers: {
Authorization: `Bearer ${access.token}`,
},
};
try {
const {
birthday,
gender,
height,
currentWeight,
goalWeight,
rateOfChange,
goal,
activity,
dailyCalories,
weeksToGoal,
} = yield call(request, requestURL, requestParameters);
yield put(getProfileSuccessAction({ weeksToGoal, goal, dailyCalories, birthday, gender, height, currentWeight, goalWeight, rateOfChange, activity}));
} catch (error) {
yield put(getProfileErrorAction({ error: error.message }));
}
}
export default function* profilePageSaga() {
yield takeLatest(GET_PROFILE_REQUEST, getProfile);
}

View File

@ -0,0 +1,57 @@
import { createSelector } from 'reselect';
import { initialState } from './reducer';
const selectProfilePageDomain = (state) => state.profilePage || initialState;
const makeSelectError = () =>
createSelector(selectProfilePageDomain, (substate) => substate.error);
const makeSelectBirthday = () =>
createSelector(selectProfilePageDomain, (substate) => substate.birthday);
const makeSelectHeight = () =>
createSelector(selectProfilePageDomain, (substate) => substate.height)
const makeSelectCurrentWeight = () =>
createSelector(selectProfilePageDomain, (substate) => substate.currentWeight);
const makeSelectGoalWeight = () =>
createSelector(selectProfilePageDomain, (substate) => substate.goalWeight);
const makeSelectRateOfChange = () =>
createSelector(selectProfilePageDomain, (substate) => substate.rateOfChange);
const makeSelectActivity = () =>
createSelector(selectProfilePageDomain, (substate) => substate.activity);
const makeSelectGoal = () =>
createSelector(selectProfilePageDomain, (substate) => substate.goal);
const makeSelectGender = () =>
createSelector(selectProfilePageDomain, (substate) => substate.gender);
const makeSelectIsLoading = () =>
createSelector(selectProfilePageDomain, (substate) => substate.isLoading);
const makeSelectDailyCalories = () =>
createSelector(selectProfilePageDomain, (substate) => substate.dailyCalories);
const makeSelectWeeksToGoal = () =>
createSelector(selectProfilePageDomain, (substate) => substate.weeksToGoal);
export {
selectProfilePageDomain,
makeSelectError,
makeSelectBirthday,
makeSelectHeight,
makeSelectCurrentWeight,
makeSelectGoalWeight,
makeSelectRateOfChange,
makeSelectActivity,
makeSelectGoal,
makeSelectGender,
makeSelectWeeksToGoal,
makeSelectDailyCalories,
makeSelectIsLoading,
};

View File

@ -5,10 +5,12 @@ import {useDispatch} from "react-redux";
import InputField from "components/InputField"; import InputField from "components/InputField";
import useStyles from './styles' import useStyles from './styles'
import {Form, Formik} from "formik"; import {Form, Formik} from "formik";
import {registerAction} from "./actions";
import formInitialValues from "./FormModel/formInitialValues"; import formInitialValues from "./FormModel/formInitialValues";
import validationSchema from "./FormModel/validationSchema"; import validationSchema from "./FormModel/validationSchema";
import {registerAction} from "./actions";
import registerFormModel from './FormModel/registerFormModel' import registerFormModel from './FormModel/registerFormModel'
import {useInjectSaga} from "redux-injectors";
import saga from './saga'
const { const {
formId, formId,
@ -18,11 +20,13 @@ const {
} }
} = registerFormModel; } = registerFormModel;
const key = 'registerPage'
const RegisterPage = () => { const RegisterPage = () => {
useInjectSaga({ key, saga });
const classes = useStyles() const classes = useStyles()
const dispatch = useDispatch(); const dispatch = useDispatch();
const handleSubmit = (values, actions) => { const onSubmit = (values, actions) => {
dispatch(registerAction(values)) dispatch(registerAction(values))
} }
@ -30,12 +34,12 @@ const RegisterPage = () => {
<Container component="main" maxWidth="xs"> <Container component="main" maxWidth="xs">
<div className={classes.paper}> <div className={classes.paper}>
<Typography component="h1" variant="h5"> <Typography component="h1" variant="h5">
Login to Account Create account
</Typography> </Typography>
<Formik <Formik
initialValues={formInitialValues} initialValues={formInitialValues}
validationSchema={validationSchema} validationSchema={validationSchema}
onSubmit={handleSubmit} onSubmit={onSubmit}
> >
{({ isSubmitting }) => ( {({ isSubmitting }) => (
<Form id={formId}> <Form id={formId}>
@ -55,10 +59,6 @@ const RegisterPage = () => {
fullWidth fullWidth
className={classes.input} className={classes.input}
/> />
<FormControlLabel
control={<Checkbox value="remember" color="primary" />}
label="Remember me"
/>
<Button <Button
type="submit" type="submit"
fullWidth fullWidth