Connect Profile page with redux and saga

This commit is contained in:
= 2020-12-08 21:32:21 +01:00
parent bac2bbe739
commit 156b78ca39
21 changed files with 770 additions and 205 deletions

View File

@ -3,13 +3,16 @@
"version": "0.1.0", "version": "0.1.0",
"private": true, "private": true,
"dependencies": { "dependencies": {
"@date-io/date-fns": "^1.3.13",
"@material-ui/core": "^4.11.1", "@material-ui/core": "^4.11.1",
"@material-ui/icons": "^4.9.1", "@material-ui/icons": "^4.9.1",
"@material-ui/lab": "^4.0.0-alpha.57", "@material-ui/lab": "^4.0.0-alpha.57",
"@material-ui/pickers": "^3.2.10",
"@testing-library/jest-dom": "^5.11.4", "@testing-library/jest-dom": "^5.11.4",
"@testing-library/react": "^11.1.0", "@testing-library/react": "^11.1.0",
"@testing-library/user-event": "^12.1.10", "@testing-library/user-event": "^12.1.10",
"connected-react-router": "^6.8.0", "connected-react-router": "^6.8.0",
"date-fns": "^2.16.1",
"history": "4.10.1", "history": "4.10.1",
"immer": "^8.0.0", "immer": "^8.0.0",
"lodash": "^4.17.20", "lodash": "^4.17.20",

View File

@ -74,7 +74,9 @@ const AddMealModal = ({ isModalOpen, handleCloseModal }) => {
key={index} key={index}
action={ action={
<ListItemSecondaryAction> <ListItemSecondaryAction>
<Checkbox /> <Checkbox
edge="end"
/>
</ListItemSecondaryAction> </ListItemSecondaryAction>
} }
/> />

View File

@ -0,0 +1,27 @@
import React from 'react';
import {Button, Grid} from "@material-ui/core";
import {Skeleton} from "@material-ui/lab";
const Loader = () => {
return (
<React.Element>
<Grid item xs={12}>
<Button variant="text" fullWidth>
<Skeleton />
</Button>
</Grid>
<Grid item xs={12}>
<Button variant="text" fullWidth>
<Skeleton />
</Button>
</Grid>
<Grid item xs={12}>
<Button variant="text" fullWidth>
<Skeleton />
</Button>
</Grid>
</React.Element>
);
};
export default Loader;

View File

@ -0,0 +1,64 @@
import React, { useEffect } from 'react';
import { Grid, Button, Typography, Radio, RadioGroup } from '@material-ui/core';
import {useInjectReducer, useInjectSaga} from "redux-injectors";
import { useSelector, useDispatch } from 'react-redux';
import {createStructuredSelector} from "reselect";
import reducer from "pages/Profile/reducer";
import saga from "pages/Profile/saga";
import { profileInputChange, getGoalsAction } from 'pages/Profile/actions'
import {
makeSelectError,
makeSelectIsLoading,
makeSelectGoals,
} from "pages/Profile/selectors";
import Loader from './Loader'
const stateSelector = createStructuredSelector({
isLoading: makeSelectIsLoading(),
error: makeSelectError(),
goals: makeSelectGoals(),
});
const key = 'profilePage'
const GoalForm = () => {
useInjectReducer({ key, reducer });
useInjectSaga({ key, saga });
const dispatch = useDispatch()
const { goals, error, isLoading } = useSelector(stateSelector)
const handleChangeGoal = ({ target: { name, value }}) => {
dispatch(profileInputChange({ name, value }))
}
useEffect(() => {
dispatch(getGoalsAction())
}, [])
return (
<React.Fragment>
<Typography variant="h6" gutterBottom>
Your goal { JSON.stringify(error) }
</Typography>
<RadioGroup defaultValue={1} onChange={handleChangeGoal}>
<Grid container spacing={3} direction="column">
{isLoading ? (
<Loader />
) : (
<React.Fragment>
{goals.map(({ label, value }, index) => (
<Grid item xs={12} key={index}>
<Button variant="text" fullWidth>
{label}
<Radio color="secondary" name="goal" value={value} />
</Button>
</Grid>
))}
</React.Fragment>
)}
</Grid>
</RadioGroup>
</React.Fragment>
);
}
export default GoalForm;

View File

@ -1,4 +1,4 @@
import {List, ListItem, ListItemText, ListItemSecondaryAction} from "@material-ui/core"; import {ListItem, ListItemText, ListItemSecondaryAction} from "@material-ui/core";
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";

View File

@ -0,0 +1,139 @@
import 'date-fns';
import React, { useEffect } from 'react';
import {Typography, InputLabel, Box, Grid, InputAdornment, Select, FormControl, MenuItem, TextField, Slider} from '@material-ui/core';
import DateFnsUtils from '@date-io/date-fns';
import {
MuiPickersUtilsProvider,
KeyboardDatePicker,
} from '@material-ui/pickers';
import {useInjectReducer, useInjectSaga} from "redux-injectors";
import { useSelector, useDispatch } from 'react-redux';
import {createStructuredSelector} from "reselect";
import reducer from "pages/Profile/reducer";
import saga from "pages/Profile/saga";
import { profileInputChange, getActivitiesAction, getGendersAction } from 'pages/Profile/actions'
import {
makeSelectError,
makeSelectIsLoading,
makeSelectActivities,
makeSelectGenders,
makeSelectWeight,
makeSelectHeight,
makeSelectBirthday,
makeSelectActivity,
makeSelectGender,
} from "pages/Profile/selectors";
const stateSelector = createStructuredSelector({
isLoading: makeSelectIsLoading(),
error: makeSelectError(),
activities: makeSelectActivities(),
genders: makeSelectGenders(),
weight: makeSelectWeight(),
height: makeSelectHeight(),
birthday: makeSelectBirthday(),
activity: makeSelectActivity(),
gender: makeSelectGender(),
});
const key = 'profilePage'
const PersonalDetailsForm = () => {
useInjectReducer({ key, reducer });
useInjectSaga({ key, saga });
const dispatch = useDispatch()
const { gender, activity, birthday, height, weight, activities, genders, error, isLoading } = useSelector(stateSelector)
const handleChangeValue = ({ target: { name, value }}) => {
dispatch(profileInputChange({ name, value }))
}
useEffect(() => {
dispatch(getActivitiesAction())
dispatch(getGendersAction())
}, [])
return (
<React.Fragment>
<Typography variant="h6" gutterBottom>
Personal details
</Typography>
<Grid container spacing={3}>
<Grid item xs={12}>
<FormControl variant="outlined" fullWidth>
<InputLabel>Gender</InputLabel>
<Select
name="gender"
label="Gender"
onChange={handleChangeValue}
value={gender}
>
{genders.map(({ label, value }) => (
<MenuItem value={value}>{label}</MenuItem>
))}
</Select>
</FormControl>
</Grid>
<Grid item xs={12}>
<MuiPickersUtilsProvider utils={DateFnsUtils}>
<KeyboardDatePicker
disableToolbar
inputVariant="outlined"
fullWidth
format="MM/dd/yyyy"
margin="normal"
name="birthday"
value={birthday}
onChange={handleChangeValue}
/>
</MuiPickersUtilsProvider>
</Grid>
<Grid item xs={12}>
<TextField
required
variant="outlined"
label="Height"
type="number"
name="height"
onChange={handleChangeValue}
value={height}
fullWidth
InputProps={{
endAdornment: <InputAdornment position="end">cm</InputAdornment>
}}
/>
</Grid>
<Grid item xs={12}>
<TextField
required
variant="outlined"
label="Weight"
type="number"
name="weight"
onChange={handleChangeValue}
value={weight}
fullWidth
InputProps={{
endAdornment: <InputAdornment position="end">Kg</InputAdornment>
}}
/>
</Grid>
<Grid item xs={12}>
<Typography gutterBottom>
Activity
</Typography>
<Box mx={4}>
<Slider
name="activity"
onChange={handleChangeValue}
value={activity}
min={0}
max={4}
marks={activities}
/>
</Box>
</Grid>
</Grid>
</React.Fragment>
);
}
export default PersonalDetailsForm

View File

@ -0,0 +1,38 @@
import React from 'react';
import { makeStyles } from '@material-ui/core/styles';
import {Typography, List, ListItem, ListItemText} from '@material-ui/core';
const useStyles = makeStyles((theme) => ({
listItem: {
padding: theme.spacing(1, 0),
},
total: {
fontWeight: 700,
},
title: {
marginTop: theme.spacing(2),
},
}));
const ReviewProfileForm = () => {
const classes = useStyles();
return (
<React.Fragment>
<Typography variant="h6" gutterBottom>
Profile summary
</Typography>
<List disablePadding>
TODO
{/*{profile.map(({ label, value }, index) => (*/}
{/* <ListItem className={classes.listItem} key={index}>*/}
{/* <ListItemText primary={label} />*/}
{/* <Typography variant="body2">{value}</Typography>*/}
{/* </ListItem>*/}
{/*))}*/}
</List>
</React.Fragment>
);
}
export default ReviewProfileForm

View File

@ -19,8 +19,8 @@ const App = () => {
return ( return (
<ThemeProvider theme={theme}> <ThemeProvider theme={theme}>
<CssBaseline /> <CssBaseline />
{/*{isLogged && <Navbar /> }*/} {isLogged && <Navbar /> }
<Navbar /> {/*<Navbar />*/}
<Routes /> <Routes />
</ThemeProvider> </ThemeProvider>
); );

View File

@ -2,11 +2,24 @@ import React from 'react';
import { useInjectReducer, useInjectSaga } from 'redux-injectors'; import { useInjectReducer, useInjectSaga } from 'redux-injectors';
import { createStructuredSelector } from 'reselect'; import { createStructuredSelector } from 'reselect';
import { useSelector, useDispatch } from 'react-redux'; import { useSelector, useDispatch } from 'react-redux';
import { Container, Grid, Box, Paper, Button, Typography, TextField } from '@material-ui/core' import {
Container,
Grid,
Box,
Paper,
Button,
Typography,
TextField,
FormControlLabel,
Checkbox,
Link
} from '@material-ui/core'
import reducer from './reducer'; import reducer from './reducer';
import saga from './saga'; import saga from './saga';
import { makeSelectEmail, makeSelectPassword, makeSelectLoading } from './selectors' import { makeSelectEmail, makeSelectPassword, makeSelectLoading } from './selectors'
import { loginInputChange, loginAction } from './actions' import { loginInputChange, loginAction } from './actions'
import {routes} from "../../utils";
import {makeStyles} from "@material-ui/core/styles";
const stateSelector = createStructuredSelector({ const stateSelector = createStructuredSelector({
email: makeSelectEmail(), email: makeSelectEmail(),
@ -14,8 +27,25 @@ const stateSelector = createStructuredSelector({
loading: makeSelectLoading(), loading: makeSelectLoading(),
}); });
const useStyles = makeStyles((theme) => ({
paper: {
marginTop: theme.spacing(8),
display: 'flex',
flexDirection: 'column',
alignItems: 'center',
},
form: {
width: '100%',
marginTop: theme.spacing(1),
},
submit: {
margin: theme.spacing(3, 0, 2),
},
}));
const key = 'loginPage' const key = 'loginPage'
const Login = () => { const Login = () => {
const classes = useStyles()
useInjectReducer({ key, reducer }); useInjectReducer({ key, reducer });
useInjectSaga({ key, saga }); useInjectSaga({ key, saga });
const { email, password, loading } = useSelector(stateSelector) const { email, password, loading } = useSelector(stateSelector)
@ -31,36 +61,61 @@ const Login = () => {
} }
return ( return (
<Container> <Container component="main" maxWidth="xs">
<Box marginTop={2}> <div className={classes.paper}>
<Paper component="form" noValidate autoComplete="off" onSubmit={handleSubmit}> <Typography component="h1" variant="h5">
<Grid Login to Account
container </Typography>
direction="column" <form className={classes.form} onSubmit={handleSubmit} noValidate>
alignItems="center"
justify="center"
>
<Typography>Login</Typography>
<TextField <TextField
type="text" variant="outlined"
label="E-mail" margin="normal"
required
fullWidth
id="email"
label="Email Address"
name="email" name="email"
variant="outlined" autoComplete="email"
value={email} autoFocus
onChange={onChangeInput} onChange={onChangeInput}
value={email}
/> />
<TextField <TextField
type="password"
label="password"
name="password"
variant="outlined" variant="outlined"
value={password} margin="normal"
required
fullWidth
name="password"
label="Password"
type="password"
id="password"
autoComplete="current-password"
onChange={onChangeInput} onChange={onChangeInput}
value={password}
/> />
<Button type="submit" disabled={loading} color="primary">Sign in</Button> <FormControlLabel
control={<Checkbox value="remember" color="primary" />}
label="Remember me"
/>
<Button
type="submit"
fullWidth
variant="contained"
color="primary"
className={classes.submit}
disabled={loading}
>
Sign Up
</Button>
<Grid container alignItems="flex-end">
<Grid item>
<Link href={routes.register.path} color="secondary" variant="body2">
Don't have an account? Sign Up
</Link>
</Grid> </Grid>
</Paper> </Grid>
</Box> </form>
</div>
</Container> </Container>
); );
}; };

View File

@ -1,4 +1,7 @@
import { import {
GET_GOALS_REQUEST,
GET_GOALS_SUCCESS,
GET_GOALS_ERROR,
GET_ACTIVITIES_REQUEST, GET_ACTIVITIES_REQUEST,
GET_ACTIVITIES_SUCCESS, GET_ACTIVITIES_SUCCESS,
GET_ACTIVITIES_ERROR, GET_ACTIVITIES_ERROR,
@ -24,18 +27,18 @@ export const getProfileAction = () => ({
type: GET_PROFILE_REQUEST, type: GET_PROFILE_REQUEST,
}) })
export const getProfileSuccessAction = ({birthday, gender, height, currentWeight, goalWeight, rateOfChange, activity}) => ({ export const getProfileSuccessAction = ({birthday, gender, height, weight, goalWeight, rateOfChange, activity}) => ({
type: GET_PROFILE_SUCCESS, type: GET_PROFILE_SUCCESS,
birthday, birthday,
gender, gender,
height, height,
currentWeight, weight,
goalWeight, goalWeight,
rateOfChange, rateOfChange,
activity activity
}) })
export const getProfileErrorAction = (error) => ({ export const getProfileErrorAction = ({error}) => ({
type: GET_PROFILE_ERROR, type: GET_PROFILE_ERROR,
error, error,
}) })
@ -45,18 +48,18 @@ export const updateProfileAction = () => ({
type: UPDATE_PROFILE_REQUEST, type: UPDATE_PROFILE_REQUEST,
}) })
export const updateProfileSuccessAction = ({birthday, gender, height, currentWeight, goalWeight, rateOfChange, activity}) => ({ export const updateProfileSuccessAction = ({birthday, gender, height, weight, goalWeight, rateOfChange, activity}) => ({
type: UPDATE_PROFILE_SUCCESS, type: UPDATE_PROFILE_SUCCESS,
birthday, birthday,
gender, gender,
height, height,
currentWeight, weight,
goalWeight, goalWeight,
rateOfChange, rateOfChange,
activity activity
}) })
export const updateProfileErrorAction = (error) => ({ export const updateProfileErrorAction = ({error}) => ({
type: UPDATE_PROFILE_ERROR, type: UPDATE_PROFILE_ERROR,
error, error,
}) })
@ -70,7 +73,7 @@ export const getActivitiesSuccessAction = ({ activities }) => ({
activities, activities,
}) })
export const getActivitiesErrorAction = (error) => ({ export const getActivitiesErrorAction = ({error}) => ({
type: GET_ACTIVITIES_ERROR, type: GET_ACTIVITIES_ERROR,
error, error,
}) })
@ -84,7 +87,22 @@ export const getGendersSuccessAction = ({ genders }) => ({
genders, genders,
}) })
export const getGendersErrorAction = (error) => ({ export const getGendersErrorAction = ({error}) => ({
type: GET_GENDERS_ERROR, type: GET_GENDERS_ERROR,
error, error,
}) })
export const getGoalsAction = () => ({
type: GET_GOALS_REQUEST,
})
export const getGoalsSuccessAction = ({ goals }) => ({
type: GET_GOALS_SUCCESS,
goals,
})
export const getGoalsErrorAction = ({error}) => ({
type: GET_GOALS_ERROR,
error,
})

