Xây dựng website bán hàng bằng Laravel - Thiết kế layout với Tailwind CSS

Ngày đăng
Tác giả
Tran Thanh Long

Trong bài này chúng ta sẽ xây dựng các bộ khung giao diện cho dự án sử dụng Tailwind CSS kết hợp với Alpine.js. Giao diện sẽ được chia làm 2 phần trong đó 1 dành cho khách hàng và một dành cho quản trị viên.


E-commerce với Laravel là loạt bài ghi lại toàn bộ quá trình xây dựng hệ thống thương mại điện tử được thực hiện bởi Transmoni team nhắm hướng dẫn mọi người làm quen với Laravel, Livewire, Alpine.js và Tailwind CSS. Hiện dự án đang được cập nhật liên tục, toàn bộ mã nguồn của dự án sẽ được công khai miễn phí theo hình thức mã nguồn mở trên trang Github của Transmoni sau khi hoàn tất loạt bài hướng dẫn này.

Quay trở lại bài trước, sau khi cài đặt Laravel Breeze thì package này đã bao gồm cả việc cài đặt Tailwind CSS và Alpine.js rồi và bạn không cần phải mất công cài đặt lại nữa mà có thể sử dụng được luôn.

Đầu tiên hãy tìm đến thư mục app/View/Components bạn sẽ thấy Laravel Breeze cung cấp sẵn 2 layouts là AppLayout và GuestLayout trong đó:

  • AppLayout dành cho các trang yêu cầu đăng nhập

  • GuestLayout dành cho các trang không cần đăng nhập.

Ở đây chúng ta sẽ thay đổi lại một chút như sau:

  • AppLayout đổi tên thành AdminLayout và nhận nhiệm vụ quản lý bố cục trang dành cho quản trị viên.

  • GuestLayout vẫn giữ nguyên, tuy nhiên chúng ta sẽ sử dụng luôn cho các trang yêu cầu đăng nhập dành cho khách hàng (ví dụ thông tin tài khoản, theo dõi đơn hàng...)

Tại file app/View/Components/AppLayout.php chúng ta tiến hành đổi thành AdminLayout.php và nội dung của nó như sau:

<?php

namespace App\View\Components;

use Illuminate\View\Component;

class AdminLayout extends Component
{
    /**
     * Get the view / contents that represents the component.
     *
     * @return \Illuminate\View\View
     */
    public function render()
    {
        return view('layouts.admin');
    }
}

Lưu ý tại return view('layouts.admin'); tên view đã được đổi thành admin thay vi app như trước đây, bạn sẽ cần phải tìm đến thư mục resources/views/layouts/app.blade.php và cập nhật lại tên cho nó thành resources/views/layouts/admin.blade.php

Giao diện khách hàng

Ở phần này chúng ta sẽ sử dụng bố cục đơn giản trong đó bao gồm phần header ở phía trên và footer ở phía dưới trong khi đó phần thân trang ở chính giữa dùng để hiển thị nội dung chính cho từng trang.

Tiến hành cập nhật nội dung cho file resources/views/layouts/guest.blade.php như sau:

