Backend DevelopmentFrontend Development

Building a Blog With Reactjs And Laravel Part9: Admin Users

Building a Blog With Reactjs And Laravel Admin Users

In this article of this series i will implement and finish the users CRUD to the React admin panel to enable admin to create, update and delete users.

 

 

 

Continuing from the previous article when we finished comments, in this tutorial i will take the same approach like we did in previous tutorials to finish users CRUD without going into too much details so at first create api file User.js in resources/js/admin/apis/

resources/js/admin/apis/User.js

import axios from "axios";

const User = {
    list: (page = 1) => {
        return axios.get('/users?page=' + page, {headers: {Authorization: 'Bearer ' + localStorage.getItem("user.api_token")}});
    },
    add: (payload) => {
        return axios.post('/users', payload, {headers: {Authorization: 'Bearer ' + localStorage.getItem("user.api_token")}});
    },
    showOne: (id) => {
        return axios.get('/users/' + id, {headers: {Authorization: 'Bearer ' + localStorage.getItem("user.api_token")}});
    },
    edit: (payload, id) => {
        return axios.put('/users/' + id, payload, {headers: {Authorization: 'Bearer ' + localStorage.getItem("user.api_token")}});
    },
    remove: (id) => {
        return axios.delete('/users/' + id, {headers: {Authorization: 'Bearer ' + localStorage.getItem("user.api_token")}});
    }
};

export default User;

This is the api file for users, next let’s add the store.

 

Preparing Store

Open resources/js/admin/store/actionTypes/UserTypes.js and add this code:

export const LIST_USERS = 'LIST_USERS';
export const LIST_USERS_SUCCESS = 'LIST_USERS_SUCCESS';
export const LIST_USERS_FAILURE = 'LIST_USERS_FAILURE';
export const CREATE_USERS = 'CREATE_USERS';
export const CREATE_USERS_SUCCESS = 'CREATE_USERS_SUCCESS';
export const CREATE_USERS_FAILURE = 'CREATE_USERS_FAILURE';
export const EDIT_USERS = 'EDIT_USERS';
export const EDIT_USERS_SUCCESS = 'EDIT_USERS_SUCCESS';
export const EDIT_USERS_FAILURE = 'EDIT_USERS_FAILURE';
export const DELETE_USERS = 'DELETE_USERS';
export const DELETE_USERS_SUCCESS = 'DELETE_USERS_SUCCESS';
export const DELETE_USERS_FAILURE = 'DELETE_USERS_FAILURE';
export const SHOW_USER = 'SHOW_USER';
export const SHOW_USER_SUCCESS = 'SHOW_USER_SUCCESS';
export const SHOW_USER_FAILURE = 'SHOW_USER_FAILURE';
export const HANDLE_USER_CHANGE = 'HANDLE_USER_CHANGE';
export const SET_USER_DEFAULTS = 'SET_USER_DEFAULTS';
export const RESET_USER_FIELDS = 'RESET_USER_FIELDS';

The above code is the action types for users to deal with every action and to enable us to manipulate the reducer.

Redux Actions

Update resources/js/admin/store/actions/UserActions.js with this code:

import * as UserTypes from '../actionTypes/UserTypes';

import User from '../../apis/User';


/**
 * set user defaults
 */
function setUserDefaults() {

    return function (dispatch, getState) {

        dispatch({
            type: UserTypes.SET_USER_DEFAULTS
        });
    }
}

/**
 * list Users action
 */
function listUsers(page = 1) {

    return function (dispatch, getState) {

        dispatch({
            type: UserTypes.LIST_USERS
        });


        User.list(page).then(response => {
            dispatch({
                type: UserTypes.LIST_USERS_SUCCESS,
                data: response.data.data
            });
        }).catch(error => {
            dispatch({
                type: UserTypes.LIST_USERS_FAILURE,
                error: error.response.data
            });
        });
    }
}

/**
 * add user action
 */