View File

@ -10,8 +10,14 @@ export const GET_GENDERS_REQUEST = 'app/ProfilePage/GET_GENDERS_REQUEST';
export const GET_GENDERS_SUCCESS = 'app/ProfilePage/GET_GENDERS_SUCCESS'; export const GET_GENDERS_SUCCESS = 'app/ProfilePage/GET_GENDERS_SUCCESS';
export const GET_GENDERS_ERROR = 'app/ProfilePage/GET_GENDERS_ERROR'; export const GET_GENDERS_ERROR = 'app/ProfilePage/GET_GENDERS_ERROR';
export const GET_GOALS_REQUEST = 'app/ProfilePage/GET_GOALS_REQUEST';
export const GET_GOALS_SUCCESS = 'app/ProfilePage/GET_GOALS_SUCCESS';
export const GET_GOALS_ERROR = 'app/ProfilePage/GET_GOALS_ERROR';
export const GET_ACTIVITIES_REQUEST = 'app/ProfilePage/GET_ACTIVITIES_REQUEST'; export const GET_ACTIVITIES_REQUEST = 'app/ProfilePage/GET_ACTIVITIES_REQUEST';
export const GET_ACTIVITIES_SUCCESS = 'app/ProfilePage/GET_ACTIVITIES_SUCCESS'; export const GET_ACTIVITIES_SUCCESS = 'app/ProfilePage/GET_ACTIVITIES_SUCCESS';
export const GET_ACTIVITIES_ERROR = 'app/ProfilePage/GET_ACTIVITIES_ERROR'; export const GET_ACTIVITIES_ERROR = 'app/ProfilePage/GET_ACTIVITIES_ERROR';
export const PROFILE_INPUT_CHANGE = 'app/ProfilePage/PROFILE_INPUT_CHANGE'; export const PROFILE_INPUT_CHANGE = 'app/ProfilePage/PROFILE_INPUT_CHANGE';

