Skip to content

hakhant21/laravel-vue-shop

Repository files navigation

Free Laravel Vue 3.x Tailwind 3.x Dashboard

Vue 3.x Tailwind 3.x admin dashboard demo

This guide will help you integrate your Laravel application with Admin One - free Vue 3 Tailwind 3 Admin Dashboard with dark mode.

Admin One is simple, fast and free Vue.js 3.x Tailwind CSS 3.x admin dashboard with Laravel 9.x integration.

  • Built with Vue.js 3, Tailwind CSS 3 framework & Composition API
  • Laravel build tools
  • Laravel Breeze with Inertia + Vue stack
  • SFC <script setup> Info
  • Pinia state library (official Vuex 5)
  • Dark mode
  • Styled scrollbars
  • Production CSS is only ≈38kb
  • Reusable components
  • Free under MIT License

Table of contents

Install

Install Laravel

First, install Laravel application

Install Breeze

Then cd to project dir and install Breeze with Vue option

composer require laravel/breeze --dev

php artisan breeze:install vue

php artisan migrate

npm install

Install dependencies

npm i pinia @tailwindcss/line-clamp @mdi/js chart.js numeral -D

Copy styles, components and scripts

Before you start, we recommend to rename Laravel Breeze's original folders (so you'll keep them for future reference) — resources/js/Components resources/js/Layouts resources/js/Pages to something like ComponentsBreeze, LayoutsBreeze, etc.

Now clone justboil/admin-one-vue-tailwind project somewhere locally (into any separate folder)

Next, copy these files from justboil/admin-one-vue-tailwind project directory to laravel project directory:

  • Copy tailwind.config.js to /
  • Copy src/components src/layouts src/stores src/colors.js src/config.js src/menuAside.js src/menuNavBar.js src/styles.js to resources/js/
  • Copy .laravel-guide/resources/js/ to resources/js/
  • Delete resources/css/app.css
  • Copy src/css to resources/css

[optional] lowecase vs Capitalized folder names

Fresh Laravel install with Breeze provides Capitalized folder names such as Components, Layouts, etc. For the sake of simplicity we just follow Vue conventions with lowercase folder names. However, you may opt-in to capitalize folder names:

  • Make sure you've removed original Laravel Breeze's resources/js/Layouts and resources/js/Components folders
  • Rename the folders you've copied in the previous section: resources/js/layouts to Layouts; components to Components; stores to Stores
  • Replace everywhere in imports: @/layouts/ with @/Layouts/; @/components/ with @/Components/; @/stores/ with @/Stores/

In tailwind.config.js

Please make sure, you've copied template's tailwind.config.js. Then replace content, to reflect Laravel's structure:

module.exports = {
  content: [
    "./vendor/laravel/framework/src/Illuminate/Pagination/resources/views/*.blade.php",
    "./storage/framework/views/*.php",
    "./resources/views/**/*.blade.php",
    "./resources/js/**/*.vue",
    "./resources/js/**/*.js",
  ],
  // ...
};

In resources/views/app.blade.php

  • Remove <link rel="stylesheet" href="https://fonts.bunny.net/css2?family=Nunito:wght@400;600;700&display=swap">

Add Pages

Let's just add first page. You can repeat these steps for other pages, if you wish to.

First, copy src/views/HomeView.vue (from justboil/admin-one-vue-tailwind project) to resources/js/Pages/ (in your Laravel project).

Then, open resources/js/Pages/HomeView.vue and add <Head>:

<script setup>
import { Head } from "@inertiajs/vue3";
// ...
</script>

<template>
  <LayoutAuthenticated>
    <Head title="Dashboard" />
    <!-- ... -->
  </LayoutAuthenticated>
</template>

Add route in routes/web.php. There's a /dashboard route already defined by default, so just replace Inertia::render('Dashboard') with Inertia::render('HomeView'):

Route::get('/dashboard', function () {
  return Inertia::render('HomeView');
})->middleware(['auth', 'verified'])->name('dashboard');

Fix router links

Here we replace RouterLink with Inertia Link.

