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


