From 156b78ca392c5835263fcb5dc3954ab961d0b4b7 Mon Sep 17 00:00:00 2001 From: = <=> Date: Tue, 8 Dec 2020 21:32:21 +0100 Subject: [PATCH] Connect Profile page with redux and saga --- package.json | 3 + src/components/AddMealModal/index.js | 4 +- src/components/GoalForm/Loader.js | 27 +++ src/components/GoalForm/index.js | 64 +++++ src/components/Meal/index.js | 2 +- src/components/PersonalDetailsForm/index.js | 139 +++++++++++ src/components/ReviewProfileForm/index.js | 38 +++ src/containers/App/index.js | 4 +- src/pages/Login/index.js | 113 ++++++--- src/pages/Profile/actions.js | 34 ++- src/pages/Profile/constants.js | 6 + src/pages/Profile/index.js | 245 ++++++++++---------- src/pages/Profile/reducer.js | 29 ++- src/pages/Profile/saga.js | 43 +++- src/pages/Profile/selectors.js | 24 +- src/pages/Register/index.js | 80 +++++++ src/utils/api.js | 2 + src/utils/mock.js | 58 ++++- src/utils/routes.js | 6 + src/utils/theme.js | 5 + yarn.lock | 49 +++- 21 files changed, 770 insertions(+), 205 deletions(-) create mode 100644 src/components/GoalForm/Loader.js create mode 100644 src/components/GoalForm/index.js create mode 100644 src/components/PersonalDetailsForm/index.js create mode 100644 src/components/ReviewProfileForm/index.js create mode 100644 src/pages/Register/index.js diff --git a/package.json b/package.json index 4601318..e432200 100644 --- a/package.json +++ b/package.json @@ -3,13 +3,16 @@ "version": "0.1.0", "private": true, "dependencies": { + "@date-io/date-fns": "^1.3.13", "@material-ui/core": "^4.11.1", "@material-ui/icons": "^4.9.1", "@material-ui/lab": "^4.0.0-alpha.57", + "@material-ui/pickers": "^3.2.10", "@testing-library/jest-dom": "^5.11.4", "@testing-library/react": "^11.1.0", "@testing-library/user-event": "^12.1.10", "connected-react-router": "^6.8.0", + "date-fns": "^2.16.1", "history": "4.10.1", "immer": "^8.0.0", "lodash": "^4.17.20", diff --git a/src/components/AddMealModal/index.js b/src/components/AddMealModal/index.js index d2d97a6..8e5e9e4 100644 --- a/src/components/AddMealModal/index.js +++ b/src/components/AddMealModal/index.js @@ -74,7 +74,9 @@ const AddMealModal = ({ isModalOpen, handleCloseModal }) => { key={index} action={ - + } /> diff --git a/src/components/GoalForm/Loader.js b/src/components/GoalForm/Loader.js new file mode 100644 index 0000000..e7051c3 --- /dev/null +++ b/src/components/GoalForm/Loader.js @@ -0,0 +1,27 @@ +import React from 'react'; +import {Button, Grid} from "@material-ui/core"; +import {Skeleton} from "@material-ui/lab"; + +const Loader = () => { + return ( + + + + + + + + + + + + + + + + + + ); +}; + +export default Loader; diff --git a/src/components/GoalForm/index.js b/src/components/GoalForm/index.js new file mode 100644 index 0000000..6f285ba --- /dev/null +++ b/src/components/GoalForm/index.js @@ -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 ( + + + Your goal { JSON.stringify(error) } + + + + {isLoading ? ( + + ) : ( + + {goals.map(({ label, value }, index) => ( + + + {label} + + + + ))} + + )} + + + + ); +} + +export default GoalForm; diff --git a/src/components/Meal/index.js b/src/components/Meal/index.js index 6474b35..d9f4c95 100644 --- a/src/components/Meal/index.js +++ b/src/components/Meal/index.js @@ -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 React from "react"; import MacronutrientsDetails from "components/MacronutrientsDetails"; diff --git a/src/components/PersonalDetailsForm/index.js b/src/components/PersonalDetailsForm/index.js new file mode 100644 index 0000000..70409a3 --- /dev/null +++ b/src/components/PersonalDetailsForm/index.js @@ -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 ( + + + Personal details + + + + + Gender + + {genders.map(({ label, value }) => ( + {label} + ))} + + + + + + + + + + cm + }} + /> + + + Kg + }} + /> + + + + Activity + + + + + + + + ); +} +export default PersonalDetailsForm diff --git a/src/components/ReviewProfileForm/index.js b/src/components/ReviewProfileForm/index.js new file mode 100644 index 0000000..3ebaceb --- /dev/null +++ b/src/components/ReviewProfileForm/index.js @@ -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 ( + + + Profile summary + + + TODO + {/*{profile.map(({ label, value }, index) => (*/} + {/* */} + {/* */} + {/* {value}*/} + {/* */} + {/*))}*/} + + + ); +} + +export default ReviewProfileForm diff --git a/src/containers/App/index.js b/src/containers/App/index.js index c19c7b9..c666c2f 100644 --- a/src/containers/App/index.js +++ b/src/containers/App/index.js @@ -19,8 +19,8 @@ const App = () => { return ( - {/*{isLogged && }*/} - + {isLogged && } + {/**/} ); diff --git a/src/pages/Login/index.js b/src/pages/Login/index.js index 2705052..6aad5e5 100644 --- a/src/pages/Login/index.js +++ b/src/pages/Login/index.js @@ -2,11 +2,24 @@ import React from 'react'; import { useInjectReducer, useInjectSaga } from 'redux-injectors'; import { createStructuredSelector } from 'reselect'; 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 saga from './saga'; import { makeSelectEmail, makeSelectPassword, makeSelectLoading } from './selectors' import { loginInputChange, loginAction } from './actions' +import {routes} from "../../utils"; +import {makeStyles} from "@material-ui/core/styles"; const stateSelector = createStructuredSelector({ email: makeSelectEmail(), @@ -14,8 +27,25 @@ const stateSelector = createStructuredSelector({ 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 Login = () => { + const classes = useStyles() useInjectReducer({ key, reducer }); useInjectSaga({ key, saga }); const { email, password, loading } = useSelector(stateSelector) @@ -31,36 +61,61 @@ const Login = () => { } return ( - - - - + + + Login to Account + + + + + } + label="Remember me" + /> + - Login - - - Sign in + Sign Up + + + + + Don't have an account? Sign Up + + - - + + ); }; diff --git a/src/pages/Profile/actions.js b/src/pages/Profile/actions.js index 42bc07c..adfe348 100644 --- a/src/pages/Profile/actions.js +++ b/src/pages/Profile/actions.js @@ -1,4 +1,7 @@ import { + GET_GOALS_REQUEST, + GET_GOALS_SUCCESS, + GET_GOALS_ERROR, GET_ACTIVITIES_REQUEST, GET_ACTIVITIES_SUCCESS, GET_ACTIVITIES_ERROR, @@ -24,18 +27,18 @@ export const getProfileAction = () => ({ 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, birthday, gender, height, - currentWeight, + weight, goalWeight, rateOfChange, activity }) -export const getProfileErrorAction = (error) => ({ +export const getProfileErrorAction = ({error}) => ({ type: GET_PROFILE_ERROR, error, }) @@ -45,18 +48,18 @@ export const updateProfileAction = () => ({ 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, birthday, gender, height, - currentWeight, + weight, goalWeight, rateOfChange, activity }) -export const updateProfileErrorAction = (error) => ({ +export const updateProfileErrorAction = ({error}) => ({ type: UPDATE_PROFILE_ERROR, error, }) @@ -70,7 +73,7 @@ export const getActivitiesSuccessAction = ({ activities }) => ({ activities, }) -export const getActivitiesErrorAction = (error) => ({ +export const getActivitiesErrorAction = ({error}) => ({ type: GET_ACTIVITIES_ERROR, error, }) @@ -84,7 +87,22 @@ export const getGendersSuccessAction = ({ genders }) => ({ genders, }) -export const getGendersErrorAction = (error) => ({ +export const getGendersErrorAction = ({error}) => ({ type: GET_GENDERS_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, +}) + diff --git a/src/pages/Profile/constants.js b/src/pages/Profile/constants.js index 744f1ba..4ce361c 100644 --- a/src/pages/Profile/constants.js +++ b/src/pages/Profile/constants.js @@ -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_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_SUCCESS = 'app/ProfilePage/GET_ACTIVITIES_SUCCESS'; export const GET_ACTIVITIES_ERROR = 'app/ProfilePage/GET_ACTIVITIES_ERROR'; + + export const PROFILE_INPUT_CHANGE = 'app/ProfilePage/PROFILE_INPUT_CHANGE'; diff --git a/src/pages/Profile/index.js b/src/pages/Profile/index.js index f8a499f..d7434f8 100644 --- a/src/pages/Profile/index.js +++ b/src/pages/Profile/index.js @@ -1,145 +1,136 @@ -import React, { useEffect } from 'react'; -import { Container, Slider, Button, TextField, FormControl, MenuItem, InputLabel, Select, Grid, Typography } from '@material-ui/core' +import React, { useState } from 'react'; +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 { useDispatch } from 'react-redux'; +import GoalForm from 'components/GoalForm'; +import PersonalDetailsForm from 'components/PersonalDetailsForm'; +import ReviewProfileForm from 'components/ReviewProfileForm'; import reducer from "./reducer"; import saga from "./saga"; -import { useSelector, useDispatch } from 'react-redux'; -import {createStructuredSelector} from "reselect"; -import { profileInputChange, getProfileAction, getActivitiesAction, getGendersAction } from './actions' -import { - makeSelectError, - makeSelectBirthday, - makeSelectHeight, - makeSelectCurrentWeight, - makeSelectRateOfChange, - makeSelectActivity, - makeSelectGoalWeight, - makeSelectGender, - makeSelectLoading, -} from "./selectors"; +import { updateProfileAction } from './actions' -const stateSelector = createStructuredSelector({ - loading: makeSelectLoading(), - error: makeSelectError(), - gender: makeSelectGender(), - birthday: makeSelectBirthday(), - height: makeSelectHeight(), - currentWeight: makeSelectCurrentWeight(), - goalWeight: makeSelectGoalWeight(), - rateOfChange: makeSelectRateOfChange(), - activity: makeSelectActivity(), -}); +const useStyles = makeStyles((theme) => ({ + layout: { + width: 'auto', + marginLeft: theme.spacing(2), + marginRight: theme.spacing(2), + [theme.breakpoints.up(600 + theme.spacing(2) * 2)]: { + width: 600, + marginLeft: 'auto', + marginRight: 'auto', + }, + }, + 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 ; + case 1: + return ; + case 2: + return ; + default: + throw new Error('Unknown step'); + } +} const key = 'profilePage' const ProfilePage = () => { + const classes = useStyles(); + const [activeStep, setActiveStep] = useState(0); + useInjectReducer({ key, reducer }); useInjectSaga({ key, saga }); + const dispatch = useDispatch() - const { - gender, - birthday, - height, - currentWeight, - goalWeight, - rateOfChange, - activity, - } = useSelector(stateSelector) - const getProfile = () => dispatch(getProfileAction()) - const getActivities = () => dispatch(getActivitiesAction()) - const getGenders = () => dispatch(getGendersAction()) + const handleNext = () => { + setActiveStep(activeStep + 1); + }; - useEffect(() => { - getProfile() - getActivities() - getGenders() - }, []) + const handleBack = () => { + setActiveStep(activeStep - 1); + }; -const handleChange = ({ target: { name, value }}) => { - dispatch(profileInputChange({ name, value })) + const handleSubmitProfile = (event) => { + event.preventDefault(); + dispatch(updateProfileAction()) } return ( - - - Profile - - Gender - - Male - Female - - - - - - - - - - - - - - The rate of weight change + + + + Profile - - - - Activity - - Small - Medium - High - - - - Save - - - - ); -}; + + {steps.map((label) => ( + + {label} + + ))} + + + {activeStep === steps.length ? ( + + + Thank you for your order. + + + Your order number is #2001539. We have emailed your order confirmation, and will + send you an update when your order has shipped. + + + ) : ( + + {getStepContent(activeStep)} + + {activeStep !== 0 && ( + + Back + + )} + + {activeStep === steps.length - 1 ? 'Save profile' : 'Next'} + + + + )} + + + + ) +} -export default ProfilePage; +export default ProfilePage diff --git a/src/pages/Profile/reducer.js b/src/pages/Profile/reducer.js index 9ecbf93..20bc36d 100644 --- a/src/pages/Profile/reducer.js +++ b/src/pages/Profile/reducer.js @@ -1,5 +1,8 @@ import produce from 'immer'; import { + GET_GOALS_REQUEST, + GET_GOALS_SUCCESS, + GET_GOALS_ERROR, GET_ACTIVITIES_SUCCESS, GET_GENDERS_SUCCESS, GET_ACTIVITIES_ERROR, @@ -17,13 +20,14 @@ import { export const initialState = { activities: [], + goals: [], genders: [], - loading: false, + isLoading: false, error: {}, gender: '', - birthday: '', + birthday: new Date(), height: 0, - currentWeight: 0, + weight: 0, goalWeight: 0, rateOfChange: 0, activity: 0, @@ -33,12 +37,17 @@ const loginPageReducer = produce((draft, action) => { switch(action.type) { case GET_ACTIVITIES_SUCCESS: draft.activities = action.activities; - draft.loading = false; + draft.isLoading = false; + break; + + case GET_GOALS_SUCCESS: + draft.goals = action.goals; + draft.isLoading = false; break; case GET_GENDERS_SUCCESS: draft.genders = action.genders; - draft.loading = false; + draft.isLoading = false; break; case GET_PROFILE_SUCCESS: @@ -46,25 +55,27 @@ const loginPageReducer = produce((draft, action) => { draft.birthday = action.birthday; draft.gender = action.gender; draft.height = action.height; - draft.currentWeight = action.currentWeight; + draft.weight = action.weight; draft.goalWeight = action.goalWeight; draft.rateOfChange = action.rateOfChange; draft.activity = action.activity; - draft.loading = false; + draft.isLoading = false; break; case GET_ACTIVITIES_REQUEST: case GET_GENDERS_REQUEST: case GET_PROFILE_REQUEST: + case GET_GOALS_REQUEST: case UPDATE_PROFILE_REQUEST: - draft.loading = true; + draft.isLoading = true; break; case GET_ACTIVITIES_ERROR: case GET_GENDERS_ERROR: case GET_PROFILE_ERROR: + case GET_GOALS_ERROR: case UPDATE_PROFILE_ERROR: - draft.loading = false; + draft.isLoading = false; draft.error = action.error; break; diff --git a/src/pages/Profile/saga.js b/src/pages/Profile/saga.js index 6c700ca..00cbdef 100644 --- a/src/pages/Profile/saga.js +++ b/src/pages/Profile/saga.js @@ -1,16 +1,16 @@ import { takeLatest, call, put, select } from 'redux-saga/effects'; 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 { makeSelectBirthday, makeSelectHeight, - makeSelectCurrentWeight, + makeSelectWeight, makeSelectRateOfChange, makeSelectActivity, makeSelectGoalWeight, makeSelectGender } 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' @@ -30,7 +30,7 @@ export function* getProfile() { const { birthday, gender, height, currentWeight, goalWeight, rateOfChange, activity } = yield call(request, requestURL, requestParameters); yield put(getProfileSuccessAction({birthday, gender, height, currentWeight, goalWeight, rateOfChange, activity})); } 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 gender = yield select(makeSelectGender()); const height = yield select(makeSelectHeight()); - const currentWeight = yield select(makeSelectCurrentWeight()); + const weight = yield select(makeSelectWeight()); const goalWeight = yield select(makeSelectGoalWeight()); const rateOfChange = yield select(makeSelectRateOfChange()); const activity = yield select(makeSelectActivity()); @@ -50,14 +50,14 @@ export function* updateProfile() { const requestParameters = { method: 'POST', 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 { - const { birthday, gender, height, currentWeight, goalWeight, rateOfChange, activity } = yield call(request, requestURL, requestParameters); - yield put(updateProfileSuccessAction({birthday, gender, height, currentWeight, goalWeight, rateOfChange, activity})); + const { birthday, gender, height, weight, goalWeight, rateOfChange, activity } = yield call(request, requestURL, requestParameters); + yield put(updateProfileSuccessAction({birthday, gender, height, weight, goalWeight, rateOfChange, activity})); } 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); yield put(getActivitiesSuccessAction({ activities })); } catch (error) { - yield put(getActivitiesErrorAction(error.message)); + yield put(getActivitiesErrorAction({error: error.message})); } } - export function* getGenders() { const { access } = yield select(makeSelectTokens()); @@ -98,7 +97,26 @@ export function* getGenders() { const { genders } = yield call(request, requestURL, requestParameters); yield put(getGendersSuccessAction({ genders })); } 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_GENDERS_REQUEST, getGenders); yield takeLatest(GET_PROFILE_REQUEST, getProfile); + yield takeLatest(GET_GOALS_REQUEST, getGoals); yield takeLatest(UPDATE_PROFILE_REQUEST, updateProfile); } diff --git a/src/pages/Profile/selectors.js b/src/pages/Profile/selectors.js index ce35d29..8deee14 100644 --- a/src/pages/Profile/selectors.js +++ b/src/pages/Profile/selectors.js @@ -12,8 +12,8 @@ const makeSelectBirthday = () => const makeSelectHeight = () => createSelector(selectProfilePageDomain, (substate) => substate.height); -const makeSelectCurrentWeight = () => - createSelector(selectProfilePageDomain, (substate) => substate.currentWeight); +const makeSelectWeight = () => + createSelector(selectProfilePageDomain, (substate) => substate.weight); const makeSelectRateOfChange = () => createSelector(selectProfilePageDomain, (substate) => substate.rateOfChange); @@ -27,18 +27,30 @@ const makeSelectGoalWeight = () => const makeSelectGender = () => createSelector(selectProfilePageDomain, (substate) => substate.gender); -const makeSelectLoading = () => - createSelector(selectProfilePageDomain, (substate) => substate.loading); +const makeSelectIsLoading = () => + 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 { selectProfilePageDomain, makeSelectError, makeSelectBirthday, makeSelectHeight, - makeSelectCurrentWeight, + makeSelectWeight, makeSelectRateOfChange, makeSelectActivity, makeSelectGoalWeight, makeSelectGender, - makeSelectLoading, + makeSelectIsLoading, + makeSelectGoals, + makeSelectActivities, + makeSelectGenders, }; diff --git a/src/pages/Register/index.js b/src/pages/Register/index.js new file mode 100644 index 0000000..3820587 --- /dev/null +++ b/src/pages/Register/index.js @@ -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 ( + + + + Create Account + + + + + } + label="Remember me" + /> + + Sign Up + + + + + Have already account? Sign In + + + + + + + ); +} + +export default RegisterPage diff --git a/src/utils/api.js b/src/utils/api.js index 575f679..2a6cd90 100644 --- a/src/utils/api.js +++ b/src/utils/api.js @@ -3,6 +3,7 @@ const AUTH = 'auth'; const PROFILE = 'profiles'; const ACTIVITY = 'activities'; const GENDER = 'genders'; +const GOALS = 'goals'; const urls = { auth: { @@ -12,6 +13,7 @@ const urls = { profile: `${API_BASE_URL}/${PROFILE}`, activities: `${API_BASE_URL}/${ACTIVITY}`, genders: `${API_BASE_URL}/${GENDER}`, + goals: `${API_BASE_URL}/${GOALS}`, } export default urls diff --git a/src/utils/mock.js b/src/utils/mock.js index 06cb105..7a75160 100644 --- a/src/utils/mock.js +++ b/src/utils/mock.js @@ -4,7 +4,7 @@ import blue from "@material-ui/core/colors/blue"; export const MEALS_LIST = [ { - name: 'eggs', + label: 'eggs', macronutrients: [ { value: 1245, @@ -25,7 +25,7 @@ export const MEALS_LIST = [ ], }, { - name: 'bread', + label: 'bread', macronutrients: [ { value: 1245, @@ -46,7 +46,7 @@ export const MEALS_LIST = [ ], }, { - name: 'corn flakes', + label: 'corn flakes', macronutrients: [ { value: 1245, @@ -88,22 +88,22 @@ export const MENU_LIST = [ unit: 'g', }, ], - listName: 'BREAKFAST', + label: 'BREAKFAST', meals: MEALS_LIST, }, { macronutrients: [], - listName: 'LUNCH', + label: 'LUNCH', meals: [] }, { macronutrients: [], - listName: 'DINNER', + label: 'DINNER', meals: [] }, { macronutrients: [], - listName: 'SUPPER', + label: 'SUPPER', 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)}`) diff --git a/src/utils/routes.js b/src/utils/routes.js index 88b12fd..dbff35b 100644 --- a/src/utils/routes.js +++ b/src/utils/routes.js @@ -1,6 +1,7 @@ import HomePage from "pages/Home"; import ProfilePage from "pages/Profile"; import LoginPage from "pages/Login"; +import RegisterPage from "pages/Register"; const routes = { dashboard: { @@ -18,6 +19,11 @@ const routes = { exact: true, component: LoginPage, }, + register: { + path: '/register', + exact: true, + component: RegisterPage, + }, } export default routes diff --git a/src/utils/theme.js b/src/utils/theme.js index 72492e6..e42530c 100644 --- a/src/utils/theme.js +++ b/src/utils/theme.js @@ -10,5 +10,10 @@ const theme = createMuiTheme({ main: '#40c4ff', }, }, + MuiSlider: { + markLabel: { + transform: 'translateX(-12%)', + }, + }, }) export default theme diff --git a/yarn.lock b/yarn.lock index 548bbbb..71e1b29 100644 --- a/yarn.lock +++ b/yarn.lock @@ -970,7 +970,7 @@ dependencies: 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" resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.12.5.tgz#410e7e487441e1b360c29be715d870d9b985882e" 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" 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": version "0.8.0" resolved "https://registry.yarnpkg.com/@emotion/hash/-/hash-0.8.0.tgz#bbbff68978fefdbe68ccb533bc8cbe1d1afb5413" @@ -1320,6 +1332,18 @@ prop-types "^15.7.2" 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": version "4.11.1" 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" 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": version "1.0.6" 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" shallow-clone "^3.0.0" -clsx@^1.0.4: +clsx@^1.0.2, clsx@^1.0.4: version "1.1.1" resolved "https://registry.yarnpkg.com/clsx/-/clsx-1.1.1.tgz#98b3134f9abbdf23b2663491ace13c5c03a73188" integrity sha512-6/bPho624p3S2pMyvP5kKBPXnI3ufHLObBFCfgx+LkeR5lg2XYy2hqZqUf45ypD8COn2bhgGJSUE+l5dhNBieA== @@ -3951,6 +3982,11 @@ data-urls@^2.0.0: whatwg-mimetype "^2.3.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: version "2.6.9" 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" 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" resolved "https://registry.yarnpkg.com/react-transition-group/-/react-transition-group-4.4.1.tgz#63868f9325a38ea5ee9535d828327f85773345c9" 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" 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: version "2.6.3" resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-2.6.3.tgz#b2d104fe0d8fb27cf9e0a1cda8262dd3833c6cab"