View File

@ -1,145 +1,136 @@
import React, { useEffect } from 'react'; import React, { useState } from 'react';
import { Container, Slider, Button, TextField, FormControl, MenuItem, InputLabel, Select, Grid, Typography } from '@material-ui/core' import { makeStyles } from '@material-ui/core/styles';
import {Paper, Stepper, Step, StepLabel, Button, Typography } from '@material-ui/core';
import {useInjectReducer, useInjectSaga} from "redux-injectors"; import {useInjectReducer, useInjectSaga} from "redux-injectors";
import { useDispatch } from 'react-redux';
import GoalForm from 'components/GoalForm';
import PersonalDetailsForm from 'components/PersonalDetailsForm';
import ReviewProfileForm from 'components/ReviewProfileForm';
import reducer from "./reducer"; import reducer from "./reducer";
import saga from "./saga"; import saga from "./saga";
import { useSelector, useDispatch } from 'react-redux'; import { updateProfileAction } from './actions'
import {createStructuredSelector} from "reselect";
import { profileInputChange, getProfileAction, getActivitiesAction, getGendersAction } from './actions'
import {
makeSelectError,
makeSelectBirthday,
makeSelectHeight,
makeSelectCurrentWeight,
makeSelectRateOfChange,
makeSelectActivity,
makeSelectGoalWeight,
makeSelectGender,
makeSelectLoading,
} from "./selectors";
const stateSelector = createStructuredSelector({ const useStyles = makeStyles((theme) => ({
loading: makeSelectLoading(), layout: {
error: makeSelectError(), width: 'auto',
gender: makeSelectGender(), marginLeft: theme.spacing(2),
birthday: makeSelectBirthday(), marginRight: theme.spacing(2),
height: makeSelectHeight(), [theme.breakpoints.up(600 + theme.spacing(2) * 2)]: {
currentWeight: makeSelectCurrentWeight(), width: 600,
goalWeight: makeSelectGoalWeight(), marginLeft: 'auto',
rateOfChange: makeSelectRateOfChange(), marginRight: 'auto',
activity: makeSelectActivity(), },
}); },
paper: {
marginTop: theme.spacing(3),
marginBottom: theme.spacing(3),
padding: theme.spacing(2),
[theme.breakpoints.up(600 + theme.spacing(3) * 2)]: {
marginTop: theme.spacing(6),
marginBottom: theme.spacing(6),
padding: theme.spacing(3),
},
},
stepper: {
padding: theme.spacing(3, 0, 5),
},
buttons: {
display: 'flex',
justifyContent: 'flex-end',
},
button: {
marginTop: theme.spacing(3),
marginLeft: theme.spacing(1),
},
}));
const steps = ['Your goal', 'Personal details', 'Review your profile'];
const getStepContent = (step) => {
switch (step) {
case 0:
return <GoalForm />;
case 1:
return <PersonalDetailsForm />;
case 2:
return <ReviewProfileForm />;
default:
throw new Error('Unknown step');
}
}
const key = 'profilePage' const key = 'profilePage'
const ProfilePage = () => { const ProfilePage = () => {
const classes = useStyles();
const [activeStep, setActiveStep] = useState(0);
useInjectReducer({ key, reducer }); useInjectReducer({ key, reducer });
useInjectSaga({ key, saga }); useInjectSaga({ key, saga });
const dispatch = useDispatch() const dispatch = useDispatch()
const {
gender,
birthday,
height,
currentWeight,
goalWeight,
rateOfChange,
activity,
} = useSelector(stateSelector)
const getProfile = () => dispatch(getProfileAction()) const handleNext = () => {
const getActivities = () => dispatch(getActivitiesAction()) setActiveStep(activeStep + 1);
const getGenders = () => dispatch(getGendersAction()) };
useEffect(() => { const handleBack = () => {
getProfile() setActiveStep(activeStep - 1);
getActivities() };
getGenders()
}, [])
const handleChange = ({ target: { name, value }}) => { const handleSubmitProfile = (event) => {
dispatch(profileInputChange({ name, value })) event.preventDefault();
dispatch(updateProfileAction())
} }
return ( return (
<Container> <main className={classes.layout}>
<Grid container direction="column" alignItems="center" justify="center"> <Paper className={classes.paper}>
<Typography variant="h5">Profile</Typography> <Typography component="h1" variant="h4" align="center">
<FormControl variant="outlined" fullWidth> Profile
<InputLabel id="gender-label">Gender</InputLabel>
<Select
labelId="gender-label"
name="gender"
value={gender}
onChange={handleChange}
label="Gender"
>
<MenuItem value={`male`}>Male</MenuItem>
<MenuItem value={`female`}>Female</MenuItem>
</Select>
</FormControl>
<FormControl fullWidth>
<TextField
label="Height"
name="height"
type="number"
value={height}
onChange={handleChange}
/>
</FormControl>
<FormControl fullWidth>
<TextField
label="Current weight"
name="currentWeight"
type="number"
value={currentWeight}
onChange={handleChange}
variant="outlined"
/>
</FormControl>
<FormControl fullWidth>
<TextField
label="Goal weight"
name="goalWeight"
type="number"
value={goalWeight}
onChange={handleChange}
variant="outlined"
/>
</FormControl>
<FormControl fullWidth>
<Typography id="rate-of-change" gutterBottom>
The rate of weight change
</Typography> </Typography>
<Slider <Stepper activeStep={activeStep} className={classes.stepper}>
value={rateOfChange} {steps.map((label) => (
onChange={handleChange} <Step key={label}>
aria-labelledby="rate-of-change" <StepLabel>{label}</StepLabel>
step={0.1} </Step>
marks ))}
min={0} </Stepper>
max={1} <React.Fragment>
valueLabelDisplay="auto" {activeStep === steps.length ? (
/> <React.Fragment>
</FormControl> <Typography variant="h5" gutterBottom>
<FormControl fullWidth> Thank you for your order.
<InputLabel id="activity-label">Activity</InputLabel> </Typography>
<Select <Typography variant="subtitle1">
labelId="activity-label" Your order number is #2001539. We have emailed your order confirmation, and will
name="activity" send you an update when your order has shipped.
value={activity} </Typography>
onChange={handleChange} </React.Fragment>
label="Activity" ) : (
<form onSubmit={handleSubmitProfile}>
{getStepContent(activeStep)}
<div className={classes.buttons}>
{activeStep !== 0 && (
<Button onClick={handleBack} className={classes.button}>
Back
</Button>
)}
<Button
variant="contained"
color="primary"
onClick={handleNext}
type={activeStep === steps.length - 1 ? 'submit' : 'button'}
className={classes.button}
> >
<MenuItem value={`1`}>Small</MenuItem> {activeStep === steps.length - 1 ? 'Save profile' : 'Next'}
<MenuItem value={`1.2`}>Medium</MenuItem> </Button>
<MenuItem value={`2`}>High</MenuItem> </div>
</Select> </form>
</FormControl> )}
<FormControl fullWidth> </React.Fragment>
<Button variant="contained" color="primary">Save</Button> </Paper>
</FormControl> </main>
</Grid> )
</Container> }
);
};
export default ProfilePage; export default ProfilePage

