Create Login and Register using Laravel 8 with React

Trong bài viết trước mình đã chia sẻ với mọi người cách kết hợp giữa Laravel với React, bằng cách tạo hai project riêng biệt. Thì trong bài viết hôm này mình cũng sử dụng project của bài viết trước cũng kết hợp  Laravel + React y chang bài viết trước, bạn nào chưa xem hãy xem lại tại đây: USING REACT IN LARAVEL 8

+ Install Laravel 8

composer create-project laravel/laravel back-end-laravel8
php artisan ui vue --auth
php artisan ui react --auth
npm install
npm run dev

Sau khi tải project thành công, Chúng ta cần thiếp lập database cho project của chúng ta , hãy mở file .env lên edit database như sau:

DB_CONNECTION=mysql
DB_HOST=127.0.0.1
DB_PORT=3306
DB_DATABASE=laravel8
DB_USERNAME=root
DB_PASSWORD=

Sau khi thiết lập xong, ta chạy lệnh migrate sau để laravel tạo một số table sẵn cho ta:

php artisan migrate

Okay, giờ hãy vào database của bạn sẽ thấy một số table như sau:

Okay, bạn hãy thử chạy câu lệnh sau để chạy project Laravel
php artisan server

http://127.0.0.1:8000 lên xem website hoạt động chưa nghe

Nếu chạy được là ok rồi nhé, ở bài chia sẻ này, mình có upload hình ảnh avatar cho user khi đăng ký, nên mình cần phải add column (avatar) đến table "users" trong database
Tạo lệnh migration thêm cột "avatar"

php artisan make:migration create_add_avatar_users --create=users

Sau khi chạy lệnh trên ta sẽ được file migration trong thư mục app/database/migrations như hình trên đây, hãy mời file vừa được tạo lên chỉnh sửa lại như sau:

<?php

use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;

class CreateAddAvatarUsers extends Migration
{
    /**
     * Run the migrations.
     *
     * @return void
     */
    public function up()
    {
        Schema::table('users', function (Blueprint $table) {
            $table->string('avatar');
        });
    }

    /**
     * Reverse the migrations.
     *
     * @return void
     */
    public function down()
    {
        Schema::dropIfExists('users');
    }
}

Chỉnh xong, bạn chạy lệnh dưới đây để add cột "avatar" đến table "users"
php artisan migrate

Phần thiết lập database ok rồi, giờ ta cần thiết lập trong Controllers để xử lý (login,register) 
Tạo file UsersController.php trong thư mục app\Http\Controllers\Api

php artisan make:controller Api\UsersController --resource

Mở file Api\UsersController.php

<?php

namespace App\Http\Controllers\Api;

use App\Http\Controllers\Controller;
use Illuminate\Http\Request;
use App\Http\Resources\UsersResource;
use App\Models\User;
use Illuminate\Support\Facades\Hash;
use Illuminate\Support\Facades\Validator;
use Carbon\Carbon;
class UsersController extends Controller
{
    /**
     * Display a listing of the resource.
     *
     * @return \Illuminate\Http\Response
     */
    public function index()
    {
        return new UsersResource(User::all());
    }

    public function onLogin(Request $request)
    {
        $validator = Validator::make($request->all(), [
            'email' => 'required|string|email',
            'password' => 'required|string',
        ]);
        if ($validator->fails()) {
            return response()->json(['error'=>"Login không thành công!"], 401);       
        } 

        $user = User::where("email",$request->email)->get();
        if($user->count()>0){
            return Response()->json(array("success"=>1,"data"=>$user[0]));
        }
        return response()->json(['error'=>"Login không thành công!"], 401);   
    }

