Base de Projeto Laravel(Personalizado)

Tecnologia infraestrutura e desenvolvimento

Base de Projeto Laravel(Personalizado)

Vite
VueJs 3 (Composition API)
Laravel 11
Reverb
Flowbite

Criando meu próprio StarterKit Laravel

Instalar o laravel em uma pasta no server:

composer create-project laravel/laravel nomeDaPasta

Logo em seguida adicionar o Jetstream ao laravel

composer require laravel/jetstream

instalar o jetstream
php artisan jetstream:install inertia –ssr –dark

Próximo passo de criar um BD apointar no .env
e entrao:
npm install
npm run build
php artisan migrate

Desta forma o sistema ja esta pronto com sistema de login.

Ajustando autenticação de API laravel 11
Dentro da pasta do bootstrap, e preciso editar o arquivo app.php
e adicionar a seguinte linha, desta forma o crfstoken ja e passado automaticamente.


Adicionando Dark Mode

Obs: lembrar de adicionar ao arquivo do tailwind.config.js o trecho:

darkMode: 'class',

Logo acima do content, como mostra a documentação do tailwind

Para adicionar o dark mode basta seguir a documentação do tailwindo onde o mesmo pede para adicionar o HEAD do sistema o seguinte script

<!-- darkmode -->
        <script>
            if (localStorage.theme === 'dark' || (!('theme' in localStorage) && window.matchMedia('(prefers-color-scheme: dark)').matches)) {
            document.documentElement.classList.add('dark')
            } else {
            document.documentElement.classList.remove('dark')
            }
        </script>

Para que funcione corretamente e preciso ter 2 bibliotecas adicionadas ao projeto
npm i –save vuex
npm i –save @heroicons/vue

Pois como podemos ver o script salva o arquivo no storage, por isso o vuex, e o componente utilizado usa os icones do Heroicons, segue componente:

<template>
    <div class="flex justify-center">
        <div class="relative">
            <button
                class="flex text-md border-2 border-transparent rounded-full focus:outline-none focus:border-gray-300 transition"
                @click="toggleDropdown"
            >
                <SunIcon
                    v-if="option === 'light'"
                    class="h-6 w-6"
                    aria-hidden="true"
                />
                <MoonIcon
                    v-if="option  === 'dark'"
                    class="h-6 w-6"
                    aria-hidden="true"
                />
                <ComputerDesktopIcon
                    v-if="option  === 'system'"
                    class="h-6 w-6"
                    aria-hidden="true"
                />
            </button>
            <div
                v-if="isDropdownOpen"
                class="absolute right-0 mt-2 bg-white border border-gray-300 rounded-b shadow-lg dark:bg-gray-800 dark:border-gray-700 rounded"
            >
                <button
                    @click="setOption('light')"
                    class="darkflex themeColor-center hover:bg-gray-100 py-1 hover:bg-gray-50 dark:hover:bg-gray-800 block w-full text-left cursor-pointer py-2 px-3 focus:outline-none focus:ring rounded truncate whitespace-nowrap text-gray-500 active:text-gray-600 dark:text-gray-500 dark:hover:text-gray-400 dark:active:text-gray-600 flex themeColor-center hover:bg-gray-100 py-1"
                >
                    <SunIcon class="h-5 w-5" aria-hidden="true"/>
                    <span class="ml-2">Light</span>
                </button>
                <button
                    @click="setOption('dark')"
                    class="flex themeColor-center hover:bg-gray-100 py-1 hover:bg-gray-50 dark:hover:bg-gray-800 block w-full text-left cursor-pointer py-2 px-3 focus:outline-none focus:ring rounded truncate whitespace-nowrap text-gray-500 active:text-gray-600 dark:text-gray-500 dark:hover:text-gray-400 dark:active:text-gray-600 flex themeColor-center hover:bg-gray-100 py-1"
                >
                    <MoonIcon class="h-5 w-5" aria-hidden="true"/>
                    <span class="ml-2">Dark</span>
                </button>
                <button
                    @click="setOption('system')"
                    class="flex themeColor-center hover:bg-gray-100 py-1 hover:bg-gray-50 dark:hover:bg-gray-800 block w-full text-left cursor-pointer py-2 px-3 focus:outline-none focus:ring rounded truncate whitespace-nowrap text-gray-500 active:text-gray-600 dark:text-gray-500 dark:hover:text-gray-400 dark:active:text-gray-600 flex themeColor-center hover:bg-gray-100 py-1"
                >
                    <ComputerDesktopIcon class="h-5 w-5" aria-hidden="true"/>
                    <span class="ml-2">System</span>
                </button>
            </div>
        </div>
    </div>