resources/js/menuAside.js and resources/js/menuNavBar.js

Optionally, you can pass menu via Inertia shared props, so you'll be able to control it with PHP. Here we'd just use JS.

to should be replaced with route which specifies route name defined in routes/web.php. For external links href should be used instead. Here's an example for menuAside.js:

export default [
  "General",
  [
    {
      route: "dashboard",
      icon: mdiMonitor,
      label: "Dashboard",
    },
    // {
    //   route: "another-route-name",
    //   icon: mdiMonitor,
    //   label: "Dashboard 2",
    // },
    {
      href: "https://example.com/",
      icon: mdiMonitor,
      label: "Example.com",
    },
  ],
];

Route names reflect ones defined in routes/web.php:

Route::middleware(['auth:sanctum', 'verified'])->get('/dashboard', function () {
    return Inertia::render('Home');
})->name('dashboard');

// Route::middleware(['auth:sanctum', 'verified'])->get('/dashboard-2', function () {
//     return Inertia::render('Home2');
// })->name('another-route-name');

Now, let's update vue files, to make them work with route names and Inertia links.

resources/js/components/AsideMenuItem.vue

Replace RouterLink imported from vue-router with Link import in <script setup> and add consts:

<script setup>
import { Link } from "@inertiajs/vue3";
// import { RouterLink } from "vue-router";
// ...

// Add itemHref
const itemHref = computed(() =>
  props.item.route ? route(props.item.route) : props.item.href
);

// Add activeInactiveStyle
const activeInactiveStyle = computed(() =>
  props.item.route && route().current(props.item.route)
    ? styleStore.asideMenuItemActiveStyle
    : ""
);

// ...
</script>

In <template> section:

  • In <component> remove v-slot and :to attributes; replace :is with :is="item.route ? Link : 'a'" and :href with :href="itemHref"
  • Inside <component> replace :class attribute for both <BaseIcon> components, <span> and another <BaseIcon> with :class="activeInactiveStyle"
  • ...and for <span> (also inside <component>) replace :class attribute with :class="[{ 'pr-12': !hasDropdown }, activeInactiveStyle]"

resources/js/components/BaseButton.vue

Replace RouterLink imported from vue-router with Link import in <script setup>:

<script setup>
import { Link } from "@inertiajs/vue3";
// import { RouterLink } from "vue-router";
// ...
</script>

Replace to prop declaration with routeName:

const props = defineProps({
  // ...
  routeName: {
    type: String,
    default: null,
  },
  // ...
});

Fix const is declaration, so it returns the Link component when props.routeName is set:

const is = computed(() => {
  if (props.as) {
    return props.as;
  }

  if (props.routeName) {
    return Link;
  }

  if (props.href) {
    return "a";
  }

  return "button";
});

Remove :to and replace :href in <component> with :href="routeName ? route(routeName) : href":

<template>
  <component
    :is="is"
    :class="componentClass"
    :href="routeName ? route(routeName) : href"
    :type="computedType"
    :target="target"
    :disabled="disabled"
  >
    <!-- ... -->
  </component>
</template>

resources/js/components/NavBarItem.vue

Replace RouterLink imported from vue-router with Link import in <script setup>:

<script setup>
import { Link } from "@inertiajs/vue3";
// import { RouterLink } from "vue-router";
// ...

// Add itemHref
const itemHref = computed(() =>
  props.item.route ? route(props.item.route) : props.item.href
);

// Update `const is` to return `Link` when `props.routeName` is set:
const is = computed(() => {
  if (props.item.href) {
    return "a";
  }

  if (props.item.route) {
    return Link;
  }

  return "div";
});
</script>

Then, remove :to attribute and set :href attribute to :href="itemHref" in <component>.

Add Inertia-related stuff

resources/js/layouts/LayoutAuthenticated.vue

<script setup>
// Remove vue-router stuff:

// import { useRouter } from 'vue-router'

// const router = useRouter()

// router.beforeEach(() => {
//   isAsideMobileExpanded.value = false
//   isAsideLgActive.value = false
// })

// Add:

import { router } from "@inertiajs/vue3";

router.on("navigate", () => {
  isAsideMobileExpanded.value = false;
  isAsideLgActive.value = false;
});

// Replace `isLogout` logic:

const menuClick = (event, item) => {
  // ...

  if (item.isLogout) {
    // Add:
    router.post(route("logout"));
  }
};

// ...
</script>

resources/js/components/UserAvatarCurrentUser.vue

Let's fetch user avatar initials based on username stored in database.

<script setup>
import { computed } from "vue";
import { usePage } from "@inertiajs/vue3";
import UserAvatar from "@/components/UserAvatar.vue";

const userName = computed(() => usePage().props.auth.user.name);
</script>

<template>
  <UserAvatar :username="userName" api="initials">
    <slot />
  </UserAvatar>
</template>

resources/js/components/NavBarItem.vue

<script setup>
// Add usePage:
import { usePage } from "@inertiajs/vue3";
// Remove unused useMainStore:
// import { useMainStore } from '@/stores/main.js'
// ...

// Update itemLabel:
const itemLabel = computed(() =>
  props.item.isCurrentUser ? usePage().props.auth.user.name : props.item.label
);

// ...
</script>

Upgrading to Premium version

Please make sure you have completed all previous steps in this guide, so you have everything up and running. Then download and uzip the Premium version files. Next, follow steps described below.

Add deps

npm i @headlessui/vue -D

Copy files

Copy files to your Laravel project (overwrite free version ones or merge if you have changed something):

  • Copy src/components/Premium to resources/js/components/Premium
  • Copy src/stores to resources/js/stores
  • Copy src/config.js to resources/js/config.js
  • Copy src/styles.js to resources/js/styles.js
  • Copy src/sampleButtonMenuOptions.js to resources/js/sampleButtonMenuOptions.js
  • Copy src/colorsPremium.js to resources/js/colorsPremium.js

Update tailwind.config.js

Replace tailwind.config.js in your Laravel project with the Premium one. Make sure to preserve module.exports.content:

module.exports = {
  content: [
    "./vendor/laravel/framework/src/Illuminate/Pagination/resources/views/*.blade.php",
    "./storage/framework/views/*.php",
    "./resources/views/**/*.blade.php",
    "./resources/js/**/*.vue",
    "./resources/js/**/*.js",
  ],
  // ...
};

Update resources/js/app.js

Add layout store to resources/js/app.js:

// Add layout store
import { useLayoutStore } from "@/stores/layout.js";

const layoutStore = useLayoutStore(pinia);

layoutStore.responsiveLayoutControl();
window.onresize = () => layoutStore.responsiveLayoutControl();

Update resources/js/layouts/LayoutAuthenticated.vue

Replace contents of resources/js/layouts/LayoutAuthenticated.vue with contents of src/js/layouts/LayoutAuthenticated.vue (from the Premium version)

<script setup>
// Replace router use:

// import { useRouter } from "vue-router";
import { router } from "@inertiajs/vue3";

// const router = useRouter();

// router.beforeEach(() => {
//   layoutStore.isAsideMobileExpanded = false;
// });

router.on("navigate", () => {
  layoutStore.isAsideMobileExpanded = false;
});

// Add logout:

const menuClick = (event, item) => {
  // ...

  if (item.isLogout) {
    router.post(route("logout"));
  }
};
</script>

Use premium login and signup layouts

Optionally, you may update layouts of login and signup, located at resources/js/Pages/Auth with Premium version layouts from src/views/Premium/LoginView.vue and src/views/Premium/SignupView.vue

Optional steps

Default style

It's likely, you'll use only one app style, either basic or one of listed in src/styles.js. Follow this guide to set one of choice.

Fix .editorconfig

Add to .editorconfig:

[*.{js,jsx,ts,tsx,vue,html,css}]
indent_size = 2

resources/js/bootstrap.js

Global lodash and axios aren't needed, as we import them directly when needed. Most likely, you'd not need axios at all, as Laravel pushes all data via Inertia.

Laravel & Inertia docs

About

No description, website, or topics provided.

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published