function addUser (title, cb) {

    return function(dispatch, getState) {

        dispatch({
            type: UserTypes.CREATE_USERS
        });

        User.add(title).then(response => {
            dispatch({
                type: UserTypes.CREATE_USERS_SUCCESS,
                data: response.data
            });

            cb();
        }).catch(error => {
            dispatch({
                type: UserTypes.CREATE_USERS_FAILURE,
                error: error.response.data
            })
        });
    }
}

/**
 * show user action
 */
function showUser(id)
{
    return function (dispatch, getState) {

        dispatch({
            type: UserTypes.SHOW_USER
        });

        User.showOne(id).then(response => {
            dispatch({
                type: UserTypes.SHOW_USER_SUCCESS,
                data: response.data
            });

        }).catch(error => {
            dispatch({
                type: UserTypes.SHOW_USER_FAILURE,
                error: error.response.data
            });
        });
    }
}

/**
 * edit user action
 */
function editUser(payload, id, cb)
{
    return function (dispatch, getState) {

        dispatch({
            type: UserTypes.EDIT_USERS
        });

        User.edit(payload, id).then(response => {
            dispatch({
                type: UserTypes.EDIT_USERS_SUCCESS,
                data: response.data
            });

            cb();
        }).catch(error => {
            dispatch({
                type: UserTypes.EDIT_USERS_FAILURE,
                error: error.response.data
            })
        });
    }
}

/**
 * delete user action
 */
function deleteUser(id)
{
    return function (dispatch, getState) {


        dispatch({
            type: UserTypes.DELETE_USERS
        });


        User.remove(id).then(response => {
            dispatch({
                type: UserTypes.DELETE_USERS_SUCCESS,
                message: response.data.message,
                id: id
            });
        }).catch(error => {
            dispatch({
                type: UserTypes.DELETE_USERS_FAILURE,
                error: error.response.data
            })
        });
    }
}

/**
 * handle user change
 *
 * fires on any field change of the user object
 */
function handleUserChange(field, value, checked) {

    return function (dispatch, getState) {

        dispatch({
            type: UserTypes.HANDLE_USER_CHANGE,
            data: value,
            field,
            checked
        });
    }
}

/**
 * reset user fields action
 */
function resetUserFields() {

    return function (dispatch, getState) {

        dispatch({
            type: UserTypes.RESET_USER_FIELDS
        });
    }
}

export {
    setUserDefaults,
    listUsers,
    addUser,
    showUser,
    editUser,
    deleteUser,
    handleUserChange,
    resetUserFields
};

 

Redux Reducer

Update resources/js/admin/store/reducers/UserReducer.js with this code:

import * as UserTypes from '../actionTypes/UserTypes';

const initialState = {
    users: {},
    user: {
        id: "",
        name: "",
        email: "",
        password: "",
        is_admin: 0
    },
    success_message: "",
    error_message: "",
    validation_errors: {},
    list_spinner: false,
    create_update_spinner: false
};

