
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