    public function onRegister(Request $request){
        $validator = Validator::make($request->all(), [
            'name' => 'required',
            'email' => 'required|email|unique:users',
            'password' => 'required',
            'confirm_password' => 'required|same:password',
        ]);
        if ($validator->fails()) {
            return response()->json(['error'=>$validator->errors()], 401);      
        } 
         
        $postArray = [
            'name'      => $request->name,
            'email'     => $request->email,
            'password'  => Hash::make($request->password),
            'remember_token' => $request->token,
            'created_at'=> Carbon::now('Asia/Ho_Chi_Minh'),
            'updated_at'=>Carbon::now('Asia/Ho_Chi_Minh'),
            'avatar'=> $request->file('UrlImage')->getClientOriginalName()
        ]; 

        if ($request->hasFile('UrlImage')) {
            $image = $request->file('UrlImage');
            $name = $image->getClientOriginalName();
            $destinationPath = public_path('/upload/images');
            $imagePath = $destinationPath . "/" . $name;
            $image->move($destinationPath, $name);
         } 
         $user = User::create($postArray);
        return Response()->json(array("success"=> 1,"data"=>$postArray ));
 
    }
}

- Trong đoạn code trên, function onRegister() dùng xử lý việc đăng ký của khách hàng
Đoạn code dưới đây ta cần kiểm tra thông tin request được gửi đến server xem có đúng yêu cầu không

$validator = Validator::make($request->all(), [
            'name' => 'required',
            'email' => 'required|email|unique:users',
            'password' => 'required',
            'confirm_password' => 'required|same:password',
        ]);

+ name,password : bắt buộc phải có 
+ email : không được trùng với email đã có trong database
+ password & confirm_password : phải giống nhau

Nếu request yêu cầu gửi đến không phù hợp thì ta sẻ trả về lỗi

if ($validator->fails()) {
        return response()->json(['error'=>$validator->errors()], 401);      
} 

Nếu request yêu cầu đạt được thì ta sẽ cấu hình các thuộc tính để create một user mới đến database

$postArray = [
            'name'      => $request->name,
            'email'     => $request->email,
            'password'  => Hash::make($request->password),
            'remember_token' => $request->token,
            'created_at'=> Carbon::now('Asia/Ho_Chi_Minh'),
            'updated_at'=>Carbon::now('Asia/Ho_Chi_Minh'),
            'avatar'=> $request->file('UrlImage')->getClientOriginalName()
        ]; 

Trong phần đăng ký này, mình có thêm một ảnh avatar, nên ta cần phải upload hình ảnh đó đến thư mục "public/upload/images"

if ($request->hasFile('UrlImage')) {
            $image = $request->file('UrlImage');
            $name = $image->getClientOriginalName();
            $destinationPath = public_path('/upload/images');
            $imagePath = $destinationPath . "/" . $name;
            $image->move($destinationPath, $name);
} 

Cuối cùng là ta sẽ create user bằng câu lệnh dưới đây, sau đó trả về thông tin cho front-end biết 

$user = User::create($postArray);
return Response()->json(array("success"=> 1,"data"=>$postArray ));

Với function onLogin() ta cũng xử lý yêu cầu tương tự như phần đăng ký
Kiểm tra request yêu cầu gửi đến server có đúng không

$validator = Validator::make($request->all(), [
            'email' => 'required|string|email',
            'password' => 'required|string',
]);

Nếu request không đúng yêu cầu thì ta sẽ return về lỗi

if ($validator->fails()) {
            return response()->json(['error'=>"Login không thành công!"], 401);       
 }

Kiểm tra thông tin đăng nhập có khớp với dữ liệu không

$user = User::where("email",$request->email)->get();
        if($user->count()>0){
            return Response()->json(array("success"=>1,"data"=>$user[0]));
        }

Mở file routes/api.php thiết lập URL sao

<?php

use Illuminate\Http\Request;
use Illuminate\Support\Facades\Route;
use app\Http\Controller\Api\UsersController;
/*
|--------------------------------------------------------------------------
| 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();
});

/* Api Register */
Route::get('token', function (Request $request) {
    $token = $request->session()->token();
    $token = csrf_token();
    return Response()->json(array("token"=>$token));
});
Route::post('/users/login', [App\Http\Controllers\Api\UsersController::class, 'onLogin'])->name('user.login');
Route::post('/users', [App\Http\Controllers\Api\UsersController::class, 'onRegister'])->name('user.register');

Trong đoạn code trên ta thấy có một route get token , để có thể tạo token trong file api.php ta cần phải khai báo trong file app\Http\Kernel.php bạn nhìn code dưới chổ 'api'

 protected $middlewareGroups = [
        'web' => [
           //
        ],

        'api' => [
            \App\Http\Middleware\EncryptCookies::class, 
            \Illuminate\Cookie\Middleware\AddQueuedCookiesToResponse::class,
            \Illuminate\Session\Middleware\StartSession::class,
            'throttle:api',
            \Illuminate\Routing\Middleware\SubstituteBindings::class,
           
        ],
    ];

 