const userReducer = function (state = initialState, action) {
    switch (action.type) {
        case UserTypes.SET_USER_DEFAULTS:
            return {
                ...state,
                user: {...state.user},
                success_message: "",
                error_message: "",
                validation_errors: {},
                list_spinner: false,
                create_update_spinner: false
            };
        case UserTypes.LIST_USERS:
            return {
                ...state,
                list_spinner: true
            };
        case UserTypes.LIST_USERS_SUCCESS:
            return {
                ...state,
                users: action.data,
                list_spinner: false
            };
        case UserTypes.LIST_USERS_FAILURE:
            return {
                ...state,
                error_message: action.error,
                list_spinner: false
            };
        case UserTypes.CREATE_USERS:
            return {
                ...state,
                create_update_spinner: true
            };
        case UserTypes.CREATE_USERS_SUCCESS:
            return {
                ...state,
                create_update_spinner: false,
                user: action.data.data,
                success_message: action.data.message,
                error_message: "",
                validation_errors: {}
            };
        case UserTypes.CREATE_USERS_FAILURE:
            return {
                ...state,
                create_update_spinner: false,
                error_message: action.error.message,
                validation_errors: action.error.errors,
                success_message: ""
            };
        case UserTypes.SHOW_USER:
            return {
                ...state,
                create_update_spinner: true
            };
        case UserTypes.SHOW_USER_SUCCESS:
            return {
                ...state,
                create_update_spinner: false,
                user: {...action.data.data, password: ""}
            };
        case UserTypes.SHOW_USER_FAILURE:
            return {
                ...state,
                create_update_spinner: false,
                error_message: action.error.message
            };
        case UserTypes.EDIT_USERS:
            return {
                ...state,
                create_update_spinner: true
            };
        case UserTypes.EDIT_USERS_SUCCESS:
            return {
                ...state,
                create_update_spinner: false,
                user: action.data.data,
                success_message: action.data.message,
                error_message: "",
                validation_errors: {}
            };
        case UserTypes.EDIT_USERS_FAILURE:
            return {
                ...state,
                create_update_spinner: false,
                error_message: action.error.message,
                validation_errors: action.error.errors,
                success_message: ""
            };
        case UserTypes.DELETE_USERS:
            return {
                ...state,
                list_spinner: true
            };
        case UserTypes.DELETE_USERS_SUCCESS:
            let users = state.users;
            users.data = state.users.data.filter(item => item.id != action.id);

            return {
                ...state,
                list_spinner: false,
                users: users,
                success_message: action.message,
                error_message: ''
            };
        case UserTypes.DELETE_USERS_FAILURE:
            return {
                ...state,
                list_spinner: false,
                error_message: action.error.message,
                success_message: ''
            };
        case UserTypes.RESET_USER_FIELDS:
            return {
                ...state,
                user: {
                    id: "",
                    name: "",
                    email: "",
                    password: "",
                    is_admin: 0
                }
            };
        case UserTypes.HANDLE_USER_CHANGE:
            return handleChange(state, action);
        default:
            return state;
    }
};

/**
 * handle field change
 */
function handleChange(state, action)
{
    if(action.field !== 'is_admin') {
        return {
            ...state,
            user: {...state.user, [action.field]: action.data}
        };
    } else {
        let checked = state.user.is_admin;

        if(action.checked == true) {
            checked = 1;
        } else if(action.checked == false) {
            checked = 0;
        }

        return {
            ...state,
            user: {...state.user, is_admin: checked}
        };
    }
}

export default userReducer;

Then update the root reducer to include the user store in resources/js/admin/store/reducers/RootReducer.js

import { combineReducers } from 'redux';

import categoryReducer  from './CategoryReducer';
import tagReducer  from './TagReducer';
import postReducer from './PostReducer';
import commentReducer from './CommentReducer';
import userReducer from './UserReducer';

const rootReducer = combineReducers({
   category: categoryReducer,
   tag: tagReducer,
   post: postReducer,
   comment: commentReducer,
   user: userReducer
});

export default rootReducer;

Now we can access users using props using this.props.user.<property>. In the next we will update components to reflect the new state object in user components.

 

Updating Components

Displaying & removing users

Open and update resources/js/admin/components/pages/users/Index.js

import React from 'react';
import { connect } from 'react-redux';
import Breadcrumb from '../../partials/Breadcrumb';
import { listUsers, setUserDefaults } from '../../../store/actions/UserActions';
import Spinner from '../../partials/Spinner';
import Row from './Row';
import { Link } from 'react-router-dom';
import Pagination from '../../partials/Pagination';
import SuccessAlert from '../../partials/SuccessAlert';
import ErrorAlert from '../../partials/ErrorAlert';

class Index extends React.Component
{
    constructor(props)
    {
        super(props);
    }

    componentDidMount() {
        this.props.setUserDefaults();

        this.props.listUsers(1);
    }