</template>

<script setup>
import {ref, onMounted, watch} from 'vue';
import {SunIcon, MoonIcon, ComputerDesktopIcon} from '@heroicons/vue/20/solid';

const systemDarkMode = window.matchMedia('(prefers-color-scheme: dark)');
const option = ref(localStorage.getItem('option'));
const isDropdownOpen = ref(false);

const toggleDropdown = () => {
    isDropdownOpen.value = !isDropdownOpen.value;
};

const setOption = (selectedOption) => {
    localStorage.setItem('option', selectedOption);
    option.value = selectedOption
    isDropdownOpen.value = false;
}

const setTheme = () => {
    if (option.value === 'system') {
        window.matchMedia('(prefers-color-scheme: dark)').matches ? toggleDarkClass('dark') : toggleDarkClass('light')
    } else {
        option.value === 'dark' ? toggleDarkClass('dark') : toggleDarkClass('light')
    }
};

const toggleDarkClass = (className) => {
    if (className === 'dark') {
        document.documentElement.classList.add('dark');
    } else {
        document.documentElement.classList.remove('dark');
    }
};

watch(option, setTheme);

onMounted(() => {
    if (!option.value) {
        setOption('system')
    }

    setTheme();

    systemDarkMode.addListener((event) => {
        if (option.value === 'system') {
            if (event.matches) {
                toggleDarkClass('dark')
            } else {
                toggleDarkClass('light')
            }
        }
    });
});
</script>

Dentro do layout do sistema e preciso instanciar o componente criado e utilizar o botão para alternar entre os modos escuro e claro do sistema.

Parametrização de rotas

Para organização e ajustes de controle de rotas e criado um page controller
mas antes e preciso adicionar ao .env o seguinte trecho ao final do arquivo

Desta forma o sanctum já irá fazer todo o controle de autenticação do back com o front via api usando o próprio token do usuario autenticado.

php artisan make:controller Web/PageController -r

App
|_Http
|_Controller
|_Web
|_Api

Padronizo desta forma por o laravel aumenta em ate 100X a velocidade com cache em rotas.

Permissionamento

Para isso vou utilizar o SPATIE

Seguindo os passos da documentação

 composer require spatie/laravel-permission

e preciso publicar arquivos de config

php artisan vendor:publish --provider="Spatie\Permission\PermissionServiceProvider"

após e preciso rodar o migrate

php artisan migrate

Depois e preciso adicionar ao model

use Illuminate\Foundation\Auth\User as Authenticatable;
use Spatie\Permission\Traits\HasRoles;

class User extends Authenticatable
{
    use HasRoles;

    // ...
}


e depois criar as permissoes, vou deixar exemplo de permissoes por meio do seeders RolesSeeder.php

 $role_admin = Role::create(['name' => 'admin']);
        $role_stardard = Role::create(['name' => 'standard']);

        $permission_read = Permission::create(['name' => 'read function']);
        $permission_edit = Permission::create(['name' => 'edit function']);
        $permission_write = Permission::create(['name' => 'write function']);
        $permission_delete = Permission::create(['name' => 'delete function']);

        $permissions_admin = [$permission_read, $permission_edit, $permission_write, $permission_delete];

        $role_admin->syncPermissions($permissions_admin);
        $role_stardard->givePermissionTo($permission_read);

UserSeeder.php

 $users = [
            [
                'name' => 'Admin',
                'email' => 'admin@admin.com',
                'password' => 'P#ssw0rd3',
                'role' => 'admin'
            ],
            [
                'name' => 'Fulano',
                'email' => 'fulano@admin.com',
                'password' => 'P#ssw0rd3',
                'role' => 'standard'
            ]
        ];

        foreach ($users as $user){
            $create_user = User::create([
                'name' => $user['name'],
                'email' => $user['email'],
                'password' => Hash::make($user['password'])
            ]);

            $create_user->assignRole($user['role']);
        }

