
This is the last article in this series and we will complete the remaining pieces such as the registration, login and commenting functionality.
Authentication
Before creating the comments functionality we need to create the authentication functionality so we have to enable the user to register and login.
Create files Auth.js and Comment.js in resources/js/website/apis/ and update them as shown below:
Auth.js
import axios from "axios"; const Auth = { login: (data, successCb, failCb) => { axios.post('/login', data).then(response => { successCb(response); }).catch(err => { failCb(err); }); }, logout: (successCb, failCb) => { axios.get('/logout', {headers: {Authorization: 'Bearer ' + localStorage.getItem("user.api_token")}}) .then(response => { localStorage.clear(); successCb(response); }).catch(err => { failCb(err); alert(err.response.data.message); }); }, register: (data, successCb, failCb) => { axios.post('/register', data) .then(response => { successCb(response); }).catch(err => { failCb(err); }); } }; export default Auth;
Comment.js
import axios from 'axios'; const Comment = { store: (payload) => { return axios.post('/comments', payload, {headers: {Authorization: 'Bearer ' + localStorage.getItem("user.api_token")}}); } }; export default Comment;
Well, the next step is to update the register and login components we have done similar thing in the admin panel before.
Update resources/js/website/components/pages/Register.js as follows:
import React from 'react'; import Sidebar from '../partials/Sidebar'; import {Link} from "react-router-dom"; import Auth from '../../apis/Auth'; import { withRouter } from "react-router"; import "../../css/form.css"; class Register extends React.Component { constructor(props) { super(props); this.state = { name: "", email: "", password: "", error_message: null, errors: null }; this.handleSubmit = this.handleSubmit.bind(this); this.handleInput = this.handleInput.bind(this); } handleInput(e) { this.setState({ [e.target.name]: e.target.value }); } handleSubmit(e) { e.preventDefault(); this.setState({ error_message: null, errors: null }); Auth.register({name: this.state.name, email: this.state.email, password: this.state.password}, (response) => { for (var i in response.data.user) { localStorage.setItem("user." + i, response.data.user[i]); setTimeout(() => { this.props.history.push("/"); }, 500); } }, (err) => { this.setState({ error_message: err.response.data.message, errors: err.response.data.errors }); }); } render() { return ( <div id="content-wrap"> <div className="row"> <div id="main" className="eight columns"> <h2>Create account</h2> { this.state.error_message?(<div className="alert alert-danger">{this.state.error_message}</div>):null } <form name="contactForm" method="post" onSubmit={this.handleSubmit}> <fieldset> <div className="group"> <label>Username</label> <input name="name" type="text" onChange={this.handleInput} value={this.state.name} placeholder="Name" /> { this.state.errors && this.state.errors.name?(<div className="error-block">{this.state.errors.name[0]}</div>):null } </div> <div className="group"> <label>Email</label> <input name="email" type="text" onChange={this.handleInput} value={this.state.email} placeholder="Email" /> { this.state.errors && this.state.errors.email?(<div className="error-block">{this.state.errors.email[0]}</div>):null } </div> <div className="group"> <label>Password</label> <input name="password" type="password" onChange={this.handleInput} value={this.state.password} placeholder="Password" /> { this.state.errors && this.state.errors.password?(<div className="error-block">{this.state.errors.password[0]}</div>):null } </div> <button type="submit" className="submit">Register</button> <Link to="/login">Already have account</Link> </fieldset> </form> </div> <Sidebar/> </div> </div> ) } } export default withRouter(Register);
Also update resources/js/website/components/pages/Login.js as follows:
import React from 'react'; import Sidebar from '../partials/Sidebar'; import {Link} from "react-router-dom"; import Auth from '../../apis/Auth'; import { withRouter } from "react-router"; import "../../css/form.css"; class Login extends React.Component { constructor(props) { super(props); this.state = { email: "", password: "", error_message: null, errors: null }; this.handleSubmit = this.handleSubmit.bind(this); this.handleInput = this.handleInput.bind(this); } handleInput(e) { this.setState({ [e.target.name]: e.target.value }); } handleSubmit(e) { e.preventDefault(); this.setState({ error_message: null, errors: null }); if(this.state.email == "" || this.state.password == "") { this.setState({ error_message: "Please enter login credentials" }); return false; } Auth.login({email: this.state.email, password: this.state.password}, (response) => { for (var i in response.data.user) { localStorage.setItem("user." + i, response.data.user[i]); setTimeout(() => { this.props.history.push("/"); }, 500); } }, (err) => { this.setState({ error_message: err.response.data.message, errors: err.response.data.errors }); }); } render() { return ( <div id="content-wrap"> <div className="row"> <div id="main" className="eight columns"> <h2>Login</h2> { this.state.error_message?(<div className="alert alert-danger">{this.state.error_message}</div>):null } <form name="contactForm" method="post" action="" onSubmit={this.handleSubmit}> <fieldset> <div className="group"> <label>Email</label> <input name="email" type="text" onChange={this.handleInput} value={this.state.email} placeholder="Email" /> { this.state.errors && this.state.errors.email?(<div className="error-block">{this.state.errors.email[0]}</div>):null } </div> <div className="group"> <label>Password</label> <input name="password" type="password" onChange={this.handleInput} value={this.state.password} placeholder="Password" /> { this.state.errors && this.state.errors.password?(<div className="error-block">{this.state.errors.password[0]}</div>):null } </div> <button type="submit" className="submit">Login</button> <Link to="/register">Create account</Link> </fieldset> </form> </div> <Sidebar/> </div> </div> ) } } export default withRouter(Login);
resources/js/website/css/form.css
.alert-danger { color: red; } .alert-success { color: green; } .error-block { color: red; } .auth-links a { color: orangered !important; font-size: 10px !important; } #comments form label { float: left !important; } #comments form .message label { display: block !important; }
Then let’s add the logout functionality, this is done in the Header component so open resources/js/website/components/partials/Header.js and update it as shown:
import React from 'react'; import {Link} from "react-router-dom"; import { withRouter } from "react-router"; import GlobalContext from '../../GlobalContext'; import "../../css/form.css"; import Auth from '../../apis/Auth'; class Header extends React.Component { constructor(props) { super(props); this.state = { category_id: "" }; this.handleLogout = this.handleLogout.bind(this); } handleLogout(e) { e.preventDefault(); Auth.logout((response) => { this.props.history.push("/"); }, (err) => { alert(err.response.data.message); }); } componentDidMount() { if(this.props.location.pathname.indexOf("category") !== -1) { let path_parts = this.props.location.pathname.split("/"); // category_id is at index 2 this.setState({ category_id: path_parts[2] }); } else { this.setState({ category_id: "" }); } } componentDidUpdate(prevProps) { if (prevProps !== this.props) { if (this.props.location.pathname.indexOf("category") !== -1) { let path_parts = this.props.location.pathname.split("/"); // category_id is at index 2 this.setState({ category_id: path_parts[2] }); } else { this.setState({ category_id: "" }); } } } render() { return ( <header id="top"> <div className="row"> <div className="header-content twelve columns"> <h1 id="logo-text"><Link to="/">React Laravel Blog</Link></h1> <p id="intro">Excellence and Perfection</p> </div> </div> <nav id="nav-wrap"><a id="toggle-btn" title="Menu" href="#">Menu</a> <div className="row"> <ul id="nav" className="nav"> <li className={this.props.location.pathname=='/'?'current':''}><Link to="/">Home</Link></li> { this.context.categories.map(category => <li className={this.state.category_id==category.id?'current':''} key={category.id}><Link to={'/category/' + category.id + '/' + category.slug}>{ category.title }</Link></li>) } { localStorage.getItem("user.api_token")!=null?( <li className="auth-links"> <a href='#' onClick={this.handleLogout}>logout</a> </li> ) : ( <li className="auth-links"> <Link to='/login'>Login</Link> <Link to='/register'>Register</Link> </li> ) } </ul> </div> </nav> </header> ) } } Header.contextType = GlobalContext; export default withRouter(Header);
Commenting
Update resources/js/website/components/partials/CommentForm.js like so:
import React from 'react'; import { withRouter } from "react-router"; import '../../css/form.css'; import CommentApi from '../../apis/Comment'; class CommentForm extends React.Component { constructor(props) { super(props); this.state = { comment: "", error_message: null, errors: null, success_message: null }; this.handleSubmit = this.handleSubmit.bind(this); this.handleInput = this.handleInput.bind(this); } handleInput(e) { this.setState({ comment: e.target.value }); } handleSubmit(e) { e.preventDefault(); this.setState({ error_message: null, errors: null }); CommentApi.store({comment: this.state.comment, post_id: this.props.match.params.id}).then(response => { this.setState({ success_message: response.data.message }); }).catch(err => { this.setState({ error_message: err.response.data.message, errors: err.response.data.errors }); }); } render() { return ( <div className="respond"> <h3>Leave a Comment</h3> { this.state.error_message?(<div className="alert alert-danger">{this.state.error_message}</div>):null } { this.state.success_message?(<div className="alert alert-success">{this.state.success_message}</div>):null } <form name="contactForm" id="contactForm" method="post" onSubmit={this.handleSubmit}> <fieldset> <div className="message group"> <textarea name="comment" id="cMessage" rows="10" cols="50" placeholder="Add your comment" onChange={this.handleInput} value={this.state.comment}></textarea> { this.state.errors && this.state.errors.comment?(<div className="error-block">{this.state.errors.comment[0]}</div>):null } </div> <button type="submit" className="submit">Submit</button> </fieldset> </form> </div> ); } } export default withRouter(CommentForm);
Now run
npm run dev
and go to the website then try to register, login, browse categories and posts. By completing the comment form we finished this series, i added the repository url in bitbucket below so you can clone and run it.
Repository in bitbucket
Clone the repository from the below url:
Then follow these steps to get it up and running:
-Set your db settings in .env
-composer install
-npm install
-php artisan migrate
-npm run dev
Final thoughts
Up till now in this series you have learned how to build a Single page application using Reactjs, this series covered a lot of features in Reactjs such as dealing with components, using react router, most important using the Redux state manager as we implemented modules in admin panel. Also you see how to connect this with backend in this case laravel powers the backend of our application. Try to extend this series to add more functionality like using roles and permissions and much more.
hello
I made a mistake during the steps.
If possible, send the final code of the blog to my email. Thanks
error :
Uncaught ReferenceError: mapDispatchToProps is not defined
it.davoodi@gmail.com
git clone https://webmobtuts@bitbucket.org/webmobtuts/react-laravel-blog.git
It didn’t allow me to login . using the seeder user in the db. Even registering a new user it will not allow , there is no error message.
What’s the error?
Hello,
thank you for very good example, a lot of hard work.
Did you think about update it to new version of Laravel and some update of npm repositories??
Thanks,
May be in the future because i am kind of working on another stuff right now. You can take a look at this tutorial about e-commerce website with Nuxtjs and Lumen
http://webmobtuts.com/backend-development/building-ecommerce-website-with-php-lumen-laravel-and-nuxtjs/
I cloned this file and see image….
What can i do??????
Did you followed every part in the tutorial?
I think you have something wrong not set correctly in javascript related to axios.
Check that .env file contain MIX_APP_URL and check in process.env.MIX_APP_URL is working properly and returning the base api url.
You can check this part where i set the MIX_APP_URL
http://webmobtuts.com/backend-development/building-a-blog-with-reactjs-and-laravel-part4-react-admin-panel/
hello,,
i have question… in this post you create auth login for user add comments..
but i want click login for move to page /admin/login..
can you tell me how to make it happen?
and another question is it normal if an url # character is added? example: i write in address url only http://localhost/esakip/admin but after enter then become http://localhost/esakip/admin#/
Because you are using the HashRouter. The hash router works by adding a # segment after the base url to determine the current page. To remove the hash you can switch to the BrowserRouter
https://reactrouter.com/web/api/BrowserRouter
i use route same on your tutorial. and please give me advice how to move routes from public routes to admin routes. example: first location in public index then i want go to login page in admin route.. how to make it happen?
thanks..