    render()
    {
        return (
            <div className="content-wrapper">
                <section className="content-header">
                    <h1>
                        Users
                    </h1>

                    <Breadcrumb />

                </section>

                <section className="content">
                    <div className="row">
                        <div className="col-md-12">
                            <div className="box">
                                <div className="box-header">
                                    <h3 className="box-title">All users</h3>

                                    <Link to='/users/add' className="btn btn-primary pull-right">Add <i className="fa fa-plus"></i></Link>
                                </div>

                                <div className="box-body">
                                    <Spinner show={this.props.user.list_spinner}/>

                                    <SuccessAlert msg={this.props.user.success_message}/>
                                    <ErrorAlert msg={this.props.user.error_message}/>

                                    <table className="table">
                                        <thead>
                                        <tr>
                                            <th>#</th>
                                            <th>Name</th>
                                            <th>Email</th>
                                            <th>Admin</th>
                                            <th>Created date</th>
                                            <th width="15%">Actions</th>
                                        </tr>
                                        </thead>
                                        <tbody>
                                        {
                                            this.props.user.users.data?(
                                                this.props.user.users.data.map(item => <Row key={item.id} user={item} />)
                                            ):null
                                        }
                                        </tbody>
                                    </table>
                                </div>

                                <Pagination data={this.props.user.users} onclick={this.props.listUsers.bind(this)} />

                            </div>
                        </div>
                    </div>
                </section>
            </div>
        );
    }
}

const mapStateToProps = (state, ownProps) => {

    return {
        user: state.user
    };
};

const mapDispatchToProps = (dispatch) => {
    return {
        listUsers: (page) => dispatch(listUsers(page)),
        setUserDefaults: () => dispatch(setUserDefaults())
    }
};

export default connect(mapStateToProps, mapDispatchToProps)(Index);

Create resources/js/admin/components/pages/users/Row.js with this code:

import React from 'react';
import { Link } from 'react-router-dom';
import { connect } from 'react-redux';
import { deleteUser } from '../../../store/actions/UserActions';

class Row extends React.Component {

    constructor(props)
    {
        super(props);

        this.handleDelete = this.handleDelete.bind(this);
    }

    handleDelete(e) {
        e.preventDefault();

        if(confirm("Are you sure?")) {
            this.props.deleteUser(this.props.user.id);
        }
    }

    render()
    {
        return (
            <tr>
                <td>{this.props.user.id}</td>
                <td>{this.props.user.name}</td>
                <td>
                    {this.props.user.email}
                </td>
                <td>{this.props.user.is_admin==1?'Yes':'No'}</td>
                <td>{this.props.user.created_at}</td>
                <td>
                    <Link to={'/users/edit/' + this.props.user.id} className="btn btn-info btn-sm"><i
                        className="fa fa-edit"></i></Link>
                    <a href="#" className="btn btn-danger btn-sm" onClick={this.handleDelete}><i
                        className="fa fa-remove"></i></a>
                </td>
            </tr>
        )
    }
};

const mapDispatchToProps = (dispatch) => {
    return {
        deleteUser: (id) => dispatch(deleteUser(id))
    }
};

export default connect(null, mapDispatchToProps)(Row);

Creating users

Update resources/js/admin/components/pages/users/Add.js

import React from 'react';
import {connect} from 'react-redux';
import { Link } from 'react-router-dom';

// partials
import Breadcrumb from '../../partials/Breadcrumb';
import UserForm from './UserForm';

// actions
import { handleUserChange, addUser, setUserDefaults, resetUserFields } from '../../../store/actions/UserActions';


class Add extends React.Component
{
    constructor(props)
    {
        super(props);

        this.handleUserChange = this.handleUserChange.bind(this);

        this.handleSubmit = this.handleSubmit.bind(this);
    }

    componentDidMount()
    {
        this.props.setUserDefaults();

        this.props.resetUserFields();
    }

    handleUserChange(e) {
        if(e.target.name == 'is_admin') {
            this.props.handleUserChange(e.target.name, e.target.value, e.target.checked);
        } else {
            this.props.handleUserChange(e.target.name, e.target.value);
        }
    }