Adicionar ao arquivo DatabaseSeeder.php

         $this->call([
            RolesSeeder::class,
            UsersSeeder::class, 
        ]);

Não esquecer de instanciar as permissions do spatie nos seus respectivos seeder’s :
use Spatie\Permission\Models\Role;
use Spatie\Permission\Models\Permission;
use App\Models\User;
use Illuminate\Support\Facades\Hash;

rodar o comando para que os usuarios com as regras e permissoes sejam criadas:

php artisan migrate:fresh –seed


Resources

Utilizo os resources para facilitar o retorno da api para o front de maneira oprganizada e segura

php artisan make:resource UserResource

retorno da seguinte forma:

 return [
            'id'   => $this->id,
            'name' => $this->name,
            'email' => $this->email,
            'role_id' => $this->roles,
            'roles' => $this->roles,
            'created_at' => $this->created_at->toDateString()
        ];
assim a lista de usuario já retorna com suas respectivas permissões

isso facilita muito o trabalho para transferir o permissionamento para o inertia e poder utiliza-lo

Passando as permissions do spatie para o inertia:

dentro de aap/http/middleware/HandleInertiaRequests.php
adicionar as sguintes linhas:

'user.roles' => fn () => $request->user() ? $request->user()->roles->pluck('name') : null,
            'user.permissions' => fn () => $request->user() ? $request->user()->getPermissionsViaRoles()->pluck('name') : null,

Agora podemos acessar as permissions dentro do inertia da seguinte forma:

import { usePage } from '@inertiajs/vue3'
import { computed } from 'vue'

const page = usePage()
const pageRole = computed(() => page.props.auth.user.role)

assim ja podemos acessar diretamente pelo vue:

<div 
                                v-if="pageRole.includes('admin') || pageRole.includes('financeiro')"
                                class="hidden space-x-8 sm:-my-px sm:ml-10 sm:flex">
                                <NavLink :href="route('financial')" :active="route().current('financial') || route().current('financialimport')">
                                    Remessas
                                </NavLink>
                            </div>

Para que o sistema ja consiga criar os novos usuarios automaticamente dentro das regras e preciso editar o arquivo app/actions/fortify/CreateNewUser.php

$create_user = User::create([
            'name' => $input['name'],
            'email' => $input['email'],
            'password' => Hash::make($input['password']),
        ]);

        $create_user->assignRole('standard');
        return $create_user;


Fazendo com que novos usuario recebam a permissao standart ao serem criados via signup ou register no padrao jetstream do laravel.

Gosto de usar icones no sistema da seguinte forma:
Icons.vue

