Add basic profile page
This commit is contained in:
parent
6e00397e60
commit
9b56dd9244
@ -3,8 +3,10 @@
|
|||||||
"version": "0.1.0",
|
"version": "0.1.0",
|
||||||
"private": true,
|
"private": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"@date-io/date-fns": "^2.10.6",
|
||||||
"@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/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",
|
||||||
|
@ -1,15 +1,13 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import {Route, Switch} from 'react-router-dom';
|
|
||||||
import { ThemeProvider } from '@material-ui/core/styles'
|
import { ThemeProvider } from '@material-ui/core/styles'
|
||||||
import {createStructuredSelector} from "reselect";
|
import {createStructuredSelector} from "reselect";
|
||||||
import CssBaseline from '@material-ui/core/CssBaseline'
|
import CssBaseline from '@material-ui/core/CssBaseline'
|
||||||
import { theme, routes } from 'utils'
|
import { theme } from 'utils'
|
||||||
import {makeSelectIsLogged} from "./selectors";
|
import {makeSelectIsLogged} from "./selectors";
|
||||||
import { useSelector } from 'react-redux';
|
import { useSelector } from 'react-redux';
|
||||||
import Navbar from 'components/Navbar'
|
import Navbar from 'components/Navbar'
|
||||||
|
|
||||||
import HomePage from 'pages/Home'
|
import Routes from 'containers/Routes'
|
||||||
import LoginPage from 'pages/Login'
|
|
||||||
|
|
||||||
const stateSelector = createStructuredSelector({
|
const stateSelector = createStructuredSelector({
|
||||||
isLogged: makeSelectIsLogged(),
|
isLogged: makeSelectIsLogged(),
|
||||||
@ -21,11 +19,9 @@ const App = () => {
|
|||||||
return (
|
return (
|
||||||
<ThemeProvider theme={theme}>
|
<ThemeProvider theme={theme}>
|
||||||
<CssBaseline />
|
<CssBaseline />
|
||||||
{isLogged && <Navbar /> }
|
{/*{isLogged && <Navbar /> }*/}
|
||||||
<Switch>
|
<Navbar />
|
||||||
<Route exact path={routes.dashboard.path} component={HomePage} />
|
<Routes />
|
||||||
<Route exact path={routes.login.path} component={LoginPage} />
|
|
||||||
</Switch>
|
|
||||||
</ThemeProvider>
|
</ThemeProvider>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
@ -19,11 +19,10 @@ const appReducer = produce((draft, action) => {
|
|||||||
draft.tokens = action.tokens;
|
draft.tokens = action.tokens;
|
||||||
draft.user = action.user;
|
draft.user = action.user;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case LOGOUT_ERROR:
|
case LOGOUT_ERROR:
|
||||||
case LOGOUT_SUCCESS:
|
case LOGOUT_SUCCESS:
|
||||||
return initialState;
|
return initialState;
|
||||||
default:
|
|
||||||
return initialState;
|
|
||||||
}
|
}
|
||||||
}, initialState);
|
}, initialState);
|
||||||
|
|
||||||
|
20
src/containers/Routes/index.js
Normal file
20
src/containers/Routes/index.js
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import {Route, Switch} from "react-router-dom";
|
||||||
|
import {routes} from "utils";
|
||||||
|
|
||||||
|
const Routes = () => {
|
||||||
|
return (
|
||||||
|
<Switch>
|
||||||
|
{Object.values(routes).map(({ exact, path, component }, index) => (
|
||||||
|
<Route
|
||||||
|
key={index}
|
||||||
|
exact={exact}
|
||||||
|
path={path}
|
||||||
|
component={component}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
</Switch>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default Routes;
|
@ -10,7 +10,7 @@ export const loginAction = () => ({
|
|||||||
type: LOGIN_REQUEST,
|
type: LOGIN_REQUEST,
|
||||||
})
|
})
|
||||||
|
|
||||||
export const loginSuccessAction = (user, tokens) => ({
|
export const loginSuccessAction = ({user, tokens}) => ({
|
||||||
type: LOGIN_SUCCESS,
|
type: LOGIN_SUCCESS,
|
||||||
user,
|
user,
|
||||||
tokens,
|
tokens,
|
||||||
|
@ -13,18 +13,19 @@ const loginPageReducer = produce((draft, action) => {
|
|||||||
case LOGIN_SUCCESS:
|
case LOGIN_SUCCESS:
|
||||||
draft.loading = false;
|
draft.loading = false;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case LOGIN_REQUEST:
|
case LOGIN_REQUEST:
|
||||||
draft.loading = true;
|
draft.loading = true;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case LOGIN_ERROR:
|
case LOGIN_ERROR:
|
||||||
draft.loading = false;
|
draft.loading = false;
|
||||||
draft.error = action.error;
|
draft.error = action.error;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case LOGIN_INPUT_CHANGE:
|
case LOGIN_INPUT_CHANGE:
|
||||||
draft[action.name] = action.value;
|
draft[action.name] = action.value;
|
||||||
break;
|
break;
|
||||||
default:
|
|
||||||
return initialState;
|
|
||||||
}
|
}
|
||||||
}, initialState);
|
}, initialState);
|
||||||
|
|
||||||
|
@ -18,8 +18,8 @@ export function* login() {
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
const { tokens, user } = yield call(request, requestURL, requestParameters);
|
const { tokens, user } = yield call(request, requestURL, requestParameters);
|
||||||
yield put(loginSuccessAction(user, tokens));
|
yield put(loginSuccessAction({user, tokens}));
|
||||||
yield put(push(routes.dashboard.path));
|
yield put(push(routes.profile.path));
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
yield put(loginErrorAction(error.message));
|
yield put(loginErrorAction(error.message));
|
||||||
}
|
}
|
||||||
|
90
src/pages/Profile/actions.js
Normal file
90
src/pages/Profile/actions.js
Normal file
@ -0,0 +1,90 @@
|
|||||||
|
import {
|
||||||
|
GET_ACTIVITIES_REQUEST,
|
||||||
|
GET_ACTIVITIES_SUCCESS,
|
||||||
|
GET_ACTIVITIES_ERROR,
|
||||||
|
GET_GENDERS_ERROR,
|
||||||
|
GET_GENDERS_REQUEST,
|
||||||
|
GET_GENDERS_SUCCESS,
|
||||||
|
PROFILE_INPUT_CHANGE,
|
||||||
|
GET_PROFILE_REQUEST,
|
||||||
|
GET_PROFILE_SUCCESS,
|
||||||
|
GET_PROFILE_ERROR,
|
||||||
|
UPDATE_PROFILE_REQUEST,
|
||||||
|
UPDATE_PROFILE_SUCCESS,
|
||||||
|
UPDATE_PROFILE_ERROR
|
||||||
|
} from './constants';
|
||||||
|
|
||||||
|
export const profileInputChange = ({name, value}) => ({
|
||||||
|
type: PROFILE_INPUT_CHANGE,
|
||||||
|
name,
|
||||||
|
value,
|
||||||
|
})
|
||||||
|
|
||||||
|
export const getProfileAction = () => ({
|
||||||
|
type: GET_PROFILE_REQUEST,
|
||||||
|
})
|
||||||
|
|
||||||
|
export const getProfileSuccessAction = ({birthday, gender, height, currentWeight, goalWeight, rateOfChange, activity}) => ({
|
||||||
|
type: GET_PROFILE_SUCCESS,
|
||||||
|
birthday,
|
||||||
|
gender,
|
||||||
|
height,
|
||||||
|
currentWeight,
|
||||||
|
goalWeight,
|
||||||
|
rateOfChange,
|
||||||
|
activity
|
||||||
|
})
|
||||||
|
|
||||||
|
export const getProfileErrorAction = (error) => ({
|
||||||
|
type: GET_PROFILE_ERROR,
|
||||||
|
error,
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
|
export const updateProfileAction = () => ({
|
||||||
|
type: UPDATE_PROFILE_REQUEST,
|
||||||
|
})
|
||||||
|
|
||||||
|
export const updateProfileSuccessAction = ({birthday, gender, height, currentWeight, goalWeight, rateOfChange, activity}) => ({
|
||||||
|
type: UPDATE_PROFILE_SUCCESS,
|
||||||
|
birthday,
|
||||||
|
gender,
|
||||||
|
height,
|
||||||
|
currentWeight,
|
||||||
|
goalWeight,
|
||||||
|
rateOfChange,
|
||||||
|
activity
|
||||||
|
})
|
||||||
|
|
||||||
|
export const updateProfileErrorAction = (error) => ({
|
||||||
|
type: UPDATE_PROFILE_ERROR,
|
||||||
|
error,
|
||||||
|
})
|
||||||
|
|
||||||
|
export const getActivitiesAction = () => ({
|
||||||
|
type: GET_ACTIVITIES_REQUEST,
|
||||||
|
})
|
||||||
|
|
||||||
|
export const getActivitiesSuccessAction = ({ activities }) => ({
|
||||||
|
type: GET_ACTIVITIES_SUCCESS,
|
||||||
|
activities,
|
||||||
|
})
|
||||||
|
|
||||||
|
export const getActivitiesErrorAction = (error) => ({
|
||||||
|
type: GET_ACTIVITIES_ERROR,
|
||||||
|
error,
|
||||||
|
})
|
||||||
|
|
||||||
|
export const getGendersAction = () => ({
|
||||||
|
type: GET_GENDERS_REQUEST,
|
||||||
|
})
|
||||||
|
|
||||||
|
export const getGendersSuccessAction = ({ genders }) => ({
|
||||||
|
type: GET_GENDERS_SUCCESS,
|
||||||
|
genders,
|
||||||
|
})
|
||||||
|
|
||||||
|
export const getGendersErrorAction = (error) => ({
|
||||||
|
type: GET_GENDERS_ERROR,
|
||||||
|
error,
|
||||||
|
})
|
17
src/pages/Profile/constants.js
Normal file
17
src/pages/Profile/constants.js
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
export const GET_PROFILE_REQUEST = 'app/ProfilePage/GET_PROFILE_REQUEST';
|
||||||
|
export const GET_PROFILE_SUCCESS = 'app/ProfilePage/GET_PROFILE_SUCCESS';
|
||||||
|
export const GET_PROFILE_ERROR = 'app/ProfilePage/GET_PROFILE_ERROR';
|
||||||
|
|
||||||
|
export const UPDATE_PROFILE_REQUEST = 'app/ProfilePage/UPDATE_PROFILE_REQUEST';
|
||||||
|
export const UPDATE_PROFILE_SUCCESS = 'app/ProfilePage/UPDATE_PROFILE_SUCCESS';
|
||||||
|
export const UPDATE_PROFILE_ERROR = 'app/ProfilePage/UPDATE_PROFILE_ERROR';
|
||||||
|
|
||||||
|
export const 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_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';
|
145
src/pages/Profile/index.js
Normal file
145
src/pages/Profile/index.js
Normal file
@ -0,0 +1,145 @@
|
|||||||
|
import React, { useEffect } from 'react';
|
||||||
|
import { Container, Slider, Button, TextField, FormControl, MenuItem, InputLabel, Select, Grid, Typography } from '@material-ui/core'
|
||||||
|
import {useInjectReducer, useInjectSaga} from "redux-injectors";
|
||||||
|
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";
|
||||||
|
|
||||||
|
const stateSelector = createStructuredSelector({
|
||||||
|
loading: makeSelectLoading(),
|
||||||
|
error: makeSelectError(),
|
||||||
|
gender: makeSelectGender(),
|
||||||
|
birthday: makeSelectBirthday(),
|
||||||
|
height: makeSelectHeight(),
|
||||||
|
currentWeight: makeSelectCurrentWeight(),
|
||||||
|
goalWeight: makeSelectGoalWeight(),
|
||||||
|
rateOfChange: makeSelectRateOfChange(),
|
||||||
|
activity: makeSelectActivity(),
|
||||||
|
});
|
||||||
|
|
||||||
|
const key = 'profilePage'
|
||||||
|
const ProfilePage = () => {
|
||||||
|
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())
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
getProfile()
|
||||||
|
getActivities()
|
||||||
|
getGenders()
|
||||||
|
}, [])
|
||||||
|
|
||||||
|
const handleChange = ({ target: { name, value }}) => {
|
||||||
|
dispatch(profileInputChange({ name, value }))
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Container>
|
||||||
|
<Grid container direction="column" alignItems="center" justify="center">
|
||||||
|
<Typography variant="h5">Profile</Typography>
|
||||||
|
<FormControl variant="outlined" fullWidth>
|
||||||
|
<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>
|
||||||
|
<Slider
|
||||||
|
value={rateOfChange}
|
||||||
|
onChange={handleChange}
|
||||||
|
aria-labelledby="rate-of-change"
|
||||||
|
step={0.1}
|
||||||
|
marks
|
||||||
|
min={0}
|
||||||
|
max={1}
|
||||||
|
valueLabelDisplay="auto"
|
||||||
|
/>
|
||||||
|
</FormControl>
|
||||||
|
<FormControl fullWidth>
|
||||||
|
<InputLabel id="activity-label">Activity</InputLabel>
|
||||||
|
<Select
|
||||||
|
labelId="activity-label"
|
||||||
|
name="activity"
|
||||||
|
value={activity}
|
||||||
|
onChange={handleChange}
|
||||||
|
label="Activity"
|
||||||
|
>
|
||||||
|
<MenuItem value={`1`}>Small</MenuItem>
|
||||||
|
<MenuItem value={`1.2`}>Medium</MenuItem>
|
||||||
|
<MenuItem value={`2`}>High</MenuItem>
|
||||||
|
</Select>
|
||||||
|
</FormControl>
|
||||||
|
<FormControl fullWidth>
|
||||||
|
<Button variant="contained" color="primary">Save</Button>
|
||||||
|
</FormControl>
|
||||||
|
</Grid>
|
||||||
|
</Container>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default ProfilePage;
|
77
src/pages/Profile/reducer.js
Normal file
77
src/pages/Profile/reducer.js
Normal file
@ -0,0 +1,77 @@
|
|||||||
|
import produce from 'immer';
|
||||||
|
import {
|
||||||
|
GET_ACTIVITIES_SUCCESS,
|
||||||
|
GET_GENDERS_SUCCESS,
|
||||||
|
GET_ACTIVITIES_ERROR,
|
||||||
|
GET_GENDERS_ERROR,
|
||||||
|
GET_ACTIVITIES_REQUEST,
|
||||||
|
GET_GENDERS_REQUEST,
|
||||||
|
PROFILE_INPUT_CHANGE,
|
||||||
|
GET_PROFILE_REQUEST,
|
||||||
|
GET_PROFILE_SUCCESS,
|
||||||
|
GET_PROFILE_ERROR,
|
||||||
|
UPDATE_PROFILE_REQUEST,
|
||||||
|
UPDATE_PROFILE_SUCCESS,
|
||||||
|
UPDATE_PROFILE_ERROR
|
||||||
|
} from './constants';
|
||||||
|
|
||||||
|
export const initialState = {
|
||||||
|
activities: [],
|
||||||
|
genders: [],
|
||||||
|
loading: false,
|
||||||
|
error: {},
|
||||||
|
gender: '',
|
||||||
|
birthday: '',
|
||||||
|
height: 0,
|
||||||
|
currentWeight: 0,
|
||||||
|
goalWeight: 0,
|
||||||
|
rateOfChange: 0,
|
||||||
|
activity: 0,
|
||||||
|
};
|
||||||
|
|
||||||
|
const loginPageReducer = produce((draft, action) => {
|
||||||
|
switch(action.type) {
|
||||||
|
case GET_ACTIVITIES_SUCCESS:
|
||||||
|
draft.activities = action.activities;
|
||||||
|
draft.loading = false;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case GET_GENDERS_SUCCESS:
|
||||||
|
draft.genders = action.genders;
|
||||||
|
draft.loading = false;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case GET_PROFILE_SUCCESS:
|
||||||
|
case UPDATE_PROFILE_SUCCESS:
|
||||||
|
draft.birthday = action.birthday;
|
||||||
|
draft.gender = action.gender;
|
||||||
|
draft.height = action.height;
|
||||||
|
draft.currentWeight = action.currentWeight;
|
||||||
|
draft.goalWeight = action.goalWeight;
|
||||||
|
draft.rateOfChange = action.rateOfChange;
|
||||||
|
draft.activity = action.activity;
|
||||||
|
draft.loading = false;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case GET_ACTIVITIES_REQUEST:
|
||||||
|
case GET_GENDERS_REQUEST:
|
||||||
|
case GET_PROFILE_REQUEST:
|
||||||
|
case UPDATE_PROFILE_REQUEST:
|
||||||
|
draft.loading = true;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case GET_ACTIVITIES_ERROR:
|
||||||
|
case GET_GENDERS_ERROR:
|
||||||
|
case GET_PROFILE_ERROR:
|
||||||
|
case UPDATE_PROFILE_ERROR:
|
||||||
|
draft.loading = false;
|
||||||
|
draft.error = action.error;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case PROFILE_INPUT_CHANGE:
|
||||||
|
draft[action.name] = action.value;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}, initialState);
|
||||||
|
|
||||||
|
export default loginPageReducer;
|
111
src/pages/Profile/saga.js
Normal file
111
src/pages/Profile/saga.js
Normal file
@ -0,0 +1,111 @@
|
|||||||
|
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 {
|
||||||
|
makeSelectBirthday,
|
||||||
|
makeSelectHeight,
|
||||||
|
makeSelectCurrentWeight,
|
||||||
|
makeSelectRateOfChange,
|
||||||
|
makeSelectActivity,
|
||||||
|
makeSelectGoalWeight,
|
||||||
|
makeSelectGender
|
||||||
|
} from './selectors';
|
||||||
|
import { getActivitiesSuccessAction, getActivitiesErrorAction, getGendersSuccessAction, getGendersErrorAction, updateProfileErrorAction, updateProfileSuccessAction, getProfileErrorAction, getProfileSuccessAction } from './actions';
|
||||||
|
|
||||||
|
import { makeSelectTokens } from 'containers/App/selectors'
|
||||||
|
|
||||||
|
export function* getProfile() {
|
||||||
|
const { access } = yield select(makeSelectTokens());
|
||||||
|
|
||||||
|
const requestURL = api.profile;
|
||||||
|
|
||||||
|
const requestParameters = {
|
||||||
|
method: 'GET',
|
||||||
|
headers: {
|
||||||
|
Authorization: `Bearer ${access.token}`,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
try {
|
||||||
|
const { birthday, gender, height, currentWeight, goalWeight, rateOfChange, activity } = yield call(request, requestURL, requestParameters);
|
||||||
|
yield put(getProfileSuccessAction({birthday, gender, height, currentWeight, goalWeight, rateOfChange, activity}));
|
||||||
|
} catch (error) {
|
||||||
|
yield put(getProfileErrorAction(error.message));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function* updateProfile() {
|
||||||
|
const { access } = yield select(makeSelectTokens());
|
||||||
|
|
||||||
|
const birthday = yield select(makeSelectBirthday());
|
||||||
|
const gender = yield select(makeSelectGender());
|
||||||
|
const height = yield select(makeSelectHeight());
|
||||||
|
const currentWeight = yield select(makeSelectCurrentWeight());
|
||||||
|
const goalWeight = yield select(makeSelectGoalWeight());
|
||||||
|
const rateOfChange = yield select(makeSelectRateOfChange());
|
||||||
|
const activity = yield select(makeSelectActivity());
|
||||||
|
|
||||||
|
const requestURL = api.profile;
|
||||||
|
|
||||||
|
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 }),
|
||||||
|
};
|
||||||
|
|
||||||
|
try {
|
||||||
|
const { birthday, gender, height, currentWeight, goalWeight, rateOfChange, activity } = yield call(request, requestURL, requestParameters);
|
||||||
|
yield put(updateProfileSuccessAction({birthday, gender, height, currentWeight, goalWeight, rateOfChange, activity}));
|
||||||
|
} catch (error) {
|
||||||
|
yield put(updateProfileErrorAction(error.message));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function* getActivities() {
|
||||||
|
const { access } = yield select(makeSelectTokens());
|
||||||
|
|
||||||
|
const requestURL = api.activities;
|
||||||
|
|
||||||
|
const requestParameters = {
|
||||||
|
method: 'GET',
|
||||||
|
headers: {
|
||||||
|
Authorization: `Bearer ${access.token}`,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
try {
|
||||||
|
const { activities } = yield call(request, requestURL, requestParameters);
|
||||||
|
yield put(getActivitiesSuccessAction({ activities }));
|
||||||
|
} catch (error) {
|
||||||
|
yield put(getActivitiesErrorAction(error.message));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
export function* getGenders() {
|
||||||
|
const { access } = yield select(makeSelectTokens());
|
||||||
|
|
||||||
|
const requestURL = api.genders;
|
||||||
|
|
||||||
|
const requestParameters = {
|
||||||
|
method: 'GET',
|
||||||
|
headers: {
|
||||||
|
Authorization: `Bearer ${access.token}`,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
try {
|
||||||
|
const { genders } = yield call(request, requestURL, requestParameters);
|
||||||
|
yield put(getGendersSuccessAction({ genders }));
|
||||||
|
} catch (error) {
|
||||||
|
yield put(getGendersErrorAction(error.message));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function* profilePageSaga() {
|
||||||
|
yield takeLatest(GET_PROFILE_REQUEST, getProfile);
|
||||||
|
yield takeLatest(GET_ACTIVITIES_REQUEST, getActivities);
|
||||||
|
yield takeLatest(GET_GENDERS_REQUEST, getGenders);
|
||||||
|
yield takeLatest(GET_PROFILE_REQUEST, getProfile);
|
||||||
|
yield takeLatest(UPDATE_PROFILE_REQUEST, updateProfile);
|
||||||
|
}
|
44
src/pages/Profile/selectors.js
Normal file
44
src/pages/Profile/selectors.js
Normal file
@ -0,0 +1,44 @@
|
|||||||
|
import { createSelector } from 'reselect';
|
||||||
|
import { initialState } from './reducer';
|
||||||
|
|
||||||
|
const selectProfilePageDomain = (state) => state.profilePage || initialState;
|
||||||
|
|
||||||
|
const makeSelectError = () =>
|
||||||
|
createSelector(selectProfilePageDomain, (substate) => substate.error);
|
||||||
|
|
||||||
|
const makeSelectBirthday = () =>
|
||||||
|
createSelector(selectProfilePageDomain, (substate) => substate.birthday);
|
||||||
|
|
||||||
|
const makeSelectHeight = () =>
|
||||||
|
createSelector(selectProfilePageDomain, (substate) => substate.height);
|
||||||
|
|
||||||
|
const makeSelectCurrentWeight = () =>
|
||||||
|
createSelector(selectProfilePageDomain, (substate) => substate.currentWeight);
|
||||||
|
|
||||||
|
const makeSelectRateOfChange = () =>
|
||||||
|
createSelector(selectProfilePageDomain, (substate) => substate.rateOfChange);
|
||||||
|
|
||||||
|
const makeSelectActivity = () =>
|
||||||
|
createSelector(selectProfilePageDomain, (substate) => substate.activity);
|
||||||
|
|
||||||
|
const makeSelectGoalWeight = () =>
|
||||||
|
createSelector(selectProfilePageDomain, (substate) => substate.goalWeight);
|
||||||
|
|
||||||
|
const makeSelectGender = () =>
|
||||||
|
createSelector(selectProfilePageDomain, (substate) => substate.gender);
|
||||||
|
|
||||||
|
const makeSelectLoading = () =>
|
||||||
|
createSelector(selectProfilePageDomain, (substate) => substate.loading);
|
||||||
|
|
||||||
|
export {
|
||||||
|
selectProfilePageDomain,
|
||||||
|
makeSelectError,
|
||||||
|
makeSelectBirthday,
|
||||||
|
makeSelectHeight,
|
||||||
|
makeSelectCurrentWeight,
|
||||||
|
makeSelectRateOfChange,
|
||||||
|
makeSelectActivity,
|
||||||
|
makeSelectGoalWeight,
|
||||||
|
makeSelectGender,
|
||||||
|
makeSelectLoading,
|
||||||
|
};
|
@ -1,11 +1,17 @@
|
|||||||
const API_BASE_URL = 'http://localhost:3001/v1'
|
const API_BASE_URL = 'http://localhost:3001/v1'
|
||||||
const AUTH = 'auth';
|
const AUTH = 'auth';
|
||||||
|
const PROFILE = 'profiles';
|
||||||
|
const ACTIVITY = 'activities';
|
||||||
|
const GENDER = 'genders';
|
||||||
|
|
||||||
const urls = {
|
const urls = {
|
||||||
auth: {
|
auth: {
|
||||||
login: `${API_BASE_URL}/${AUTH}/login`,
|
login: `${API_BASE_URL}/${AUTH}/login`,
|
||||||
register: `${API_BASE_URL}/${AUTH}/register`,
|
register: `${API_BASE_URL}/${AUTH}/register`,
|
||||||
}
|
},
|
||||||
|
profile: `${API_BASE_URL}/${PROFILE}`,
|
||||||
|
activities: `${API_BASE_URL}/${ACTIVITY}`,
|
||||||
|
genders: `${API_BASE_URL}/${GENDER}`,
|
||||||
}
|
}
|
||||||
|
|
||||||
export default urls
|
export default urls
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import { createStore, applyMiddleware, compose } from 'redux';
|
import { createStore, applyMiddleware, compose } from 'redux';
|
||||||
import { routerMiddleware } from 'connected-react-router';
|
import { routerMiddleware } from 'connected-react-router';
|
||||||
import { createInjectorsEnhancer } from 'redux-injectors';
|
import { createInjectorsEnhancer, forceReducerReload } from 'redux-injectors';
|
||||||
import createSagaMiddleware from 'redux-saga';
|
import createSagaMiddleware from 'redux-saga';
|
||||||
import createReducer from './reducers';
|
import createReducer from './reducers';
|
||||||
|
|
||||||
@ -8,19 +8,17 @@ export default function configureStore(initialState = {}, history) {
|
|||||||
let composeEnhancers = compose;
|
let composeEnhancers = compose;
|
||||||
const reduxSagaMonitorOptions = {};
|
const reduxSagaMonitorOptions = {};
|
||||||
|
|
||||||
|
// If Redux Dev Tools and Saga Dev Tools Extensions are installed, enable them
|
||||||
|
/* istanbul ignore next */
|
||||||
if (typeof window === 'object') {
|
if (typeof window === 'object') {
|
||||||
/* eslint-disable no-underscore-dangle */
|
/* eslint-disable no-underscore-dangle */
|
||||||
if (window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__)
|
if (window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__)
|
||||||
composeEnhancers = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__({});
|
composeEnhancers = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__({});
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const sagaMiddleware = createSagaMiddleware(reduxSagaMonitorOptions);
|
const sagaMiddleware = createSagaMiddleware(reduxSagaMonitorOptions);
|
||||||
const { run: runSaga } = sagaMiddleware;
|
const { run: runSaga } = sagaMiddleware;
|
||||||
|
|
||||||
// Create the store with two middlewares
|
|
||||||
// 1. sagaMiddleware: Makes redux-sagas work
|
|
||||||
// 2. routerMiddleware: Syncs the location/URL path to the state
|
|
||||||
const middlewares = [sagaMiddleware, routerMiddleware(history)];
|
const middlewares = [sagaMiddleware, routerMiddleware(history)];
|
||||||
|
|
||||||
const enhancers = [
|
const enhancers = [
|
||||||
@ -37,5 +35,11 @@ export default function configureStore(initialState = {}, history) {
|
|||||||
composeEnhancers(...enhancers),
|
composeEnhancers(...enhancers),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
if (module.hot) {
|
||||||
|
module.hot.accept('./reducers', () => {
|
||||||
|
forceReducerReload(store);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
return store;
|
return store;
|
||||||
}
|
}
|
||||||
|
@ -1,12 +1,22 @@
|
|||||||
|
import HomePage from "pages/Home";
|
||||||
|
import ProfilePage from "pages/Profile";
|
||||||
|
import LoginPage from "pages/Login";
|
||||||
|
|
||||||
const routes = {
|
const routes = {
|
||||||
dashboard: {
|
dashboard: {
|
||||||
path: '/',
|
path: '/',
|
||||||
exact: true,
|
exact: true,
|
||||||
|
component: HomePage,
|
||||||
|
},
|
||||||
|
profile: {
|
||||||
|
path: '/profile',
|
||||||
|
exact: true,
|
||||||
|
component: ProfilePage,
|
||||||
},
|
},
|
||||||
login: {
|
login: {
|
||||||
path: '/login',
|
path: '/login',
|
||||||
exact: true,
|
exact: true,
|
||||||
|
component: LoginPage,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
49
yarn.lock
49
yarn.lock
@ -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,23 @@
|
|||||||
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":
|
||||||
|
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/core@^2.10.6":
|
||||||
|
version "2.10.6"
|
||||||
|
resolved "https://registry.yarnpkg.com/@date-io/core/-/core-2.10.6.tgz#1a6e671b590a08af8bd0784f3a93670e5d2d5bd7"
|
||||||
|
integrity sha512-MGYt4GEB/4ZMdSbj6FS7/gPBvuhHUwnn5O6t8PlkSqGF1310qxypVyK4CZg5RQgev25L3R5eLVdNTyYrJOL8Rw==
|
||||||
|
|
||||||
|
"@date-io/date-fns@^2.10.6":
|
||||||
|
version "2.10.6"
|
||||||
|
resolved "https://registry.yarnpkg.com/@date-io/date-fns/-/date-fns-2.10.6.tgz#d0afee6452d80112017f42af4912ba22d95b11b6"
|
||||||
|
integrity sha512-jUiIbs4VCmACy2Ml2xu3tqf0AUSZu4qQ3cRz8SoG4YPzeg1fqII8y/gTa7GJkXiH0bUKUWaf/G2dfJa9tUnmJA==
|
||||||
|
dependencies:
|
||||||
|
"@date-io/core" "^2.10.6"
|
||||||
|
|
||||||
"@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"
|
||||||
@ -1309,6 +1326,18 @@
|
|||||||
dependencies:
|
dependencies:
|
||||||
"@babel/runtime" "^7.4.4"
|
"@babel/runtime" "^7.4.4"
|
||||||
|
|
||||||
|
"@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"
|
||||||
@ -1820,6 +1849,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"
|
||||||
@ -3305,7 +3341,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==
|
||||||
@ -9586,7 +9622,7 @@ react-transition-group@^2.5.0:
|
|||||||
prop-types "^15.6.2"
|
prop-types "^15.6.2"
|
||||||
react-lifecycles-compat "^3.0.4"
|
react-lifecycles-compat "^3.0.4"
|
||||||
|
|
||||||
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==
|
||||||
@ -10049,6 +10085,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"
|
||||||
|
Loading…
Reference in New Issue
Block a user