Fetched User data on Login, modified PrivateRoute logic

This commit is contained in:
Alexander Wong 2017-09-03 16:24:43 -06:00
parent 4cadf5df3a
commit e69773ac8e
15 changed files with 295 additions and 28 deletions

View File

@ -0,0 +1,50 @@
import {
IS_SENDING_USER_REQUEST,
SET_USER_REQUEST_ERROR,
CLEAR_USER_REQUEST_ERROR,
SET_USER_REQUEST_SUCCESS,
CLEAR_USER_REQUEST_SUCCESS,
SET_SELF_USER
} from "../../constants/user.constants";
import { parseError } from "../common.actions";
export function isSendingUserRequest(sendingRequest) {
return {
type: IS_SENDING_USER_REQUEST,
data: sendingRequest
};
}
export function setUserRequestError(exception) {
let error = parseError(exception);
return {
type: SET_USER_REQUEST_ERROR,
data: error
};
}
export function clearUserRequestError() {
return {
type: CLEAR_USER_REQUEST_ERROR
};
}
export function setUserRequestSuccess(response) {
return {
type: SET_USER_REQUEST_SUCCESS,
data: response.detail || response
};
}
export function clearUserRequestSuccess() {
return {
type: CLEAR_USER_REQUEST_SUCCESS
};
}
export function setSelfUser(selfUser) {
return {
type: SET_SELF_USER,
data: selfUser
};
}

View File

@ -0,0 +1,7 @@
import { SEND_GET_SELF_USER_REQUEST } from "../../constants/user.constants";
export function sendGetSelfUserRequest() {
return {
type: SEND_GET_SELF_USER_REQUEST
};
}

5
src/api/user.api.js Normal file
View File

@ -0,0 +1,5 @@
import { get } from "./baseApi";
export function getSelfUser() {
return get("/user/").then(resp => Promise.resolve(resp));
}

View File