    handleSubmit(e) {
        e.preventDefault();
        let self = this;

        this.props.addUser(this.props.user.user, function () {

            // reset fields
            self.props.resetUserFields();

            // redirect
            setTimeout(() => self.props.history.push('/users'), 2000);
        });
    }

    render()
    {
        return (
            <div className="content-wrapper">
                <section className="content-header">
                    <h1>
                        Add user
                    </h1>

                    <Breadcrumb />

                </section>

                <section className="content">
                    <div className="row">
                        <div className="col-md-12">
                            <div className="box box-warning">
                                <div className="box-header with-border">
                                    <h3 className="box-title">Add user</h3>

                                    <Link to='/users' className="btn btn-warning btn-sm pull-right"><i className="fa fa-arrow-left"></i> Return back</Link>
                                </div>
                                <form role="form" method="post" onSubmit={this.handleSubmit}>

                                    <div className="box-body">
                                        <UserForm user={this.props.user.user} create_update_spinner={this.props.user.create_update_spinner}
                                                  success_message={this.props.user.success_message} error_message={this.props.user.error_message}
                                                  handleUserChange={this.handleUserChange}
                                                  validation_errors={this.props.user.validation_errors} />
                                    </div>
                                    <div className="box-footer">
                                        <button type="submit" className="btn btn-success">Submit</button>
                                    </div>
                                </form>
                            </div>
                        </div>
                    </div>
                </section>
            </div>
        );
    }
}

const mapStateToProps = (state, ownProps) => {
    return {
        user: state.user
    };
};

const mapDispatchToProps = (dispatch) => {
    return {
        handleUserChange: (field, value, checked = null) => dispatch(handleUserChange(field, value, checked)),
        addUser: (payload, cb) => dispatch(addUser(payload, cb)),
        setUserDefaults: () => dispatch(setUserDefaults()),
        resetUserFields: () => dispatch(resetUserFields())
    };
};

export default connect(mapStateToProps, mapDispatchToProps)(Add);

Updating users

Update resources/js/admin/components/pages/users/Edit.js

import React from 'react';
import {connect} from 'react-redux';
import { Link } from 'react-router-dom';

// partials
import Breadcrumb from '../../partials/Breadcrumb';
import UserForm from './UserForm';

// actions
import { handleUserChange, showUser, editUser, setUserDefaults, resetUserFields } from '../../../store/actions/UserActions';


class Edit extends React.Component
{
    constructor(props)
    {
        super(props);

        this.handleUserChange = this.handleUserChange.bind(this);

        this.handleSubmit = this.handleSubmit.bind(this);
    }

    componentDidMount()
    {
        this.props.setUserDefaults();

        this.props.resetUserFields();

        this.props.showUser(this.props.match.params.id);
    }

    handleUserChange(e) {
        if(e.target.name == 'is_admin') {
            this.props.handleUserChange(e.target.name, e.target.value, e.target.checked);
        } else {
            this.props.handleUserChange(e.target.name, e.target.value);
        }
    }

    handleSubmit(e) {
        e.preventDefault();
        let self = this;

        this.props.editUser(this.props.user.user, this.props.match.params.id, function () {

            // reset fields
            self.props.resetUserFields();

            // redirect
            setTimeout(() => self.props.history.push('/users'), 2000);
        });
    }

    render()
    {
        return (
            <div className="content-wrapper">
                <section className="content-header">
                    <h1>
                        Edit user
                    </h1>

                    <Breadcrumb />

                </section>

                <section className="content">
                    <div className="row">
                        <div className="col-md-12">
                            <div className="box box-warning">
                                <div className="box-header with-border">
                                    <h3 className="box-title">Edit user #{this.props.match.params.id}</h3>

                                    <Link to='/users' className="btn btn-warning btn-sm pull-right"><i className="fa fa-arrow-left"></i> Return back</Link>
                                </div>
                                <form role="form" method="post" onSubmit={this.handleSubmit}>

                                    <div className="box-body">
                                        <UserForm user={this.props.user.user} create_update_spinner={this.props.user.create_update_spinner}
                                                  success_message={this.props.user.success_message} error_message={this.props.user.error_message}
                                                  handleUserChange={this.handleUserChange}
                                                  validation_errors={this.props.user.validation_errors} />
                                    </div>
                                    <div className="box-footer">
                                        <button type="submit" className="btn btn-success">Submit</button>
                                    </div>
                                </form>
                            </div>
                        </div>
                    </div>
                </section>
            </div>
        );
    }
}