<template>
    <svg v-if="name === 'cheveron-down'" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20"><path d="M9.293 12.95l.707.707L15.657 8l-1.414-1.414L10 10.828 5.757 6.586 4.343 8z" /></svg>
    <svg v-else-if="name === 'laravel'" height="2500" viewBox="0 -.11376601 49.74245785 51.31690859" width="2418" xmlns="http://www.w3.org/2000/svg"><path d="m49.626 11.564a.809.809 0 0 1 .028.209v10.972a.8.8 0 0 1 -.402.694l-9.209 5.302v10.509c0 .286-.152.55-.4.694l-19.223 11.066c-.044.025-.092.041-.14.058-.018.006-.035.017-.054.022a.805.805 0 0 1 -.41 0c-.022-.006-.042-.018-.063-.026-.044-.016-.09-.03-.132-.054l-19.219-11.066a.801.801 0 0 1 -.402-.694v-32.916c0-.072.01-.142.028-.21.006-.023.02-.044.028-.067.015-.042.029-.085.051-.124.015-.026.037-.047.055-.071.023-.032.044-.065.071-.093.023-.023.053-.04.079-.06.029-.024.055-.05.088-.069h.001l9.61-5.533a.802.802 0 0 1 .8 0l9.61 5.533h.002c.032.02.059.045.088.068.026.02.055.038.078.06.028.029.048.062.072.094.017.024.04.045.054.071.023.04.036.082.052.124.008.023.022.044.028.068a.809.809 0 0 1 .028.209v20.559l8.008-4.611v-10.51c0-.07.01-.141.028-.208.007-.024.02-.045.028-.068.016-.042.03-.085.052-.124.015-.026.037-.047.054-.071.024-.032.044-.065.072-.093.023-.023.052-.04.078-.06.03-.024.056-.05.088-.069h.001l9.611-5.533a.801.801 0 0 1 .8 0l9.61 5.533c.034.02.06.045.09.068.025.02.054.038.077.06.028.029.048.062.072.094.018.024.04.045.054.071.023.039.036.082.052.124.009.023.022.044.028.068zm-1.574 10.718v-9.124l-3.363 1.936-4.646 2.675v9.124l8.01-4.611zm-9.61 16.505v-9.13l-4.57 2.61-13.05 7.448v9.216zm-36.84-31.068v31.068l17.618 10.143v-9.214l-9.204-5.209-.003-.002-.004-.002c-.031-.018-.057-.044-.086-.066-.025-.02-.054-.036-.076-.058l-.002-.003c-.026-.025-.044-.056-.066-.084-.02-.027-.044-.05-.06-.078l-.001-.003c-.018-.03-.029-.066-.042-.1-.013-.03-.03-.058-.038-.09v-.001c-.01-.038-.012-.078-.016-.117-.004-.03-.012-.06-.012-.09v-21.483l-4.645-2.676-3.363-1.934zm8.81-5.994-8.007 4.609 8.005 4.609 8.006-4.61-8.006-4.608zm4.164 28.764 4.645-2.674v-20.096l-3.363 1.936-4.646 2.675v20.096zm24.667-23.325-8.006 4.609 8.006 4.609 8.005-4.61zm-.801 10.605-4.646-2.675-3.363-1.936v9.124l4.645 2.674 3.364 1.937zm-18.422 20.561 11.743-6.704 5.87-3.35-8-4.606-9.211 5.303-8.395 4.833z" fill="#ff2d20"/></svg>
    <svg v-else-if="name === 'information'" aria-hidden="true" fill="none" stroke="currentColor" stroke-width="1.5" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
        <path d="M11.25 11.25l.041-.02a.75.75 0 011.063.852l-.708 2.836a.75.75 0 001.063.853l.041-.021M21 12a9 9 0 11-18 0 9 9 0 0118 0zm-9-3.75h.008v.008H12V8.25z" stroke-linecap="round" stroke-linejoin="round"></path>
    </svg>
    <svg v-else-if="name === 'edit'" aria-hidden="true" fill="currentColor" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
      <path d="M21.731 2.269a2.625 2.625 0 00-3.712 0l-1.157 1.157 3.712 3.712 1.157-1.157a2.625 2.625 0 000-3.712zM19.513 8.199l-3.712-3.712-8.4 8.4a5.25 5.25 0 00-1.32 2.214l-.8 2.685a.75.75 0 00.933.933l2.685-.8a5.25 5.25 0 002.214-1.32l8.4-8.4z"></path>
      <path d="M5.25 5.25a3 3 0 00-3 3v10.5a3 3 0 003 3h10.5a3 3 0 003-3V13.5a.75.75 0 00-1.5 0v5.25a1.5 1.5 0 01-1.5 1.5H5.25a1.5 1.5 0 01-1.5-1.5V8.25a1.5 1.5 0 011.5-1.5h5.25a.75.75 0 000-1.5H5.25z"></path>
    </svg>
    <svg v-else-if="name === 'trash'" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20"><path d="M6 2l2-2h4l2 2h4v2H2V2h4zM3 6h14l-1 14H4L3 6zm5 2v10h1V8H8zm3 0v10h1V8h-1z" /></svg>
    <svg v-else-if="name === 'eye1'" aria-hidden="true" fill="none" stroke="currentColor" stroke-width="1.5" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
        <path d="M2.036 12.322a1.012 1.012 0 010-.639C3.423 7.51 7.36 4.5 12 4.5c4.638 0 8.573 3.007 9.963 7.178.07.207.07.431 0 .639C20.577 16.49 16.64 19.5 12 19.5c-4.638 0-8.573-3.007-9.963-7.178z" stroke-linecap="round" stroke-linejoin="round"></path>
        <path d="M15 12a3 3 0 11-6 0 3 3 0 016 0z" stroke-linecap="round" stroke-linejoin="round"></path>
    </svg>
    <svg v-else-if="name === 'checkcircle'" aria-hidden="true" fill="currentColor" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
        <path clip-rule="evenodd" d="M2.25 12c0-5.385 4.365-9.75 9.75-9.75s9.75 4.365 9.75 9.75-4.365 9.75-9.75 9.75S2.25 17.385 2.25 12zm13.36-1.814a.75.75 0 10-1.22-.872l-3.236 4.53L9.53 12.22a.75.75 0 00-1.06 1.06l2.25 2.25a.75.75 0 001.14-.094l3.75-5.25z" fill-rule="evenodd"></path>
    </svg>
    <svg v-else-if="name === 'xcircle'" aria-hidden="true" fill="currentColor" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
        <path clip-rule="evenodd" d="M12 2.25c-5.385 0-9.75 4.365-9.75 9.75s4.365 9.75 9.75 9.75 9.75-4.365 9.75-9.75S17.385 2.25 12 2.25zm-1.72 6.97a.75.75 0 10-1.06 1.06L10.94 12l-1.72 1.72a.75.75 0 101.06 1.06L12 13.06l1.72 1.72a.75.75 0 101.06-1.06L13.06 12l1.72-1.72a.75.75 0 10-1.06-1.06L12 10.94l-1.72-1.72z" fill-rule="evenodd"></path>
    </svg>
    <svg v-else-if="name === 'eye'" aria-hidden="true" fill="none" stroke="currentColor" stroke-width="1.5" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
        <path d="M2.036 12.322a1.012 1.012 0 010-.639C3.423 7.51 7.36 4.5 12 4.5c4.638 0 8.573 3.007 9.963 7.178.07.207.07.431 0 .639C20.577 16.49 16.64 19.5 12 19.5c-4.638 0-8.573-3.007-9.963-7.178z" stroke-linecap="round" stroke-linejoin="round"></path>
        <path d="M15 12a3 3 0 11-6 0 3 3 0 016 0z" stroke-linecap="round" stroke-linejoin="round"></path>
    </svg>

