Backend DevelopmentFrontend Development

Building a Blog With Reactjs And Laravel Part10: Admin Finish

Building a Blog With Reactjs And Laravel Admin Finish

In this article of this series i will finish the missing pieces of the React admin panel, those include breadcrumb, user profile, and dashboard home page.

 

 

 

Breadcrumb

The first thing i will fix is the breadcrumbs, remember that in previous lessons in that series we added a component for breadcrumb but that component is not dynamic in terms it should display links depending on current page. You can also use a custom npm package for breadcrumbs but for simplicity i modified the breadcrumb component to be dynamic.

resources/js/admin/components/partials/Breadcrumb.js

import React from 'react';
import { withRouter } from "react-router";
import BreadcrumbItem from './BreadcrumbItem';

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

    componentDidMount()
    {

    }

    prepareLinks() {
        let links = [{
            text: 'Dashboard',
            url: '/',
            icon: 'fa fa-dashboard'
        }];

        if(this.props.location.pathname != '/') {

            // split pathname using '/'
            let parts = this.props.location.pathname.split('/');

            // filter parts to exclude empty and numeric parts
            parts = parts.filter(val => val != "" && isNaN(val));

            // loop through parts and push in the links array
            for(let i=0; i<parts.length; i++) {
                if (i == parts.length - 1) {
                    links.push({
                        text: parts[i].replace(/^\w/, c => c.toUpperCase()),
                        url: '#'
                    });
                } else {
                    links.push({
                        text: parts[i].replace(/^\w/, c => c.toUpperCase()),
                        url: '/' + parts[i]
                    });
                }
            }
        }

        return links;
    }

    render()
    {
        return (
                <ol className="breadcrumb">
                    {this.prepareLinks().map((link, index) => <BreadcrumbItem key={index} link={link} is_active={index===link.length-1}/>)}
                </ol>
        )
    }
}

export default withRouter(Breadcrumb);

resources/js/admin/components/partials/BreadcrumbItem.js

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

const BreadcrumbItem = (props) => {

    return (
        <li className={props.is_active?'active':''}>
            <Link to={props.link.url}>{
                props.link.icon? (<i className={props.link.icon}></i>):null
            }
                {props.link.text}
                </Link>
        </li>
    )
};

export default BreadcrumbItem;

In the above code i added a function prepareLinks() to return the links for the current page starting from the root link which is the dashboard. The function splits pathname sub property in location property by / then we loop through the resulting array and push it through the main array then we display it using <BreadcrumbItem/> component.



Profile

To enable the user to view his profile and update i added another component but first let’s modify the UsersController.php add this method:

app/Http/Controllers/UsersController.php

public function updateProfile(Request $request)
    {
        $user = auth("api")->user();

        $this->validate($request, [
            'name' => 'required|unique:users,name,'.$user->id,
            'email' => 'required|email|unique:users,email,'.$user->id,
            'password' => ($request->password!=''?'min:6':''),
        ]);

        $user->name = $request->name;
        $user->email = $request->email;

        if($request->has('password') && !empty($request->password)) {
            $user->password = bcrypt($request->password);
        }

        $user->save();

        return response()->json(['data' => $user, 'message' => 'Profile updated successfully'], 200);
    }

Update routes/api.php

<?php

use Illuminate\Http\Request;

/*
|--------------------------------------------------------------------------
| API Routes
|--------------------------------------------------------------------------
|
| Here is where you can register API routes for your application. These
| routes are loaded by the RouteServiceProvider within a group which
| is assigned the "api" middleware group. Enjoy building your API!
|
*/

Route::middleware('auth:api')->get('/user', function (Request $request) {
    return $request->user();
});

Route::post('login', 'Auth\\LoginController@login')->name('login');
Route::post('register', 'Auth\\RegisterController@register')->name('register');

Route::get('logout', 'Auth\\LoginController@logout')->name('logout');

Route::get('check-auth', 'Auth\\LoginController@checkAuth')->name('logout');

Route::resource('categories', 'CategoryController');

Route::resource('posts', 'PostsController');

Route::resource('tags', 'TagsController');

Route::resource('comments', 'CommentsController');

Route::get('profile', 'UsersController@profile');
Route::post('profile/update', 'UsersController@updateProfile');
Route::resource('users', 'UsersController');