const mapStateToProps = (state, ownProps) => {
    return {
        user: state.user
    };
};

const mapDispatchToProps = (dispatch) => {
    return {
        showUser: (id) => dispatch(showUser(id)),
        handleUserChange: (field, value, checked = null) => dispatch(handleUserChange(field, value, checked)),
        editUser: (payload, id, cb) => dispatch(editUser(payload, id, cb)),
        setUserDefaults: () => dispatch(setUserDefaults()),
        resetUserFields: () => dispatch(resetUserFields())
    };
};

export default connect(mapStateToProps, mapDispatchToProps)(Edit);

Create partial component resources/js/admin/components/pages/users/UserForm.js with the below code:

import React from 'react';
import Spinner from '../../partials/Spinner';
import SuccessAlert from '../../partials/SuccessAlert';
import ErrorAlert from '../../partials/ErrorAlert';

class Form extends React.Component
{
    constructor(props)
    {
        super(props);
    }

    render()
    {
        return (
            <div className="row">

                <Spinner show={this.props.create_update_spinner}/>
                <SuccessAlert msg={this.props.success_message}/>
                <ErrorAlert msg={this.props.error_message}/>

                <div className="col-md-6">
                    <div className={`form-group ${this.props.validation_errors.name?'has-error':''}`}>
                        <label>Name</label>
                        <input type="text" className="form-control" placeholder="User name" onChange={this.props.handleUserChange} value={this.props.user.name?this.props.user.name:''} name="name" />
                        {
                            this.props.validation_errors.name!=null?(<div className="help-block">{this.props.validation_errors.name[0]}</div>):null
                        }
                    </div>
                </div>

                <div className="col-md-6">
                    <div className={`form-group ${this.props.validation_errors.email?'has-error':''}`}>
                        <label>Email</label>
                        <input type="text" className="form-control" placeholder="User email" onChange={this.props.handleUserChange} value={this.props.user.email?this.props.user.email:''} name="email" />
                        {
                            this.props.validation_errors.email!=null?(<div className="help-block">{this.props.validation_errors.email[0]}</div>):null
                        }
                    </div>
                </div>

                <div className="col-md-6">
                    <div className={`form-group ${this.props.validation_errors.password?'has-error':''}`}>
                        <label>Password</label>
                        <input type="password" className="form-control" placeholder="Password" onChange={this.props.handleUserChange} value={this.props.user.password?this.props.user.password:''} name="password" />
                        {
                            this.props.validation_errors.password!=null?(<div className="help-block">{this.props.validation_errors.password[0]}</div>):null
                        }
                    </div>
                </div>

                <div className="col-md-6">
                    <div className='form-group'>
                        <div className="checkbox">
                            <label>
                                <input type="checkbox" onChange={this.props.handleUserChange} value="1" checked={this.props.user.is_admin==1} name="is_admin" />
                                Is Admin
                            </label>
                        </div>
                    </div>
                </div>

            </div>
        )
    }
}

export default Form;

Now in the command line terminal run

npm run dev

Login and reload the page you should see the users when navigating to it.

 

 

Continue to part10: Admin Finish

 

 

5 1 vote
Article Rating

What's your reaction?

Excited
0
Happy
0
Not Sure
0
Confused
0

You may also like

Subscribe
Notify of
guest

0 Comments
Oldest
Newest Most Voted
Inline Feedbacks
View all comments