+ routes/web.php 

<?php
use Illuminate\Support\Facades\Route;
/*
|--------------------------------------------------------------------------
| Web Routes
|--------------------------------------------------------------------------
|
| Here is where you can register web routes for your application. These
| routes are loaded by the RouteServiceProvider within a group which
| contains the "web" middleware group. Now create something great!
|
*/

/*  Route::get('/', function () {
    return view('welcome');
});
  */

Auth::routes();
Route::get('/home', [App\Http\Controllers\HomeController::class, 'index'])->name('home');
Route::get('{any}', function () {
    return view('index'); // or wherever your React app is bootstrapped.
})->where('any', '.*');

Trong file routes/web.php bạn nhìn sẽ thấy đoạn code dưới đây, là bởi vì mình cài auth của laravel

Auth::routes();
Route::get('/home', [App\Http\Controllers\HomeController::class, 'index'])->name('home');

Còn code dưới đây mình để nằm bên dưới đoạn code Auth::routes() để nói, khi nào route đứng trước không có, nó sẽ chạy vào route {any} này

Route::get('{any}', function () {
    return view('index'); //call resources/views/index.blade.php
})->where('any', '.*');

File resources/views/index.blade.php các bạn có thể xem lại bài USING REACT IN LARAVEL 8 để biết cách cấu hình nhé

+ Ở đây mình nói thêm, nếu các bạn xử lý restful api mà bị lỗi Cors , thì các bạn cài đặt thư viện này đề fix nó nhé : https://github.com/fruitcake/laravel-cors

+ Phần xử lý Backend đã xong rồi, giờ chúng ta chỉ cần mở project front-end để viết layout xử lý thôi

* Cài đặt React xử lý Front-End

npx create-react-app front-end-react
cd front-end-react
npm start

Sau khi mọi thứ ok rồi, ta cần cài đặt bộ định tuyến route để chuyển tới lui các component trong React

npm install react-router-dom

Bạn có thể xem lại cách cấu hình router : URL ROUTER IN REACT

Tạo file theo đường dẫn src/components/Home.js

import React from 'react'
const Home = () => {
    return (
       <div className="styleHome">
           <h1>Home</h1>
       </div>
    )
}
export default Home

+ src/components/Login.js

import React,{useState,useEffect} from 'react'

const Login=()=>{
    const [dataForm,setDataForm] = useState({
        "email":"",
        "password":""
    });
    const onLogin = () => {
        if(dataForm.email!="" && dataForm.password!=""){
            const _formData = new FormData();
            _formData.append("email",dataForm.email)
            _formData.append("password",dataForm.password)
            const requestOptions = {
                method: 'POST',
                body: _formData
            };
            fetch('http://127.0.0.1:8000/api/users/login', requestOptions)
            .then(res => res.json())
            .then(json =>{
                console.log(json)
            });
        }
    }
    
    return (
        <div className="form-login">
            <div className="box-login">
                <div className="form-group-login">
                  <h3>Login Admin</h3>
                </div>
                <div className="form-group-login">
                    <label>Email</label>
                    <input type="email" name="email" onChange={(e)=>setDataForm({...dataForm,"email":e.target.value})}/>
                </div>
                <div className="form-group-login">
                    <label>Password</label>
                    <input type="password" name="password" onChange={(e)=>setDataForm({...dataForm,"password":e.target.value})}/>
                </div>
                <div className="form-group-login">
                   <button className="btn-login" onClick={()=>onLogin()}>Login</button>
                </div>
            </div>
        </div>
    )
}

export default Login

Code trên ta cần cấu hình cấu trúc dữ liệu cho form, để lưu thông tin "email" & "password"

const [dataForm,setDataForm] = useState({
        "email":"",
        "password":""
});

Dùng onChange() để cập nhật lại dữ liệu của cấu trúc dữ liệu form

<input type="email" name="email" onChange={(e)=>setDataForm({...dataForm,"email":e.target.value})}/>
 <input type="password" name="password" onChange={(e)=>setDataForm({...dataForm,"password":e.target.value})}/>

