Skip to content

Commit

Permalink
feat(request): Member should be able to create permission request
Browse files Browse the repository at this point in the history
- create action that consume the request endpoint
- create reducer to manage the state
- test the action and the reducer

[Finishes #161059614]
  • Loading branch information
Jacob Nouwatin authored and Jacob Nouwatin committed Oct 11, 2018
1 parent 4f5551a commit a054973
Show file tree
Hide file tree
Showing 12 changed files with 3,612 additions and 3,406 deletions.
41 changes: 36 additions & 5 deletions client/src/modules/common/Navbar/index.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,18 +4,27 @@ import PropTypes from 'prop-types';
import { NavLink } from 'react-router-dom';
import { connect } from 'react-redux';
import { signOut } from '../../../redux/actions/auth';
import createAdminRequest from '../../../redux/actions/requests';
import { warningMessage, successMessage } from '../../../toasts';
import errorFormatter from '../../../utils/errorFormatter.json';

class Navbar extends Component {
static propTypes = {
signOut: PropTypes.func.isRequired,
createAdminRequest: PropTypes.func.isRequired,
auth: PropTypes.shape({
name: PropTypes.string
}).isRequired,
requestsReducer: PropTypes.shape({
error: PropTypes.string,
request: PropTypes.object,
success: PropTypes.bool
}).isRequired,
handleSubmit: PropTypes.func,
switchContent: PropTypes.func,
showTabs: PropTypes.bool,
showIcon: PropTypes.bool,
gotoHome: PropTypes.func
gotoHome: PropTypes.func,
};
constructor(props) {
super(props);
Expand All @@ -26,13 +35,24 @@ class Navbar extends Component {
this.toggleState = this.toggleState.bind(this);
this.handleSearch = this.handleSearch.bind(this);
this.signOut = this.signOut.bind(this);
this.handleAdminRequest = this.handleAdminRequest.bind(this);
}
componentDidMount = () => {
if (this.props.auth) {
this.setState(() => ({ name: this.props.auth.name }));
}
};

componentDidUpdate(prevProps) {
const { requestsReducer: { error, success } } = this.props;
if (error && !success) {
warningMessage(errorFormatter[error]);
}
if (success) {
successMessage('Request Successful');
}
}

handleSearch(event) {
event.preventDefault();
this.props.handleSubmit(event);
Expand All @@ -46,6 +66,11 @@ class Navbar extends Component {
this.props.signOut();
}

handleAdminRequest(event) {
event.preventDefault();
this.props.createAdminRequest({ type: 'admin_request' });
}

toggleState(state) {
this.setState(prevState => ({
[state]: !prevState[state]
Expand Down Expand Up @@ -114,7 +139,10 @@ class Navbar extends Component {
</NavLink>
</li> :
<li>
<button className="admin-request-btn">
<button
className="admin-request-btn"
onClick={this.handleAdminRequest}
>
<i
className="tiny material-icons"
data-tip="Only LFs can make this request">
Expand Down Expand Up @@ -273,8 +301,11 @@ class Navbar extends Component {
}
}

const mapStateToProps = ({ auth }) => ({
auth
const mapStateToProps = ({ auth, requestsReducer }) => ({
auth,
requestsReducer
});

export default connect(mapStateToProps, { signOut })(Navbar);
export default connect(mapStateToProps, {
signOut, createAdminRequest
})(Navbar);
15 changes: 15 additions & 0 deletions client/src/redux/actions/requests/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import instance from '../../../config/axios';
import { CREATE_ADMIN_REQUEST_SUCCESS, CREATE_ADMIN_REQUEST_ERROR } from '../types';
import { success, isErrored } from '../index';

const createAdminRequest = requestData => dispatch => instance
.post('/requests', requestData)
.then((response) => {
if (response.data.errors) {
return dispatch(isErrored(CREATE_ADMIN_REQUEST_ERROR, response.data.errors[0]));
}
return dispatch(success(CREATE_ADMIN_REQUEST_SUCCESS, response.data));
})
.catch((error) => dispatch(isErrored(CREATE_ADMIN_REQUEST_ERROR, error)));

export default createAdminRequest;
2 changes: 2 additions & 0 deletions client/src/redux/actions/types.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,3 +18,5 @@ export const FETCH_FAVORITES = 'FETCH_FAVORITES';
export const REMOVE_FAVORITE_TEAM = 'REMOVE_FAVORITE_TEAM';
export const SHOW_RESPONSE = 'SHOW_RESPONSE';
export const ISMODAL_OPENED = 'ISMODAL_OPENED';
export const CREATE_ADMIN_REQUEST_SUCCESS = 'CREATE_ADMIN_REQUEST_SUCCESS';
export const CREATE_ADMIN_REQUEST_ERROR = 'CREATE_ADMIN_REQUEST_ERROR';
4 changes: 3 additions & 1 deletion client/src/redux/reducers/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,14 @@ import teams from './teamsReducer';
import users from './userReducer';
import auth from './authReducer';
import isLoading from './networkRequestReducer';
import requestsReducer from './requestsReducer';

const root = combineReducers({
teams,
users,
auth,
isLoading
isLoading,
requestsReducer
});

export default root;
28 changes: 28 additions & 0 deletions client/src/redux/reducers/requestsReducer.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import { CREATE_ADMIN_REQUEST_SUCCESS, CREATE_ADMIN_REQUEST_ERROR } from '../actions/types';

const initialState = {
request: null,
success: false,
error: ''
};

const requestReducer = (state = initialState, action) => {
switch (action.type) {
case CREATE_ADMIN_REQUEST_SUCCESS:
return {
...state,
success: true,
request: action.payload
};
case CREATE_ADMIN_REQUEST_ERROR:
return {
...state,
error: action.payload,
success: false
};
default:
return state;
}
};

export default requestReducer;
15 changes: 15 additions & 0 deletions client/src/tests/__mockData__/requestsMock.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
const requestsResponse = {
data: {
request: {
id: "cffda78d-d218-49e7-9382-0afba282cd8a",
type: "admin_request",
data: "123",
userId: "76e999da-31b0-4f5e-ba34-b66bc58b93f1",
updatedAt: "2018-10-10T06:53:47.967Z",
createdAt: "2018-10-10T06:53:47.967Z",
teamId: null
}
}
};

export default requestsResponse;
48 changes: 48 additions & 0 deletions client/src/tests/redux/actions/requests/index.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
import configureMockStore from 'redux-mock-store';
import thunk from 'redux-thunk';
import moxios from 'moxios';
import instance from '../../../../config/axios';
import { CREATE_ADMIN_REQUEST_SUCCESS, CREATE_ADMIN_REQUEST_ERROR } from '../../../../../src/redux/actions/types';
import createAdminRequest from '../../../../redux/actions/requests';
import requestsReponse from '../../../__mockData__/requestsMock';

const middlewares = [thunk];
const mockStore = configureMockStore(middlewares);

describe('Request Actions', () => {
beforeEach(() => moxios.install(instance));
afterEach(() => moxios.uninstall(instance));
it('should dispatch CREATE_ADMIN_REQUEST_SUCCESS after successfull admin request sent', () => {
const store = mockStore({});
moxios.stubRequest('/requests', {
status: 200,
response: requestsReponse
});
const expectedAction = {
type: CREATE_ADMIN_REQUEST_SUCCESS,
data: { requestsReponse }
};
store.dispatch(createAdminRequest({ type: 'admin_request' }))
.then(() => {
const actions = store.getActions();
expect(actions[0]).toEqual(expectedAction);
}, 3000);
});

it('should dispatch CREATE_ADMIN_REQUEST_ERROR if admin request is not successfull', () => {
const store = mockStore({});
moxios.stubRequest('/requests', {
status: 200,
response: { errors: ['The type field is required.'] }
});
const expectedAction = {
type: CREATE_ADMIN_REQUEST_ERROR,
data: { errors: ['The type field is required.'] }
};
store.dispatch(createAdminRequest())
.then(() => {
const actions = store.getActions();
expect(actions[0]).toEqual(expectedAction);
}, 3000);
});
});
38 changes: 38 additions & 0 deletions client/src/tests/redux/reducers/requestsReducer.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import configureMockStore from 'redux-mock-store';
import thunk from 'redux-thunk';
import requestsReducer from '../../../redux/reducers/requestsReducer';
import { CREATE_ADMIN_REQUEST_SUCCESS, CREATE_ADMIN_REQUEST_ERROR } from '../../../../src/redux/actions/types';
import { success, isErrored } from '../../../redux/actions';
import requestsReponse from '../../__mockData__/requestsMock';

const middlewares = [thunk];
const mockStore = configureMockStore(middlewares);
const store = mockStore({});

const initialState = {
request: null,
success: false,
error: ''
};

const errorResponse = {
data: {
errors: ['The type field is required.']
}
};

describe('Test requestsReducer', () => {
it(`should update state with request data when admin request is successful`, () => {
const action = success(CREATE_ADMIN_REQUEST_SUCCESS, requestsReponse);
const newState = requestsReducer(initialState, action);
expect(newState.success).toEqual(true);
expect(newState.request.data.request).toEqual(requestsReponse.data.request);
});

it(`should update state with error when admin request is not successful`, () => {
const action = isErrored(CREATE_ADMIN_REQUEST_ERROR, errorResponse);
const newState = requestsReducer(initialState, action);
expect(newState.success).toEqual(false);
expect(newState.error).toEqual(errorResponse);
});
});
3 changes: 3 additions & 0 deletions client/src/utils/errorFormatter.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"You have made a request with the same type.": "This request has been made by you"
}
4 changes: 4 additions & 0 deletions client/styles/components/_navbar.scss
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,10 @@
color: #ffffff;
}

.admin-request-btn:focus {
background-color: #78a4de !important;
}


.admin-request-btn {
.material-icons {
Expand Down
Loading

0 comments on commit a054973

Please sign in to comment.