Update 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")}});
    },
    profile: () => {
        return axios.get('/profile', {headers: {Authorization: 'Bearer ' + localStorage.getItem("user.api_token")}});
    },
    updateProfile: (payload) => {
        return axios.post('/profile/update', payload, {headers: {Authorization: 'Bearer ' + localStorage.getItem("user.api_token")}});
    },
};

export default User;

Create a new directory profile/ inside of resources/js/admin/components/pages/ then add this component Profile.js

resources/js/admin/components/pages/profile/Profile.js

import React from 'react';
import Breadcrumb from '../../partials/Breadcrumb';
import SuccessAlert from '../../partials/SuccessAlert';
import ErrorAlert from '../../partials/ErrorAlert';
import User from '../../../apis/User';

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

        this.state = {
            name: "",
            email: "",
            password: "",
            success: "",
            error: "",
            validation_errors: {}
        };

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

    componentDidMount()
    {
        let self = this;

        User.profile().then(response => {
            self.setState({
               name: response.data.data.name,
               email: response.data.data.email
            });
        });
    }

    handleChange(e) {
        let inputName = e.target.name;

        this.setState({
            [inputName]: e.target.value
        });
    }

    handleSubmit(e) {
        e.preventDefault();

        let self = this;

        User.updateProfile({name: this.state.name,
            email: this.state.email,
            password: this.state.password}).then(response => {


            localStorage.removeItem("user.email");
            localStorage.removeItem("user.name");

            for (let i in response.data.data) {
                localStorage.setItem("user." + i, response.data.data[i]);
            }

            self.setState({
               success: response.data.message,
                error: "",
                validation_errors: {}
            });
        }).catch(error => {
            self.setState({
                success: "",
                error: error.response.data.message,
                validation_errors: error.response.data.errors
            });
        });
    }

    render()
    {
        return (
            <div className="content-wrapper">

                <section className="content-header">
                    <h1>
                        My profile
                    </h1>

                    <Breadcrumb />

                </section>

                <section className="content">
                    <div className="row">
                        <div className="col-md-3">
                            <div className="box box-primary">
                                <div className="box-body box-profile">
                                    <img src={process.env.MIX_APP_URL + 'assets/admin/dist/img/avatar04.png'} className="profile-user-img img-responsive img-circle" />
                                    <h3 className="profile-username text-center">{localStorage.getItem("user.name")}</h3>
                                    <p className="text-muted text-center">Member since {localStorage.getItem("user.created_at")}</p>
                                    <ul className="list-group list-group-unbordered">
                                        <li className="list-group-item">
                                            <b>Name</b> <a className="pull-right">{localStorage.getItem("user.name")}</a>
                                        </li>
                                        <li className="list-group-item">
                                            <b>Email</b> <a className="pull-right">{localStorage.getItem("user.email")}</a>
                                        </li>
                                        <li className="list-group-item">
                                            <b>Created</b> <a className="pull-right">{localStorage.getItem("user.created_at")}</a>
                                        </li>
                                    </ul>
                                </div>
                            </div>
                        </div>

                        <div className="col-md-9">
                            <SuccessAlert msg={this.state.success} />
                            <ErrorAlert msg={this.state.error} />
                            <form className="form-horizontal" onSubmit={this.handleSubmit}>
                                <div className={`form-group ${this.state.validation_errors.name?'has-error':''}`}>
                                    <label className="col-sm-2 control-label">Name</label>

                                    <div className="col-sm-10">
                                        <input type="text" className="form-control" name="name" placeholder="Name" value={this.state.name} onChange={this.handleChange} />
                                    </div>
                                    {
                                        this.state.validation_errors.name!=null?(<div className="help-block">{this.state.validation_errors.name[0]}</div>):null
                                    }
                                </div>
                                <div className={`form-group ${this.state.validation_errors.email?'has-error':''}`}>
                                    <label className="col-sm-2 control-label">Email</label>

                                    <div className="col-sm-10">
                                        <input type="email" className="form-control" name="email" placeholder="Email" value={this.state.email} onChange={this.handleChange} />
                                    </div>
                                    {
                                        this.state.validation_errors.email!=null?(<div className="help-block">{this.state.validation_errors.email[0]}</div>):null
                                    }
                                </div>
                                <div className={`form-group ${this.state.validation_errors.password?'has-error':''}`}>
                                    <label className="col-sm-2 control-label">Password</label>

                                    <div className="col-sm-10">
                                        <input type="password" className="form-control" name="password" placeholder="Password" value={this.state.password} onChange={this.handleChange} />
                                    </div>
                                    {
                                        this.state.validation_errors.password!=null?(<div className="help-block">{this.state.validation_errors.password[0]}</div>):null
                                    }
                                </div>
                                <div className="form-group">
                                    <div className="col-sm-offset-2 col-sm-10">
                                        <button type="submit" className="btn btn-danger">Submit</button>
                                    </div>
                                </div>
                            </form>
                        </div>
                    </div>
                </section>
            </div>
        );
    }
}

