Add redux saga for home apge
This commit is contained in:
parent
64ab7aef58
commit
8160437379
@ -1,91 +0,0 @@
|
|||||||
import React from 'react'
|
|
||||||
import {
|
|
||||||
Grid,
|
|
||||||
IconButton,
|
|
||||||
Typography,
|
|
||||||
TextField,
|
|
||||||
List,
|
|
||||||
Checkbox,
|
|
||||||
Dialog,
|
|
||||||
DialogContent,
|
|
||||||
ListSubheader,
|
|
||||||
ListItemSecondaryAction,
|
|
||||||
DialogTitle
|
|
||||||
} from '@material-ui/core';
|
|
||||||
import { makeStyles } from '@material-ui/core/styles';
|
|
||||||
import {Close, CropFree} from '@material-ui/icons';
|
|
||||||
import Meal from "components/Dish";
|
|
||||||
|
|
||||||
import { MEALS_LIST } from 'utils/mock'
|
|
||||||
|
|
||||||
const useStyles = makeStyles((theme) => ({
|
|
||||||
closeButton: {
|
|
||||||
position: 'absolute',
|
|
||||||
right: theme.spacing(1),
|
|
||||||
top: theme.spacing(1),
|
|
||||||
color: theme.palette.grey[500],
|
|
||||||
},
|
|
||||||
searchInput: {
|
|
||||||
marginRight: theme.spacing(3),
|
|
||||||
}
|
|
||||||
}))
|
|
||||||
|
|
||||||
const AddMealModal = ({ isModalOpen, handleCloseModal }) => {
|
|
||||||
const styles = useStyles()
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Dialog
|
|
||||||
open={isModalOpen}
|
|
||||||
onClose={handleCloseModal}
|
|
||||||
className={styles.root}
|
|
||||||
fullWidth
|
|
||||||
maxWidth="md"
|
|
||||||
>
|
|
||||||
<DialogTitle className={styles.header}>
|
|
||||||
<Typography variant="h6">Add product</Typography>
|
|
||||||
<IconButton className={styles.closeButton} onClick={handleCloseModal}>
|
|
||||||
<Close />
|
|
||||||
</IconButton>
|
|
||||||
</DialogTitle>
|
|
||||||
<DialogContent>
|
|
||||||
<Grid>
|
|
||||||
<List
|
|
||||||
dense
|
|
||||||
subheader={
|
|
||||||
<Grid container alignItems="center" justify="space-between" wrap="nowrap">
|
|
||||||
<TextField
|
|
||||||
label="Product name"
|
|
||||||
name="product name"
|
|
||||||
variant="outlined"
|
|
||||||
size="small"
|
|
||||||
fullWidth
|
|
||||||
className={styles.searchInput}
|
|
||||||
/>
|
|
||||||
<IconButton size="medium" color="primary">
|
|
||||||
<CropFree />
|
|
||||||
</IconButton>
|
|
||||||
</Grid>
|
|
||||||
}
|
|
||||||
>
|
|
||||||
{MEALS_LIST.map(({ name, macronutrients }, index) => (
|
|
||||||
<Meal
|
|
||||||
name={name}
|
|
||||||
macronutrients={macronutrients}
|
|
||||||
key={index}
|
|
||||||
action={
|
|
||||||
<ListItemSecondaryAction>
|
|
||||||
<Checkbox
|
|
||||||
edge="end"
|
|
||||||
/>
|
|
||||||
</ListItemSecondaryAction>
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
))}
|
|
||||||
</List>
|
|
||||||
</Grid>
|
|
||||||
</DialogContent>
|
|
||||||
</Dialog>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default AddMealModal;
|
|
@ -1,61 +0,0 @@
|
|||||||
import React from "react";
|
|
||||||
import PropTypes from "prop-types";
|
|
||||||
import {Grid, Paper, Typography} from "@material-ui/core";
|
|
||||||
import {makeStyles} from "@material-ui/core/styles";
|
|
||||||
import grey from '@material-ui/core/colors/grey';
|
|
||||||
import MacronutrientsChart from 'components/MacronutrientsChart'
|
|
||||||
|
|
||||||
const useStyles = makeStyles((theme) => ({
|
|
||||||
root: {
|
|
||||||
paddingTop: theme.spacing(2),
|
|
||||||
paddingBottom: theme.spacing(2),
|
|
||||||
},
|
|
||||||
subtitle: {
|
|
||||||
color: grey[400]
|
|
||||||
}
|
|
||||||
}))
|
|
||||||
|
|
||||||
const DailyStats = ({ caloriesLeft, macronutrients }) => {
|
|
||||||
const classes = useStyles()
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Paper className={classes.root}>
|
|
||||||
<Grid
|
|
||||||
container
|
|
||||||
direction="column"
|
|
||||||
justify="center"
|
|
||||||
alignItems="center"
|
|
||||||
>
|
|
||||||
<Grid item>
|
|
||||||
<Typography variant="subtitle1" align="center" className={classes.subtitle}>
|
|
||||||
Calories left
|
|
||||||
</Typography>
|
|
||||||
<Typography variant="h3">
|
|
||||||
{caloriesLeft} kcal
|
|
||||||
</Typography>
|
|
||||||
</Grid>
|
|
||||||
<Grid
|
|
||||||
container
|
|
||||||
justify="space-between"
|
|
||||||
alignItems="center"
|
|
||||||
>
|
|
||||||
{macronutrients.map(({ current, max, label, color }, index) => (
|
|
||||||
<MacronutrientsChart current={current} max={max} label={label} color={color} key={index} />
|
|
||||||
))}
|
|
||||||
</Grid>
|
|
||||||
</Grid>
|
|
||||||
</Paper>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
DailyStats.propTypes = {
|
|
||||||
caloriesLeft: PropTypes.number.isRequired,
|
|
||||||
macronutrients: PropTypes.arrayOf(PropTypes.shape({
|
|
||||||
current: PropTypes.number.isRequired,
|
|
||||||
max: PropTypes.number.isRequired,
|
|
||||||
label: PropTypes.string.isRequired,
|
|
||||||
color: PropTypes.string.isRequired,
|
|
||||||
})).isRequired
|
|
||||||
}
|
|
||||||
|
|
||||||
export default DailyStats
|
|
71
src/components/EditProductDialog/index.js
Normal file
71
src/components/EditProductDialog/index.js
Normal file
@ -0,0 +1,71 @@
|
|||||||
|
import React, { useState } from 'react';
|
||||||
|
import {Dialog, DialogTitle, Tooltip, DialogContent, Grid, Typography, TextField, DialogContentText, DialogActions, IconButton, Button} from "@material-ui/core";
|
||||||
|
import InputField from "components/InputField";
|
||||||
|
import {Edit as EditIcon, VerifiedUserTwoTone as VerifiedUserIcon, EcoTwoTone as EcoIcon} from "@material-ui/icons";
|
||||||
|
|
||||||
|
const ProductLabel = ({ label, eco, verified }) => {
|
||||||
|
return (
|
||||||
|
<DialogTitle>
|
||||||
|
<Grid container alignItems="center">
|
||||||
|
{label}
|
||||||
|
{verified && (
|
||||||
|
<Tooltip title="Verified product">
|
||||||
|
<VerifiedUserIcon fontSize="small" color="primary" />
|
||||||
|
</Tooltip>
|
||||||
|
)}
|
||||||
|
{eco && (
|
||||||
|
<Tooltip title="Eco product">
|
||||||
|
<EcoIcon color="primary" />
|
||||||
|
</Tooltip>
|
||||||
|
)}
|
||||||
|
</Grid>
|
||||||
|
</DialogTitle>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
const EditProductDialog = () => {
|
||||||
|
const [isDialogOpen, setIsDialogOpen] = useState(false)
|
||||||
|
|
||||||
|
const handleOpenDialog = () => {
|
||||||
|
setIsDialogOpen(true)
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleCloseDialog = () => {
|
||||||
|
setIsDialogOpen(false)
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<React.Fragment>
|
||||||
|
<IconButton variant="outlined" color="primary" onClick={handleOpenDialog}>
|
||||||
|
<EditIcon />
|
||||||
|
</IconButton>
|
||||||
|
<Dialog open={isDialogOpen} onClose={handleCloseDialog}>
|
||||||
|
<ProductLabel label={"eggs"} eco={true} verified={true}/>
|
||||||
|
<DialogContent>
|
||||||
|
<DialogContentText>
|
||||||
|
To subscribe to this website, please enter your email address here. We will send updates
|
||||||
|
occasionally.
|
||||||
|
</DialogContentText>
|
||||||
|
<TextField
|
||||||
|
autoFocus
|
||||||
|
margin="dense"
|
||||||
|
id="name"
|
||||||
|
label="Email Address"
|
||||||
|
type="email"
|
||||||
|
fullWidth
|
||||||
|
/>
|
||||||
|
</DialogContent>
|
||||||
|
<DialogActions>
|
||||||
|
<Button onClick={handleCloseDialog} color="secondary">
|
||||||
|
Discard
|
||||||
|
</Button>
|
||||||
|
<Button onClick={handleCloseDialog} variant="contained" color="primary">
|
||||||
|
Save
|
||||||
|
</Button>
|
||||||
|
</DialogActions>
|
||||||
|
</Dialog>
|
||||||
|
</React.Fragment>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default EditProductDialog;
|
@ -11,9 +11,9 @@ import {Add as AddIcon} from "@material-ui/icons";
|
|||||||
import React from "react";
|
import React from "react";
|
||||||
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/Dish";
|
import Dish from "components/ProductCard";
|
||||||
|
|
||||||
const Meal = ({ label }) => {
|
const MealCard = ({ label }) => {
|
||||||
|
|
||||||
const calcMealMacronutrients = (dishes) => {
|
const calcMealMacronutrients = (dishes) => {
|
||||||
const mealMacronutrients = dishes
|
const mealMacronutrients = dishes
|
||||||
@ -73,4 +73,4 @@ const Meal = ({ label }) => {
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export default Meal;
|
export default MealCard;
|
@ -2,9 +2,11 @@ import {ListItem, ListItemText, ListItemSecondaryAction, IconButton} from "@mate
|
|||||||
import PropTypes from 'prop-types'
|
import PropTypes from 'prop-types'
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import MacronutrientsDetails from "components/MacronutrientsDetails";
|
import MacronutrientsDetails from "components/MacronutrientsDetails";
|
||||||
import {Delete as DeleteIcon, Edit as EditIcon} from "@material-ui/icons";
|
import EditProductDialog from "components/EditProductDialog";
|
||||||
|
import {Delete as DeleteIcon} from "@material-ui/icons";
|
||||||
|
|
||||||
|
const ProductCard = ({ label, macronutrients }) => {
|
||||||
|
|
||||||
const Dish = ({ label, macronutrients }) => {
|
|
||||||
return (
|
return (
|
||||||
<ListItem>
|
<ListItem>
|
||||||
<ListItemText
|
<ListItemText
|
||||||
@ -16,10 +18,8 @@ const Dish = ({ label, macronutrients }) => {
|
|||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
<ListItemSecondaryAction>
|
<ListItemSecondaryAction>
|
||||||
<IconButton onClick={() => console.log('elo')}>
|
<EditProductDialog />
|
||||||
<EditIcon />
|
<IconButton onClick={() => alert('remove')}>
|
||||||
</IconButton>
|
|
||||||
<IconButton onClick={() => console.log('elo')}>
|
|
||||||
<DeleteIcon />
|
<DeleteIcon />
|
||||||
</IconButton>
|
</IconButton>
|
||||||
</ListItemSecondaryAction>
|
</ListItemSecondaryAction>
|
||||||
@ -27,7 +27,7 @@ const Dish = ({ label, macronutrients }) => {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
Dish.propTypes = {
|
ProductCard.propTypes = {
|
||||||
label: PropTypes.string.isRequired,
|
label: PropTypes.string.isRequired,
|
||||||
macronutrients: PropTypes.arrayOf(PropTypes.shape({
|
macronutrients: PropTypes.arrayOf(PropTypes.shape({
|
||||||
value: PropTypes.number.isRequired,
|
value: PropTypes.number.isRequired,
|
||||||
@ -35,4 +35,4 @@ Dish.propTypes = {
|
|||||||
})).isRequired,
|
})).isRequired,
|
||||||
}
|
}
|
||||||
|
|
||||||
export default Dish
|
export default ProductCard
|
@ -1,68 +0,0 @@
|
|||||||
import React, {useState} from "react";
|
|
||||||
import PropTypes from "prop-types";
|
|
||||||
import { Box, Tab, Tabs } from "@material-ui/core";
|
|
||||||
import {makeStyles} from "@material-ui/core/styles";
|
|
||||||
|
|
||||||
import TabLabel from 'components/TabLabel';
|
|
||||||
import TabPanel from 'components/TabPanel';
|
|
||||||
// import MenuList from 'components/MenuList'
|
|
||||||
|
|
||||||
import { MENU_LIST } from 'utils/mock'
|
|
||||||
|
|
||||||
const a11yProps = (index) => {
|
|
||||||
return {
|
|
||||||
id: `scrollable-force-tab-${index}`,
|
|
||||||
'aria-controls': `scrollable-force-tabpanel-${index}`,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
const useStyles = makeStyles((theme) => ({
|
|
||||||
tab: {
|
|
||||||
width: `100%`
|
|
||||||
}
|
|
||||||
}))
|
|
||||||
|
|
||||||
const ScrollableTabs = ({ tabs }) => {
|
|
||||||
const classes = useStyles()
|
|
||||||
|
|
||||||
const [value, setValue] = useState(0);
|
|
||||||
|
|
||||||
const handleChangeDay = (event, newDay) => {
|
|
||||||
setValue(newDay);
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Box>
|
|
||||||
<Tabs
|
|
||||||
value={value}
|
|
||||||
onChange={handleChangeDay}
|
|
||||||
variant="scrollable"
|
|
||||||
indicatorColor="primary"
|
|
||||||
scrollButtons="on"
|
|
||||||
>
|
|
||||||
{tabs.map((label, index) => (
|
|
||||||
<Tab
|
|
||||||
key={index}
|
|
||||||
className={classes.tab}
|
|
||||||
label={<TabLabel label={label} />}
|
|
||||||
{...a11yProps(index)}
|
|
||||||
/>
|
|
||||||
))}
|
|
||||||
</Tabs>
|
|
||||||
<Box>
|
|
||||||
{tabs.map((_, index) => (
|
|
||||||
<TabPanel value={value} index={index} key={index}>
|
|
||||||
{/*<MenuList list={MENU_LIST} />*/}
|
|
||||||
hello from Scrollabel tabs
|
|
||||||
</TabPanel>
|
|
||||||
))}
|
|
||||||
</Box>
|
|
||||||
</Box>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
ScrollableTabs.propTypes = {
|
|
||||||
tabs: PropTypes.arrayOf(PropTypes.string).isRequired,
|
|
||||||
}
|
|
||||||
|
|
||||||
export default ScrollableTabs
|
|
@ -1,24 +0,0 @@
|
|||||||
import React from 'react';
|
|
||||||
import { at } from 'lodash';
|
|
||||||
import { useField } from 'formik';
|
|
||||||
import {
|
|
||||||
Slider,
|
|
||||||
FormControl,
|
|
||||||
FormControlLabel,
|
|
||||||
FormHelperText
|
|
||||||
} from '@material-ui/core';
|
|
||||||
|
|
||||||
const SliderField = ({ label, ...rest }) => {
|
|
||||||
const [field, meta, helper] = useField({ label, ...rest });
|
|
||||||
const { setValue } = helper;
|
|
||||||
|
|
||||||
const _onChange = (e) => {
|
|
||||||
setValue(e.target.checked);
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Slider onChange={_onChange} />
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export default SliderField
|
|
@ -1,23 +0,0 @@
|
|||||||
import React from 'react';
|
|
||||||
import { Box, Typography } from "@material-ui/core";
|
|
||||||
import PropTypes from "prop-types";
|
|
||||||
|
|
||||||
const TabLabel = ({ label }) => {
|
|
||||||
console.log('TabLabel - todo')
|
|
||||||
return (
|
|
||||||
<Box display="flex" flexDirection="column">
|
|
||||||
<Typography component="span" variant="caption">
|
|
||||||
30 lis
|
|
||||||
</Typography>
|
|
||||||
<Typography component="span" variant="caption">
|
|
||||||
poniedziałek
|
|
||||||
</Typography>
|
|
||||||
</Box>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
TabLabel.propTypes = {
|
|
||||||
label: PropTypes.string.isRequired,
|
|
||||||
}
|
|
||||||
|
|
||||||
export default TabLabel;
|
|
@ -1,29 +0,0 @@
|
|||||||
import {Box} from "@material-ui/core";
|
|
||||||
import React from "react";
|
|
||||||
import PropTypes from "prop-types";
|
|
||||||
|
|
||||||
const TabPanel = ({ children, value, index, ...other }) => {
|
|
||||||
return (
|
|
||||||
<Box
|
|
||||||
role="tabpanel"
|
|
||||||
hidden={value !== index}
|
|
||||||
id={`scrollable-force-tabpanel-${index}`}
|
|
||||||
aria-labelledby={`scrollable-force-tab-${index}`}
|
|
||||||
{...other}
|
|
||||||
>
|
|
||||||
{value === index && (
|
|
||||||
<Box p={3}>
|
|
||||||
{children}
|
|
||||||
</Box>
|
|
||||||
)}
|
|
||||||
</Box>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
TabPanel.propTypes = {
|
|
||||||
children: PropTypes.node.isRequired,
|
|
||||||
index: PropTypes.any.isRequired,
|
|
||||||
value: PropTypes.any.isRequired,
|
|
||||||
};
|
|
||||||
|
|
||||||
export default TabPanel
|
|
72
src/pages/AddDish/index.js
Normal file
72
src/pages/AddDish/index.js
Normal file
@ -0,0 +1,72 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import {Paper, InputBase, FormLabel, ListItemSecondaryAction, ListItem, ListItemText, List, FormControl, FormGroup, FormControlLabel, Divider, Checkbox, Grid, Typography, FilledInput , DialogContentText, DialogActions, IconButton, Button} from "@material-ui/core";
|
||||||
|
import { CropFree as CropFreeIcon, Search as SearchIcon } from '@material-ui/icons'
|
||||||
|
import EditProductDialog from 'components/EditProductDialog'
|
||||||
|
import useStyles from './styles'
|
||||||
|
|
||||||
|
function generate(element) {
|
||||||
|
return [0, 1, 2].map((value) =>
|
||||||
|
React.cloneElement(element, {
|
||||||
|
key: value,
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const AddProductPage = () => {
|
||||||
|
const classes = useStyles()
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Paper>
|
||||||
|
<Grid container xs={12} className={classes.addDishContainer}>
|
||||||
|
<Grid item xs={3}>
|
||||||
|
<FormControl>
|
||||||
|
<FormLabel>Filters</FormLabel>
|
||||||
|
<FormGroup>
|
||||||
|
<FormControlLabel
|
||||||
|
control={<Checkbox name="gilad" />}
|
||||||
|
label="Verified"
|
||||||
|
/>
|
||||||
|
<FormControlLabel
|
||||||
|
control={<Checkbox name="jason" />}
|
||||||
|
label="Eco"
|
||||||
|
/>
|
||||||
|
</FormGroup>
|
||||||
|
</FormControl>
|
||||||
|
</Grid>
|
||||||
|
<Grid item xs={9}>
|
||||||
|
<Grid item xs={12}>
|
||||||
|
<Paper component="form" className={classes.root}>
|
||||||
|
<IconButton>
|
||||||
|
<SearchIcon />
|
||||||
|
</IconButton>
|
||||||
|
<InputBase
|
||||||
|
className={classes.input}
|
||||||
|
placeholder="Search meals"
|
||||||
|
/>
|
||||||
|
<Divider className={classes.divider} orientation="vertical" />
|
||||||
|
<IconButton color="primary" className={classes.iconButton} aria-label="directions">
|
||||||
|
<CropFreeIcon />
|
||||||
|
</IconButton>
|
||||||
|
</Paper>
|
||||||
|
<List>
|
||||||
|
{generate(
|
||||||
|
<ListItem button onClick={alert('elo')}>
|
||||||
|
<ListItemText
|
||||||
|
primary="eggs"
|
||||||
|
/>
|
||||||
|
<ListItemSecondaryAction>
|
||||||
|
<Checkbox
|
||||||
|
edge="end"
|
||||||
|
/>
|
||||||
|
</ListItemSecondaryAction>
|
||||||
|
</ListItem>
|
||||||
|
)}
|
||||||
|
</List>
|
||||||
|
</Grid>
|
||||||
|
</Grid>
|
||||||
|
</Grid>
|
||||||
|
</Paper>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default AddProductPage;
|
27
src/pages/AddDish/styles.js
Normal file
27
src/pages/AddDish/styles.js
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
import {makeStyles} from "@material-ui/core/styles";
|
||||||
|
|
||||||
|
const useStyles = makeStyles((theme) => ({
|
||||||
|
addDishContainer: {
|
||||||
|
padding: theme.spacing(2),
|
||||||
|
minWidth: 750,
|
||||||
|
},
|
||||||
|
root: {
|
||||||
|
padding: '2px 4px',
|
||||||
|
display: 'flex',
|
||||||
|
alignItems: 'center',
|
||||||
|
minWidth: 400,
|
||||||
|
},
|
||||||
|
input: {
|
||||||
|
marginLeft: theme.spacing(1),
|
||||||
|
flex: 1,
|
||||||
|
},
|
||||||
|
iconButton: {
|
||||||
|
padding: 10,
|
||||||
|
},
|
||||||
|
divider: {
|
||||||
|
height: 28,
|
||||||
|
margin: 4,
|
||||||
|
},
|
||||||
|
}))
|
||||||
|
|
||||||
|
export default useStyles
|
79
src/pages/Home/actions.js
Normal file
79
src/pages/Home/actions.js
Normal file
@ -0,0 +1,79 @@
|
|||||||
|
import {
|
||||||
|
CREATE_MEAL_REQUEST,
|
||||||
|
CREATE_MEAL_SUCCESS,
|
||||||
|
CREATE_MEAL_ERROR,
|
||||||
|
GET_MEALS_REQUEST,
|
||||||
|
GET_MEALS_SUCCESS,
|
||||||
|
GET_MEALS_ERROR,
|
||||||
|
UPDATE_MEAL_REQUEST,
|
||||||
|
UPDATE_MEAL_SUCCESS,
|
||||||
|
UPDATE_MEAL_ERROR,
|
||||||
|
ADD_PRODUCTS_TO_MEAL_REQUEST,
|
||||||
|
ADD_PRODUCTS_TO_MEAL_SUCCESS,
|
||||||
|
ADD_PRODUCTS_TO_MEAL_ERROR,
|
||||||
|
} from './constants';
|
||||||
|
|
||||||
|
export const getMealsAction = () => ({
|
||||||
|
type: GET_MEALS_REQUEST,
|
||||||
|
})
|
||||||
|
|
||||||
|
export const getMealsSuccessAction = ({ meals }) => ({
|
||||||
|
type: GET_MEALS_SUCCESS,
|
||||||
|
meals
|
||||||
|
})
|
||||||
|
|
||||||
|
export const getMealsErrorAction = ({ error }) => ({
|
||||||
|
type: GET_MEALS_ERROR,
|
||||||
|
error,
|
||||||
|
})
|
||||||
|
|
||||||
|
export const updateMealAction = () => ({
|
||||||
|
type: UPDATE_MEAL_REQUEST,
|
||||||
|
})
|
||||||
|
|
||||||
|
export const updateMealSuccessAction = ({ label, products, date }) => ({
|
||||||
|
type: UPDATE_MEAL_SUCCESS,
|
||||||
|
label,
|
||||||
|
products,
|
||||||
|
date,
|
||||||
|
})
|
||||||
|
|
||||||
|
export const updateMealErrorAction = ({error}) => ({
|
||||||
|
type: UPDATE_MEAL_ERROR,
|
||||||
|
error,
|
||||||
|
})
|
||||||
|
|
||||||
|
export const createMealAction = ({ label, products, date }) => ({
|
||||||
|
type: CREATE_MEAL_REQUEST,
|
||||||
|
label,
|
||||||
|
products,
|
||||||
|
date,
|
||||||
|
})
|
||||||
|
|
||||||
|
export const createMealSuccessAction = ({ label, products, date }) => ({
|
||||||
|
type: CREATE_MEAL_SUCCESS,
|
||||||
|
label,
|
||||||
|
products,
|
||||||
|
date,
|
||||||
|
})
|
||||||
|
|
||||||
|
export const createMealErrorAction = ({error}) => ({
|
||||||
|
type: CREATE_MEAL_ERROR,
|
||||||
|
error,
|
||||||
|
})
|
||||||
|
|
||||||
|
export const addProductsToMealAction = ({ products }) => ({
|
||||||
|
type: ADD_PRODUCTS_TO_MEAL_REQUEST,
|
||||||
|
products,
|
||||||
|
})
|
||||||
|
|
||||||
|
export const addProductsToSuccessAction = ({ meal }) => ({
|
||||||
|
type: ADD_PRODUCTS_TO_MEAL_SUCCESS,
|
||||||
|
meal
|
||||||
|
})
|
||||||
|
|
||||||
|
export const addProductsToMealErrorAction = ({error}) => ({
|
||||||
|
type: ADD_PRODUCTS_TO_MEAL_ERROR,
|
||||||
|
error,
|
||||||
|
})
|
||||||
|
|
15
src/pages/Home/constants.js
Normal file
15
src/pages/Home/constants.js
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
export const GET_MEALS_REQUEST = 'app/HomePage/GET_MEALS_REQUEST'
|
||||||
|
export const GET_MEALS_SUCCESS = 'app/HomePage/GET_MEALS_SUCCESS';
|
||||||
|
export const GET_MEALS_ERROR = 'app/LoginPage/GET_MEALS_ERROR';
|
||||||
|
|
||||||
|
export const CREATE_MEAL_REQUEST = 'app/HomePage/CREATE_MEAL_REQUEST'
|
||||||
|
export const CREATE_MEAL_SUCCESS = 'app/HomePage/CREATE_MEAL_SUCCESS';
|
||||||
|
export const CREATE_MEAL_ERROR = 'app/LoginPage/CREATE_MEAL_ERROR';
|
||||||
|
|
||||||
|
export const UPDATE_MEAL_REQUEST = 'app/HomePage/UPDATE_MEAL_REQUEST'
|
||||||
|
export const UPDATE_MEAL_SUCCESS = 'app/HomePage/UPDATE_MEAL_SUCCESS';
|
||||||
|
export const UPDATE_MEAL_ERROR = 'app/LoginPage/UPDATE_MEAL_ERROR';
|
||||||
|
|
||||||
|
export const ADD_PRODUCTS_TO_MEAL_REQUEST = 'app/HomePage/ADD_PRODUCTS_TO_MEAL_REQUEST'
|
||||||
|
export const ADD_PRODUCTS_TO_MEAL_SUCCESS = 'app/HomePage/ADD_PRODUCTS_TO_MEAL_SUCCESS';
|
||||||
|
export const ADD_PRODUCTS_TO_MEAL_ERROR = 'app/LoginPage/ADD_PRODUCTS_TO_MEAL_ERROR';
|
@ -1,23 +1,22 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { Grid, IconButton, Accordion, Container, List, AccordionSummary, Typography, AccordionDetails } from '@material-ui/core';
|
import { Grid, IconButton, Accordion, Container, List, AccordionSummary, Typography, AccordionDetails } from '@material-ui/core';
|
||||||
import {Add as AddIcon} from '@material-ui/icons';
|
import {Add as AddIcon} from '@material-ui/icons';
|
||||||
import ScrollableTabs from 'components/ScrollableTabs';
|
|
||||||
import MacronutrientsChart from 'components/MacronutrientsChart'
|
import MacronutrientsChart from 'components/MacronutrientsChart'
|
||||||
|
|
||||||
import { TABS, MEALS_LIST, MACRONUTRIENTS } from 'utils/mock'
|
import { TABS, MEALS_LIST, MACRONUTRIENTS } from 'utils/mock'
|
||||||
|
|
||||||
import Meal from "components/Meal";
|
import Meal from "components/MealCard";
|
||||||
|
|
||||||
const HomePage = () => {
|
const HomePage = () => {
|
||||||
return (
|
return (
|
||||||
<Container >
|
<Container >
|
||||||
<Meal label="Breakfast" />
|
<Meal label="Breakfast" />
|
||||||
{/*<Meal label="Snack I" />*/}
|
<Meal label="Snack I" />
|
||||||
{/*<Meal label="Lunch" />*/}
|
<Meal label="Lunch" />
|
||||||
{/*<Meal label="Snack II" />*/}
|
<Meal label="Snack II" />
|
||||||
{/*<Meal label="Dinner" />*/}
|
<Meal label="Dinner" />
|
||||||
{/*<Meal label="Snack III" />*/}
|
<Meal label="Snack III" />
|
||||||
{/*<Meal label="Supper" />*/}
|
<Meal label="Supper" />
|
||||||
</Container>
|
</Container>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
100
src/pages/Home/reducer.js
Normal file
100
src/pages/Home/reducer.js
Normal file
@ -0,0 +1,100 @@
|
|||||||
|
import { format } from 'date-fns'
|
||||||
|
import produce from 'immer';
|
||||||
|
import {
|
||||||
|
CREATE_MEAL_REQUEST,
|
||||||
|
CREATE_MEAL_SUCCESS,
|
||||||
|
CREATE_MEAL_ERROR,
|
||||||
|
GET_MEALS_REQUEST,
|
||||||
|
GET_MEALS_SUCCESS,
|
||||||
|
GET_MEALS_ERROR,
|
||||||
|
UPDATE_MEAL_REQUEST,
|
||||||
|
UPDATE_MEAL_SUCCESS,
|
||||||
|
UPDATE_MEAL_ERROR,
|
||||||
|
ADD_PRODUCTS_TO_MEAL_REQUEST,
|
||||||
|
ADD_PRODUCTS_TO_MEAL_SUCCESS,
|
||||||
|
ADD_PRODUCTS_TO_MEAL_ERROR,
|
||||||
|
} from './constants';
|
||||||
|
|
||||||
|
export const initialState = {
|
||||||
|
isLoading: false,
|
||||||
|
error: {},
|
||||||
|
label: '',
|
||||||
|
products: [],
|
||||||
|
date: format(new Date(), "yyyy-MM-dd"),
|
||||||
|
mealId: '',
|
||||||
|
meals: [
|
||||||
|
{
|
||||||
|
label: 'Breakfast',
|
||||||
|
products: [],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Snack I',
|
||||||
|
products: [],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Lunch',
|
||||||
|
products: [],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Snack II',
|
||||||
|
products: [],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Dinner',
|
||||||
|
products: [],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Snack III',
|
||||||
|
products: [],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Supper',
|
||||||
|
products: [],
|
||||||
|
},
|
||||||
|
]
|
||||||
|
};
|
||||||
|
|
||||||
|
const homePageReducer = produce((draft, action) => {
|
||||||
|
switch(action.type) {
|
||||||
|
case GET_MEALS_SUCCESS:
|
||||||
|
draft.meals = action.meals;
|
||||||
|
draft.isLoading = false;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case UPDATE_MEAL_SUCCESS:
|
||||||
|
console.log(UPDATE_MEAL_SUCCESS, 'UPDATE_MEAL_SUCCESS')
|
||||||
|
draft.isLoading = false;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case ADD_PRODUCTS_TO_MEAL_SUCCESS:
|
||||||
|
console.log(ADD_PRODUCTS_TO_MEAL_SUCCESS, 'ADD_PRODUCTS_TO_MEAL_SUCCESS')
|
||||||
|
|
||||||
|
const restMeals = draft.meals.filter(({ id }) => id === action.meal.id)
|
||||||
|
|
||||||
|
draft.meals = [...restMeals, action.meal]
|
||||||
|
draft.isLoading = false;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case CREATE_MEAL_SUCCESS:
|
||||||
|
draft.meals.push(action.meal);
|
||||||
|
draft.isLoading = false;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case GET_MEALS_REQUEST:
|
||||||
|
case CREATE_MEAL_REQUEST:
|
||||||
|
case UPDATE_MEAL_REQUEST:
|
||||||
|
case ADD_PRODUCTS_TO_MEAL_REQUEST:
|
||||||
|
draft.isLoading = true;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case CREATE_MEAL_ERROR:
|
||||||
|
case GET_MEALS_ERROR:
|
||||||
|
case UPDATE_MEAL_ERROR:
|
||||||
|
case ADD_PRODUCTS_TO_MEAL_ERROR:
|
||||||
|
draft.isLoading = false;
|
||||||
|
draft.error = action.error;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}, initialState);
|
||||||
|
|
||||||
|
export default homePageReducer;
|
105
src/pages/Home/saga.js
Normal file
105
src/pages/Home/saga.js
Normal file
@ -0,0 +1,105 @@
|
|||||||
|
import { takeLatest, call, put, select } from 'redux-saga/effects';
|
||||||
|
import {api, request, routes} from 'utils';
|
||||||
|
import { GET_MEALS_REQUEST, UPDATE_MEAL_REQUEST, CREATE_MEAL_REQUEST, ADD_PRODUCTS_TO_MEAL_REQUEST } from './constants';
|
||||||
|
import {
|
||||||
|
|
||||||
|
} from './selectors';
|
||||||
|
import { createMealSuccessAction, createMealErrorAction, updateMealErrorAction, updateMealSuccessAction, getMealsSuccessAction, getMealsErrorAction } from './actions';
|
||||||
|
import { makeSelectLabel, makeSelectMealId, makeSelectProducts, makeSelectDate, } from './selectors'
|
||||||
|
import { makeSelectTokens } from 'containers/App/selectors'
|
||||||
|
import {push} from "connected-react-router";
|
||||||
|
|
||||||
|
export function* getMeals() {
|
||||||
|
const { access } = yield select(makeSelectTokens());
|
||||||
|
|
||||||
|
const requestURL = api.meals;
|
||||||
|
|
||||||
|
const requestParameters = {
|
||||||
|
method: 'GET',
|
||||||
|
headers: {
|
||||||
|
Authorization: `Bearer ${access.token}`,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
try {
|
||||||
|
const { meals } = yield call(request, requestURL, requestParameters);
|
||||||
|
yield put(getMealsSuccessAction({ meals }));
|
||||||
|
} catch (error) {
|
||||||
|
yield put(getMealsErrorAction({ error: error.message }));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function* updateMeal() {
|
||||||
|
const { access } = yield select(makeSelectTokens());
|
||||||
|
const label = yield select(makeSelectLabel());
|
||||||
|
const products = yield select(makeSelectProducts());
|
||||||
|
const date = yield select(makeSelectDate());
|
||||||
|
const mealId = yield select(makeSelectMealId());
|
||||||
|
|
||||||
|
const requestURL = `${api.meals}/${mealId}`;
|
||||||
|
|
||||||
|
const requestParameters = {
|
||||||
|
method: 'PUT',
|
||||||
|
headers: { Accept: 'application/json', 'Content-Type': 'application/json', Authorization: `Bearer ${access.token}`, },
|
||||||
|
body: JSON.stringify({ label, products, date }),
|
||||||
|
};
|
||||||
|
|
||||||
|
try {
|
||||||
|
const { birthday, gender, height, currentWeight, goalWeight, rateOfChange, activity } = yield call(request, requestURL, requestParameters);
|
||||||
|
yield put(updateMealSuccessAction({birthday, gender, height, currentWeight, goalWeight, rateOfChange, activity}));
|
||||||
|
} catch (error) {
|
||||||
|
yield put(updateMealErrorAction({error: error.message}));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function* createMeal() {
|
||||||
|
const { access } = yield select(makeSelectTokens());
|
||||||
|
const label = yield select(makeSelectLabel());
|
||||||
|
const products = yield select(makeSelectProducts());
|
||||||
|
const date = yield select(makeSelectDate());
|
||||||
|
|
||||||
|
const requestURL = api.meals;
|
||||||
|
|
||||||
|
const requestParameters = {
|
||||||
|
method: 'POST',
|
||||||
|
headers: { Accept: 'application/json', 'Content-Type': 'application/json', Authorization: `Bearer ${access.token}`, },
|
||||||
|
body: JSON.stringify({ label, products, date }),
|
||||||
|
};
|
||||||
|
|
||||||
|
try {
|
||||||
|
const { id, label, products, date } = yield call(request, requestURL, requestParameters);
|
||||||
|
yield put(createMealSuccessAction({ id, label, products, date }));
|
||||||
|
yield put(push(routes.dashboard.path));
|
||||||
|
} catch (error) {
|
||||||
|
yield put(createMealErrorAction({error: error.message}));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function* addProductsToMeal() {
|
||||||
|
const { access } = yield select(makeSelectTokens());
|
||||||
|
const products = yield select(makeSelectProducts());
|
||||||
|
const mealId = yield select(makeSelectMealId());
|
||||||
|
|
||||||
|
const requestURL = `${api.meals}/${mealId}`;
|
||||||
|
|
||||||
|
const requestParameters = {
|
||||||
|
method: 'POST',
|
||||||
|
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 }));
|
||||||
|
yield put(push(routes.dashboard.path));
|
||||||
|
} catch (error) {
|
||||||
|
yield put(createMealErrorAction({error: error.message}));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function* MealPageSaga() {
|
||||||
|
yield takeLatest(GET_MEALS_REQUEST, getMeals);
|
||||||
|
yield takeLatest(UPDATE_MEAL_REQUEST, updateMeal);
|
||||||
|
yield takeLatest(CREATE_MEAL_REQUEST, createMeal);
|
||||||
|
yield takeLatest(ADD_PRODUCTS_TO_MEAL_REQUEST, addProductsToMeal);
|
||||||
|
}
|
36
src/pages/Home/selectors.js
Normal file
36
src/pages/Home/selectors.js
Normal file
@ -0,0 +1,36 @@
|
|||||||
|
import { createSelector } from 'reselect';
|
||||||
|
import { initialState } from './reducer';
|
||||||
|
|
||||||
|
const selectHomePageDomain = (state) => state.homePage || initialState;
|
||||||
|
|
||||||
|
const makeSelectError = () =>
|
||||||
|
createSelector(selectHomePageDomain, (substate) => substate.error);
|
||||||
|
|
||||||
|
const makeSelectIsLoading = () =>
|
||||||
|
createSelector(selectHomePageDomain, (substate) => substate.isLoading);
|
||||||
|
|
||||||
|
const makeSelectMeals = () =>
|
||||||
|
createSelector(selectHomePageDomain, (substate) => substate.meals);
|
||||||
|
|
||||||
|
const makeSelectMealId = () =>
|
||||||
|
createSelector(selectHomePageDomain, (substate) => substate.mealId);
|
||||||
|
|
||||||
|
const makeSelectDate = () =>
|
||||||
|
createSelector(selectHomePageDomain, (substate) => substate.date);
|
||||||
|
|
||||||
|
const makeSelectLabel = () =>
|
||||||
|
createSelector(selectHomePageDomain, (substate) => substate.label);
|
||||||
|
|
||||||
|
const makeSelectProducts = () =>
|
||||||
|
createSelector(selectHomePageDomain, (substate) => substate.products);
|
||||||
|
|
||||||
|
export {
|
||||||
|
selectHomePageDomain,
|
||||||
|
makeSelectMealId,
|
||||||
|
makeSelectDate,
|
||||||
|
makeSelectLabel,
|
||||||
|
makeSelectProducts,
|
||||||
|
makeSelectError,
|
||||||
|
makeSelectIsLoading,
|
||||||
|
makeSelectMeals,
|
||||||
|
};
|
@ -1,6 +1,7 @@
|
|||||||
const API_BASE_URL = 'http://localhost:3001/v1'
|
const API_BASE_URL = 'http://localhost:3001/v1'
|
||||||
const AUTH = 'auth';
|
const AUTH = 'auth';
|
||||||
const PROFILE = 'profiles';
|
const PROFILE = 'profiles';
|
||||||
|
const MEALS = 'meals';
|
||||||
|
|
||||||
const urls = {
|
const urls = {
|
||||||
auth: {
|
auth: {
|
||||||
@ -9,6 +10,7 @@ const urls = {
|
|||||||
refreshToken: `${API_BASE_URL}/${AUTH}/refresh-tokens`
|
refreshToken: `${API_BASE_URL}/${AUTH}/refresh-tokens`
|
||||||
},
|
},
|
||||||
profile: `${API_BASE_URL}/${PROFILE}`,
|
profile: `${API_BASE_URL}/${PROFILE}`,
|
||||||
|
meals: `${API_BASE_URL}/${MEALS}`,
|
||||||
}
|
}
|
||||||
|
|
||||||
export default urls
|
export default urls
|
||||||
|
@ -3,6 +3,7 @@ import CreateProfilePage from "pages/CreateProfile";
|
|||||||
import ProfilePage from "pages/Profile";
|
import ProfilePage from "pages/Profile";
|
||||||
import LoginPage from "pages/Login";
|
import LoginPage from "pages/Login";
|
||||||
import RegisterPage from "pages/Register";
|
import RegisterPage from "pages/Register";
|
||||||
|
import AddDishPage from "pages/AddDish";
|
||||||
|
|
||||||
const routes = {
|
const routes = {
|
||||||
dashboard: {
|
dashboard: {
|
||||||
@ -17,6 +18,12 @@ const routes = {
|
|||||||
privateRoute: true,
|
privateRoute: true,
|
||||||
component: CreateProfilePage,
|
component: CreateProfilePage,
|
||||||
},
|
},
|
||||||
|
addDish: {
|
||||||
|
path: '/add-dish',
|
||||||
|
exact: true,
|
||||||
|
privateRoute: true,
|
||||||
|
component: AddDishPage,
|
||||||
|
},
|
||||||
profile: {
|
profile: {
|
||||||
path: '/profile',
|
path: '/profile',
|
||||||
exact: true,
|
exact: true,
|
||||||
|
Loading…
Reference in New Issue
Block a user