</template>
<script setup>
import { defineProps } from 'vue';

defineProps({
    name: String,
    });
</script>

Desta forma fica tudo centralizado e mais facil de utilizar.

Proximo passo e listar os usuarios do sistema para editar as permissoes e facilitar o controle de acesso do sistema.

Para que não tenhamos o trabalho de criar componente, vou utilizar os componentes do Flowbite-vue
npm i –save flowbite flowbite-vue
e adicionar estas linhas conforma documentação do flowbite dentro do tailwind.config.js

module.exports = {
  content: [
    'node_modules/flowbite-vue/**/*.{js,jsx,ts,tsx,vue}',
    'node_modules/flowbite/**/*.{js,jsx,ts,tsx}'
  ],
  plugins: [
      require('flowbite/plugin')
  ],
  theme: {}
}

Tambem vamos utilizar o switalert2 e toast pra notificações do sistema ao usuario.
npm i –save vue-sweetalert2
npm i –save vue-toast-notification
npm i –save laravel-vue-pagination



e preciso instacialos globalmente para faciltiar o acesso aos mesmos: app.js()main.js)

import VueSweetalert2 from 'vue-sweetalert2';
import 'sweetalert2/dist/sweetalert2.min.css';
import ToastPlugin from 'vue-toast-notification';
import 'vue-toast-notification/dist/theme-sugar.css';
import { TailwindPagination } from 'laravel-vue-pagination';

.use(VueSweetalert2)

            .use(ToastPlugin)

            .component(‘Pagination’, TailwindPagination)

Proximo passo e criar administração de usuarios

->listar
->editar
->permissoes
->regras

Ajustando LandingPage com AOS

E preciso instalar as dependendicas:

npm i –save @popperjs/core
npm i –save @fortawesome/fontawesome-free
npm i –save aos

apos a instalação extrairi or arquivos e colocalos dentro do projeto

Depois e preciso instancialos globalmente incluindo vuex tbm

Desta forma todos os icones e ações(feitas com biblioteca AOS) serao acionados

Como usar AOS aqui

Deixe um comentário

O seu endereço de e-mail não será publicado. Campos obrigatórios são marcados com *