@ -7,6 +7,7 @@ import Register from "./Auth/Register";
import ResetPassword from "./Auth/ResetPassword"; import ResetPassword from "./Auth/ResetPassword";
import Settings from "./Auth/Settings"; import Settings from "./Auth/Settings";
import VerifyEmail from "./Auth/VerifyEmail"; import VerifyEmail from "./Auth/VerifyEmail";
import Profile from "./User/Profile";
import PrivateRoute from "./Shared/PrivateRoute"; import PrivateRoute from "./Shared/PrivateRoute";
import About from "./Static/About"; import About from "./Static/About";
import Footer from "./Static/Footer"; import Footer from "./Static/Footer";
@ -15,10 +16,6 @@ import NoMatch from "./Static/NoMatch";
import Navbar from "./Navbar"; import Navbar from "./Navbar";
class App extends Component { class App extends Component {
componentDidMount() {
}
render() { render() {
const footSmash = { const footSmash = {
display: "flex", display: "flex",
@ -39,13 +36,14 @@ class App extends Component {
path="/auth/verify-email/:emailKey" path="/auth/verify-email/:emailKey"
component={VerifyEmail} component={VerifyEmail}
/> />
<Route <Route path="/auth/verify-email" component={VerifyEmail} />
path="/auth/verify-email"
component={VerifyEmail}
/>
<PrivateRoute path="/auth/settings" component={Settings} /> <PrivateRoute path="/auth/settings" component={Settings} />
<Route path="/auth/forgot-password" component={ForgotPassword} /> <Route path="/auth/forgot-password" component={ForgotPassword} />
<Route path="/auth/reset-password/:uid/:token" component={ResetPassword} /> <Route
path="/auth/reset-password/:uid/:token"
component={ResetPassword}
/>
<PrivateRoute path="/user/profile" component={Profile} />
<Route component={NoMatch} /> <Route component={NoMatch} />
</Switch> </Switch>
</div> </div>

View File

@ -41,7 +41,7 @@ class Login extends Component {
password, password,
userToken userToken
} = this.props; } = this.props;
if (userToken) return <Redirect to={"/"} />; if (userToken) return <Redirect to={"/user/profile"} />;
return ( return (
<LoginView <LoginView
isSendingAuthRequest={isSendingAuthRequest} isSendingAuthRequest={isSendingAuthRequest}

View File

@ -1,6 +1,6 @@
import React, { Component } from "react"; import React, { Component } from "react";
import { connect } from "react-redux"; import { connect } from "react-redux";
import { Link } from "react-router-dom"; import { Link, Redirect } from "react-router-dom";
import { Container, Form, Header, Message } from "semantic-ui-react"; import { Container, Form, Header, Message } from "semantic-ui-react";
import { import {
@ -39,8 +39,10 @@ class VerifyEmail extends Component {
isSendingAuthRequest, isSendingAuthRequest,
authRequestError, authRequestError,
authRequestSuccess, authRequestSuccess,
emailVerificationString emailVerificationString,
userToken
} = this.props; } = this.props;
if (userToken) return <Redirect to={"/"} />;
return ( return (
<VerifyEmailView <VerifyEmailView
isSendingAuthRequest={isSendingAuthRequest} isSendingAuthRequest={isSendingAuthRequest}

View File

@ -46,9 +46,13 @@ const NavbarView = ({ isAuthenticated, dispatchLogoutRequest }) => (
<Menu.Menu position="right"> <Menu.Menu position="right">
<Dropdown item text="Account"> <Dropdown item text="Account">
<Dropdown.Menu> <Dropdown.Menu>
<Dropdown.Item as={Link} to="/user/profile">
My Profile
</Dropdown.Item>
<Dropdown.Item as={Link} to="/auth/settings"> <Dropdown.Item as={Link} to="/auth/settings">
Settings Settings
</Dropdown.Item> </Dropdown.Item>
<Dropdown.Divider />
<Dropdown.Item onClick={dispatchLogoutRequest}> <Dropdown.Item onClick={dispatchLogoutRequest}>
Logout Logout
</Dropdown.Item> </Dropdown.Item>

View File

@ -1,15 +1,48 @@
import PropTypes from "prop-types"; import PropTypes from "prop-types";
import React from "react"; import React, { Component } from "react";
import { connect } from "react-redux"; import { connect } from "react-redux";
import { Redirect, Route } from "react-router-dom"; import { Redirect, Route } from "react-router-dom";
import { Loader } from "semantic-ui-react";
import { sendGetSelfUserRequest } from "../../actions/user/saga.actions";
const propTypes = { const propTypes = {
path: PropTypes.string.isRequired,
userToken: PropTypes.string.isRequired, userToken: PropTypes.string.isRequired,
component: PropTypes.oneOfType([PropTypes.element, PropTypes.func]).isRequired selfUser: PropTypes.object.isRequired,
isSendingUserRequest: PropTypes.bool.isRequired,
path: PropTypes.string.isRequired,
component: PropTypes.oneOfType([PropTypes.element, PropTypes.func])
.isRequired,
dispatch: PropTypes.func.isRequired
}; };
const PrivateRoute = ({ userToken, component, ...rest }) => { class PrivateRoute extends Component {
componentWillMount() {
const { dispatch, userToken, selfUser } = this.props;
// If the user token exists and the self user object isn't loaded, dispatch
if (userToken && Object.keys(selfUser).length === 0) {
dispatch(sendGetSelfUserRequest());
}
}
render() {
const {
userToken,
selfUser,
isSendingUserRequest,
component,
...rest
} = this.props;
// If the user token exists and
// * self user object isn't loaded yet or
// * we are still sending user request
// show loading spinner
if (
!!userToken &&
(Object.keys(selfUser).length === 0 || isSendingUserRequest)
) {
return <Loader active />;
}
return ( return (
<Route <Route
{...rest} {...rest}
@ -19,12 +52,15 @@ const PrivateRoute = ({ userToken, component, ...rest }) => {
}} }}
/> />
); );
}; }
}
PrivateRoute.propTypes = propTypes; PrivateRoute.propTypes = propTypes;
const mapStateToProps = state => ({ const mapStateToProps = state => ({
userToken: state.auth.userToken userToken: state.auth.userToken,
selfUser: state.user.selfUser,
isSendingUserRequest: state.user.isSendingUserRequest
}); });
export default connect(mapStateToProps)(PrivateRoute); export default connect(mapStateToProps)(PrivateRoute);

View File

@ -0,0 +1,47 @@
import React, { Component } from "react";
import { connect } from "react-redux";
import { Card, Container, Header, Label, List } from "semantic-ui-react";
class Profile extends Component {
render() {
const { selfUser } = this.props;
return <ProfileView user={selfUser} />;
}
}
function mapStateToProps(state) {
return { ...state.user };
}
const ProfileView = ({ user }) => (
<Container>
<Header>Profile</Header>
<Card>
<Card.Content>
<Card.Header>{user.username || "No username!"}</Card.Header>
<Card.Meta>{user.email || "No email!"}</Card.Meta>
<Card.Description>
<span>
{user.client && "Client"}{user.provider && "Provider"}
{!user.client && !user.provider && "User Registration Not Completed"}
</span>
{user.first_name} {user.last_name}
{user.userinfo && <List>
{Object.keys(user.userinfo).map(function(key) {
return (<List.Item key={key}>
{user.userinfo[key]}
</List.Item>)
})}
</List>}
</Card.Description>
</Card.Content>
<Card.Content extra>
<Label color={user.is_active ? "teal" : "red"} size="tiny" tag>
{user.is_active ? "Active" : "Deactivated"}
</Label>
</Card.Content>
</Card>
</Container>
);
export default connect(mapStateToProps)(Profile);

View File

@ -0,0 +1,10 @@
// Reducer User Action Constants
export const IS_SENDING_USER_REQUEST = "IS_SENDING_USER_REQUEST";
export const SET_USER_REQUEST_ERROR = "SET_USER_REQUEST_ERROR";
export const CLEAR_USER_REQUEST_ERROR = "CLEAR_USER_REQUEST_ERROR";
export const SET_USER_REQUEST_SUCCESS = "SET_USER_REQUEST_SUCCESS";
export const CLEAR_USER_REQUEST_SUCCESS = "CLEAR_USER_REQUEST_SUCCESS";
export const SET_SELF_USER = "SET_SELF_USER";
// Saga User Action Constants
export const SEND_GET_SELF_USER_REQUEST = "SEND_GET_SELF_USER_REQUEST";

View File

@ -1,8 +1,10 @@
import { combineReducers } from "redux"; import { combineReducers } from "redux";
import authReducer from "./authReducer"; import authReducer from "./authReducer";
import userReducer from "./userReducer";
const reducer = combineReducers({ const reducer = combineReducers({
auth: authReducer auth: authReducer,
user: userReducer
}); });
export default reducer; export default reducer;

View File

@ -0,0 +1,54 @@
import {
IS_SENDING_USER_REQUEST,
SET_USER_REQUEST_ERROR,
CLEAR_USER_REQUEST_ERROR,
SET_USER_REQUEST_SUCCESS,
CLEAR_USER_REQUEST_SUCCESS,
SET_SELF_USER
} from "../constants/user.constants";
const initialState = {
isSendingUserRequest: false,
userRequestError: "",
userRequestSuccess: "",
selfUser: {}
};
function userReducer(state = initialState, action) {
switch (action.type) {
case IS_SENDING_USER_REQUEST:
return {
...state,
isSendingUserRequest: action.data
};
case SET_USER_REQUEST_ERROR:
return {
...state,
userRequestError: action.data
};
case CLEAR_USER_REQUEST_ERROR:
return {
...state,
userRequestError: ""
};
case SET_USER_REQUEST_SUCCESS:
return {
...state,
userRequestSuccess: action.data
};
case CLEAR_USER_REQUEST_SUCCESS:
return {
...state,
userRequestSuccess: ""
};
case SET_SELF_USER:
return {
...state,
selfUser: action.data
};
default:
return state;
}
}
export default userReducer;

View File

@ -11,6 +11,7 @@ import {
setFormPasswordConfirmation, setFormPasswordConfirmation,
setFormOldPassword setFormOldPassword
} from "../actions/auth/reducer.actions"; } from "../actions/auth/reducer.actions";
import { setSelfUser } from "../actions/user/reducer.actions";
import { import {
registerUser, registerUser,
verifyEmail, verifyEmail,
@ -156,6 +157,7 @@ export function* logoutUserFlow(request) {
yield effects.put(clearAuthRequestError()); yield effects.put(clearAuthRequestError());
yield effects.call(logoutUserCall); yield effects.call(logoutUserCall);
yield effects.put(setSelfUserToken("")); yield effects.put(setSelfUserToken(""));
yield effects.put(setSelfUser({}));
} }
export function* changePasswordFlow(request) { export function* changePasswordFlow(request) {

View File

@ -17,6 +17,12 @@ import {
forgotPasswordFlow, forgotPasswordFlow,
resetPasswordFlow, resetPasswordFlow,
} from "./auth.sagas"; } from "./auth.sagas";
import {
SEND_GET_SELF_USER_REQUEST
} from "../constants/user.constants";
import {
getSelfUserFlow
} from "./user.sagas";
export default function* rootSaga() { export default function* rootSaga() {
yield takeLatest(SEND_REGISTER_REQUEST, registerUserFlow); yield takeLatest(SEND_REGISTER_REQUEST, registerUserFlow);
@ -26,4 +32,5 @@ export default function* rootSaga() {
yield takeLatest(SEND_CHANGE_PASSWORD_REQUEST, changePasswordFlow); yield takeLatest(SEND_CHANGE_PASSWORD_REQUEST, changePasswordFlow);
yield takeLatest(SEND_FORGOT_PASSWORD_REQUEST, forgotPasswordFlow); yield takeLatest(SEND_FORGOT_PASSWORD_REQUEST, forgotPasswordFlow);
yield takeLatest(SEND_RESET_PASSWORD_REQUEST, resetPasswordFlow); yield takeLatest(SEND_RESET_PASSWORD_REQUEST, resetPasswordFlow);
yield takeLatest(SEND_GET_SELF_USER_REQUEST, getSelfUserFlow);
} }

43
src/sagas/user.sagas.js Normal file
View File

@ -0,0 +1,43 @@
import { effects } from "redux-saga";
import { setSelfUserToken } from "../actions/auth/reducer.actions";
import {
isSendingUserRequest,
setUserRequestError,
setUserRequestSuccess,
clearUserRequestError,
clearUserRequestSuccess,
setSelfUser
} from "../actions/user/reducer.actions";
import { getSelfUser } from "../api/user.api";
function* getSelfUserCall() {
yield effects.put(isSendingUserRequest(true));
try {
const wasSuccessful = yield effects.call(getSelfUser);
yield effects.put(setUserRequestSuccess(wasSuccessful));
yield effects.put(clearUserRequestError());
// Check if the user exists, if yes set the user, otherwise force logout
if (wasSuccessful.results && wasSuccessful.results.length) {
yield effects.put(setSelfUser(wasSuccessful.results[0]));
} else {
yield effects.put(setSelfUserToken(""));
yield effects.put(setSelfUser({}));
}
return wasSuccessful;
} catch (exception) {
yield effects.put(setUserRequestError(exception));
return false;
} finally {
yield effects.put(isSendingUserRequest(false));
}
}
export function* getSelfUserFlow(request) {
yield effects.put(clearUserRequestSuccess());
yield effects.put(clearUserRequestError());
const wasSuccessful = yield effects.call(getSelfUserCall);
if (!wasSuccessful) {
yield effects.put(setSelfUserToken(""));
yield effects.put(setSelfUser({}));
}
}