View File

@ -1,5 +1,8 @@
import produce from 'immer'; import produce from 'immer';
import { import {
GET_GOALS_REQUEST,
GET_GOALS_SUCCESS,
GET_GOALS_ERROR,
GET_ACTIVITIES_SUCCESS, GET_ACTIVITIES_SUCCESS,
GET_GENDERS_SUCCESS, GET_GENDERS_SUCCESS,
GET_ACTIVITIES_ERROR, GET_ACTIVITIES_ERROR,
@ -17,13 +20,14 @@ import {
export const initialState = { export const initialState = {
activities: [], activities: [],
goals: [],
genders: [], genders: [],
loading: false, isLoading: false,
error: {}, error: {},
gender: '', gender: '',
birthday: '', birthday: new Date(),
height: 0, height: 0,
currentWeight: 0, weight: 0,
goalWeight: 0, goalWeight: 0,
rateOfChange: 0, rateOfChange: 0,
activity: 0, activity: 0,
@ -33,12 +37,17 @@ const loginPageReducer = produce((draft, action) => {
switch(action.type) { switch(action.type) {
case GET_ACTIVITIES_SUCCESS: case GET_ACTIVITIES_SUCCESS:
draft.activities = action.activities; draft.activities = action.activities;
draft.loading = false; draft.isLoading = false;
break;
case GET_GOALS_SUCCESS:
draft.goals = action.goals;
draft.isLoading = false;
break; break;
case GET_GENDERS_SUCCESS: case GET_GENDERS_SUCCESS:
draft.genders = action.genders; draft.genders = action.genders;
draft.loading = false; draft.isLoading = false;
break; break;
case GET_PROFILE_SUCCESS: case GET_PROFILE_SUCCESS:
@ -46,25 +55,27 @@ const loginPageReducer = produce((draft, action) => {
draft.birthday = action.birthday; draft.birthday = action.birthday;
draft.gender = action.gender; draft.gender = action.gender;
draft.height = action.height; draft.height = action.height;
draft.currentWeight = action.currentWeight; draft.weight = action.weight;
draft.goalWeight = action.goalWeight; draft.goalWeight = action.goalWeight;
draft.rateOfChange = action.rateOfChange; draft.rateOfChange = action.rateOfChange;
draft.activity = action.activity; draft.activity = action.activity;
draft.loading = false; draft.isLoading = false;
break; break;
case GET_ACTIVITIES_REQUEST: case GET_ACTIVITIES_REQUEST:
case GET_GENDERS_REQUEST: case GET_GENDERS_REQUEST:
case GET_PROFILE_REQUEST: case GET_PROFILE_REQUEST:
case GET_GOALS_REQUEST:
case UPDATE_PROFILE_REQUEST: case UPDATE_PROFILE_REQUEST:
draft.loading = true; draft.isLoading = true;
break; break;
case GET_ACTIVITIES_ERROR: case GET_ACTIVITIES_ERROR:
case GET_GENDERS_ERROR: case GET_GENDERS_ERROR:
case GET_PROFILE_ERROR: case GET_PROFILE_ERROR:
case GET_GOALS_ERROR:
case UPDATE_PROFILE_ERROR: case UPDATE_PROFILE_ERROR:
draft.loading = false; draft.isLoading = false;
draft.error = action.error; draft.error = action.error;
break; break;

View File

@ -1,16 +1,16 @@
import { takeLatest, call, put, select } from 'redux-saga/effects'; import { takeLatest, call, put, select } from 'redux-saga/effects';
import { api, request } from 'utils'; import { api, request } from 'utils';
import { GET_ACTIVITIES_REQUEST, GET_GENDERS_REQUEST, GET_PROFILE_REQUEST, UPDATE_PROFILE_REQUEST } from './constants'; import { GET_GOALS_REQUEST, GET_ACTIVITIES_REQUEST, GET_GENDERS_REQUEST, GET_PROFILE_REQUEST, UPDATE_PROFILE_REQUEST } from './constants';
import { import {
makeSelectBirthday, makeSelectBirthday,
makeSelectHeight, makeSelectHeight,
makeSelectCurrentWeight, makeSelectWeight,
makeSelectRateOfChange, makeSelectRateOfChange,
makeSelectActivity, makeSelectActivity,
makeSelectGoalWeight, makeSelectGoalWeight,
makeSelectGender makeSelectGender
} from './selectors'; } from './selectors';
import { getActivitiesSuccessAction, getActivitiesErrorAction, getGendersSuccessAction, getGendersErrorAction, updateProfileErrorAction, updateProfileSuccessAction, getProfileErrorAction, getProfileSuccessAction } from './actions'; import { getGoalsErrorAction, getGoalsSuccessAction, getActivitiesSuccessAction, getActivitiesErrorAction, getGendersSuccessAction, getGendersErrorAction, updateProfileErrorAction, updateProfileSuccessAction, getProfileErrorAction, getProfileSuccessAction } from './actions';
import { makeSelectTokens } from 'containers/App/selectors' import { makeSelectTokens } from 'containers/App/selectors'
@ -30,7 +30,7 @@ export function* getProfile() {
const { birthday, gender, height, currentWeight, goalWeight, rateOfChange, activity } = yield call(request, requestURL, requestParameters); const { birthday, gender, height, currentWeight, goalWeight, rateOfChange, activity } = yield call(request, requestURL, requestParameters);
yield put(getProfileSuccessAction({birthday, gender, height, currentWeight, goalWeight, rateOfChange, activity})); yield put(getProfileSuccessAction({birthday, gender, height, currentWeight, goalWeight, rateOfChange, activity}));
} catch (error) { } catch (error) {
yield put(getProfileErrorAction(error.message)); yield put(getProfileErrorAction({ error: error.message }));
} }
} }
@ -40,7 +40,7 @@ export function* updateProfile() {
const birthday = yield select(makeSelectBirthday()); const birthday = yield select(makeSelectBirthday());
const gender = yield select(makeSelectGender()); const gender = yield select(makeSelectGender());
const height = yield select(makeSelectHeight()); const height = yield select(makeSelectHeight());
const currentWeight = yield select(makeSelectCurrentWeight()); const weight = yield select(makeSelectWeight());
const goalWeight = yield select(makeSelectGoalWeight()); const goalWeight = yield select(makeSelectGoalWeight());
const rateOfChange = yield select(makeSelectRateOfChange()); const rateOfChange = yield select(makeSelectRateOfChange());
const activity = yield select(makeSelectActivity()); const activity = yield select(makeSelectActivity());
@ -50,14 +50,14 @@ export function* updateProfile() {
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}`, },
body: JSON.stringify({ birthday, gender, height, currentWeight, goalWeight, rateOfChange, activity }), body: JSON.stringify({ birthday, gender, height, weight, goalWeight, rateOfChange, activity }),
}; };
try { try {
const { birthday, gender, height, currentWeight, goalWeight, rateOfChange, activity } = yield call(request, requestURL, requestParameters); const { birthday, gender, height, weight, goalWeight, rateOfChange, activity } = yield call(request, requestURL, requestParameters);
yield put(updateProfileSuccessAction({birthday, gender, height, currentWeight, goalWeight, rateOfChange, activity})); yield put(updateProfileSuccessAction({birthday, gender, height, weight, goalWeight, rateOfChange, activity}));
} catch (error) { } catch (error) {
yield put(updateProfileErrorAction(error.message)); yield put(updateProfileErrorAction({error: error.message}));
} }
} }
@ -77,11 +77,10 @@ export function* getActivities() {
const { activities } = yield call(request, requestURL, requestParameters); const { activities } = yield call(request, requestURL, requestParameters);
yield put(getActivitiesSuccessAction({ activities })); yield put(getActivitiesSuccessAction({ activities }));
} catch (error) { } catch (error) {
yield put(getActivitiesErrorAction(error.message)); yield put(getActivitiesErrorAction({error: error.message}));
} }
} }
export function* getGenders() { export function* getGenders() {
const { access } = yield select(makeSelectTokens()); const { access } = yield select(makeSelectTokens());
@ -98,7 +97,26 @@ export function* getGenders() {
const { genders } = yield call(request, requestURL, requestParameters); const { genders } = yield call(request, requestURL, requestParameters);
yield put(getGendersSuccessAction({ genders })); yield put(getGendersSuccessAction({ genders }));
} catch (error) { } catch (error) {
yield put(getGendersErrorAction(error.message)); yield put(getGendersErrorAction({ error: error.message }));
}
}
export function* getGoals() {
const { access } = yield select(makeSelectTokens());
const requestURL = api.goals;
const requestParameters = {
method: 'GET',
headers: {
Authorization: `Bearer ${access.token}`,
},
};
try {
const { goals } = yield call(request, requestURL, requestParameters);
yield put(getGoalsSuccessAction({ goals }));
} catch (error) {
yield put(getGoalsErrorAction({ error: error.message }));
} }
} }
@ -107,5 +125,6 @@ export default function* profilePageSaga() {
yield takeLatest(GET_ACTIVITIES_REQUEST, getActivities); yield takeLatest(GET_ACTIVITIES_REQUEST, getActivities);
yield takeLatest(GET_GENDERS_REQUEST, getGenders); yield takeLatest(GET_GENDERS_REQUEST, getGenders);
yield takeLatest(GET_PROFILE_REQUEST, getProfile); yield takeLatest(GET_PROFILE_REQUEST, getProfile);
yield takeLatest(GET_GOALS_REQUEST, getGoals);
yield takeLatest(UPDATE_PROFILE_REQUEST, updateProfile); yield takeLatest(UPDATE_PROFILE_REQUEST, updateProfile);
} }

View File

@ -12,8 +12,8 @@ const makeSelectBirthday = () =>
const makeSelectHeight = () => const makeSelectHeight = () =>
createSelector(selectProfilePageDomain, (substate) => substate.height); createSelector(selectProfilePageDomain, (substate) => substate.height);
const makeSelectCurrentWeight = () => const makeSelectWeight = () =>
createSelector(selectProfilePageDomain, (substate) => substate.currentWeight); createSelector(selectProfilePageDomain, (substate) => substate.weight);
const makeSelectRateOfChange = () => const makeSelectRateOfChange = () =>
createSelector(selectProfilePageDomain, (substate) => substate.rateOfChange); createSelector(selectProfilePageDomain, (substate) => substate.rateOfChange);
@ -27,18 +27,30 @@ const makeSelectGoalWeight = () =>
const makeSelectGender = () => const makeSelectGender = () =>
createSelector(selectProfilePageDomain, (substate) => substate.gender); createSelector(selectProfilePageDomain, (substate) => substate.gender);
const makeSelectLoading = () => const makeSelectIsLoading = () =>
createSelector(selectProfilePageDomain, (substate) => substate.loading); createSelector(selectProfilePageDomain, (substate) => substate.isLoading);
const makeSelectGoals = () =>
createSelector(selectProfilePageDomain, (substate) => substate.goals);
const makeSelectActivities = () =>
createSelector(selectProfilePageDomain, (substate) => substate.activities);
const makeSelectGenders = () =>
createSelector(selectProfilePageDomain, (substate) => substate.genders);
export { export {
selectProfilePageDomain, selectProfilePageDomain,
makeSelectError, makeSelectError,
makeSelectBirthday, makeSelectBirthday,
makeSelectHeight, makeSelectHeight,
makeSelectCurrentWeight, makeSelectWeight,
makeSelectRateOfChange, makeSelectRateOfChange,
makeSelectActivity, makeSelectActivity,
makeSelectGoalWeight, makeSelectGoalWeight,
makeSelectGender, makeSelectGender,
makeSelectLoading, makeSelectIsLoading,
makeSelectGoals,
makeSelectActivities,
makeSelectGenders,
}; };

View File

@ -0,0 +1,80 @@
import React from 'react';
import { makeStyles } from '@material-ui/core/styles';
import {Container, Typography, Grid, Button, TextField, FormControlLabel, Checkbox, Link} from '@material-ui/core';
import { routes } from 'utils';
const useStyles = makeStyles((theme) => ({
paper: {
marginTop: theme.spacing(8),
display: 'flex',
flexDirection: 'column',
alignItems: 'center',
},
form: {
width: '100%',
marginTop: theme.spacing(1),
},
submit: {
margin: theme.spacing(3, 0, 2),
},
}));
const RegisterPage = () => {
const classes = useStyles();
return (
<Container component="main" maxWidth="xs">
<div className={classes.paper}>
<Typography component="h1" variant="h5">
Create Account
</Typography>
<form className={classes.form} noValidate>
<TextField
variant="outlined"
margin="normal"
required
fullWidth
id="email"
label="Email Address"
name="email"
autoComplete="email"
autoFocus
/>
<TextField
variant="outlined"
margin="normal"
required
fullWidth
name="password"
label="Password"
type="password"
id="password"
autoComplete="current-password"
/>
<FormControlLabel
control={<Checkbox value="remember" color="primary" />}
label="Remember me"
/>
<Button
type="submit"
fullWidth
variant="contained"
color="primary"
className={classes.submit}
>
Sign Up
</Button>
<Grid container alignItems="flex-end">
<Grid item>
<Link href={routes.login.path} color="secondary" variant="body2">
Have already account? Sign In
</Link>
</Grid>
</Grid>
</form>
</div>
</Container>
);
}
export default RegisterPage

View File

@ -3,6 +3,7 @@ const AUTH = 'auth';
const PROFILE = 'profiles'; const PROFILE = 'profiles';
const ACTIVITY = 'activities'; const ACTIVITY = 'activities';
const GENDER = 'genders'; const GENDER = 'genders';
const GOALS = 'goals';
const urls = { const urls = {
auth: { auth: {
@ -12,6 +13,7 @@ const urls = {
profile: `${API_BASE_URL}/${PROFILE}`, profile: `${API_BASE_URL}/${PROFILE}`,
activities: `${API_BASE_URL}/${ACTIVITY}`, activities: `${API_BASE_URL}/${ACTIVITY}`,
genders: `${API_BASE_URL}/${GENDER}`, genders: `${API_BASE_URL}/${GENDER}`,
goals: `${API_BASE_URL}/${GOALS}`,
} }
export default urls export default urls

View File

@ -4,7 +4,7 @@ import blue from "@material-ui/core/colors/blue";
export const MEALS_LIST = [ export const MEALS_LIST = [
{ {
name: 'eggs', label: 'eggs',
macronutrients: [ macronutrients: [
{ {
value: 1245, value: 1245,
@ -25,7 +25,7 @@ export const MEALS_LIST = [
], ],
}, },
{ {
name: 'bread', label: 'bread',
macronutrients: [ macronutrients: [
{ {
value: 1245, value: 1245,
@ -46,7 +46,7 @@ export const MEALS_LIST = [
], ],
}, },
{ {
name: 'corn flakes', label: 'corn flakes',
macronutrients: [ macronutrients: [
{ {
value: 1245, value: 1245,
@ -88,22 +88,22 @@ export const MENU_LIST = [
unit: 'g', unit: 'g',
}, },
], ],
listName: 'BREAKFAST', label: 'BREAKFAST',
meals: MEALS_LIST, meals: MEALS_LIST,
}, },
{ {
macronutrients: [], macronutrients: [],
listName: 'LUNCH', label: 'LUNCH',
meals: [] meals: []
}, },
{ {
macronutrients: [], macronutrients: [],
listName: 'DINNER', label: 'DINNER',
meals: [] meals: []
}, },
{ {
macronutrients: [], macronutrients: [],
listName: 'SUPPER', label: 'SUPPER',
meals: [] meals: []
} }
] ]
@ -131,4 +131,48 @@ export const MACRONUTRIENTS = [
}, },
] ]
export const GOALS = [
{
name: 'lose weight',
label: 1,
},
{
name: 'put on weight',
label: 2,
},
{
name: 'maintain weight',
label: 3,
}
]
export const GENDERS = [
{label: 'male', value: 'male'},
{label: 'female', value: 'female'},
]
export const ACTIVITY = [
{
label: 'very low',
value: 0,
},
{
label: 'low',
value: 1,
},
{
label: 'average',
value: 2,
},
{
label: 'high',
value: 3,
},
{
label: 'very high',
value: 4,
},
];
export const TABS = Array.from({ length: 30 }, () => `${Math.floor(Math.random() * 120)}`) export const TABS = Array.from({ length: 30 }, () => `${Math.floor(Math.random() * 120)}`)

View File

@ -1,6 +1,7 @@
import HomePage from "pages/Home"; import HomePage from "pages/Home";
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";
const routes = { const routes = {
dashboard: { dashboard: {
@ -18,6 +19,11 @@ const routes = {
exact: true, exact: true,
component: LoginPage, component: LoginPage,
}, },
register: {
path: '/register',
exact: true,
component: RegisterPage,
},
} }
export default routes export default routes

View File

@ -10,5 +10,10 @@ const theme = createMuiTheme({
main: '#40c4ff', main: '#40c4ff',
}, },
}, },
MuiSlider: {
markLabel: {
transform: 'translateX(-12%)',
},
},
}) })
export default theme export default theme

View File

@ -970,7 +970,7 @@
dependencies: dependencies:
regenerator-runtime "^0.13.4" regenerator-runtime "^0.13.4"
"@babel/runtime@^7.1.2", "@babel/runtime@^7.12.1", "@babel/runtime@^7.12.5", "@babel/runtime@^7.3.1", "@babel/runtime@^7.4.4", "@babel/runtime@^7.6.3", "@babel/runtime@^7.8.3", "@babel/runtime@^7.8.7", "@babel/runtime@^7.9.2": "@babel/runtime@^7.1.2", "@babel/runtime@^7.12.1", "@babel/runtime@^7.12.5", "@babel/runtime@^7.3.1", "@babel/runtime@^7.4.4", "@babel/runtime@^7.6.0", "@babel/runtime@^7.6.3", "@babel/runtime@^7.8.3", "@babel/runtime@^7.8.7", "@babel/runtime@^7.9.2":
version "7.12.5" version "7.12.5"
resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.12.5.tgz#410e7e487441e1b360c29be715d870d9b985882e" resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.12.5.tgz#410e7e487441e1b360c29be715d870d9b985882e"
integrity sha512-plcc+hbExy3McchJCEQG3knOsuh3HH+Prx1P6cLIkET/0dLuQDEnrT+s27Axgc9bqfsmNUNHfscgMUdBpC9xfg== integrity sha512-plcc+hbExy3McchJCEQG3knOsuh3HH+Prx1P6cLIkET/0dLuQDEnrT+s27Axgc9bqfsmNUNHfscgMUdBpC9xfg==
@ -1033,6 +1033,18 @@
resolved "https://registry.yarnpkg.com/@csstools/normalize.css/-/normalize.css-10.1.0.tgz#f0950bba18819512d42f7197e56c518aa491cf18" resolved "https://registry.yarnpkg.com/@csstools/normalize.css/-/normalize.css-10.1.0.tgz#f0950bba18819512d42f7197e56c518aa491cf18"
integrity sha512-ij4wRiunFfaJxjB0BdrYHIH8FxBJpOwNPhhAcunlmPdXudL1WQV1qoP9un6JsEBAgQH+7UXyyjh0g7jTxXK6tg== integrity sha512-ij4wRiunFfaJxjB0BdrYHIH8FxBJpOwNPhhAcunlmPdXudL1WQV1qoP9un6JsEBAgQH+7UXyyjh0g7jTxXK6tg==
"@date-io/core@1.x", "@date-io/core@^1.3.13":
version "1.3.13"
resolved "https://registry.yarnpkg.com/@date-io/core/-/core-1.3.13.tgz#90c71da493f20204b7a972929cc5c482d078b3fa"
integrity sha512-AlEKV7TxjeK+jxWVKcCFrfYAk8spX9aCyiToFIiLPtfQbsjmRGLIhb5VZgptQcJdHtLXo7+m0DuurwFgUToQuA==
"@date-io/date-fns@^1.3.13":
version "1.3.13"
resolved "https://registry.yarnpkg.com/@date-io/date-fns/-/date-fns-1.3.13.tgz#7798844041640ab393f7e21a7769a65d672f4735"
integrity sha512-yXxGzcRUPcogiMj58wVgFjc9qUYrCnnU9eLcyNbsQCmae4jPuZCDoIBR21j8ZURsM7GRtU62VOw5yNd4dDHunA==
dependencies:
"@date-io/core" "^1.3.13"
"@emotion/hash@^0.8.0": "@emotion/hash@^0.8.0":
version "0.8.0" version "0.8.0"
resolved "https://registry.yarnpkg.com/@emotion/hash/-/hash-0.8.0.tgz#bbbff68978fefdbe68ccb533bc8cbe1d1afb5413" resolved "https://registry.yarnpkg.com/@emotion/hash/-/hash-0.8.0.tgz#bbbff68978fefdbe68ccb533bc8cbe1d1afb5413"
@ -1320,6 +1332,18 @@
prop-types "^15.7.2" prop-types "^15.7.2"
react-is "^16.8.0 || ^17.0.0" react-is "^16.8.0 || ^17.0.0"
"@material-ui/pickers@^3.2.10":
version "3.2.10"
resolved "https://registry.yarnpkg.com/@material-ui/pickers/-/pickers-3.2.10.tgz#19df024895876eb0ec7cd239bbaea595f703f0ae"
integrity sha512-B8G6Obn5S3RCl7hwahkQj9sKUapwXWFjiaz/Bsw1fhYFdNMnDUolRiWQSoKPb1/oKe37Dtfszoywi1Ynbo3y8w==
dependencies:
"@babel/runtime" "^7.6.0"
"@date-io/core" "1.x"
"@types/styled-jsx" "^2.2.8"
clsx "^1.0.2"
react-transition-group "^4.0.0"
rifm "^0.7.0"
"@material-ui/styles@^4.11.1": "@material-ui/styles@^4.11.1":
version "4.11.1" version "4.11.1"
resolved "https://registry.yarnpkg.com/@material-ui/styles/-/styles-4.11.1.tgz#089338637e9c358eddccd75c32f0bafd0237d573" resolved "https://registry.yarnpkg.com/@material-ui/styles/-/styles-4.11.1.tgz#089338637e9c358eddccd75c32f0bafd0237d573"
@ -1840,6 +1864,13 @@
resolved "https://registry.yarnpkg.com/@types/stack-utils/-/stack-utils-2.0.0.tgz#7036640b4e21cc2f259ae826ce843d277dad8cff" resolved "https://registry.yarnpkg.com/@types/stack-utils/-/stack-utils-2.0.0.tgz#7036640b4e21cc2f259ae826ce843d277dad8cff"
integrity sha512-RJJrrySY7A8havqpGObOB4W92QXKJo63/jFLLgpvOtsGUqbQZ9Sbgl35KMm1DjC6j7AvmmU2bIno+3IyEaemaw== integrity sha512-RJJrrySY7A8havqpGObOB4W92QXKJo63/jFLLgpvOtsGUqbQZ9Sbgl35KMm1DjC6j7AvmmU2bIno+3IyEaemaw==
"@types/styled-jsx@^2.2.8":
version "2.2.8"
resolved "https://registry.yarnpkg.com/@types/styled-jsx/-/styled-jsx-2.2.8.tgz#b50d13d8a3c34036282d65194554cf186bab7234"
integrity sha512-Yjye9VwMdYeXfS71ihueWRSxrruuXTwKCbzue4+5b2rjnQ//AtyM7myZ1BEhNhBQ/nL/RE7bdToUoLln2miKvg==
dependencies:
"@types/react" "*"
"@types/tapable@*", "@types/tapable@^1.0.5": "@types/tapable@*", "@types/tapable@^1.0.5":
version "1.0.6" version "1.0.6"
resolved "https://registry.yarnpkg.com/@types/tapable/-/tapable-1.0.6.tgz#a9ca4b70a18b270ccb2bc0aaafefd1d486b7ea74" resolved "https://registry.yarnpkg.com/@types/tapable/-/tapable-1.0.6.tgz#a9ca4b70a18b270ccb2bc0aaafefd1d486b7ea74"
@ -3296,7 +3327,7 @@ clone-deep@^4.0.1:
kind-of "^6.0.2" kind-of "^6.0.2"
shallow-clone "^3.0.0" shallow-clone "^3.0.0"
clsx@^1.0.4: clsx@^1.0.2, clsx@^1.0.4:
version "1.1.1" version "1.1.1"
resolved "https://registry.yarnpkg.com/clsx/-/clsx-1.1.1.tgz#98b3134f9abbdf23b2663491ace13c5c03a73188" resolved "https://registry.yarnpkg.com/clsx/-/clsx-1.1.1.tgz#98b3134f9abbdf23b2663491ace13c5c03a73188"
integrity sha512-6/bPho624p3S2pMyvP5kKBPXnI3ufHLObBFCfgx+LkeR5lg2XYy2hqZqUf45ypD8COn2bhgGJSUE+l5dhNBieA== integrity sha512-6/bPho624p3S2pMyvP5kKBPXnI3ufHLObBFCfgx+LkeR5lg2XYy2hqZqUf45ypD8COn2bhgGJSUE+l5dhNBieA==
@ -3951,6 +3982,11 @@ data-urls@^2.0.0:
whatwg-mimetype "^2.3.0" whatwg-mimetype "^2.3.0"
whatwg-url "^8.0.0" whatwg-url "^8.0.0"
date-fns@^2.16.1:
version "2.16.1"
resolved "https://registry.yarnpkg.com/date-fns/-/date-fns-2.16.1.tgz#05775792c3f3331da812af253e1a935851d3834b"
integrity sha512-sAJVKx/FqrLYHAQeN7VpJrPhagZc9R4ImZIWYRFZaaohR3KzmuK88touwsSwSVT8Qcbd4zoDsnGfX4GFB4imyQ==
debug@2.6.9, debug@^2.2.0, debug@^2.3.3, debug@^2.6.0, debug@^2.6.9: debug@2.6.9, debug@^2.2.0, debug@^2.3.3, debug@^2.6.0, debug@^2.6.9:
version "2.6.9" version "2.6.9"
resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.9.tgz#5d128515df134ff327e90a4c93f4e077a536341f" resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.9.tgz#5d128515df134ff327e90a4c93f4e077a536341f"
@ -9557,7 +9593,7 @@ react-side-effect@^2.1.0:
resolved "https://registry.yarnpkg.com/react-side-effect/-/react-side-effect-2.1.1.tgz#66c5701c3e7560ab4822a4ee2742dee215d72eb3" resolved "https://registry.yarnpkg.com/react-side-effect/-/react-side-effect-2.1.1.tgz#66c5701c3e7560ab4822a4ee2742dee215d72eb3"
integrity sha512-2FoTQzRNTncBVtnzxFOk2mCpcfxQpenBMbk5kSVBg5UcPqV9fRbgY2zhb7GTWWOlpFmAxhClBDlIq8Rsubz1yQ== integrity sha512-2FoTQzRNTncBVtnzxFOk2mCpcfxQpenBMbk5kSVBg5UcPqV9fRbgY2zhb7GTWWOlpFmAxhClBDlIq8Rsubz1yQ==
react-transition-group@^4.4.0: react-transition-group@^4.0.0, react-transition-group@^4.4.0:
version "4.4.1" version "4.4.1"
resolved "https://registry.yarnpkg.com/react-transition-group/-/react-transition-group-4.4.1.tgz#63868f9325a38ea5ee9535d828327f85773345c9" resolved "https://registry.yarnpkg.com/react-transition-group/-/react-transition-group-4.4.1.tgz#63868f9325a38ea5ee9535d828327f85773345c9"
integrity sha512-Djqr7OQ2aPUiYurhPalTrVy9ddmFCCzwhqQmtN+J3+3DzLO209Fdr70QrN8Z3DsglWql6iY1lDWAfpFiBtuKGw== integrity sha512-Djqr7OQ2aPUiYurhPalTrVy9ddmFCCzwhqQmtN+J3+3DzLO209Fdr70QrN8Z3DsglWql6iY1lDWAfpFiBtuKGw==
@ -9970,6 +10006,13 @@ rgba-regex@^1.0.0:
resolved "https://registry.yarnpkg.com/rgba-regex/-/rgba-regex-1.0.0.tgz#43374e2e2ca0968b0ef1523460b7d730ff22eeb3" resolved "https://registry.yarnpkg.com/rgba-regex/-/rgba-regex-1.0.0.tgz#43374e2e2ca0968b0ef1523460b7d730ff22eeb3"
integrity sha1-QzdOLiyglosO8VI0YLfXMP8i7rM= integrity sha1-QzdOLiyglosO8VI0YLfXMP8i7rM=
rifm@^0.7.0:
version "0.7.0"
resolved "https://registry.yarnpkg.com/rifm/-/rifm-0.7.0.tgz#debe951a9c83549ca6b33e5919f716044c2230be"
integrity sha512-DSOJTWHD67860I5ojetXdEQRIBvF6YcpNe53j0vn1vp9EUb9N80EiZTxgP+FkDKorWC8PZw052kTF4C1GOivCQ==
dependencies:
"@babel/runtime" "^7.3.1"
rimraf@2.6.3: rimraf@2.6.3:
version "2.6.3" version "2.6.3"
resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-2.6.3.tgz#b2d104fe0d8fb27cf9e0a1cda8262dd3833c6cab" resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-2.6.3.tgz#b2d104fe0d8fb27cf9e0a1cda8262dd3833c6cab"