<!DOCTYPE html>
<html lang="{{ str_replace('_', '-', app()->getLocale()) }}">
    <head>
        <meta charset="utf-8">
        <meta name="viewport" content="width=device-width, initial-scale=1">
        <meta name="csrf-token" content="{{ csrf_token() }}">

        <title>{{ config('app.name', 'Laravel') }}</title>

        <!-- Fonts -->
        <link rel="preconnect" href="https://fonts.googleapis.com">
        <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
        <link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&display=swap" rel="stylesheet">

        <!-- Styles -->
        <link rel="stylesheet" href="{{ asset('css/app.css') }}">

        <!-- Scripts -->
        <script src="{{ asset('js/app.js') }}" defer></script>
    </head>
    <body class="antialiased font-sans bg-gray-200">
        <div>
            <div x-data="{ open: false }" @keydown.window.escape="open = false" class="bg-gray-50">
                <div x-show="open"
                     class="fixed inset-0 flex z-40 lg:hidden" role="dialog" aria-modal="true"
                >
                    <div x-show="open"
                         x-transition:enter="transition-opacity ease-linear duration-300"
                         x-transition:enter-start="opacity-0"
                         x-transition:enter-end="opacity-100"
                         x-transition:leave="transition-opacity ease-linear duration-300"
                         x-transition:leave-start="opacity-100"
                         x-transition:leave-end="opacity-0"
                         class="fixed inset-0 bg-black bg-opacity-25" @click="open = false" aria-hidden="true"
                    ></div>

                    <div x-show="open"
                         x-transition:enter="transition ease-in-out duration-300 transform"
                         x-transition:enter-start="-translate-x-full"
                         x-transition:enter-end="translate-x-0"
                         x-transition:leave="transition ease-in-out duration-300 transform"
                         x-transition:leave-start="translate-x-0"
                         x-transition:leave-end="-translate-x-full"
                         class="relative max-w-xs w-full bg-white shadow-xl pb-12 flex flex-col overflow-y-auto"
                    >
                        <div class="px-4 pt-5 pb-2 flex">
                            <button @click="open = false" type="button" class="-m-2 p-2 rounded-md inline-flex items-center justify-center text-gray-400">
                                <span class="sr-only">Close menu</span>
                                <!-- Heroicon name: outline/x -->
                                <svg class="h-6 w-6" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor" aria-hidden="true">
                                    <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12" />
                                </svg>
                            </button>
                        </div>

                        <div class="border-t border-gray-200 py-6 px-4 space-y-6">
                            <div class="flow-root">
                                <a href="#" class="-m-2 p-2 block font-medium text-gray-900">Women</a>
                            </div>

                            <div class="flow-root">
                                <a href="#" class="-m-2 p-2 block font-medium text-gray-900">Men</a>
                            </div>

                            <div class="flow-root">
                                <a href="#" class="-m-2 p-2 block font-medium text-gray-900">Kid</a>
                            </div>

                            <div class="flow-root">
                                <a href="#" class="-m-2 p-2 block font-medium text-gray-900">Accessories</a>
                            </div>
                        </div>

                        <div class="border-t border-gray-200 py-6 px-4 space-y-6">
                            <div class="flow-root">
                                <a href="#" class="-m-2 p-2 block font-medium text-gray-900">Create an account</a>
                            </div>
                            <div class="flow-root">
                                <a href="#" class="-m-2 p-2 block font-medium text-gray-900">Sign in</a>
                            </div>
                        </div>

                        <div class="border-t border-gray-200 py-6 px-4 space-y-6">
                            <!-- Currency selector -->
                            <form>
                                <div class="inline-block">
                                    <label for="mobile-currency" class="sr-only">Currency</label>
                                    <div class="-ml-2 group relative border-transparent rounded-md focus-within:ring-2 focus-within:ring-white">
                                        <select id="mobile-currency" name="currency" class="bg-none border-transparent rounded-md py-0.5 pl-2 pr-5 flex items-center text-sm font-medium text-gray-700 group-hover:text-gray-800 focus:outline-none focus:ring-0 focus:border-transparent">
                                            <option>CAD</option>

                                            <option>USD</option>

                                            <option>AUD</option>

                                            <option>EUR</option>

                                            <option>GBP</option>
                                        </select>
                                        <div class="absolute right-0 inset-y-0 flex items-center pointer-events-none">
                                            <svg aria-hidden="true" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 20 20" class="w-5 h-5 text-gray-500">
                                                <path stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5" d="M6 8l4 4 4-4" />
                                            </svg>
                                        </div>
                                    </div>
                                </div>
                            </form>
                        </div>
                    </div>
                </div>

                <header class="relative">
                    <nav aria-label="Top">
                        <!-- Top navigation -->
                        <div class="bg-gray-900">
                            <div class="max-w-7xl mx-auto h-10 px-4 flex items-center justify-between sm:px-6 lg:px-8">
                                <!-- Currency selector -->
                                <form>
                                    <div>
                                        <label for="desktop-currency" class="sr-only">Currency</label>
                                        <div class="-ml-2 group relative bg-gray-900 border-transparent rounded-md focus-within:ring-2 focus-within:ring-white">
                                            <select id="desktop-currency" name="currency" class="bg-none bg-gray-900 border-transparent rounded-md py-0.5 pl-2 pr-5 flex items-center text-sm font-medium text-white group-hover:text-gray-100 focus:outline-none focus:ring-0 focus:border-transparent">
                                                <option>CAD</option>

                                                <option>USD</option>

                                                <option>AUD</option>

                                                <option>EUR</option>

                                                <option>GBP</option>
                                            </select>
                                            <div class="absolute right-0 inset-y-0 flex items-center pointer-events-none">
                                                <svg aria-hidden="true" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 20 20" class="w-5 h-5 text-gray-300">
                                                    <path stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5" d="M6 8l4 4 4-4" />
                                                </svg>
                                            </div>
                                        </div>
                                    </div>
                                </form>

                                <div class="flex items-center space-x-6">
                                    <a href="{{ route('login') }}" class="text-sm font-medium text-white hover:text-gray-100">Sign in</a>
                                    <a href="{{ route('register') }}" class="text-sm font-medium text-white hover:text-gray-100">Create an account</a>
                                </div>
                            </div>
                        </div>

                        <!-- Secondary navigation -->
                        <div class="bg-white shadow-sm">
                            <div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
                                <div class="h-16 flex items-center justify-between">
                                    <!-- Logo (lg+) -->
                                    <div class="hidden lg:flex-1 lg:flex lg:items-center">
                                        <a href="/">
                                            <span class="sr-only">{{ config('app.name') }}</span>
                                            <x-application-logo class="h-8 w-auto text-indigo-500 fill-current" />
                                        </a>
                                    </div>

                                    <div class="hidden h-full lg:flex">
                                        <!-- Flyout menus -->
                                        <div class="px-4 bottom-0 inset-x-0">
                                            <div class="h-full flex justify-center space-x-8">
                                                <a href="#" class="flex items-center text-sm font-medium text-gray-700 hover:text-gray-800">Women</a>

                                                <a href="#" class="flex items-center text-sm font-medium text-gray-700 hover:text-gray-800">Men</a>

                                                <a href="#" class="flex items-center text-sm font-medium text-gray-700 hover:text-gray-800">Kid</a>

                                                <a href="#" class="flex items-center text-sm font-medium text-gray-700 hover:text-gray-800">Accessories</a>
                                            </div>
                                        </div>
                                    </div>

                                    <!-- Mobile menu and search (lg-) -->
                                    <div class="flex-1 flex items-center lg:hidden">
                                        <!-- Mobile menu toggle, controls the 'mobileMenuOpen' state. -->
                                        <button @click="open = true" type="button" class="-ml-2 bg-white p-2 rounded-md text-gray-400">
                                            <span class="sr-only">Open menu</span>
                                            <!-- Heroicon name: outline/menu -->
                                            <svg class="h-6 w-6" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor" aria-hidden="true">
                                                <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 6h16M4 12h16M4 18h16" />
                                            </svg>
                                        </button>

                                        <!-- Search -->
                                        <a href="#" class="ml-2 p-2 text-gray-400 hover:text-gray-500">
                                            <span class="sr-only">Search</span>
                                            <!-- Heroicon name: outline/search -->
                                            <svg class="w-6 h-6" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor" aria-hidden="true">
                                                <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z" />
                                            </svg>
                                        </a>
                                    </div>

                                    <!-- Logo (lg-) -->
                                    <a href="#" class="lg:hidden">
                                        <span class="sr-only">{{ config('app.name') }}</span>
                                        <x-application-logo class="h-8 w-auto text-indigo-500 fill-current" />
                                    </a>

                                    <div class="flex-1 flex items-center justify-end">
                                        <a href="#" class="hidden text-sm font-medium text-gray-700 hover:text-gray-800 lg:block"> Search </a>

                                        <div class="flex items-center lg:ml-8">
                                            <!-- Help -->
                                            <a href="#" class="p-2 text-gray-400 hover:text-gray-500 lg:hidden">
                                                <span class="sr-only">Help</span>
                                                <!-- Heroicon name: outline/question-mark-circle -->
                                                <svg class="w-6 h-6" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor" aria-hidden="true">
                                                    <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8.228 9c.549-1.165 2.03-2 3.772-2 2.21 0 4 1.343 4 3 0 1.4-1.278 2.575-3.006 2.907-.542.104-.994.54-.994 1.093m0 3h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z" />
                                                </svg>
                                            </a>
                                            <a href="#" class="hidden text-sm font-medium text-gray-700 hover:text-gray-800 lg:block">Help</a>

                                            <!-- Cart -->
                                            <div class="ml-4 flow-root lg:ml-8">
                                                <a href="#" class="group -m-2 p-2 flex items-center">
                                                    <!-- Heroicon name: outline/shopping-bag -->
                                                    <svg class="flex-shrink-0 h-6 w-6 text-gray-400 group-hover:text-gray-500" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor" aria-hidden="true">
                                                        <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M16 11V7a4 4 0 00-8 0v4M5 9h14l1 12H4L5 9z" />
                                                    </svg>
                                                    <span class="ml-2 text-sm font-medium text-gray-700 group-hover:text-gray-800">0</span>
                                                    <span class="sr-only">items in cart, view bag</span>
                                                </a>
                                            </div>
                                        </div>
                                    </div>
                                </div>
                            </div>
                        </div>
                    </nav>
                </header>

                <main class="max-w-7xl mx-auto pt-16 pb-24 px-4 sm:px-6 lg:px-8">
                    {{ $slot }}
                </main>

                <footer aria-labelledby="footer-heading" class="bg-white border-t border-gray-200">
                    <h2 id="footer-heading" class="sr-only">Footer</h2>
                    <div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
                        <div class="py-20">
                            <div class="grid grid-cols-1 md:grid-cols-12 md:grid-flow-col md:gap-x-8 md:gap-y-16 md:auto-rows-min">
                                <!-- Image section -->
                                <div class="col-span-1 md:col-span-2 lg:row-start-1 lg:col-start-1">
                                    <x-application-logo class="h-8 w-auto text-indigo-500 fill-current" />
                                </div>

                                <!-- Sitemap sections -->
                                <div class="mt-10 col-span-6 grid grid-cols-2 gap-8 sm:grid-cols-3 md:mt-0 md:row-start-1 md:col-start-3 md:col-span-8 lg:col-start-2 lg:col-span-6">
                                    <div class="grid grid-cols-1 gap-y-12 sm:col-span-2 sm:grid-cols-2 sm:gap-x-8">
                                        <div>
                                            <h3 class="text-sm font-medium text-gray-900">Products</h3>
                                            <ul role="list" class="mt-6 space-y-6">
                                                <li class="text-sm">
                                                    <a href="#" class="text-gray-500 hover:text-gray-600"> Bags </a>
                                                </li>

                                                <li class="text-sm">
                                                    <a href="#" class="text-gray-500 hover:text-gray-600"> Tees </a>
                                                </li>

                                                <li class="text-sm">
                                                    <a href="#" class="text-gray-500 hover:text-gray-600"> Objects </a>
                                                </li>

                                                <li class="text-sm">
                                                    <a href="#" class="text-gray-500 hover:text-gray-600"> Home Goods </a>
                                                </li>

                                                <li class="text-sm">
                                                    <a href="#" class="text-gray-500 hover:text-gray-600"> Accessories </a>
                                                </li>
                                            </ul>
                                        </div>
                                        <div>
                                            <h3 class="text-sm font-medium text-gray-900">Company</h3>
                                            <ul role="list" class="mt-6 space-y-6">
                                                <li class="text-sm">
                                                    <a href="#" class="text-gray-500 hover:text-gray-600"> Who we are </a>
                                                </li>

                                                <li class="text-sm">
                                                    <a href="#" class="text-gray-500 hover:text-gray-600"> Sustainability </a>
                                                </li>

                                                <li class="text-sm">
                                                    <a href="#" class="text-gray-500 hover:text-gray-600"> Press </a>
                                                </li>

                                                <li class="text-sm">
                                                    <a href="#" class="text-gray-500 hover:text-gray-600"> Careers </a>
                                                </li>

                                                <li class="text-sm">
                                                    <a href="#" class="text-gray-500 hover:text-gray-600"> Terms &amp; Conditions </a>
                                                </li>

                                                <li class="text-sm">
                                                    <a href="#" class="text-gray-500 hover:text-gray-600"> Privacy </a>
                                                </li>
                                            </ul>
                                        </div>
                                    </div>
                                    <div>
                                        <h3 class="text-sm font-medium text-gray-900">Customer Service</h3>
                                        <ul role="list" class="mt-6 space-y-6">
                                            <li class="text-sm">
                                                <a href="#" class="text-gray-500 hover:text-gray-600"> Contact </a>
                                            </li>

                                            <li class="text-sm">
                                                <a href="#" class="text-gray-500 hover:text-gray-600"> Shipping </a>
                                            </li>

                                            <li class="text-sm">
                                                <a href="#" class="text-gray-500 hover:text-gray-600"> Returns </a>
                                            </li>

                                            <li class="text-sm">
                                                <a href="#" class="text-gray-500 hover:text-gray-600"> Warranty </a>
                                            </li>

                                            <li class="text-sm">
                                                <a href="#" class="text-gray-500 hover:text-gray-600"> Secure Payments </a>
                                            </li>

                                            <li class="text-sm">
                                                <a href="#" class="text-gray-500 hover:text-gray-600"> FAQ </a>
                                            </li>

                                            <li class="text-sm">
                                                <a href="#" class="text-gray-500 hover:text-gray-600"> Find a store </a>
                                            </li>
                                        </ul>
                                    </div>
                                </div>

                                <!-- Newsletter section -->
                                <div class="mt-12 md:mt-0 md:row-start-2 md:col-start-3 md:col-span-8 lg:row-start-1 lg:col-start-9 lg:col-span-4">
                                    <h3 class="text-sm font-medium text-gray-900">Sign up for our newsletter</h3>
                                    <p class="mt-6 text-sm text-gray-500">The latest deals and savings, sent to your inbox weekly.</p>
                                    <form class="mt-2 flex sm:max-w-md">
                                        <label for="newsletter-email-address" class="sr-only">Email address</label>
                                        <input id="newsletter-email-address" type="text" autocomplete="email" required class="appearance-none min-w-0 w-full bg-white border border-gray-300 rounded-md shadow-sm py-2 px-4 text-base text-gray-900 placeholder-gray-500 focus:outline-none focus:border-indigo-500 focus:ring-1 focus:ring-indigo-500">
                                        <div class="ml-4 flex-shrink-0">
                                            <button type="submit" class="w-full bg-indigo-600 border border-transparent rounded-md shadow-sm py-2 px-4 flex items-center justify-center text-base font-medium text-white hover:bg-indigo-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500">Sign up</button>
                                        </div>
                                    </form>
                                </div>
                            </div>
                        </div>

                        <div class="border-t border-gray-100 py-10 text-center">
                            <p class="text-sm text-gray-500">&copy; 2021 {{ config('app.name') }}, Inc. All rights reserved.</p>
                        </div>
                    </div>
                </footer>
            </div>
        </div>
    </body>