Xây dựng hàm onLogin() để xử lý thông tin đăng nhập, mình nghĩ đoạn code dưới đây quá quen thuộc với mọi người rồi, khai báo formdata() lưu dữ liệu để gửi đến server xử lý

const onLogin = () => {
        if(dataForm.email!="" && dataForm.password!=""){
            const _formData = new FormData();
            _formData.append("email",dataForm.email)
            _formData.append("password",dataForm.password)
            const requestOptions = {
                method: 'POST',
                body: _formData
            };
            fetch('http://127.0.0.1:8000/api/users/login', requestOptions)
            .then(res => res.json())
            .then(json =>{
                console.log(json)
            });
        }
    }

+ src/components/Register.js

import React, { useState, useEffect } from 'react'
import {useHistory} from 'react-router-dom'
import "../svg.css"
const Register = () => {
    
    const history = useHistory();
    /* register step */
    const [phantram,setPhanTram] = useState(0);

    /* form data */
    const [submit,setSubmit] = useState(false)
    const [dataForm, setDataForm] = useState({
        "email": "",
        "name": "",
        "password": "",
        "confirm_password": ""
    });


    /* check form register step */
    const [checkForm, setCheckForm] = useState({
        inputForm: false,
        uploadAvatar: false,
        btnRegister: false
    })

    /* config avatar */
    const [selectedFile, setSelectedFile] = useState()
    const [preview, setPreview] = useState()

    /* token */
    const [token,setToken] = useState("");

    useEffect(() => {
        
        setPhanTram(25);

        fetch('http://127.0.0.1:8000/api/token')
        .then(response => response.json())
        .then(data => setToken(data.token)); 
        return () => {

        }
    }, []);

    /* change avatar */
    const onSelectFile = e => {
        if (!e.target.files || e.target.files.length === 0) {
            setSelectedFile(undefined)
            return
        }

        setSelectedFile(e.target.files[0])
        const reader = new FileReader();

        reader.onloadend = () => {
            setPreview(reader.result)
        }
        if (e.target.files[0]) {
            reader.readAsDataURL(e.target.files[0]);
            setPreview(reader.result)
        }
        else {
            setPreview(undefined)
        }
    }

    /* check form state */
    const checkState = (value) => {

        if (value === 'inputForm') {
            if (dataForm.name.length > 2
                && validateEmail(dataForm.email)
                && dataForm.password.length >= 8
                && (dataForm.password == dataForm.confirm_password)) {
                setPhanTram(75)
                setCheckForm({ ...checkForm, inputForm: true })
            }
            setSubmit(true)
        }

        if (value === "uploadAvatar") {
            if (selectedFile !== undefined) {
                setCheckForm({ ...checkForm, uploadAvatar: true })
                setPhanTram(100)
            }
        }

        if (value === 'btnRegister') {
            const _formData = new FormData();
            _formData.append("name", dataForm.name);
            _formData.append("email", dataForm.email);
            _formData.append("password", dataForm.password);
            _formData.append("confirm_password",dataForm.confirm_password)
            _formData.append("UrlImage", selectedFile);
            _formData.append("token", token);
            const requestOptions = {
                method: 'POST',
               /*  headers: { 'Content-Type': 'application/json' }, */
                body: _formData
            };
             
            fetch('http://127.0.0.1:8000/api/users', requestOptions)
                .then(res => res.json())
                .then(json =>{
                    if(json['success']>0){
                        alert("Bạn đã đăng ký thành công!");
                        history.push("/login")
                    }
                    else{
                        alert(JSON.stringify(json.error))
                    }
            });

        }
      
    }

  
    const renderCheckValidationForm = () => {
        return (
            <div className="errors" >
                {dataForm.name === "" && <span>Name bạn chưa nhập</span>}
                {dataForm.name.length <= 2 && <span>Name cần 3 ký tự</span>}
                {!validateEmail(dataForm.email) && <span>Email bạn không đúng</span>}
                {dataForm.password === "" && <span>Password bạn chưa nhập</span>}
                {dataForm.password.length < 8 && <span>Password cần 8 ký tự</span>}
                {dataForm.confirm_password === "" && <span>Confirm Password chưa nhập</span>}
                {dataForm.password !== dataForm.confirm_password && <span>Password không khớp</span>}
            </div>
        )
    }

    const validateEmail = (email) => {
        const re = /^(([^<>()[\]\\.,;:\s@"]+(\.[^<>()[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/;
        return re.test(String(email).toLowerCase());
    }

    const backForm = (value) => {

        if (value === 'inputForm') {
            setCheckForm({ ...checkForm, inputForm: false })
            setPhanTram(25)
        }

        if (value === 'uploadAvatar') {
            setCheckForm({ ...checkForm, uploadAvatar: false })
            setPhanTram(75)
        }
       
    }


    const renderFormRegister =
        <div className="form-register" >
            <div className="form-label">
                <label>Name</label>
                <input type="text" name="name" placeholder="" onChange={(e) => setDataForm({ ...dataForm, "name": e.target.value })} value={dataForm.name} />
            </div>
            <div className="form-label">
                <label>Email</label>
                <input type="email" name="name" placeholder="" onChange={(e) => setDataForm({ ...dataForm, "email": e.target.value })} value={dataForm.email} />
            </div>
            <div className="form-label">
                <label>Password</label>
                <input type="password" name="name" placeholder="" onChange={(e) => setDataForm({ ...dataForm, "password": e.target.value })} value={dataForm.password} />
            </div>
            <div className="form-label">
                <label>Confirm Password</label>
                <input type="password" name="name" placeholder="" onChange={(e) => setDataForm({ ...dataForm, "confirm_password": e.target.value })} value={dataForm.confirm_password} />
            </div>
            <div className="form-label">
                <button className="btn-next" onClick={() => checkState("inputForm")}>Next</button>
            </div>
            <div className="form-label">
                {submit && renderCheckValidationForm()}
            </div>
        </div>

    const renderUploadAvatar = <div className="form-upload">
        <div className="upload-file">
            <div className="box-avatar">
                <label className="label-avatar">Avatar</label>
                {selectedFile && <img src={preview} alt="avatar" className="img-avatar" />}
                <input type="file" name="file" className="input-file" onChange={onSelectFile} />
            </div>
        </div>
        <div className="upload-file">
            <div className="box-event">
                <button className="btn-next" onClick={() => backForm("inputForm")}>Back</button>
                <button className="btn-next" onClick={() => checkState("uploadAvatar")}>Next</button>
            </div>
        </div>
    </div>

    const renderEventRegister = <div className="form-event-register">
        <div className="form-event">
            <label>Vui lòng bấm xác nhận để hoàn thành đăng ký</label>
            <div className="box-event">
                <button className="btn-next" onClick={() => backForm("uploadAvatar")}>Back</button>
                <button className="btn-next" onClick={() => checkState("btnRegister")}>Register</button>
            </div>
            {
               
            }

        </div>
    </div>

    return (
        <div>
            <div className="boxState">
                <div className="box">
                    <svg>
                        <circle cx="70px" cy="70px" r="70px"></circle>
                        <circle className="p50" className={phantram>0?'p'+phantram:'p25'} cx="70px" cy="70px" r="70px"></circle>
                    </svg>
                    <div className="number_precent"><span>{phantram}</span>%</div>
                </div>
            </div>

            {
                !checkForm.inputForm && renderFormRegister
            }
            {
                checkForm.inputForm && !checkForm.uploadAvatar && renderUploadAvatar
            }
            {
                checkForm.uploadAvatar && !checkForm.btnRegister && renderEventRegister
            }
        </div>

    )
}

export default Register

Đoạn code trên hơi nhiều nhưng cũng đơn giản thôi mọi người!
Đâu tiên mình thiết lập các trạng thái như sau:

+ Thiếp lập trạng thái tiến trình % của các bước thao tác khi điền thông tin đăng ký form

const [phantram,setPhanTram] = useState(0);

+ Thiếp lập dữ liệu form nhập và button submit form

const [submit,setSubmit] = useState(false)
    const [dataForm, setDataForm] = useState({
        "email": "",
        "name": "",
        "password": "",
        "confirm_password": ""
    });

+ Thiếp lập các bước của form, ở đây mình có 3 form, vì mình làm 3 bước để hoàn thành việc đăng ký

const [checkForm, setCheckForm] = useState({
        inputForm: false,
        uploadAvatar: false,
        btnRegister: false
    })

+ Thiếp lập việc người dùng có chọn file hình, sau đó hiện thị hình ảnh avatar ra

const [selectedFile, setSelectedFile] = useState()
 const [preview, setPreview] = useState()

+ Thiết lập lấy token từ server

const [token,setToken] = useState("");

+ xử lý việc gọi API lấy token từ server, sau đó update state token lại

fetch('http://127.0.0.1:8000/api/token')
        .then(response => response.json())
        .then(data => setToken(data.token)); 

+ Xây dựng function xử lý việc chọn hình ảnh

const onSelectFile = e => {
        if (!e.target.files || e.target.files.length === 0) {
            setSelectedFile(undefined)
            return
        }

        setSelectedFile(e.target.files[0])
        const reader = new FileReader();

        reader.onloadend = () => {
            setPreview(reader.result)
        }
        if (e.target.files[0]) {
            reader.readAsDataURL(e.target.files[0]);
            setPreview(reader.result)
        }
        else {
            setPreview(undefined)
        }
    }

+ Xây dựng hàm checkState() để xử lý từng bước của form
- Bắt sự kiện các trường field có nhập đầy đủ thông tin chưa
- Nếu hoàn thành bước nào, thì phải cập nhật lại trạng thái của form đó VD: setCheckForm({ ...checkForm, inputForm: true })
- Đồng thời cập nhật lại setPhanTram() cho từng bước
- Khai báo formdata() lưu thông tin dữ liệu gửi đến server xử lý việc đăng ký

const checkState = (value) => {

        if (value === 'inputForm') {
            if (dataForm.name.length > 2
                && validateEmail(dataForm.email)
                && dataForm.password.length >= 8
                && (dataForm.password == dataForm.confirm_password)) {
                setPhanTram(75)
                setCheckForm({ ...checkForm, inputForm: true })
            }
            setSubmit(true)
        }

        if (value === "uploadAvatar") {
            if (selectedFile !== undefined) {
                setCheckForm({ ...checkForm, uploadAvatar: true })
                setPhanTram(100)
            }
        }

        if (value === 'btnRegister') {
            const _formData = new FormData();
            _formData.append("name", dataForm.name);
            _formData.append("email", dataForm.email);
            _formData.append("password", dataForm.password);
            _formData.append("confirm_password",dataForm.confirm_password)
            _formData.append("UrlImage", selectedFile);
            _formData.append("token", token);
            const requestOptions = {
                method: 'POST',
               /*  headers: { 'Content-Type': 'application/json' }, */
                body: _formData
            };
             
            fetch('http://127.0.0.1:8000/api/users', requestOptions)
                .then(res => res.json())
                .then(json =>{
                    if(json['success']>0){
                        alert("Bạn đã đăng ký thành công!");
                        history.push("/login")
                    }
                    else{
                        alert(JSON.stringify(json.error))
                    }
            });

        }
      
    }

+ Xây dựng hàm validateEmail() kiểm tra giá trị email có đúng cú pháp "email" không

const validateEmail = (email) => {
        const re = /^(([^<>()[\]\\.,;:\s@"]+(\.[^<>()[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/;
        return re.test(String(email).toLowerCase());
    }

+ Xây dựng hàm backForm() để xử lý việc, người dùng bấm nút lùi lại form trước

const backForm = (value) => {

        if (value === 'inputForm') {
            setCheckForm({ ...checkForm, inputForm: false })
            setPhanTram(25)
        }

        if (value === 'uploadAvatar') {
            setCheckForm({ ...checkForm, uploadAvatar: false })
            setPhanTram(75)
        }
       
    }

 

+ Bắt sự kiện để show form, chúng ta dựa vào thông tin được lưu trong checkForm mà ta xử lý việc hiển thị form

return (
        <div>
            <div className="boxState">
                <div className="box">
                    <svg>
                        <circle cx="70px" cy="70px" r="70px"></circle>
                        <circle className="p50" className={phantram>0?'p'+phantram:'p25'} cx="70px" cy="70px" r="70px"></circle>
                    </svg>
                    <div className="number_precent"><span>{phantram}</span>%</div>
                </div>
            </div>

            {
                !checkForm.inputForm && renderFormRegister
            }
            {
                checkForm.inputForm && !checkForm.uploadAvatar && renderUploadAvatar
            }
            {
                checkForm.uploadAvatar && !checkForm.btnRegister && renderEventRegister
            }
        </div>

    )

+ File src/App.css : chỉnh sửa giao diện

.menu-top{
  padding:0 10px;
  background-color: #0082d0;
  display: flex;
  flex-wrap: wrap;
  justify-content: space-between;
  height: 40px;
  align-items: center;
}
.btnHome{
  width: 60px;
  position: relative;
 
}
.btnHome a{
  padding-right: 10px;
  box-sizing: border-box;
  text-decoration: none;
  font-size: 14px;
  background: #fff;
  position: absolute;
  top: -50%;
  left: 0;
  display: block;
  width: 40px;
  height: 40px;
  background-size: contain;
 
  transform: translateY(-50%);
}
.menu{
  width: calc(100% - 60px);
  box-sizing: border-box;
  display: flex;
  flex-wrap: wrap;
  justify-content: flex-end;
}

.menu a{
  color:#0082d0;
  background: #fff;
  box-sizing: border-box;
  text-decoration: none;
  font-size: 14px;
  border:1px solid #0082d0;
  font-weight: bold;
  margin: 0 1%;
  padding: 3px 10px;
  text-align: center;
}

/* boxState */
.boxState{
  width: 100%;
  margin:30px auto;
  padding:10px;
  box-sizing: border-box;
  max-width: 80%;
  display: flex;
    justify-content: center;


  
 
}
.col-state{
  height: 10px;
  width: 100%;
  border:1px solid #069B04;
  position: relative;
  border-radius: 5px;
  box-sizing: border-box;
  overflow: hidden;
 
}
.state-check{
  position: absolute;
  top:0;
  left:0;
  height: 100%;
  width: 100%;
 
  background: #069B04;
  transition: .5s all ease-in-out;
}
.state-check p{
  position: absolute;
    font-size: 10px;
    top: -2px;
    color: #fff;
    left:0;
    z-index: 10000;
    /* display: block; */
    padding: 0;
    margin: 0;
}


/* REGISTER */
.form-register{
  
  width:40%;
  margin: 0 auto;
  
}
.form-label{
   width: 100%;
  
   margin: 5px 0px;
   display: flex;
   flex-wrap: wrap;
   vertical-align: middle;
  
}
.form-label label{
  width:30%;
  display: flex;
  font-size: 13px;
  padding: 0 10px;
  box-sizing: border-box;
  background: #0082d0;
  color:#fff;
  align-items: center;
  border-top-left-radius:5px ;
  border-bottom-left-radius:5px ;

 

 
}
.form-label input{
  width: calc(100% - 30%);
  padding:8px 10px;
  border:none;
  box-sizing: border-box;
  border:1px solid #000;
  border-left: none;
  outline: none;
  border-top-right-radius:5px ;
  border-bottom-right-radius:5px ;
}
.btn-next{
  width: 100px;
  padding: 8px;
  box-sizing: border-box;
  border:none;
  background: #069B04;
  color:#fff;
  font-weight: bold;
  border-radius: 5px;
}
.errors{
    width: 100%;
}
.errors span{
  display: block;
  color:#000;
  font-size: 13px;
  padding: 5px;
  box-sizing: border-box;
  margin:2px 0px;
  background-color: #B3E5FC;
  border-radius: 5px;
  
}

/* Form upload avatar */
.form-upload{
  
  width:40%;
  margin: 0 auto;
 
}
.upload-file{
  margin:0 auto;
}
.label-avatar{
  text-align: center;
  display: block;
  padding: 10px 0px;
}
.img-avatar{
  width: 150px;
  display: block;
  margin: auto;
}
.box-event{
  width: 100%;
  display: flex;
  justify-content: center;
  
}
.input-file{
  display: block;
    width: 88px;
    color: #fff;
    margin: 5px auto;
}
.box-event button{
  margin:0 5px;
}

/* succes register */
.form-event-register{
  
  width:40%;
  margin: 0 auto;
 
}
.form-event{
  display: flex;
  flex-direction: column;

}
.form-event label{
  text-align: center;
}
.submit-register{
 
  padding: 8px;
  box-sizing: border-box;
  border:none;
  background: #069B04;
  color:#fff;
  font-weight: bold;
  border-radius: 5px;
}

/* form-login */
.form-login{
  width: 35%;
  margin:0 auto;
}
.box-login{
  width:100%;
}
.form-group-login{
  width:100%;
  display: flex;
  flex-wrap: wrap;
  justify-content: space-between;
  align-items: center;
  margin:5px 0px;
}
.form-group-login h3{
  color: #069B04;
}
.form-group-login label{
  color:#0082d0;
  font-weight: bold;
  font-size: 13px;
}
.form-group-login input{
  padding: 4px;
  box-sizing: border-box;
  border: 1px solid #069B04;
  outline: none;
}
.form-group-login button{
  background-color: #069B04;
  padding:5px;
  border:none;
  color:#fff;
  
}
@media only screen and (max-width: 600px) {
 .form-register{
   width: 90%;

 }
}

+ src/svg.css : viết hình tròn % của tiến trình bằng css

.box
	{
		position: relative;
		height: 150px;
		width: 150px;
	}
	.box .number_precent
	{
		position: absolute;
		top: 50%;
		left: 50%;
		transform: translate(-50%,-50%);
	}
	.box svg
	{
		position: absolute;
		left: 0%;
		top: 0%;
		transform: translate(-50%,-50%);
		transform: rotate(-90deg);
		width: 150px;
		height: 150px;
	}

	.box svg circle
	{	
		height: 150px;
		width: 150px;
		transform: translate(5px, 5px);
		fill: transparent;
		stroke: #007eff;
		stroke-width:10;
		stroke-linecap:round;
		stroke-dasharray: 440;
		stroke-dashoffset: 440;
		transition: all 1s;
		/*animation: animate 5s forwards;*/
	}
	.box svg circle:nth-of-type(1)
	{	
		stroke-dashoffset: 0;
		stroke: #dcd9d9;
	}
	.box svg circle.p25
	{
		stroke-dashoffset: 330;
		
	}
	.box svg circle.p50
	{
		stroke-dashoffset: 220 ;
	}
	.box svg circle.p75
	{
		stroke-dashoffset: 110 ;
	}
	.box svg circle.p100
	{
		stroke-dashoffset: 0 ;
	}

+ Mở file src/App.js lên khai báo thiết lập bộ định tuyến route để trỏ tới các components(Home,Login,Register)

import React from 'react';
import './App.css';
import { BrowserRouter as Router, Switch, Route, Link } from 'react-router-dom'
import Home from './components/Home'
import Login from './components/Login'
import Register from './components/Register'
function App() {
  return (
    <div className="container-fluid">
      <Router>
        <div className="menu-top">
            <div className="btnHome">
                <Link to="/" style={{backgroundImage:`url(${process.env.PUBLIC_URL + '/bg-menu-top.png'})`}}></Link>
            </div>
            <div className="menu">
              <Link to="/login">Login</Link>
              <Link to="/register">Register</Link>
            </div>
        </div>
        <Switch>
          <Route path="/" exact component={Home} />
          <Route path="/login" component={Login} />
          <Route path="/register" component={Register} />
        </Switch>
      </Router>
    </div>
  );
}

export default App;

Hoàn thành các bước trên là bạn đã thành công! Việc còn lại là phải build nó ra source để chạy
npx react-scripts build 

Sau khi bạn chạy lệnh Build xong bạn sẽ được một folder "build"
- Bước 1: Copy file index.html tới project back-end-laravel8/resources/views/ đổi index.html thành index.blade.php
- Bước 2: Copy thư mục static và các file khác còn lại đến project back-end-laravel8/public

Chạy Project Laravel

php artisan serve

Vậy là xong nhé các bạn!

 

Bài Viết Liên Quan

Messsage

Ủng hộ tôi bằng cách click vào quảng cáo. Để tôi có kinh phí tiếp tục phát triển Website!(Support me by clicking on the ad. Let me have the money to continue developing the Website!)