From 1a611fbea16f1394ab574befd52a7c305c3a0854 Mon Sep 17 00:00:00 2001 From: = <=> Date: Sat, 16 Jan 2021 22:53:26 +0100 Subject: [PATCH] Fix creating meal list and adding products --- .../FormModel/formInitialValues.js | 14 +++ .../FormModel/productFormModel.js | 17 +++ .../FormModel/validationSchema.js | 15 +++ .../Forms/ProductDetailsForm/index.js | 111 ++++++++++++++++++ .../AddProductToMealDialog/ProductItem.js | 54 +++++++-- .../AddProductToMealDialog/index.js | 19 +-- src/components/MealCard/index.js | 52 ++++---- src/components/ProductCard/index.js | 32 +++-- .../CreateProfile/ReviewProfile/index.js | 31 ----- .../CreateProfile/ReviewProfile/styles.js | 15 --- src/pages/CreateProfile/constants.js | 20 ++-- src/pages/CreateProfile/index.js | 6 +- src/pages/Home/actions.js | 22 ++-- src/pages/Home/constants.js | 2 + src/pages/Home/index.js | 9 +- src/pages/Home/reducer.js | 56 ++++++--- src/pages/Home/saga.js | 33 +++--- src/pages/Home/selectors.js | 14 ++- src/pages/Login/index.js | 1 - src/pages/Profile/actions.js | 29 +++++ src/pages/Profile/constants.js | 3 + src/pages/Profile/index.js | 57 ++++++++- src/pages/Profile/reducer.js | 51 ++++++++ src/pages/Profile/saga.js | 40 +++++++ src/pages/Profile/selectors.js | 57 +++++++++ src/pages/Register/index.js | 16 +-- 26 files changed, 600 insertions(+), 176 deletions(-) create mode 100644 src/components/AddProductToMealDialog/FormModel/formInitialValues.js create mode 100644 src/components/AddProductToMealDialog/FormModel/productFormModel.js create mode 100644 src/components/AddProductToMealDialog/FormModel/validationSchema.js create mode 100644 src/components/AddProductToMealDialog/Forms/ProductDetailsForm/index.js delete mode 100644 src/pages/CreateProfile/ReviewProfile/index.js delete mode 100644 src/pages/CreateProfile/ReviewProfile/styles.js create mode 100644 src/pages/Profile/actions.js create mode 100644 src/pages/Profile/constants.js create mode 100644 src/pages/Profile/reducer.js create mode 100644 src/pages/Profile/saga.js create mode 100644 src/pages/Profile/selectors.js diff --git a/src/components/AddProductToMealDialog/FormModel/formInitialValues.js b/src/components/AddProductToMealDialog/FormModel/formInitialValues.js new file mode 100644 index 0000000..975f893 --- /dev/null +++ b/src/components/AddProductToMealDialog/FormModel/formInitialValues.js @@ -0,0 +1,14 @@ +import productFormModel from './productFormModel'; +const { + formField: { + quantity, + unit, + } +} = productFormModel; + +const formInitialValues = { + [quantity.name]: 1, + [unit.name]: 'portion', +}; + +export default formInitialValues diff --git a/src/components/AddProductToMealDialog/FormModel/productFormModel.js b/src/components/AddProductToMealDialog/FormModel/productFormModel.js new file mode 100644 index 0000000..424ca79 --- /dev/null +++ b/src/components/AddProductToMealDialog/FormModel/productFormModel.js @@ -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 diff --git a/src/components/AddProductToMealDialog/FormModel/validationSchema.js b/src/components/AddProductToMealDialog/FormModel/validationSchema.js new file mode 100644 index 0000000..79703ee --- /dev/null +++ b/src/components/AddProductToMealDialog/FormModel/validationSchema.js @@ -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 diff --git a/src/components/AddProductToMealDialog/Forms/ProductDetailsForm/index.js b/src/components/AddProductToMealDialog/Forms/ProductDetailsForm/index.js new file mode 100644 index 0000000..70d7811 --- /dev/null +++ b/src/components/AddProductToMealDialog/Forms/ProductDetailsForm/index.js @@ -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 ( + + + {({ isSubmitting }) => ( + + {label} + + 1 portion = {servingCapacity}${unit} + + + + + + + + + + cancel + + + Ok + + + + )} + + + ) +} + +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 diff --git a/src/components/AddProductToMealDialog/ProductItem.js b/src/components/AddProductToMealDialog/ProductItem.js index fac8ac1..06dcfe1 100644 --- a/src/components/AddProductToMealDialog/ProductItem.js +++ b/src/components/AddProductToMealDialog/ProductItem.js @@ -1,25 +1,55 @@ -import React from 'react'; +import React, { useState } from 'react'; 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 ProductDetailsForm from './Forms/ProductDetailsForm' 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 ( - - } - disableTypography - /> - - + + } + secondary={ + 1 portion = {Math.floor((servingCapacity / 100) * calories)}kcal + } + disableTypography /> - - + + + + + + ); }; ProductItem.propTypes = { + selected: PropTypes.bool.isRequired, id: PropTypes.string.isRequired, verified: PropTypes.bool.isRequired, eco: PropTypes.bool.isRequired, diff --git a/src/components/AddProductToMealDialog/index.js b/src/components/AddProductToMealDialog/index.js index 91d75c9..447241c 100644 --- a/src/components/AddProductToMealDialog/index.js +++ b/src/components/AddProductToMealDialog/index.js @@ -10,22 +10,26 @@ import { Dialog, List, } 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 BarcodeScanner from 'components/BarcodeScanner' import { createStructuredSelector } from 'reselect' -import { makeSelectProducts, makeSelectProduct } from 'pages/Home/selectors' import { useSelector } from 'react-redux' +import { makeSelectProducts } from 'pages/Home/selectors' import ProductItem from './ProductItem' +import { setSelectedMealAction } from 'pages/Home/actions' const stateSelector = createStructuredSelector({ products: makeSelectProducts(), - product: makeSelectProduct(), }) -const AddProductToMealDialog = ({ mealLabel }) => { +const AddProductToMealDialog = ({ mealId, mealLabel }) => { 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) => { event.stopPropagation(); @@ -35,8 +39,8 @@ const AddProductToMealDialog = ({ mealLabel }) => { const handleOpen = (event) => { event.stopPropagation(); setIsOpen(true) + selectMeal(); } - return ( event.stopPropagation()}> @@ -61,6 +65,7 @@ const AddProductToMealDialog = ({ mealLabel }) => { {products.map(({ id, verified, eco, salt, brand, servingCapacity, barcode, label, unit, calories, fat, carbohydrates, protein }) => ( { protein, }} /> - ))} + ))} diff --git a/src/components/MealCard/index.js b/src/components/MealCard/index.js index c995b9d..77b19dd 100644 --- a/src/components/MealCard/index.js +++ b/src/components/MealCard/index.js @@ -7,26 +7,20 @@ import { Typography } from "@material-ui/core"; import React from "react"; +import PropTypes from 'prop-types' import {MACRONUTRIENTS, MEALS_LIST} from "utils/mock"; import MacronutrientsChart from "components/MacronutrientsChart"; -import Dish from "components/ProductCard"; +import ProductCard from "components/ProductCard"; 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) => (*/} +// {/* */} +// {/* ))*/} +// {/*}*/} + +const MealCard = ({ id, label, products }) => { return ( @@ -36,25 +30,22 @@ const MealCard = ({ label, products }) => { {label} - + - { - calcMealMacronutrients(products).map(({ unit, value, label }, index) => ( - - )) - } - {MEALS_LIST.map(({ label, macronutrients }, index) => ( - ( + ))} @@ -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; diff --git a/src/components/ProductCard/index.js b/src/components/ProductCard/index.js index b636939..9d90d93 100644 --- a/src/components/ProductCard/index.js +++ b/src/components/ProductCard/index.js @@ -4,19 +4,28 @@ import React from "react"; import MacronutrientsDetails from "components/MacronutrientsDetails"; import EditProductDialog from "components/EditProductDialog"; 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 ( } disableTypography - secondary={ - - } + secondary={`calories: ${calculateCalories()}`} /> @@ -29,11 +38,10 @@ const ProductCard = ({ label, macronutrients }) => { } ProductCard.propTypes = { - label: PropTypes.string.isRequired, - macronutrients: PropTypes.arrayOf(PropTypes.shape({ - value: PropTypes.number.isRequired, - unit: PropTypes.string.isRequired, - })).isRequired, + id: PropTypes.string.isRequired, + product: PropTypes.object.isRequired, + quantity: PropTypes.number.isRequired, + unit: PropTypes.string.isRequired, } export default ProductCard diff --git a/src/pages/CreateProfile/ReviewProfile/index.js b/src/pages/CreateProfile/ReviewProfile/index.js deleted file mode 100644 index 9a59394..0000000 --- a/src/pages/CreateProfile/ReviewProfile/index.js +++ /dev/null @@ -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 ( - - - - Payment details - - - {Object.entries(formValues).map(([label, value], index) => ( - - {label} - {value} - - ))} - - - - ); -} - -export default ReviewProfile diff --git a/src/pages/CreateProfile/ReviewProfile/styles.js b/src/pages/CreateProfile/ReviewProfile/styles.js deleted file mode 100644 index 5a60d50..0000000 --- a/src/pages/CreateProfile/ReviewProfile/styles.js +++ /dev/null @@ -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 diff --git a/src/pages/CreateProfile/constants.js b/src/pages/CreateProfile/constants.js index 1656bca..6b64ab9 100644 --- a/src/pages/CreateProfile/constants.js +++ b/src/pages/CreateProfile/constants.js @@ -1,13 +1,13 @@ -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'; +export const GET_PROFILE_REQUEST = 'app/CreateProfilePage/GET_PROFILE_REQUEST'; +export const GET_PROFILE_SUCCESS = 'app/CreateProfilePage/GET_PROFILE_SUCCESS'; +export const GET_PROFILE_ERROR = 'app/CreateProfilePage/GET_PROFILE_ERROR'; -export const CREATE_PROFILE_REQUEST = 'app/ProfilePage/CREATE_PROFILE_REQUEST'; -export const CREATE_PROFILE_SUCCESS = 'app/ProfilePage/CREATE_PROFILE_SUCCESS'; -export const CREATE_PROFILE_ERROR = 'app/ProfilePage/CREATE_PROFILE_ERROR'; +export const CREATE_PROFILE_REQUEST = 'app/CreateProfilePage/CREATE_PROFILE_REQUEST'; +export const CREATE_PROFILE_SUCCESS = 'app/CreateProfilePage/CREATE_PROFILE_SUCCESS'; +export const CREATE_PROFILE_ERROR = 'app/CreateProfilePage/CREATE_PROFILE_ERROR'; -export const UPDATE_PROFILE_REQUEST = 'app/ProfilePage/UPDATE_PROFILE_REQUEST'; -export const UPDATE_PROFILE_SUCCESS = 'app/ProfilePage/UPDATE_PROFILE_SUCCESS'; -export const UPDATE_PROFILE_ERROR = 'app/ProfilePage/UPDATE_PROFILE_ERROR'; +export const UPDATE_PROFILE_REQUEST = 'app/CreateProfilePage/UPDATE_PROFILE_REQUEST'; +export const UPDATE_PROFILE_SUCCESS = 'app/CreateProfilePage/UPDATE_PROFILE_SUCCESS'; +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'; diff --git a/src/pages/CreateProfile/index.js b/src/pages/CreateProfile/index.js index 1b082b6..e027e39 100644 --- a/src/pages/CreateProfile/index.js +++ b/src/pages/CreateProfile/index.js @@ -5,7 +5,6 @@ import { useDispatch } from 'react-redux'; import { Formik, Form } from 'formik'; import GoalForm from './Forms/GoalForm'; import PersonalDetailsForm from './Forms/PersonalDetailsForm'; -import ReviewProfile from './ReviewProfile'; import reducer from "./reducer"; import saga from "./saga"; import { createProfileAction } from './actions' @@ -14,7 +13,7 @@ import validationSchema from './FormModel/validationSchema' import formInitialValues from './FormModel/formInitialValues' 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 renderStepContent = (step) => { @@ -23,8 +22,6 @@ const renderStepContent = (step) => { return ; case 1: return ; - case 2: - return ; default: throw new Error('Unknown step'); } @@ -42,7 +39,6 @@ const ProfilePage = () => { const dispatch = useDispatch(); const submitForm = (values, actions) => { - console.log(values) dispatch(createProfileAction(values)) actions.setSubmitting(false); setActiveStep(activeStep + 1); diff --git a/src/pages/Home/actions.js b/src/pages/Home/actions.js index 4cf4e90..13a69d7 100644 --- a/src/pages/Home/actions.js +++ b/src/pages/Home/actions.js @@ -16,9 +16,16 @@ import { SEARCH_PRODUCT_BY_BARCODE_SUCCESS, SEARCH_PRODUCT_BY_BARCODE_ERROR, SEARCH_PRODUCT_BY_LABEL_SUCCESS, - SEARCH_PRODUCT_BY_LABEL_ERROR + SEARCH_PRODUCT_BY_LABEL_ERROR, + SET_SELECTED_MEAL, } from './constants'; +export const setSelectedMealAction = ({ label, id }) => ({ + type: SET_SELECTED_MEAL, + label, + id, +}) + export const searchProductByLabelAction = ({ label }) => ({ type: SEARCH_PRODUCT_BY_LABEL_REQUEST, label, @@ -68,11 +75,9 @@ export const updateMealAction = () => ({ type: UPDATE_MEAL_REQUEST, }) -export const updateMealSuccessAction = ({ label, products, date }) => ({ +export const updateMealSuccessAction = ({ meal }) => ({ type: UPDATE_MEAL_SUCCESS, - label, - products, - date, + meal }) export const updateMealErrorAction = ({error}) => ({ @@ -87,8 +92,9 @@ export const createMealAction = ({ label, products, date }) => ({ date, }) -export const createMealSuccessAction = ({ label, products, date }) => ({ +export const createMealSuccessAction = ({ id, label, products, date }) => ({ type: CREATE_MEAL_SUCCESS, + id, label, products, date, @@ -99,9 +105,10 @@ export const createMealErrorAction = ({error}) => ({ error, }) -export const addProductsToMealAction = ({ products }) => ({ +export const addProductsToMealAction = ({ id, products }) => ({ type: ADD_PRODUCTS_TO_MEAL_REQUEST, products, + id, }) export const addProductsToSuccessAction = ({ meal }) => ({ @@ -113,4 +120,3 @@ export const addProductsToMealErrorAction = ({error}) => ({ type: ADD_PRODUCTS_TO_MEAL_ERROR, error, }) - diff --git a/src/pages/Home/constants.js b/src/pages/Home/constants.js index 99df3da..15389b2 100644 --- a/src/pages/Home/constants.js +++ b/src/pages/Home/constants.js @@ -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_SUCCESS = 'app/HomePage/SEARCH_PRODUCT_BY_BARCODE_SUCCESS'; export const SEARCH_PRODUCT_BY_BARCODE_ERROR = 'app/HomePage/SEARCH_PRODUCT_BY_BARCODE_ERROR'; + +export const SET_SELECTED_MEAL = 'app/HomePage/SET_SELECTED_MEAL' diff --git a/src/pages/Home/index.js b/src/pages/Home/index.js index 82b1a95..0362b17 100644 --- a/src/pages/Home/index.js +++ b/src/pages/Home/index.js @@ -11,8 +11,6 @@ import { getMealsAction } from './actions' import MealCard from "components/MealCard"; import { createStructuredSelector } from 'reselect' -const TODAY = format(new Date(), "yyyy-MM-dd") - const stateSelector = createStructuredSelector({ meals: makeSelectMeals() }) @@ -26,13 +24,14 @@ const HomePage = () => { const dispatch = useDispatch(); useEffect(() => { - dispatch(getMealsAction({ date: '2020-12-29' })) + const today = format(new Date(), "yyyy-MM-dd"); + dispatch(getMealsAction({ date: today })); }, []); return ( - {meals.map(({ id, products, label }) => ( - + {meals.map(({ id, products, label }, index) => ( + ))} ); diff --git a/src/pages/Home/reducer.js b/src/pages/Home/reducer.js index 32e8525..800f187 100644 --- a/src/pages/Home/reducer.js +++ b/src/pages/Home/reducer.js @@ -18,42 +18,43 @@ import { SEARCH_PRODUCT_BY_BARCODE_ERROR, SEARCH_PRODUCT_BY_LABEL_REQUEST, SEARCH_PRODUCT_BY_LABEL_SUCCESS, - SEARCH_PRODUCT_BY_LABEL_ERROR + SEARCH_PRODUCT_BY_LABEL_ERROR, + SET_SELECTED_MEAL, } from './constants'; const defaultMeals = [ { - id: 1, + id: null, label: 'Breakfast', products: [], }, { - id: 2, + id: null, label: 'Snack I', products: [], }, { - id: 3, + id: null, label: 'Lunch', products: [], }, { - id: 4, + id: null, label: 'Snack II', products: [], }, { - id: 5, + id: null, label: 'Dinner', products: [], }, { - id: 6, + id: null, label: 'Snack III', products: [], }, { - id: 7, + id: null, label: 'Supper', products: [], }, @@ -68,14 +69,35 @@ export const initialState = { barcode: '', product: {}, date: '', - mealId: '', + form: { + id: '', + label: '', + products: [], + date: null, + }, meals: defaultMeals }; const homePageReducer = produce((draft, action) => { 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: - draft.meals = action.meals.length || defaultMeals; + draft.meals = + action.meals.length > 0 + ? action.meals + : defaultMeals; + draft.isLoading = false; break; @@ -105,7 +127,7 @@ const homePageReducer = produce((draft, action) => { break; case UPDATE_MEAL_SUCCESS: - console.log(UPDATE_MEAL_SUCCESS, 'UPDATE_MEAL_SUCCESS') + draft.isLoading = false; break; @@ -119,13 +141,19 @@ const homePageReducer = produce((draft, action) => { break; 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; break; - case CREATE_MEAL_REQUEST: - case UPDATE_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; break; diff --git a/src/pages/Home/saga.js b/src/pages/Home/saga.js index d9d5102..a583dd3 100644 --- a/src/pages/Home/saga.js +++ b/src/pages/Home/saga.js @@ -1,8 +1,8 @@ import { takeLatest, call, put, select } from 'redux-saga/effects'; 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 { searchProductByLabelErrorAction, searchProductByLabelSuccessAction, createMealSuccessAction, createMealErrorAction, searchProductByBarcodeSuccessAction, searchProductByBarcodeErrorAction, updateMealErrorAction, updateMealSuccessAction, getMealsSuccessAction, getMealsErrorAction } from './actions'; -import { makeSelectLabel, makeSelectMealId, makeSelectBarcode, makeSelectProducts, makeSelectDate, } from './selectors' +import { searchProductByLabelErrorAction, addProductsToSuccessAction, addProductsToMealErrorAction, searchProductByLabelSuccessAction, createMealSuccessAction, createMealErrorAction, searchProductByBarcodeSuccessAction, searchProductByBarcodeErrorAction, updateMealErrorAction, updateMealSuccessAction, getMealsSuccessAction, getMealsErrorAction } from './actions'; +import { makeSelectLabel, makeSelectFormProducts, makeSelectMealLabel, makeSelectMealId, makeSelectBarcode, makeSelectDate, } from './selectors' import { makeSelectTokens } from 'containers/App/selectors' import {push} from "connected-react-router"; @@ -29,8 +29,8 @@ export function* getMeals() { export function* updateMeal() { const { access } = yield select(makeSelectTokens()); - const label = yield select(makeSelectLabel()); - const products = yield select(makeSelectProducts()); + const products = yield select(makeSelectFormProducts()); + const label = yield select(makeSelectMealLabel()); const date = yield select(makeSelectDate()); const mealId = yield select(makeSelectMealId()); @@ -43,8 +43,9 @@ export function* updateMeal() { }; try { - const { birthday, gender, height, currentWeight, goalWeight, rateOfChange, activity } = yield call(request, requestURL, requestParameters); - yield put(updateMealSuccessAction({birthday, gender, height, currentWeight, goalWeight, rateOfChange, activity})); + const meal = yield call(request, requestURL, requestParameters); + console.log('pages/home/saga/UPDATE_MEAL', meal) + // yield put(updateMealSuccessAction({ meal })); } catch (error) { yield put(updateMealErrorAction({error: error.message})); } @@ -52,12 +53,14 @@ export function* updateMeal() { export function* createMeal() { const { access } = yield select(makeSelectTokens()); - const label = yield select(makeSelectLabel()); - const products = yield select(makeSelectProducts()); + const label = yield select(makeSelectMealLabel()); + const products = yield select(makeSelectFormProducts()); const date = yield select(makeSelectDate()); const requestURL = api.meals; + console.log(products) + const requestParameters = { method: 'POST', headers: { Accept: 'application/json', 'Content-Type': 'application/json', Authorization: `Bearer ${access.token}`, }, @@ -65,8 +68,8 @@ export function* createMeal() { }; try { - const { id, label, products, date } = yield call(request, requestURL, requestParameters); - yield put(createMealSuccessAction({ id, label, products, date })); + const meal = yield call(request, requestURL, requestParameters); + yield put(createMealSuccessAction(meal)); yield put(push(routes.dashboard.path)); } catch (error) { yield put(createMealErrorAction({error: error.message})); @@ -75,23 +78,23 @@ export function* createMeal() { export function* addProductsToMeal() { const { access } = yield select(makeSelectTokens()); - const products = yield select(makeSelectProducts()); + const products = yield select(makeSelectFormProducts()); const mealId = yield select(makeSelectMealId()); const requestURL = `${api.meals}/${mealId}`; const requestParameters = { - method: 'POST', + method: 'PATCH', headers: { Accept: 'application/json', 'Content-Type': 'application/json', Authorization: `Bearer ${access.token}`, }, body: JSON.stringify({ products }), }; try { - const { meal } = yield call(request, requestURL, requestParameters); - yield put(createMealSuccessAction({ meal })); + const meal = yield call(request, requestURL, requestParameters); + yield put(addProductsToSuccessAction({ meal })); yield put(push(routes.dashboard.path)); } catch (error) { - yield put(createMealErrorAction({error: error.message})); + yield put(addProductsToMealErrorAction({error: error.message})); } } diff --git a/src/pages/Home/selectors.js b/src/pages/Home/selectors.js index b659b27..8227b1c 100644 --- a/src/pages/Home/selectors.js +++ b/src/pages/Home/selectors.js @@ -12,9 +12,6 @@ const makeSelectIsLoading = () => const makeSelectMeals = () => createSelector(selectHomePageDomain, (substate) => substate.meals); -const makeSelectMealId = () => - createSelector(selectHomePageDomain, (substate) => substate.mealId); - const makeSelectDate = () => createSelector(selectHomePageDomain, (substate) => substate.date); @@ -30,8 +27,19 @@ const makeSelectProduct = () => const makeSelectBarcode = () => 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 { selectHomePageDomain, + makeSelectFormProducts, + makeSelectMealLabel, makeSelectMealId, makeSelectDate, makeSelectLabel, diff --git a/src/pages/Login/index.js b/src/pages/Login/index.js index ec4cf63..1d45425 100644 --- a/src/pages/Login/index.js +++ b/src/pages/Login/index.js @@ -18,7 +18,6 @@ import useStyles from './styles' import validationSchema from './FormModel/validationSchema' import formInitialValues from './FormModel/formInitialValues' import loginFormModel from './FormModel/loginFormModel' -import reducer from "./reducer"; import saga from "./saga"; const { diff --git a/src/pages/Profile/actions.js b/src/pages/Profile/actions.js new file mode 100644 index 0000000..60d0289 --- /dev/null +++ b/src/pages/Profile/actions.js @@ -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, +}) diff --git a/src/pages/Profile/constants.js b/src/pages/Profile/constants.js new file mode 100644 index 0000000..4ec5028 --- /dev/null +++ b/src/pages/Profile/constants.js @@ -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'; diff --git a/src/pages/Profile/index.js b/src/pages/Profile/index.js index ff31dea..0cea737 100644 --- a/src/pages/Profile/index.js +++ b/src/pages/Profile/index.js @@ -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 = () => { + useInjectSaga({ key, saga }); + useInjectReducer({ key, reducer }) + const dispatch = useDispatch() + const userProfileDetails = useSelector(stateSelector) + + useEffect(() => { + dispatch(getProfileAction()) + }, []) + return ( - - ProfilePage - + + + {Object.keys(userProfileDetails).map((key, index) => ( + + + + ))} + + ); }; diff --git a/src/pages/Profile/reducer.js b/src/pages/Profile/reducer.js new file mode 100644 index 0000000..c55b61b --- /dev/null +++ b/src/pages/Profile/reducer.js @@ -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; diff --git a/src/pages/Profile/saga.js b/src/pages/Profile/saga.js new file mode 100644 index 0000000..3518b75 --- /dev/null +++ b/src/pages/Profile/saga.js @@ -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); +} diff --git a/src/pages/Profile/selectors.js b/src/pages/Profile/selectors.js new file mode 100644 index 0000000..30cfb9f --- /dev/null +++ b/src/pages/Profile/selectors.js @@ -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, +}; diff --git a/src/pages/Register/index.js b/src/pages/Register/index.js index d19a324..14a808b 100644 --- a/src/pages/Register/index.js +++ b/src/pages/Register/index.js @@ -5,10 +5,12 @@ import {useDispatch} from "react-redux"; import InputField from "components/InputField"; import useStyles from './styles' import {Form, Formik} from "formik"; +import {registerAction} from "./actions"; import formInitialValues from "./FormModel/formInitialValues"; import validationSchema from "./FormModel/validationSchema"; -import {registerAction} from "./actions"; import registerFormModel from './FormModel/registerFormModel' +import {useInjectSaga} from "redux-injectors"; +import saga from './saga' const { formId, @@ -18,11 +20,13 @@ const { } } = registerFormModel; +const key = 'registerPage' const RegisterPage = () => { + useInjectSaga({ key, saga }); const classes = useStyles() const dispatch = useDispatch(); - const handleSubmit = (values, actions) => { + const onSubmit = (values, actions) => { dispatch(registerAction(values)) } @@ -30,12 +34,12 @@ const RegisterPage = () => { - Login to Account + Create account {({ isSubmitting }) => ( @@ -55,10 +59,6 @@ const RegisterPage = () => { fullWidth className={classes.input} /> - } - label="Remember me" - />