</html>

Tiếp đến tại file resources/views/welcome.blade.php chúng ta cập nhật lại nội dung như sau:

<x-guest-layout>
    <div class="py-20">
        <h1 class="text-3xl font-bold text-center">Laravel E-commerce Application</h1>
    </div>
</x-guest-layout>

Trên đây chỉ là nội dung nháp, chúng ta sẽ tiến hành đổ content cho nó ở các bài sau. Và kết quả nhận được khi truy cập trang chủ ứng dụng của chúng ta:

Giao diện dành cho khách hàng

Giao diện dành cho Quản trị viên

Với phần quản trị, chúng ta sẽ sử dụng giao diện thường dùng dành cho các ứng dụng quản lý. Theo đó phần bên trái sẽ được sử dụng làm sidebar navigation, trong khi đó phần bên phải trang lớn hơn sẽ để hiển thị nội dung chính của từng trang.

Tiến hành cập nhật nội dung cho file resources/views/layouts/admin.blade.php như sau:

<!DOCTYPE html>
<html lang="{{ str_replace('_', '-', app()->getLocale()) }}" class="h-full bg-gray-100">
<head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <meta name="csrf-token" content="{{ csrf_token() }}">

    <title>{{ config('app.name', 'Laravel') }}</title>

    <!-- Fonts -->
    <link rel="preconnect" href="https://fonts.googleapis.com">
    <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
    <link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&display=swap" rel="stylesheet">

    <!-- Styles -->
    <link rel="stylesheet" href="{{ asset('css/app.css') }}">

    <!-- Scripts -->
    <script src="{{ asset('js/app.js') }}" defer></script>