export default Profile;



Add new route in resources/js/admin/Routes.js

import React from 'react';
import {
    HashRouter as Router,
    Link,
    Route,
    Switch
} from 'react-router-dom';
import Login from "./components/login/Login";
import AuthenticatedRoute from './AuthenticatedRoute';
import Dashboard from "./components/pages/Dashboard";
import ListPosts from "./components/pages/posts/Index";
import AddPosts from "./components/pages/posts/Add";
import EditPosts from "./components/pages/posts/Edit";
import ListCategories from "./components/pages/categories/Index";
import AddCategories from "./components/pages/categories/Add";
import EditCategories from "./components/pages/categories/Edit";
import ListTags from "./components/pages/tags/Index";
import AddTags from "./components/pages/tags/Add";
import EditTags from "./components/pages/tags/Edit";
import ListComments from "./components/pages/comments/Index";
import ListUsers from "./components/pages/users/Index";
import AddUsers from "./components/pages/users/Add";
import EditUsers from "./components/pages/users/Edit";
import Profile from "./components/pages/profile/Profile";

class Routes extends React.Component
{
    render()
    {
        return (
            <Switch>
                <Route exact path='/login' component={Login} />
                <AuthenticatedRoute exact path='/' component={Dashboard} />
                <AuthenticatedRoute exact path='/posts' component={ListPosts} />
                <AuthenticatedRoute path='/posts/add' component={AddPosts} />
                <AuthenticatedRoute path='/posts/edit/:id' component={EditPosts} />
                <AuthenticatedRoute exact path='/tags' component={ListTags} />
                <AuthenticatedRoute path='/tags/add' component={AddTags} />
                <AuthenticatedRoute path='/tags/edit/:id' component={EditTags} />
                <AuthenticatedRoute exact path='/categories' component={ListCategories} />
                <AuthenticatedRoute path='/categories/add' component={AddCategories} />
                <AuthenticatedRoute path='/categories/edit/:id' component={EditCategories} />
                <AuthenticatedRoute exact path='/comments' component={ListComments} />
                <AuthenticatedRoute exact path='/users' component={ListUsers} />
                <AuthenticatedRoute path='/users/add' component={AddUsers} />
                <AuthenticatedRoute path='/users/edit/:id' component={EditUsers} />
                <AuthenticatedRoute path='/profile' component={Profile} />
            </Switch>
        )
    }
}

export default Routes;

Update resources/js/admin/components/partials/Header.js

import React from 'react';
import { withRouter } from "react-router";
import { Link } from "react-router-dom";
import Auth from '../../apis/Auth';

class Header extends React.Component {

