diff --git a/package.json b/package.json index 6c5a13e..16954ab 100644 --- a/package.json +++ b/package.json @@ -12,7 +12,7 @@ "@testing-library/react": "^11.1.0", "@testing-library/user-event": "^12.1.10", "connected-react-router": "^6.8.0", - "date-fns": "^2.16.1", + "date-fns": "^2.9.0", "formik": "^2.2.6", "history": "4.10.1", "immer": "^8.0.0", @@ -23,7 +23,6 @@ "react": "^17.0.1", "react-dom": "^17.0.1", "react-helmet": "^6.1.0", - "react-hook-form": "^6.13.1", "react-redux": "^7.2.2", "react-router-dom": "^5.2.0", "react-scripts": "4.0.0", diff --git a/src/components/DatePickerField/index.js b/src/components/DatePickerField/index.js new file mode 100644 index 0000000..dacac8f --- /dev/null +++ b/src/components/DatePickerField/index.js @@ -0,0 +1,57 @@ +import React, { useState, useEffect } from 'react'; +import { useField } from 'formik'; +import Grid from '@material-ui/core/Grid'; +import { + MuiPickersUtilsProvider, + KeyboardDatePicker +} from '@material-ui/pickers'; +import DateFnsUtils from '@date-io/date-fns'; + +const DatePickerField = (props) => { + const [field, meta, helper] = useField(props); + const { touched, error } = meta; + const { setValue } = helper; + const isError = touched && error && true; + const { value } = field; + const [selectedDate, setSelectedDate] = useState(null); + + useEffect(() => { + if (value) { + const date = new Date(value); + setSelectedDate(date); + } + }, [value]); + + const _onChange = (date) => { + if (date) { + setSelectedDate(date); + try { + const ISODateString = date.toISOString(); + setValue(ISODateString); + } catch (error) { + setValue(date); + } + } else { + setValue(date); + } + } + + return ( + + + + + + ); +} + +export default DatePickerField + diff --git a/src/components/GoalForm/Loader.js b/src/components/GoalForm/Loader.js deleted file mode 100644 index 6a935ef..0000000 --- a/src/components/GoalForm/Loader.js +++ /dev/null @@ -1,27 +0,0 @@ -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 deleted file mode 100644 index 1561c82..0000000 --- a/src/components/GoalForm/index.js +++ /dev/null @@ -1,105 +0,0 @@ -import React from 'react'; -import {Grid, FormControlLabel, Slider, Button, Typography, Radio, RadioGroup, Box} from '@material-ui/core'; -import {useInjectReducer} from "redux-injectors"; -import { useSelector } from 'react-redux'; -import {createStructuredSelector} from "reselect"; -import { useFormContext, Controller } from "react-hook-form"; -import reducer from "pages/Profile/reducer"; -import { - makeSelectGoals, - makeSelectActivities, - makeSelectRatesOfChange, -} from "pages/Profile/selectors"; - -const stateSelector = createStructuredSelector({ - goals: makeSelectGoals(), - activities: makeSelectActivities(), -}); - -const key = 'profilePage' -const GoalForm = () => { - useInjectReducer({ key, reducer }); - const { goals, activities, ratesOfChange } = useSelector(stateSelector) - const { control } = useFormContext() - - return ( - - - Goal - - - - - - {goals.map(({ label, value }) => ( - - - - ))} - - - } - /> - - - - Activity - - - ( - onChange(value)} - min={1.2} - step={null} - max={1.9} - marks={activities} - /> - )} - /> - - - - - Rate of change - - - ( - onChange(value)} - min={0} - step={0.1} - max={1} - valueLabelDisplay="auto" - marks={ratesOfChange} - /> - )} - /> - - - - - ); -} - -export default GoalForm; diff --git a/src/components/InputField/index.js b/src/components/InputField/index.js new file mode 100644 index 0000000..073d3dc --- /dev/null +++ b/src/components/InputField/index.js @@ -0,0 +1,27 @@ +import React from 'react'; +import { at } from 'lodash'; +import { useField } from 'formik'; +import { TextField } from '@material-ui/core'; + +const InputField = (props) => { + const [field, meta] = useField(props); + + const _renderHelperText = () => { + const [touched, error] = at(meta, 'touched', 'error'); + if (touched && error) { + return error; + } + } + + return ( + + ); +} + +export default InputField diff --git a/src/components/RadioField/index.js b/src/components/RadioField/index.js new file mode 100644 index 0000000..aa9d97d --- /dev/null +++ b/src/components/RadioField/index.js @@ -0,0 +1,39 @@ +import React from 'react'; +import { at } from 'lodash'; +import { useField } from 'formik'; +import { + Radio, + FormControl, + FormControlLabel, + FormHelperText +} from '@material-ui/core'; + +const CheckboxField = ({ label, ...rest }) => { + const [field, meta, helper] = useField({ label, ...rest }); + const { setValue } = helper; + + const _renderHelperText = () => { + const [touched, error] = at(meta, 'touched', 'error'); + if (touched && error) { + return {error}; + } + } + + const _onChange = (e) => { + setValue(e.target.checked); + } + + return ( + + } + label={label} + /> + {_renderHelperText()} + + ); +} + +export default CheckboxField diff --git a/src/components/SelectField/index.js b/src/components/SelectField/index.js new file mode 100644 index 0000000..417262d --- /dev/null +++ b/src/components/SelectField/index.js @@ -0,0 +1,48 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import { at } from 'lodash'; +import { useField } from 'formik'; +import { + InputLabel, + FormControl, + Select, + MenuItem, + FormHelperText +} from '@material-ui/core'; + +const SelectField = ({ label, data, ...rest }) => { + const [field, meta] = useField({ label, data, ...rest }); + const { value: selectedValue } = field; + const [touched, error] = at(meta, 'touched', 'error'); + const isError = touched && error && true; + + const _renderHelperText = () => { + if (isError) { + return {error}; + } + } + + return ( + + {label} + + {_renderHelperText()} + + ); +} + +SelectField.defaultProps = { + data: [] +}; + +SelectField.propTypes = { + data: PropTypes.array.isRequired +}; + +export default SelectField; diff --git a/src/components/SliderField/index.js b/src/components/SliderField/index.js new file mode 100644 index 0000000..ee71567 --- /dev/null +++ b/src/components/SliderField/index.js @@ -0,0 +1,24 @@ +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 ( + + ); +} + +export default SliderField diff --git a/src/pages/Profile/GoalForm/index.js b/src/pages/Profile/GoalForm/index.js new file mode 100644 index 0000000..e578f62 --- /dev/null +++ b/src/pages/Profile/GoalForm/index.js @@ -0,0 +1,52 @@ +import React from 'react'; +import {Grid, Typography, Box} from '@material-ui/core'; +import {useInjectReducer} from "redux-injectors"; +import { useSelector } from 'react-redux'; +import {createStructuredSelector} from "reselect"; +import reducer from "pages/Profile/reducer"; +import { + makeSelectGoals, + makeSelectActivities, + makeSelectRatesOfChange, +} from "pages/Profile/selectors"; + +import SelectField from 'components/SelectField'; + +const stateSelector = createStructuredSelector({ + goals: makeSelectGoals(), + activities: makeSelectActivities(), + ratesOfChange: makeSelectRatesOfChange(), +}); + +const key = 'profilePage' +const GoalForm = () => { + useInjectReducer({ key, reducer }); + const { goals, activities, ratesOfChange } = useSelector(stateSelector) + + return ( + + + Goal + + + + + + + + Activity + + + + + + Rate of change + + + + + + ); +} + +export default GoalForm; diff --git a/src/pages/Profile/PersonalDetailsForm/index.js b/src/pages/Profile/PersonalDetailsForm/index.js new file mode 100644 index 0000000..1b6cc5f --- /dev/null +++ b/src/pages/Profile/PersonalDetailsForm/index.js @@ -0,0 +1,86 @@ +import 'date-fns'; +import React from 'react'; +import {Typography, InputLabel, Grid, InputAdornment, Select, FormControl, MenuItem, TextField, Slider} from '@material-ui/core'; +import DatePickerField from 'components/DatePickerField' +import InputField from 'components/InputField' +import {useInjectReducer} from "redux-injectors"; +import { useSelector } from 'react-redux'; +import {createStructuredSelector} from "reselect"; +import reducer from "pages/Profile/reducer"; +import { + makeSelectActivities, + makeSelectGenders, +} from "pages/Profile/selectors"; +import SelectField from "../../../components/SelectField"; + +const stateSelector = createStructuredSelector({ + activities: makeSelectActivities(), + genders: makeSelectGenders(), +}); + +const key = 'profilePage' +const PersonalDetailsForm = () => { + useInjectReducer({ key, reducer }); + const { genders } = useSelector(stateSelector) + + return ( + + + Personal details + + + + + + + + + + cm + }} + /> + + + Kg + }} + /> + + + Kg + }} + /> + + + + ); +} +export default PersonalDetailsForm diff --git a/src/pages/Profile/ReviewProfileForm/index.js b/src/pages/Profile/ReviewProfileForm/index.js new file mode 100644 index 0000000..1231114 --- /dev/null +++ b/src/pages/Profile/ReviewProfileForm/index.js @@ -0,0 +1,11 @@ +import React from 'react'; + +const ReviewProfileForm = () => { + return ( +
+ ReviewProfileForm +
+ ); +}; + +export default ReviewProfileForm; diff --git a/src/pages/Profile/index.js b/src/pages/Profile/index.js index 9674528..ad1ca89 100644 --- a/src/pages/Profile/index.js +++ b/src/pages/Profile/index.js @@ -3,13 +3,12 @@ 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 { useForm, useFormContext, FormProvider } from "react-hook-form"; -import GoalForm from 'components/GoalForm'; -import PersonalDetailsForm from 'components/PersonalDetailsForm'; -import ReviewProfileForm from 'components/ReviewProfileForm'; +import { Formik, Form } from 'formik'; +import GoalForm from './GoalForm'; +import PersonalDetailsForm from './PersonalDetailsForm'; +import ReviewProfileForm from './ReviewProfileForm'; import reducer from "./reducer"; import saga from "./saga"; -import { updateProfileAction } from './actions' const useStyles = makeStyles((theme) => ({ layout: { @@ -47,7 +46,7 @@ const useStyles = makeStyles((theme) => ({ const steps = ['Your goal', 'Personal details', 'Review your profile']; -const getStepContent = (step) => { +const renderStepContent = (step) => { switch (step) { case 0: return ; @@ -60,40 +59,56 @@ const getStepContent = (step) => { } } +const formInitialValues = { + gender: '', + goal: '', + birthday: '', + height: '', + weight: { + current: '', + goal: '', + }, + rateOfChange: '', + activity: '', +} + const key = 'profilePage' const ProfilePage = () => { - const classes = useStyles(); const [activeStep, setActiveStep] = useState(0); - const methods = useForm({ - defaultValues: { - gender: '', - goal: 0, - birthday: '', - height: 0, - weight: { - current: 0, - goal: 0, - }, - rateOfChange: 0, - activity: 0, - } - }); + const isLastStep = activeStep === steps.length - 1; + const isFirstStep = activeStep === 0; + const classes = useStyles(); useInjectReducer({ key, reducer }); useInjectSaga({ key, saga }); - const dispatch = useDispatch() + const sleep = (ms) => { + return new Promise(resolve => setTimeout(resolve, ms)); + } + + const submitForm = async (values, actions) => { + console.log(JSON.stringify(values, null, 2)); + actions.setSubmitting(false); - const handleNext = () => { setActiveStep(activeStep + 1); - }; + } + + const handleSubmit = (values, actions) => { + if (isLastStep) { + submitForm(values, actions); + } else { + setActiveStep(activeStep + 1); + actions.setTouched({}); + actions.setSubmitting(false); + } + } const handleBack = () => { setActiveStep(activeStep - 1); - }; + } - const handleSubmitProfile = data => { - console.log('data', data) + const handleNext = () => { + setActiveStep(activeStep + 1); } return ( @@ -121,29 +136,34 @@ const ProfilePage = () => { ) : ( - -
- {getStepContent(activeStep)} -
- {activeStep !== 0 && ( - + )} + - )} - -
-
-
- )} + + + )} + + )} diff --git a/src/pages/Profile/reducer.js b/src/pages/Profile/reducer.js index dbbf8d1..34dbb50 100644 --- a/src/pages/Profile/reducer.js +++ b/src/pages/Profile/reducer.js @@ -34,19 +34,26 @@ export const initialState = { goals: [ { label: 'lose weight', - value: -1, + value: 1, }, { label: 'maintain weight', - value: 0, + value: 2, }, { label: 'put on weight', - value: 1, + value: 3, } ], - ratesOfChange: [0, 0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9, 1], - genders: ['male', 'female'], + ratesOfChange: [ + { value: 1, label: '0 kg'}, + { value: 2, label: '0.5 kg'}, + { value: 3, label: '1 kg'}, + ], + genders: [ + {value: 1, label: 'male'}, + {value: 2, label: 'female'}, + ], isLoading: false, error: {}, gender: '', diff --git a/yarn.lock b/yarn.lock index 4d9d28c..79d8133 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4011,7 +4011,7 @@ data-urls@^2.0.0: whatwg-mimetype "^2.3.0" whatwg-url "^8.0.0" -date-fns@^2.16.1: +date-fns@^2.9.0: version "2.16.1" resolved "https://registry.yarnpkg.com/date-fns/-/date-fns-2.16.1.tgz#05775792c3f3331da812af253e1a935851d3834b" integrity sha512-sAJVKx/FqrLYHAQeN7VpJrPhagZc9R4ImZIWYRFZaaohR3KzmuK88touwsSwSVT8Qcbd4zoDsnGfX4GFB4imyQ== @@ -9536,11 +9536,6 @@ react-helmet@^6.1.0: react-fast-compare "^3.1.1" react-side-effect "^2.1.0" -react-hook-form@^6.13.1: - version "6.13.1" - resolved "https://registry.yarnpkg.com/react-hook-form/-/react-hook-form-6.13.1.tgz#b9c0aa61f746db8169ed5e1050de21cacb1947d6" - integrity sha512-Q0N7MYcbA8SigYufb02h9z97ZKCpIbe62rywOTPsK4Ntvh6fRTGDXSuzWuRhLHhArLoWbGrWYSNSS4tlb+OFXg== - react-is@^16.13.1, react-is@^16.6.0, react-is@^16.7.0, react-is@^16.8.0, react-is@^16.8.1: version "16.13.1" resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.13.1.tgz#789729a4dc36de2999dc156dd6c1d9c18cea56a4"