</head>
<body class="h-full antialiased font-sans bg-gray-200">
    <div x-data="{ open: false }" @keydown.window.escape="open = false">
        {{-- Responsive mobile navigation --}}
        <div x-show="open" class="fixed inset-0 flex z-40 md:hidden" role="dialog" aria-modal="true">
            <div x-show="open"
                 x-transition:enter="transition-opacity ease-linear duration-300"
                 x-transition:enter-start="opacity-0"
                 x-transition:enter-end="opacity-100"
                 x-transition:leave="transition-opacity ease-linear duration-300"
                 x-transition:leave-start="opacity-100"
                 x-transition:leave-end="opacity-0"
                 @click="open = false"
                 class="fixed inset-0 bg-gray-600 bg-opacity-75"
                 aria-hidden="true"
            ></div>

            <div x-show="open"
                 x-transition:enter="transition ease-in-out duration-300 transform"
                 x-transition:enter-start="-translate-x-full"
                 x-transition:enter-end="translate-x-0"
                 x-transition:leave="transition ease-in-out duration-300 transform"
                 x-transition:leave-start="translate-x-0"
                 x-transition:leave-end="-translate-x-full"
                 class="relative flex-1 flex flex-col max-w-xs w-full pt-5 pb-4 bg-gray-800"
            >
                <div x-show="open"
                     x-transition:enter="ease-in-out duration-300"
                     x-transition:enter-start="opacity-0"
                     x-transition:enter-end="opacity-100"
                     x-transition:leave="ease-in-out duration-300"
                     x-transition:leave-start="opacity-100"
                     x-transition:leave-end="opacity-0"
                     class="absolute top-0 right-0 -mr-12 pt-2">
                    <button @click="open = false" type="button" class="ml-1 flex items-center justify-center h-10 w-10 rounded-full focus:outline-none focus:ring-2 focus:ring-inset focus:ring-white">
                        <span class="sr-only">Close sidebar</span>
                        <!-- Heroicon name: outline/x -->
                        <svg class="h-6 w-6 text-white" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor" aria-hidden="true">
                            <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12" />
                        </svg>
                    </button>
                </div>

                <div class="flex-shrink-0 flex items-center px-4">
                    <x-application-logo class="h-8 w-auto text-indigo-500 fill-current" />
                    <span class="ml-2 text-white text-2xl font-bold">{{ config('app.name') }}</span>
                </div>
                <div class="mt-5 flex-1 h-0 overflow-y-auto">
                    <nav class="px-2 space-y-1">
                        <!-- Current: "bg-gray-900 text-white", Default: "text-gray-300 hover:bg-gray-700 hover:text-white" -->
                        <a href="#" class="bg-gray-900 text-white group flex items-center px-2 py-2 text-base font-medium rounded-md">
                            <!-- Heroicon name: outline/home -->
                            <svg class="text-gray-300 mr-4 flex-shrink-0 h-6 w-6" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor" aria-hidden="true">
                                <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M3 12l2-2m0 0l7-7 7 7M5 10v10a1 1 0 001 1h3m10-11l2 2m-2-2v10a1 1 0 01-1 1h-3m-6 0a1 1 0 001-1v-4a1 1 0 011-1h2a1 1 0 011 1v4a1 1 0 001 1m-6 0h6" />
                            </svg>
                            Dashboard
                        </a>

                        <a href="#" class="text-gray-300 hover:bg-gray-700 hover:text-white group flex items-center px-2 py-2 text-sm font-medium rounded-md">
                            <!-- Heroicon name: outline/folder -->
                            <svg class="text-gray-400 group-hover:text-gray-300 mr-4 flex-shrink-0 h-6 w-6" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor" aria-hidden="true">
                                <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M3 7v10a2 2 0 002 2h14a2 2 0 002-2V9a2 2 0 00-2-2h-6l-2-2H5a2 2 0 00-2 2z" />
                            </svg>
                            Categories
                        </a>

                        <a href="#" class="text-gray-300 hover:bg-gray-700 hover:text-white group flex items-center px-2 py-2 text-sm font-medium rounded-md">
                            <!-- Heroicon name: outline/document-text -->
                            <svg class="text-gray-400 group-hover:text-gray-300 mr-4 flex-shrink-0 h-6 w-6" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor" aria-hidden="true">
                                <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 12h6m-6 4h6m2 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z" />
                            </svg>
                            Products
                        </a>

                        <a href="#" class="text-gray-300 hover:bg-gray-700 hover:text-white group flex items-center px-2 py-2 text-sm font-medium rounded-md">
                            <!-- Heroicon name: outline/users -->
                            <svg class="text-gray-400 group-hover:text-gray-300 mr-4 flex-shrink-0 h-6 w-6" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor" aria-hidden="true">
                                <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 4.354a4 4 0 110 5.292M15 21H3v-1a6 6 0 0112 0v1zm0 0h6v-1a6 6 0 00-9-5.197M13 7a4 4 0 11-8 0 4 4 0 018 0z" />
                            </svg>
                            Customers
                        </a>

                        <a href="#" class="text-gray-300 hover:bg-gray-700 hover:text-white group flex items-center px-2 py-2 text-sm font-medium rounded-md">
                            <!-- Heroicon name: outline/inbox -->
                            <svg class="text-gray-400 group-hover:text-gray-300 mr-4 flex-shrink-0 h-6 w-6" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor" aria-hidden="true">
                                <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M20 13V6a2 2 0 00-2-2H6a2 2 0 00-2 2v7m16 0v5a2 2 0 01-2 2H6a2 2 0 01-2-2v-5m16 0h-2.586a1 1 0 00-.707.293l-2.414 2.414a1 1 0 01-.707.293h-3.172a1 1 0 01-.707-.293l-2.414-2.414A1 1 0 006.586 13H4" />
                            </svg>
                            Orders
                        </a>

                        <a href="#" class="text-gray-300 hover:bg-gray-700 hover:text-white group flex items-center px-2 py-2 text-sm font-medium rounded-md">
                            <!-- Heroicon name: outline/chart-bar -->
                            <svg class="text-gray-400 group-hover:text-gray-300 mr-4 flex-shrink-0 h-6 w-6" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor" aria-hidden="true">
                                <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 19v-6a2 2 0 00-2-2H5a2 2 0 00-2 2v6a2 2 0 002 2h2a2 2 0 002-2zm0 0V9a2 2 0 012-2h2a2 2 0 012 2v10m-6 0a2 2 0 002 2h2a2 2 0 002-2m0 0V5a2 2 0 012-2h2a2 2 0 012 2v14a2 2 0 01-2 2h-2a2 2 0 01-2-2z" />
                            </svg>
                            Reports
                        </a>
                    </nav>
                </div>
            </div>

            <div class="flex-shrink-0 w-14" aria-hidden="true">
                <!-- Dummy element to force sidebar to shrink to fit close icon -->
            </div>
        </div>

        {{-- Desktop navigation --}}
        <div class="hidden md:flex md:w-64 md:flex-col md:fixed md:inset-y-0">
            <!-- Sidebar component, swap this element with another sidebar if you like -->
            <div class="flex-1 flex flex-col min-h-0 bg-gray-800">
                <div class="flex items-center h-16 flex-shrink-0 px-4 bg-gray-900">
                    {{--<x-application-logo class="h-8 w-auto text-indigo-500 fill-current" />--}}
                    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 640 512" class="h-8 w-auto text-indigo-500 fill-current"><path d="M520 400c-30.93 0-56 25.07-56 56s25.07 56 56 56c30.93 0 56-25.07 56-56S550.9 400 520 400zM520 480c-13.23 0-24-10.77-24-24c0-13.23 10.77-24 24-24c13.24 0 24 10.77 24 24C544 469.2 533.2 480 520 480zM633.9 76.72C627.9 68.75 618.2 64 608 64H171.3L159.6 12.47C157.9 5.156 151.5 0 144 0H80C71.16 0 64 7.156 64 16S71.16 32 80 32h51.22l77.19 339.5C210.1 378.8 216.5 384 224 384h336c8.844 0 16-7.156 16-16s-7.156-16-16-16H236.8l-7.275-32h310.7c21.56 0 40.63-13.88 46.31-33.75l52.28-183.1C641.4 94.03 639.6 84.41 633.9 76.72zM555.8 277.4C554 283.7 547.6 288 540.2 288h-316.2c-.5996 0-1.109 .2754-1.691 .3398L178.6 95.93l429.5-1.559L555.8 277.4zM248 400c-30.93 0-56 25.07-56 56S217.1 512 248 512s56-25.07 56-56S278.9 400 248 400zM248 480c-13.23 0-24-10.77-24-24c0-13.23 10.77-24 24-24s24 10.77 24 24C272 469.2 261.2 480 248 480zM16 224H128c8.838 0 16-7.164 16-16C144 199.2 136.8 192 128 192H16C7.164 192 0 199.2 0 208C0 216.8 7.164 224 16 224zM16 160h96C120.8 160 128 152.8 128 144C128 135.2 120.8 128 112 128h-96C7.164 128 0 135.2 0 144C0 152.8 7.164 160 16 160zM144 256h-128C7.164 256 0 263.2 0 272C0 280.8 7.164 288 16 288h128C152.8 288 160 280.8 160 272C160 263.2 152.8 256 144 256z"/></svg>
                    <span class="ml-1 text-white text-2xl font-bold">{{ config('app.name') }}</span>
                </div>
                <div class="flex-1 flex flex-col overflow-y-auto">
                    <nav class="flex-1 px-2 py-4 space-y-1">
                        <!-- Current: "bg-gray-900 text-white", Default: "text-gray-300 hover:bg-gray-700 hover:text-white" -->
                        <a href="#" class="bg-gray-900 text-white group flex items-center px-2 py-2 text-sm font-medium rounded-md">
                            <!-- Heroicon name: outline/home -->
                            <svg class="text-gray-300 mr-3 flex-shrink-0 h-6 w-6" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor" aria-hidden="true">
                                <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M3 12l2-2m0 0l7-7 7 7M5 10v10a1 1 0 001 1h3m10-11l2 2m-2-2v10a1 1 0 01-1 1h-3m-6 0a1 1 0 001-1v-4a1 1 0 011-1h2a1 1 0 011 1v4a1 1 0 001 1m-6 0h6" />
                            </svg>
                            Dashboard
                        </a>

                        <a href="#" class="text-gray-300 hover:bg-gray-700 hover:text-white group flex items-center px-2 py-2 text-sm font-medium rounded-md">
                            <!-- Heroicon name: outline/folder -->
                            <svg class="text-gray-400 group-hover:text-gray-300 mr-3 flex-shrink-0 h-6 w-6" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor" aria-hidden="true">
                                <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M3 7v10a2 2 0 002 2h14a2 2 0 002-2V9a2 2 0 00-2-2h-6l-2-2H5a2 2 0 00-2 2z" />
                            </svg>
                            Categories
                        </a>

                        <a href="#" class="text-gray-300 hover:bg-gray-700 hover:text-white group flex items-center px-2 py-2 text-sm font-medium rounded-md">
                            <!-- Heroicon name: outline/document-text -->
                            <svg class="text-gray-400 group-hover:text-gray-300 mr-3 flex-shrink-0 h-6 w-6" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor" aria-hidden="true">
                                <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 12h6m-6 4h6m2 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z" />
                            </svg>
                            Products
                        </a>

                        <a href="#" class="text-gray-300 hover:bg-gray-700 hover:text-white group flex items-center px-2 py-2 text-sm font-medium rounded-md">
                            <!-- Heroicon name: outline/users -->
                            <svg class="text-gray-400 group-hover:text-gray-300 mr-3 flex-shrink-0 h-6 w-6" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor" aria-hidden="true">
                                <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 4.354a4 4 0 110 5.292M15 21H3v-1a6 6 0 0112 0v1zm0 0h6v-1a6 6 0 00-9-5.197M13 7a4 4 0 11-8 0 4 4 0 018 0z" />
                            </svg>
                            Customers
                        </a>

                        <a href="#" class="text-gray-300 hover:bg-gray-700 hover:text-white group flex items-center px-2 py-2 text-sm font-medium rounded-md">
                            <!-- Heroicon name: outline/inbox -->
                            <svg class="text-gray-400 group-hover:text-gray-300 mr-3 flex-shrink-0 h-6 w-6" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor" aria-hidden="true">
                                <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M20 13V6a2 2 0 00-2-2H6a2 2 0 00-2 2v7m16 0v5a2 2 0 01-2 2H6a2 2 0 01-2-2v-5m16 0h-2.586a1 1 0 00-.707.293l-2.414 2.414a1 1 0 01-.707.293h-3.172a1 1 0 01-.707-.293l-2.414-2.414A1 1 0 006.586 13H4" />
                            </svg>
                            Orders
                        </a>

                        <a href="#" class="text-gray-300 hover:bg-gray-700 hover:text-white group flex items-center px-2 py-2 text-sm font-medium rounded-md">
                            <!-- Heroicon name: outline/chart-bar -->
                            <svg class="text-gray-400 group-hover:text-gray-300 mr-3 flex-shrink-0 h-6 w-6" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor" aria-hidden="true">
                                <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 19v-6a2 2 0 00-2-2H5a2 2 0 00-2 2v6a2 2 0 002 2h2a2 2 0 002-2zm0 0V9a2 2 0 012-2h2a2 2 0 012 2v10m-6 0a2 2 0 002 2h2a2 2 0 002-2m0 0V5a2 2 0 012-2h2a2 2 0 012 2v14a2 2 0 01-2 2h-2a2 2 0 01-2-2z" />
                            </svg>
                            Reports
                        </a>
                    </nav>
                </div>
            </div>
        </div>

        <div class="md:pl-64 flex flex-col">
            <div class="sticky top-0 z-10 flex-shrink-0 flex h-16 bg-white shadow">
                <button @click="open = true" type="button" class="px-4 border-r border-gray-200 text-gray-500 focus:outline-none focus:ring-2 focus:ring-inset focus:ring-indigo-500 md:hidden">
                    <span class="sr-only">Open sidebar</span>
                    <!-- Heroicon name: outline/menu-alt-2 -->
                    <svg class="h-6 w-6" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor" aria-hidden="true">
                        <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 6h16M4 12h16M4 18h7" />
                    </svg>
                </button>
                <div class="flex-1 px-4 flex justify-between">
                    <div class="flex-1 flex">
                        <form class="w-full flex md:ml-0" action="#" method="GET">
                            <label for="search-field" class="sr-only">Search</label>
                            <div class="relative w-full text-gray-400 focus-within:text-gray-600">
                                <div class="absolute inset-y-0 left-0 flex items-center pointer-events-none">
                                    <!-- Heroicon name: solid/search -->
                                    <svg class="h-5 w-5" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true">
                                        <path fill-rule="evenodd" d="M8 4a4 4 0 100 8 4 4 0 000-8zM2 8a6 6 0 1110.89 3.476l4.817 4.817a1 1 0 01-1.414 1.414l-4.816-4.816A6 6 0 012 8z" clip-rule="evenodd" />
                                    </svg>
                                </div>
                                <input id="search-field" class="block w-full h-full pl-8 pr-3 py-2 border-transparent text-gray-900 placeholder-gray-500 focus:outline-none focus:placeholder-gray-400 focus:ring-0 focus:border-transparent sm:text-sm" placeholder="Search" type="search" name="search">
                            </div>
                        </form>
                    </div>
                    <div class="ml-4 flex items-center md:ml-6">
                        <button type="button" class="bg-white p-1 rounded-full text-gray-400 hover:text-gray-500 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500">
                            <span class="sr-only">View notifications</span>
                            <!-- Heroicon name: outline/bell -->
                            <svg class="h-6 w-6" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor" aria-hidden="true">
                                <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 17h5l-1.405-1.405A2.032 2.032 0 0118 14.158V11a6.002 6.002 0 00-4-5.659V5a2 2 0 10-4 0v.341C7.67 6.165 6 8.388 6 11v3.159c0 .538-.214 1.055-.595 1.436L4 17h5m6 0v1a3 3 0 11-6 0v-1m6 0H9" />
                            </svg>
                        </button>

                        <!-- Profile dropdown -->
                        <div class="ml-3 relative">
                            <x-dropdown>
                                <x-slot name="trigger">
                                    <button type="button" class="max-w-xs bg-white flex items-center text-sm rounded-full focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500" id="user-menu-button" aria-expanded="false" aria-haspopup="true">
                                        <span class="sr-only">Open user menu</span>
                                        <img class="h-8 w-8 rounded-full" src="https://images.unsplash.com/photo-1472099645785-5658abf4ff4e?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=facearea&facepad=2&w=256&h=256&q=80" alt="">
                                    </button>
                                </x-slot>

                                <x-slot name="content">
                                    <x-dropdown-link href="#">Your profile</x-dropdown-link>
                                    <x-dropdown-link href="#">Settings</x-dropdown-link>
                                    <form method="POST" action="{{ route('logout') }}">
                                        @csrf
                                        <x-dropdown-link href="{{ route('logout') }}" onclick="event.preventDefault(); this.closest('form').submit();">Sign out</x-dropdown-link>
                                    </form>
                                </x-slot>
                            </x-dropdown>
                        </div>
                    </div>
                </div>
            </div>

            <main class="flex-1">
                <div class="py-6">
                    <div class="max-w-7xl mx-auto px-4 sm:px-6 md:px-8">
                        {{ $slot }}
                    </div>
                </div>
            </main>
        </div>
    </div>