    constructor(props)
    {
        super(props);

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

    handleLogout(e) {
        e.preventDefault();

        Auth.logout((response) => {
            this.props.history.push("/login");
        }, (err) => {
            alert(err.response.data.message);
        });
    }

    componentDidMount()
    {
        const checkauth = setInterval(() => {
            Auth.checkAuth((response) => {}, (err) => {
                clearInterval(checkauth);

                localStorage.clear();

                this.props.history.push("/login");
            });
        }, 2000);
    }

    render() {
        return this.props.location.pathname != '/login' ? (

            <header className="main-header">
                <a href="#" className="logo">
                    <span className="logo-mini"><b>B</b>RL</span>
                    <span className="logo-lg"><b>Blog</b>RL</span>
                </a>
                <nav className="navbar navbar-static-top">
                    <a href="#" className="sidebar-toggle" data-toggle="push-menu" role="button">
                        <span className="sr-only">Toggle navigation</span>
                    </a>

                    <div className="navbar-custom-menu">
                        <ul className="nav navbar-nav">

                            <li className="dropdown user user-menu">
                                <a href="#" className="dropdown-toggle" data-toggle="dropdown">
                                    <img src={process.env.MIX_APP_URL + 'assets/admin/dist/img/avatar04.png'}
                                         className="user-image" alt="User Image"/>
                                    <span className="hidden-xs">{localStorage.getItem("user.name")}</span>
                                </a>
                                <ul className="dropdown-menu">
                                    <li className="user-header">
                                        <img src={process.env.MIX_APP_URL + 'assets/admin/dist/img/avatar04.png'}
                                             className="img-circle" alt="User Image"/>

                                        <p>
                                            {localStorage.getItem("user.name")}
                                            <small>Member since {localStorage.getItem("user.created_at")}</small>
                                        </p>
                                    </li>
                                    <li className="user-footer">
                                        <div className="pull-left">
                                            <Link to='/profile' className="btn btn-default btn-flat">Profile</Link>
                                        </div>
                                        <div className="pull-right">
                                            <a href="#" onClick={this.handleLogout}
                                               className="btn btn-default btn-flat">Sign out</a>
                                        </div>
                                    </li>
                                </ul>
                            </li>
                        </ul>
                    </div>
                </nav>
            </header>
        ) : null
    }
}

export default withRouter(Header)



Dashboard Home

I modified the dashboard component to include some links to other pages

resources/js/admin/components/pages/Dashboard.js

import React, { Component } from 'react';
import Breadcrumb from '../partials/Breadcrumb';
import { Link } from 'react-router-dom';

class Dashboard extends Component
{
    constructor(props)
    {
       super(props);

        document.body.classList.remove("login-page");
        document.body.classList.add("skin-green");
    }

    render() {
        return (
            <div className="content-wrapper">

                <section className="content-header">
                    <h1>
                        Dashboard
                        <small>Control panel</small>
                    </h1>

                    <Breadcrumb />

                </section>

                <section className="content">
                    <div className="row">
                        <div className="col-lg-3 col-xs-6">
                            <div className="small-box bg-aqua">
                                <div className="inner">
                                    <h3>Posts</h3>
                                </div>
                                <div className="icon">
                                    <i className="fa fa-newspaper-o"></i>
                                </div>
                                <Link to="/posts" className="small-box-footer">More info <i className="fa fa-arrow-circle-right"></i></Link>
                            </div>
                        </div>
                        <div className="col-lg-3 col-xs-6">
                            <div className="small-box bg-green">
                                <div className="inner">
                                    <h3>Categories</h3>

                                    <p></p>
                                </div>
                                <div className="icon">
                                    <i className="fa fa-list"></i>
                                </div>
                                <Link to="/categories" className="small-box-footer">More info <i className="fa fa-arrow-circle-right"></i></Link>
                            </div>
                        </div>
                        <div className="col-lg-3 col-xs-6">
                            <div className="small-box bg-yellow">
                                <div className="inner">
                                    <h3>Comments</h3>

                                    <p></p>
                                </div>
                                <div className="icon">
                                    <i className="fa fa-comments-o"></i>
                                </div>
                                <Link to="/comments" className="small-box-footer">More info <i className="fa fa-arrow-circle-right"></i></Link>
                            </div>
                        </div>
                        <div className="col-lg-3 col-xs-6">
                            <div className="small-box bg-red">
                                <div className="inner">
                                    <h3>Users</h3>

                                    <p></p>
                                </div>
                                <div className="icon">
                                    <i className="fa fa-users"></i>
                                </div>
                                <Link to="/users" className="small-box-footer">More info <i className="fa fa-arrow-circle-right"></i></Link>
                            </div>
                        </div>
                    </div>
                </section>
            </div>
        )
    }
}

export default Dashboard;

 

 

Continue to part11: Website Start

 

1 1 vote
Article Rating

What's your reaction?

Excited
0
Happy
0
Not Sure
0
Confused
1

You may also like

Subscribe
Notify of
guest

0 Comments
Oldest
Newest Most Voted
Inline Feedbacks
View all comments