</body>
</html>

Và tại file resources/views/dashboard.blade.php chúng ta cập nhật lại nội dung nháp cho nó như này:

<x-admin-layout>
    <h1 class="text-3xl font-bold text-center">Laravel E-commerce Application</h1>
</x-admin-layout>

Và tiếp đến bạn truy cập trang /dashboard sẽ thấy giao diện như sau:

Giao diện dành cho quản trị viên

Kết luận

Vậy là chúng ta đã hoàn thành công đoạn xây dựng layout cơ bản dành cho khách hàng cũng như quản trị viên bằng cách kết hợp giữa Tailwind CSS và Alpine.js. Như các bạn có thể thấy, Tailwind CSS không quá khó để làm quen và thực hành, bạn sẽ không bị bó buộc vào việc phải sử dụng các component có sẵn như ở các CSS framework khác, ví dụ như Bootstrap, mà có thể tùy biến thoải mái theo sở thích của mình.

Ở các bài tiếp theo chúng ta sẽ đi vào xây dựng nội dung của ứng dụng cũng như xử lý business logic cho ứng dụng của mình.

Hẹn gặp lại các bạn!

Thông báo biến động số dư qua API & Webhook.

Hỗ trợ tích hợp giao dịch chuyển khoản vào hệ thống thanh toán trực tuyến Tự động, Nhanh chóng, Chính xác.

Đăng ký dùng thử trong 14 ngày
Bài viết mới nhất

Xây dựng website bán hàng bằng Laravel - Dựng trang thông tin Sản phẩm

Để Khách hàng có thể xem và tìm hiểu kỹ hơn về thông tin của một Sản phẩm bao gồm giá bán, số lượng tồn kho hay các mô tả về những đặc tính kỹ thuật ví dụ chất liệu, kiểu dáng... chúng ta sẽ cần đến một trang riêng cho mỗi sản phẩm, hãy cùng tìm hiểu cách để tạo một trang như vậy trong bài viết này.

Xây dựng website bán hàng bằng Laravel - Thiết lập và quản lý đơn hàng

Quản lý đơn hàng là quy trình back-end để quản lý và kiện toàn các đơn hàng trực tuyến. Việc này bao gồm mọi thứ từ định tuyến đơn hàng, in nhãn vận chuyển cho đến đến trả hàng cho khách. Trong bài viết của ngày hôm nay chúng ta sẽ cùng tìm hiểu cách để tạo và quản lý các đơn đặt hàng.