├── resources ├── stubs │ ├── install │ │ ├── public │ │ │ ├── json │ │ │ │ └── manifest.json │ │ │ └── images │ │ │ │ ├── logo.png │ │ │ │ ├── icon-fav.png │ │ │ │ └── icon-touch.png │ │ ├── resources │ │ │ ├── js │ │ │ │ └── app.js │ │ │ ├── scss │ │ │ │ ├── app.scss │ │ │ │ └── _variables.scss │ │ │ └── views │ │ │ │ ├── home.blade.php │ │ │ │ ├── welcome.blade.php │ │ │ │ ├── auth │ │ │ │ ├── password-forgot.blade.php │ │ │ │ ├── password-reset.blade.php │ │ │ │ ├── profile-update.blade.php │ │ │ │ ├── register.blade.php │ │ │ │ ├── password-change.blade.php │ │ │ │ └── login.blade.php │ │ │ │ ├── users │ │ │ │ ├── password.blade.php │ │ │ │ ├── save.blade.php │ │ │ │ ├── read.blade.php │ │ │ │ └── index.blade.php │ │ │ │ └── layouts │ │ │ │ ├── app.blade.php │ │ │ │ └── nav.blade.php │ │ ├── app │ │ │ ├── Http │ │ │ │ └── Livewire │ │ │ │ │ ├── Welcome.php │ │ │ │ │ ├── Users │ │ │ │ │ ├── Read.php │ │ │ │ │ ├── Save.php │ │ │ │ │ ├── Password.php │ │ │ │ │ └── Index.php │ │ │ │ │ ├── Home.php │ │ │ │ │ ├── Layouts │ │ │ │ │ └── Nav.php │ │ │ │ │ └── Auth │ │ │ │ │ ├── PasswordChange.php │ │ │ │ │ ├── ProfileUpdate.php │ │ │ │ │ ├── PasswordForgot.php │ │ │ │ │ ├── Register.php │ │ │ │ │ ├── PasswordReset.php │ │ │ │ │ └── Login.php │ │ │ └── Models │ │ │ │ └── User.php │ │ ├── database │ │ │ ├── seeders │ │ │ │ └── DatabaseSeeder.php │ │ │ └── factories │ │ │ │ └── UserFactory.php │ │ ├── routes │ │ │ └── web.php │ │ ├── webpack.mix.js │ │ ├── package.json │ │ └── config │ │ │ ├── timezone.php │ │ │ ├── livewire.php │ │ │ ├── geoip.php │ │ │ ├── database.php │ │ │ └── app.php │ ├── make │ │ ├── DummyComponentPartial.blade.php │ │ ├── DummyComponentFull.blade.php │ │ ├── DummyComponentModalClass.php │ │ ├── DummyComponentPartialClass.php │ │ ├── DummyFactoryClass.php │ │ ├── DummyComponentFullClass.php │ │ ├── DummyComponentModal.blade.php │ │ └── DummyModelClass.php │ └── crud │ │ ├── Read.php │ │ ├── save.blade.php │ │ ├── Save.php │ │ ├── read.blade.php │ │ ├── Index.php │ │ └── index.blade.php └── views │ └── components │ ├── icon.blade.php │ ├── dropdown-item.blade.php │ ├── dropdown.blade.php │ ├── action.blade.php │ ├── checkbox.blade.php │ ├── modal.blade.php │ ├── textarea.blade.php │ ├── pagination.blade.php │ ├── select.blade.php │ ├── input.blade.php │ └── radio.blade.php ├── src ├── Traits │ ├── MakesStubs.php │ ├── HasHashes.php │ └── WithModel.php ├── Components │ └── ModalComponent.php ├── Providers │ └── UiProvider.php └── Commands │ ├── ModelCommand.php │ ├── InstallCommand.php │ ├── ComponentCommand.php │ ├── MigrateCommand.php │ └── CrudCommand.php ├── routes └── web.php ├── composer.json ├── config └── ui.php └── readme.md /resources/stubs/install/public/json/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Laravel", 3 | "display": "standalone" 4 | } 5 | -------------------------------------------------------------------------------- /resources/stubs/install/resources/js/app.js: -------------------------------------------------------------------------------- 1 | require('@popperjs/core'); 2 | window.bootstrap = require('bootstrap'); 3 | -------------------------------------------------------------------------------- /resources/stubs/make/DummyComponentPartial.blade.php: -------------------------------------------------------------------------------- 1 |
2 |

{{ __("DummyWisdomOfTheTao") }}

3 |
4 | -------------------------------------------------------------------------------- /resources/stubs/install/public/images/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bastinald/ui/HEAD/resources/stubs/install/public/images/logo.png -------------------------------------------------------------------------------- /resources/stubs/install/public/images/icon-fav.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bastinald/ui/HEAD/resources/stubs/install/public/images/icon-fav.png -------------------------------------------------------------------------------- /resources/stubs/install/public/images/icon-touch.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bastinald/ui/HEAD/resources/stubs/install/public/images/icon-touch.png -------------------------------------------------------------------------------- /resources/stubs/install/resources/scss/app.scss: -------------------------------------------------------------------------------- 1 | @import 'variables'; 2 | @import 'bootstrap'; 3 | @import '~@fortawesome/fontawesome-free/css/all.css'; 4 | -------------------------------------------------------------------------------- /resources/stubs/make/DummyComponentFull.blade.php: -------------------------------------------------------------------------------- 1 | @section('title', __('DummyViewTitle')) 2 | 3 |
4 |

@yield('title')

5 | 6 |

{{ __("DummyWisdomOfTheTao") }}

7 |
8 | -------------------------------------------------------------------------------- /resources/stubs/install/resources/scss/_variables.scss: -------------------------------------------------------------------------------- 1 | $font-size-base: 0.9rem; 2 | $enable-negative-margins: true; 3 | $container-max-width: 960px; 4 | 5 | .container { 6 | max-width: $container-max-width !important; 7 | } 8 | -------------------------------------------------------------------------------- /resources/stubs/make/DummyComponentModalClass.php: -------------------------------------------------------------------------------- 1 | config('ui.font_awesome_style'), 4 | ]) 5 | 6 | @php 7 | $attributes = $attributes->class([ 8 | 'fa' . Str::limit($style, 1, null) . ' fa-' . $name, 9 | ])->merge([ 10 | // 11 | ]); 12 | @endphp 13 | 14 | 15 | -------------------------------------------------------------------------------- /resources/stubs/install/resources/views/home.blade.php: -------------------------------------------------------------------------------- 1 | @section('title', __('Home')) 2 | 3 |
4 |
5 |
6 | @yield('title') 7 |
8 |
9 | {{ __('You are logged in!') }} 10 |
11 |
12 |
13 | -------------------------------------------------------------------------------- /resources/stubs/install/resources/views/welcome.blade.php: -------------------------------------------------------------------------------- 1 | @section('title', __('Welcome')) 2 | 3 |
4 |
5 |
6 | @yield('title') 7 |
8 |
9 | {{ __('Welcome to the app!') }} 10 |
11 |
12 |
13 | -------------------------------------------------------------------------------- /resources/stubs/install/app/Http/Livewire/Welcome.php: -------------------------------------------------------------------------------- 1 | create([ 12 | 'email' => 'user@example.com', 13 | ]); 14 | 15 | // \App\Models\User::factory(100)->create(); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /resources/stubs/make/DummyFactoryClass.php: -------------------------------------------------------------------------------- 1 | model)->definition($this->faker); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /resources/stubs/install/app/Http/Livewire/Users/Read.php: -------------------------------------------------------------------------------- 1 | user = $user; 15 | } 16 | 17 | public function render() 18 | { 19 | return view('users.read'); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /resources/stubs/install/app/Http/Livewire/Home.php: -------------------------------------------------------------------------------- 1 | middleware('auth'); 14 | } 15 | 16 | public function render() 17 | { 18 | return view('home'); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /resources/stubs/make/DummyComponentFullClass.php: -------------------------------------------------------------------------------- 1 | name('DummyRouteName'); 14 | } 15 | 16 | public function render() 17 | { 18 | return view('DummyViewName'); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /resources/stubs/crud/Read.php: -------------------------------------------------------------------------------- 1 | DummyModelVariable = $DummyModelVariable; 15 | } 16 | 17 | public function render() 18 | { 19 | return view('DummyViewName.read'); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /resources/stubs/install/app/Http/Livewire/Layouts/Nav.php: -------------------------------------------------------------------------------- 1 | to('/'); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /resources/stubs/install/routes/web.php: -------------------------------------------------------------------------------- 1 | null, 4 | 'url' => null, 5 | 'href' => null, 6 | 'click' => null, 7 | ]) 8 | 9 | @php 10 | if ($route) $href = route($route); 11 | else if ($url) $href = url($url); 12 | 13 | $attributes = $attributes->class([ 14 | 'dropdown-item', 15 | ])->merge([ 16 | 'type' => $click ? 'button' : null, 17 | 'href' => $href, 18 | 'wire:click' => $click, 19 | ]); 20 | @endphp 21 | 22 | <{{ $href ? 'a' : 'button' }} {{ $attributes }}> 23 | {{ $label }} 24 | 25 | -------------------------------------------------------------------------------- /resources/stubs/make/DummyComponentModal.blade.php: -------------------------------------------------------------------------------- 1 | 15 | -------------------------------------------------------------------------------- /resources/stubs/install/database/factories/UserFactory.php: -------------------------------------------------------------------------------- 1 | model)->definition($this->faker); 15 | } 16 | 17 | public function unverified() 18 | { 19 | return $this->state(function (array $attributes) { 20 | return [ 21 | 'email_verified_at' => null, 22 | ]; 23 | }); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /resources/stubs/install/webpack.mix.js: -------------------------------------------------------------------------------- 1 | const mix = require('laravel-mix'); 2 | 3 | /* 4 | |-------------------------------------------------------------------------- 5 | | Mix Asset Management 6 | |-------------------------------------------------------------------------- 7 | | 8 | | Mix provides a clean, fluent API for defining some Webpack build steps 9 | | for your Laravel applications. By default, we are compiling the CSS 10 | | file for the application as well as bundling up all the JS files. 11 | | 12 | */ 13 | 14 | mix.js('resources/js/app.js', 'public/js') 15 | .sass('resources/scss/app.scss', 'public/css') 16 | .sourceMaps(); 17 | -------------------------------------------------------------------------------- /src/Traits/MakesStubs.php: -------------------------------------------------------------------------------- 1 | get(config('ui.stub_path') . $stub); 15 | 16 | foreach ($this->stubReplaces as $search => $replace) { 17 | $contents = str_replace($search, $replace, $contents); 18 | } 19 | 20 | $filesystem->ensureDirectoryExists(dirname($path)); 21 | $filesystem->put($path, $contents); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /resources/views/components/dropdown.blade.php: -------------------------------------------------------------------------------- 1 | @props([ 2 | 'icon' => null, 3 | 'label', 4 | 'position' => 'end', 5 | ]) 6 | 7 | @php 8 | $attributes = $attributes->class([ 9 | 'dropdown d-inline-block', 10 | ])->merge([ 11 | // 12 | ]); 13 | @endphp 14 | 15 |
16 | 23 | 24 | 27 |
28 | -------------------------------------------------------------------------------- /resources/views/components/action.blade.php: -------------------------------------------------------------------------------- 1 | @props([ 2 | 'icon', 3 | 'title', 4 | 'route' => null, 5 | 'url' => null, 6 | 'href' => null, 7 | 'click' => null, 8 | ]) 9 | 10 | @php 11 | if ($route) $href = route($route); 12 | else if ($url) $href = url($url); 13 | 14 | $attributes = $attributes->class([ 15 | 'btn btn-link px-1 py-0', 16 | ])->merge([ 17 | 'type' => $click ? 'button' : null, 18 | 'title' => $title, 19 | 'href' => $href, 20 | 'wire:click' => $click, 21 | ]); 22 | @endphp 23 | 24 | <{{ $href ? 'a' : 'button' }} {{ $attributes }}> 25 | 26 | 27 | -------------------------------------------------------------------------------- /src/Components/ModalComponent.php: -------------------------------------------------------------------------------- 1 | component = $component; 22 | $this->params = $params; 23 | 24 | $this->emit('showBootstrapModal'); 25 | } 26 | 27 | public function resetModal() 28 | { 29 | $this->reset(); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /resources/views/components/checkbox.blade.php: -------------------------------------------------------------------------------- 1 | @props([ 2 | 'label', 3 | 'model', 4 | 'lazy' => false, 5 | ]) 6 | 7 | @php 8 | if ($lazy) $bind = '.lazy'; 9 | else $bind = '.defer'; 10 | 11 | $attributes = $attributes->class([ 12 | 'form-check-input', 13 | 'is-invalid' => $errors->has($model), 14 | ])->merge([ 15 | 'type' => 'checkbox', 16 | 'id' => $model, 17 | 'wire:model' . $bind => 'model.' . $model, 18 | ]); 19 | @endphp 20 | 21 |
22 | 23 | 24 | 25 | 26 | @error($model) 27 |
{{ $message }}
28 | @enderror 29 |
30 | -------------------------------------------------------------------------------- /resources/stubs/install/resources/views/auth/password-forgot.blade.php: -------------------------------------------------------------------------------- 1 | @section('title', __('Forgot Password')) 2 | 3 |
4 |
5 |
6 | @yield('title') 7 |
8 |
9 | @if($status) 10 |
{{ $status }}
11 | @endif 12 | 13 | 14 |
15 | 18 |
19 |
20 | -------------------------------------------------------------------------------- /src/Traits/HasHashes.php: -------------------------------------------------------------------------------- 1 | hashes) { 15 | return; 16 | } 17 | 18 | foreach (Arr::wrap($model->hashes) as $hash) { 19 | if ($model->$hash && 20 | Str::length($model->$hash) < 60 && 21 | !Str::startsWith($model->$hash, '$2y$')) { 22 | $model->$hash = Hash::make($model->$hash); 23 | } 24 | } 25 | }); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /resources/stubs/install/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "private": true, 3 | "scripts": { 4 | "dev": "npm run development", 5 | "development": "mix", 6 | "watch": "mix watch", 7 | "watch-poll": "mix watch -- --watch-options-poll=1000", 8 | "hot": "mix watch --hot", 9 | "prod": "npm run production", 10 | "production": "mix --production" 11 | }, 12 | "devDependencies": { 13 | "@fortawesome/fontawesome-free": "^5.15.3", 14 | "@popperjs/core": "^2.9.2", 15 | "axios": "^0.21", 16 | "bootstrap": "^5.0.1", 17 | "laravel-mix": "^6.0.6", 18 | "lodash": "^4.17.19", 19 | "postcss": "^8.1.14", 20 | "resolve-url-loader": "^3.1.3", 21 | "sass": "^1.35.1", 22 | "sass-loader": "^12.1.0" 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /resources/stubs/crud/save.blade.php: -------------------------------------------------------------------------------- 1 | 16 | -------------------------------------------------------------------------------- /resources/stubs/install/app/Http/Livewire/Auth/PasswordChange.php: -------------------------------------------------------------------------------- 1 | ['required', 'password'], 22 | 'password' => ['required', 'confirmed'], 23 | ]; 24 | } 25 | 26 | public function save() 27 | { 28 | $this->validateModel(); 29 | 30 | Auth::user()->update($this->getModel(['password'])); 31 | 32 | $this->emit('hideModal'); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /resources/stubs/install/resources/views/auth/password-reset.blade.php: -------------------------------------------------------------------------------- 1 | @section('title', __('Reset Password')) 2 | 3 |
4 |
5 |
6 | @yield('title') 7 |
8 |
9 | 10 | 11 | 12 |
13 | 16 |
17 |
18 | -------------------------------------------------------------------------------- /resources/views/components/modal.blade.php: -------------------------------------------------------------------------------- 1 | 6 | 7 | @push('scripts') 8 | 27 | @endpush 28 | -------------------------------------------------------------------------------- /resources/stubs/install/app/Http/Livewire/Users/Save.php: -------------------------------------------------------------------------------- 1 | user = $user; 18 | 19 | $this->setModel($user->toArray()); 20 | } 21 | 22 | public function render() 23 | { 24 | return view('users.save'); 25 | } 26 | 27 | public function save() 28 | { 29 | $this->validateModel($this->user->rules()); 30 | 31 | $this->user->fill($this->getModel(['name', 'email', 'password']))->save(); 32 | 33 | $this->emit('hideModal'); 34 | $this->emit('$refresh'); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /routes/web.php: -------------------------------------------------------------------------------- 1 | group(function () { 9 | $path = ComponentParser::generatePathFromNamespace(config('livewire.class_namespace')); 10 | $namespace = app()->getNamespace(); 11 | 12 | if (!is_dir($path)) { 13 | return; 14 | } 15 | 16 | foreach ((new Finder)->in($path) as $component) { 17 | $component = $namespace . str_replace( 18 | ['/', '.php'], 19 | ['\\', ''], 20 | Str::after($component->getRealPath(), realpath(app_path()) . DIRECTORY_SEPARATOR) 21 | ); 22 | 23 | if (method_exists($component, 'route')) { 24 | app($component)->route(); 25 | } 26 | } 27 | }); 28 | -------------------------------------------------------------------------------- /resources/stubs/install/app/Http/Livewire/Users/Password.php: -------------------------------------------------------------------------------- 1 | user = $user; 18 | } 19 | 20 | public function render() 21 | { 22 | return view('users.password'); 23 | } 24 | 25 | public function rules() 26 | { 27 | return [ 28 | 'password' => ['required', 'confirmed'], 29 | ]; 30 | } 31 | 32 | public function save() 33 | { 34 | $this->validateModel(); 35 | 36 | $this->user->update($this->getModel(['password'])); 37 | 38 | $this->emit('hideModal'); 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /resources/stubs/install/resources/views/auth/profile-update.blade.php: -------------------------------------------------------------------------------- 1 | 17 | -------------------------------------------------------------------------------- /resources/stubs/install/resources/views/auth/register.blade.php: -------------------------------------------------------------------------------- 1 | @section('title', __('Register')) 2 | 3 |
4 |
5 |
6 | @yield('title') 7 |
8 |
9 | 10 | 11 | 12 | 13 | 14 | 15 |
16 | 19 |
20 |
21 | -------------------------------------------------------------------------------- /resources/stubs/crud/Save.php: -------------------------------------------------------------------------------- 1 | DummyModelVariable = $DummyModelVariable; 18 | 19 | $this->setModel($DummyModelVariable->toArray()); 20 | } 21 | 22 | public function render() 23 | { 24 | return view('DummyViewName.save'); 25 | } 26 | 27 | public function save() 28 | { 29 | $this->validateModel($this->DummyModelVariable->rules()); 30 | 31 | $this->DummyModelVariable->fill($this->getModel())->save(); 32 | 33 | $this->emit('hideModal'); 34 | $this->emit('$refresh'); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /resources/stubs/install/resources/views/users/password.blade.php: -------------------------------------------------------------------------------- 1 | 17 | -------------------------------------------------------------------------------- /resources/views/components/textarea.blade.php: -------------------------------------------------------------------------------- 1 | @props([ 2 | 'label' => null, 3 | 'model', 4 | 'lazy' => false, 5 | 'debounce' => false, 6 | ]) 7 | 8 | @php 9 | if ($lazy) $bind = '.lazy'; 10 | else if (ctype_digit($debounce)) $bind = '.debounce.' . $debounce . 'ms'; 11 | else if ($debounce) $bind = ''; 12 | else $bind = '.defer'; 13 | 14 | $attributes = $attributes->class([ 15 | 'form-control', 16 | 'is-invalid' => $errors->has($model), 17 | ])->merge([ 18 | 'id' => $model, 19 | 'wire:model' . $bind => 'model.' . $model, 20 | ]); 21 | @endphp 22 | 23 |
24 | @if($label) 25 | 26 | @endif 27 | 28 | 29 | 30 | @error($model) 31 |
{{ $message }}
32 | @enderror 33 |
34 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "bastinald/ui", 3 | "homepage": "https://github.com/bastinald/ui", 4 | "description": "Laravel Livewire & Bootstrap 5 UI starter kit.", 5 | "license": "MIT", 6 | "authors": [ 7 | { 8 | "name": "Kevin Dion", 9 | "email": "bastinald@icloud.com", 10 | "role": "Developer" 11 | } 12 | ], 13 | "require": { 14 | "barryvdh/laravel-ide-helper": "^2.10", 15 | "doctrine/dbal": "^3.0", 16 | "jamesmills/laravel-timezone": "^1.9", 17 | "laravel/framework": "^8.0", 18 | "livewire/livewire": "^2.0", 19 | "lukeraymonddowning/honey": "^0.3" 20 | }, 21 | "autoload": { 22 | "psr-4": { 23 | "Bastinald\\Ui\\": "src" 24 | } 25 | }, 26 | "extra": { 27 | "laravel": { 28 | "providers": [ 29 | "Bastinald\\Ui\\Providers\\UiProvider" 30 | ] 31 | } 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /resources/views/components/pagination.blade.php: -------------------------------------------------------------------------------- 1 | @props([ 2 | 'links', 3 | 'count' => false, 4 | 'justify' => null, 5 | ]) 6 | 7 | @php 8 | $justify = $justify ?? ($count ? 'between' : 'end'); 9 | 10 | $attributes = $attributes->class([ 11 | 'row align-items-center justify-content-' . $justify, 12 | ])->merge([ 13 | // 14 | ]); 15 | @endphp 16 | 17 |
18 | @if($links->count() && $count) 19 |
20 | {{ $links->firstItem() }} {{ __('-') }} {{ $links->lastItem() }} 21 | {{ __('of') }} {{ $links->total() }} 22 |
23 | @endif 24 | 25 |
26 |
27 | {{ $links->links('livewire::simple-bootstrap') }} 28 |
29 | 30 |
31 | {{ $links->links('livewire::bootstrap') }} 32 |
33 |
34 |
35 | -------------------------------------------------------------------------------- /resources/stubs/install/resources/views/auth/password-change.blade.php: -------------------------------------------------------------------------------- 1 | 18 | -------------------------------------------------------------------------------- /resources/stubs/make/DummyModelClass.php: -------------------------------------------------------------------------------- 1 | id(); 19 | $table->string('name'); 20 | $table->timestamp('created_at')->nullable(); 21 | $table->timestamp('updated_at')->nullable(); 22 | } 23 | 24 | public function definition(Generator $faker) 25 | { 26 | return [ 27 | 'name' => $faker->name, 28 | 'created_at' => $faker->dateTimeBetween(now()->subMonth(), now()), 29 | ]; 30 | } 31 | 32 | public function rules() 33 | { 34 | return [ 35 | 'name' => ['required', 'string', 'max:255'], 36 | ]; 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /resources/stubs/install/app/Http/Livewire/Auth/ProfileUpdate.php: -------------------------------------------------------------------------------- 1 | setModel(Auth::user()->toArray()); 17 | } 18 | 19 | public function render() 20 | { 21 | return view('auth.profile-update'); 22 | } 23 | 24 | public function rules() 25 | { 26 | return [ 27 | 'name' => ['required', 'string', 'max:255'], 28 | 'email' => ['required', 'email', Rule::unique('users')->ignore(Auth::user()->id)], 29 | ]; 30 | } 31 | 32 | public function save() 33 | { 34 | $this->validateModel(); 35 | 36 | Auth::user()->update($this->getModel()); 37 | 38 | $this->emit('hideModal'); 39 | $this->emit('$refresh'); 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /resources/stubs/install/resources/views/auth/login.blade.php: -------------------------------------------------------------------------------- 1 | @section('title', __('Login')) 2 | 3 |
4 |
5 |
6 | @yield('title') 7 |
8 |
9 | 10 | 11 | 12 |
13 | 14 | 15 | @if(Route::has('password.forgot')) 16 | {{ __('Forgot password?') }} 17 | @endif 18 |
19 |
20 | 23 |
24 |
25 | -------------------------------------------------------------------------------- /resources/stubs/crud/read.blade.php: -------------------------------------------------------------------------------- 1 | 27 | -------------------------------------------------------------------------------- /resources/views/components/select.blade.php: -------------------------------------------------------------------------------- 1 | @props([ 2 | 'label' => null, 3 | 'options' => [], 4 | 'model', 5 | 'lazy' => false, 6 | ]) 7 | 8 | @php 9 | $options = Arr::isAssoc($options) ? $options : array_combine($options, $options); 10 | 11 | if ($lazy) $bind = '.lazy'; 12 | else $bind = '.defer'; 13 | 14 | $attributes = $attributes->class([ 15 | 'form-select', 16 | 'is-invalid' => $errors->has($model), 17 | ])->merge([ 18 | 'id' => $model, 19 | 'wire:model' . $bind => 'model.' . $model, 20 | ]); 21 | @endphp 22 | 23 |
24 | @if($label) 25 | 26 | @endif 27 | 28 | 35 | 36 | @error($model) 37 |
{{ $message }}
38 | @enderror 39 |
40 | -------------------------------------------------------------------------------- /resources/stubs/install/resources/views/layouts/app.blade.php: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | @yield('title') | {{ config('app.name') }} 9 | 10 | 11 | 12 | 13 | 14 | 15 | @stack('styles') 16 | 17 | 18 | 19 | 20 |
21 | {{ $slot }} 22 |
23 | 24 | 25 | 26 | 27 | 28 | @stack('scripts') 29 | 30 | 31 | -------------------------------------------------------------------------------- /resources/stubs/install/resources/views/users/save.blade.php: -------------------------------------------------------------------------------- 1 | 22 | -------------------------------------------------------------------------------- /resources/views/components/input.blade.php: -------------------------------------------------------------------------------- 1 | @props([ 2 | 'label' => null, 3 | 'type', 4 | 'model', 5 | 'lazy' => false, 6 | 'debounce' => false, 7 | ]) 8 | 9 | @php 10 | if ($type == 'number') $inputmode = 'decimal'; 11 | else if (in_array($type, ['tel', 'search', 'email', 'url'])) $inputmode = $type; 12 | else $inputmode = 'text'; 13 | 14 | if ($lazy) $bind = '.lazy'; 15 | else if (ctype_digit($debounce)) $bind = '.debounce.' . $debounce . 'ms'; 16 | else if ($debounce) $bind = ''; 17 | else $bind = '.defer'; 18 | 19 | $attributes = $attributes->class([ 20 | 'form-control', 21 | 'is-invalid' => $errors->has($model), 22 | ])->merge([ 23 | 'type' => $type, 24 | 'inputmode' => $inputmode, 25 | 'id' => $model, 26 | 'wire:model' . $bind => 'model.' . $model, 27 | ]); 28 | @endphp 29 | 30 |
31 | @if($label) 32 | 33 | @endif 34 | 35 | 36 | 37 | @error($model) 38 |
{{ $message }}
39 | @enderror 40 |
41 | -------------------------------------------------------------------------------- /config/ui.php: -------------------------------------------------------------------------------- 1 | env('UI_STUB_PATH', base_path('vendor/bastinald/ui/resources/stubs')), 17 | 18 | /* 19 | |-------------------------------------------------------------------------- 20 | | Font Awesome Style 21 | |-------------------------------------------------------------------------- 22 | | 23 | | This value is the styling that Font Awesome icons use by default via the 24 | | x-ui::icon component. Using the regular or light variations require a 25 | | Font Awesome Pro license. 26 | | 27 | | Supported: "solid", "regular", "light" 28 | | 29 | */ 30 | 31 | 'font_awesome_style' => env('UI_FONT_AWESOME_STYLE', 'solid'), 32 | 33 | ]; 34 | -------------------------------------------------------------------------------- /resources/stubs/install/app/Http/Livewire/Auth/PasswordForgot.php: -------------------------------------------------------------------------------- 1 | name('password.forgot') 20 | ->middleware('guest'); 21 | } 22 | 23 | public function render() 24 | { 25 | return view('auth.password-forgot'); 26 | } 27 | 28 | public function rules() 29 | { 30 | return [ 31 | 'email' => ['required', 'email'], 32 | ]; 33 | } 34 | 35 | public function send() 36 | { 37 | $this->validateModel(); 38 | 39 | $status = Password::sendResetLink($this->getModel(['email'])); 40 | 41 | if ($status != Password::RESET_LINK_SENT) { 42 | $this->reset('status'); 43 | $this->addError('email', __($status)); 44 | 45 | return; 46 | } 47 | 48 | $this->status = __($status); 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /src/Traits/WithModel.php: -------------------------------------------------------------------------------- 1 | model; 16 | } else if (is_array($key)) { 17 | return Arr::only($this->model, $key); 18 | } else { 19 | return Arr::get($this->model, $key, $default); 20 | } 21 | } 22 | 23 | public function setModel($key, $value = null) 24 | { 25 | if (is_array($key)) { 26 | foreach ($key as $arrayKey => $arrayValue) { 27 | Arr::set($this->model, $arrayKey, $arrayValue); 28 | } 29 | } else { 30 | Arr::set($this->model, $key, $value); 31 | } 32 | } 33 | 34 | public function resetModel() 35 | { 36 | $this->reset('model'); 37 | } 38 | 39 | public function validateModel($rules = null) 40 | { 41 | $validator = Validator::make($this->model, $rules ?? $this->getRules()); 42 | $validatedModel = $validator->validate(); 43 | 44 | $this->resetErrorBag(); 45 | 46 | return $validatedModel; 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /resources/views/components/radio.blade.php: -------------------------------------------------------------------------------- 1 | @props([ 2 | 'label' => null, 3 | 'options' => [], 4 | 'model', 5 | 'lazy' => false, 6 | ]) 7 | 8 | @php 9 | $options = Arr::isAssoc($options) ? $options : array_combine($options, $options); 10 | 11 | if ($lazy) $bind = '.lazy'; 12 | else $bind = '.defer'; 13 | 14 | $attributes = $attributes->class([ 15 | 'form-check-input', 16 | 'is-invalid' => $errors->has($model), 17 | ])->merge([ 18 | 'type' => 'radio', 19 | 'name' => $model, 20 | 'wire:model' . $bind => 'model.' . $model, 21 | ]); 22 | @endphp 23 | 24 |
25 | @if($label) 26 | 27 | @endif 28 | 29 | @foreach($options as $optionValue => $optionLabel) 30 |
31 | @php($optionId = $model . '.' . $loop->index) 32 | 33 | merge(['id' => $optionId, 'value' => $optionValue]) }}> 34 | 35 | 36 | 37 | @if($loop->last) 38 | @error($model) 39 |
{{ $message }}
40 | @enderror 41 | @endif 42 |
43 | @endforeach 44 |
45 | -------------------------------------------------------------------------------- /resources/stubs/install/resources/views/users/read.blade.php: -------------------------------------------------------------------------------- 1 | 36 | -------------------------------------------------------------------------------- /resources/stubs/install/app/Http/Livewire/Auth/Register.php: -------------------------------------------------------------------------------- 1 | name('register') 22 | ->middleware('guest'); 23 | } 24 | 25 | public function render() 26 | { 27 | return view('auth.register'); 28 | } 29 | 30 | public function rules() 31 | { 32 | return [ 33 | 'name' => ['required', 'string', 'max:255'], 34 | 'email' => ['required', 'email', Rule::unique('users')], 35 | 'password' => ['required', 'confirmed'], 36 | ]; 37 | } 38 | 39 | public function register() 40 | { 41 | $this->validateModel(); 42 | 43 | if (!$this->honeyPasses()) { 44 | return; 45 | } 46 | 47 | $user = User::create($this->getModel(['name', 'email', 'password'])); 48 | 49 | Auth::login($user, true); 50 | 51 | return redirect()->to(RouteServiceProvider::HOME); 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /resources/stubs/install/app/Http/Livewire/Auth/PasswordReset.php: -------------------------------------------------------------------------------- 1 | name('password.reset') 21 | ->middleware('guest'); 22 | } 23 | 24 | public function mount($token, $email) 25 | { 26 | $this->setModel([ 27 | 'token' => $token, 28 | 'email' => $email, 29 | ]); 30 | } 31 | 32 | public function render() 33 | { 34 | return view('auth.password-reset'); 35 | } 36 | 37 | public function rules() 38 | { 39 | return [ 40 | 'email' => ['required', 'email'], 41 | 'password' => ['required', 'confirmed'], 42 | ]; 43 | } 44 | 45 | public function resetPassword() 46 | { 47 | $this->validateModel(); 48 | 49 | $status = Password::reset($this->getModel(), function (User $user) { 50 | $user->update($this->getModel(['password'])); 51 | 52 | Auth::login($user, true); 53 | }); 54 | 55 | if ($status != Password::PASSWORD_RESET) { 56 | $this->addError('email', __($status)); 57 | 58 | return; 59 | } 60 | 61 | return redirect()->to(RouteServiceProvider::HOME); 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /resources/stubs/install/app/Http/Livewire/Auth/Login.php: -------------------------------------------------------------------------------- 1 | name('login') 21 | ->middleware('guest'); 22 | } 23 | 24 | public function render() 25 | { 26 | return view('auth.login'); 27 | } 28 | 29 | public function rules() 30 | { 31 | return [ 32 | 'email' => ['required', 'email'], 33 | 'password' => ['required'], 34 | ]; 35 | } 36 | 37 | public function login() 38 | { 39 | $this->validateModel(); 40 | 41 | $throttleKey = Str::lower($this->getModel('email')) . '|' . request()->ip(); 42 | 43 | if (RateLimiter::tooManyAttempts($throttleKey, 5)) { 44 | $this->addError('email', __('auth.throttle', [ 45 | 'seconds' => RateLimiter::availableIn($throttleKey), 46 | ])); 47 | 48 | return; 49 | } 50 | 51 | if (!Auth::attempt($this->getModel(['email', 'password']), $this->getModel('remember'))) { 52 | RateLimiter::hit($throttleKey); 53 | 54 | $this->addError('email', __('auth.failed')); 55 | 56 | return; 57 | } 58 | 59 | return redirect()->intended(RouteServiceProvider::HOME); 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /src/Providers/UiProvider.php: -------------------------------------------------------------------------------- 1 | app->runningInConsole()) { 19 | $this->commands([ 20 | ComponentCommand::class, 21 | CrudCommand::class, 22 | InstallCommand::class, 23 | MigrateCommand::class, 24 | ModelCommand::class, 25 | ]); 26 | } 27 | 28 | $this->loadRoutesFrom(__DIR__ . '/../../routes/web.php'); 29 | $this->loadViewsFrom(__DIR__ . '/../../resources/views', 'ui'); 30 | 31 | $this->publishes([ 32 | __DIR__ . '/../../config/ui.php' => config_path('ui.php'), 33 | ], ['ui', 'ui:config']); 34 | 35 | $this->publishes([ 36 | __DIR__ . '/../../resources/stubs/crud' => resource_path('stubs/vendor/ui/crud'), 37 | __DIR__ . '/../../resources/stubs/make' => resource_path('stubs/vendor/ui/make'), 38 | ], ['ui', 'ui:stubs']); 39 | 40 | $this->publishes([ 41 | __DIR__ . '/../../resources/views' => resource_path('views/vendor/ui'), 42 | ], ['ui', 'ui:views']); 43 | 44 | Livewire::component('modal', ModalComponent::class); 45 | } 46 | 47 | public function register() 48 | { 49 | $this->mergeConfigFrom(__DIR__ . '/../../config/ui.php', 'ui'); 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /resources/stubs/crud/Index.php: -------------------------------------------------------------------------------- 1 | name('DummyRouteName') 27 | ->middleware('auth'); 28 | } 29 | 30 | public function render() 31 | { 32 | return view('DummyViewName.index', [ 33 | 'DummyModelVariables' => $this->query()->paginate(), 34 | ]); 35 | } 36 | 37 | public function query() 38 | { 39 | $query = DummyModelClass::query(); 40 | 41 | if ($this->search) { 42 | $query->where(function (Builder $query) { 43 | $query->orWhere('name', 'like', '%' . $this->search . '%'); 44 | }); 45 | } 46 | 47 | switch ($this->sort) { 48 | case 'Name': $query->orderBy('name'); break; 49 | case 'Newest': $query->orderByDesc('created_at'); break; 50 | case 'Oldest': $query->orderBy('created_at'); break; 51 | } 52 | 53 | switch ($this->filter) { 54 | case 'All': break; 55 | case 'Custom': $query->whereNull('created_at'); break; 56 | } 57 | 58 | return $query; 59 | } 60 | 61 | public function delete(DummyModelClass $DummyModelVariable) 62 | { 63 | $DummyModelVariable->delete(); 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /resources/stubs/install/config/timezone.php: -------------------------------------------------------------------------------- 1 | 'laravel', 17 | 18 | /* 19 | |-------------------------------------------------------------------------- 20 | | Overwrite Existing Timezone 21 | |-------------------------------------------------------------------------- 22 | | 23 | | Here you may configure if you would like to overwrite existing 24 | | timezones if they have been already set in the database. 25 | | options [true, false] 26 | | 27 | */ 28 | 29 | 'overwrite' => true, 30 | 31 | /* 32 | |-------------------------------------------------------------------------- 33 | | Overwrite Default Format 34 | |-------------------------------------------------------------------------- 35 | | 36 | | Here you may configure if you would like to overwrite the 37 | | default format. 38 | | 39 | */ 40 | 41 | 'format' => 'M j Y g:i A', 42 | 43 | /* 44 | |-------------------------------------------------------------------------- 45 | | Lookup Array 46 | |-------------------------------------------------------------------------- 47 | | 48 | | Here you may configure the lookup array whom it will be used to fetch the user remote address. 49 | | When a key is found inside the lookup array that key it will be used. 50 | | 51 | */ 52 | 53 | 'lookup' => [ 54 | 'server' => [ 55 | 'REMOTE_ADDR', 56 | ], 57 | 'headers' => [ 58 | 59 | ], 60 | ], 61 | 62 | ]; 63 | -------------------------------------------------------------------------------- /resources/stubs/install/app/Http/Livewire/Users/Index.php: -------------------------------------------------------------------------------- 1 | name('users') 27 | ->middleware('auth'); 28 | } 29 | 30 | public function render() 31 | { 32 | return view('users.index', [ 33 | 'users' => $this->query()->paginate(), 34 | ]); 35 | } 36 | 37 | public function query() 38 | { 39 | $query = User::query(); 40 | 41 | if ($this->search) { 42 | $query->where(function (Builder $query) { 43 | $query->orWhere('name', 'like', '%' . $this->search . '%'); 44 | $query->orWhere('email', 'like', '%' . $this->search . '%'); 45 | }); 46 | } 47 | 48 | switch ($this->sort) { 49 | case 'Name': $query->orderBy('name'); break; 50 | case 'Newest': $query->orderByDesc('created_at'); break; 51 | case 'Oldest': $query->orderBy('created_at'); break; 52 | } 53 | 54 | switch ($this->filter) { 55 | case 'All': break; 56 | case 'Unverified': $query->whereNull('email_verified_at'); break; 57 | case 'Verified': $query->whereNotNull('email_verified_at'); break; 58 | } 59 | 60 | return $query; 61 | } 62 | 63 | public function delete(User $user) 64 | { 65 | $user->delete(); 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /resources/stubs/install/app/Models/User.php: -------------------------------------------------------------------------------- 1 | 'datetime']; 22 | 23 | public function migration(Blueprint $table) 24 | { 25 | $table->id(); 26 | $table->string('name')->index(); 27 | $table->string('email')->unique(); 28 | $table->string('password'); 29 | $table->rememberToken(); 30 | $table->string('timezone')->nullable(); 31 | $table->timestamp('email_verified_at')->nullable()->index(); 32 | $table->timestamp('created_at')->nullable()->index(); 33 | $table->timestamp('updated_at')->nullable(); 34 | } 35 | 36 | public function definition(Generator $faker) 37 | { 38 | return [ 39 | 'name' => $faker->firstName, 40 | 'email' => $faker->unique()->safeEmail, 41 | 'password' => '$2y$10$92IXUNpkjO0rOQ5byMi.Ye4oKoEa3Ro9llC/.og/at2.uheWG/igi', 42 | 'remember_token' => Str::random(10), 43 | 'timezone' => $faker->timezone, 44 | 'email_verified_at' => $faker->dateTimeBetween(now()->subMonth(), now()), 45 | 'created_at' => $faker->dateTimeBetween(now()->subMonth(), now()), 46 | ]; 47 | } 48 | 49 | public function rules() 50 | { 51 | return [ 52 | 'name' => ['required', 'string', 'max:255'], 53 | 'email' => ['required', 'email', Rule::unique('users')->ignore($this->id)], 54 | 'password' => [!$this->exists ? 'required' : 'nullable', 'confirmed'], 55 | ]; 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /src/Commands/ModelCommand.php: -------------------------------------------------------------------------------- 1 | setParsers(); 22 | 23 | if (file_exists($this->modelParser->classPath()) && !$this->option('force')) { 24 | $this->warn('Model already exists, use the --force to overwrite it!'); 25 | 26 | return; 27 | } 28 | 29 | $this->setStubReplaces(); 30 | $this->makeStubs(); 31 | 32 | $this->info('Model & factory made!'); 33 | } 34 | 35 | public function setParsers() 36 | { 37 | $this->modelParser = new ComponentParser( 38 | 'App\\Models', 39 | config('livewire.view_path'), 40 | $this->argument('class') 41 | ); 42 | 43 | $this->factoryParser = new ComponentParser( 44 | 'Database\\Factories', 45 | config('livewire.view_path'), 46 | $this->argument('class') . 'Factory' 47 | ); 48 | } 49 | 50 | public function setStubReplaces() 51 | { 52 | $this->stubReplaces = [ 53 | 'DummyModelNamespace' => $this->modelParser->classNamespace(), 54 | 'DummyModelClass' => $this->modelParser->className(), 55 | 'DummyFactoryNamespace' => $this->factoryParser->classNamespace(), 56 | 'DummyFactoryClass' => $this->factoryParser->className(), 57 | ]; 58 | } 59 | 60 | public function makeStubs() 61 | { 62 | $this->makeStub( 63 | $this->modelParser->classPath(), 64 | '/make/DummyModelClass.php' 65 | ); 66 | 67 | $this->makeStub( 68 | Str::replaceFirst('app/', '', $this->factoryParser->classPath()), 69 | '/make/DummyFactoryClass.php' 70 | ); 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /src/Commands/InstallCommand.php: -------------------------------------------------------------------------------- 1 | filesystem = new Filesystem; 18 | $this->filesystem->copyDirectory(__DIR__ . '/../../resources/stubs/install', base_path()); 19 | 20 | $this->determineIconVersion(); 21 | $this->deleteUserMigration(); 22 | $this->runCommands(); 23 | 24 | $this->info('UI installed! ' . config('app.url')); 25 | } 26 | 27 | public function determineIconVersion() 28 | { 29 | $version = $this->choice( 30 | 'Which version of Font Awesome?', 31 | ['Free', 'Pro (requires global NPM token config)'] 32 | ); 33 | 34 | if ($version != 'Free') { 35 | Artisan::call('vendor:publish --tag=ui:config'); 36 | 37 | $files = [base_path('package.json'), config_path('ui.php'), resource_path('scss/app.scss')]; 38 | 39 | foreach ($files as $file) { 40 | $contents = str_replace( 41 | ['@fortawesome/fontawesome-free', "'UI_FONT_AWESOME_STYLE', 'solid'"], 42 | ['@fortawesome/fontawesome-pro', "'UI_FONT_AWESOME_STYLE', 'regular'"], 43 | $this->filesystem->get($file) 44 | ); 45 | 46 | $this->filesystem->put($file, $contents); 47 | } 48 | } 49 | } 50 | 51 | public function deleteUserMigration() 52 | { 53 | $userMigration = database_path('migrations/2014_10_12_000000_create_users_table.php'); 54 | 55 | if ($this->filesystem->exists($userMigration)) { 56 | $this->filesystem->delete($userMigration); 57 | } 58 | } 59 | 60 | public function runCommands() 61 | { 62 | exec('npm install'); 63 | exec('npm run dev'); 64 | 65 | Artisan::call('ide-helper:generate'); 66 | Artisan::call('ui:migrate -fs'); 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /resources/stubs/install/resources/views/layouts/nav.blade.php: -------------------------------------------------------------------------------- 1 | 48 | -------------------------------------------------------------------------------- /src/Commands/ComponentCommand.php: -------------------------------------------------------------------------------- 1 | setParser(); 21 | 22 | if (file_exists($this->componentParser->classPath()) && !$this->option('force')) { 23 | $this->warn('Component already exists, use the --force to overwrite it!'); 24 | 25 | return; 26 | } 27 | 28 | $this->setStubReplaces(); 29 | $this->makeStubs(); 30 | 31 | $this->info('Component & view made!'); 32 | } 33 | 34 | public function setParser() 35 | { 36 | $this->componentParser = new ComponentParser( 37 | config('livewire.class_namespace'), 38 | config('livewire.view_path'), 39 | $this->argument('class') 40 | ); 41 | } 42 | 43 | public function setStubReplaces() 44 | { 45 | $this->stubReplaces = [ 46 | 'DummyComponentNamespace' => $this->componentParser->classNamespace(), 47 | 'DummyComponentClass' => $this->componentParser->className(), 48 | 'DummyRouteUri' => Str::replace('.', '/', $this->componentParser->viewName()), 49 | 'DummyRouteName' => $this->componentParser->viewName(), 50 | 'DummyViewName' => $this->componentParser->viewName(), 51 | 'DummyViewTitle' => preg_replace('/(.)(?=[A-Z])/u', '$1 ', $this->componentParser->className()), 52 | 'DummyWisdomOfTheTao' => $this->componentParser->wisdomOfTheTao(), 53 | ]; 54 | } 55 | 56 | public function makeStubs() 57 | { 58 | if ($this->option('full')) { 59 | $type = 'Full'; 60 | } else if ($this->option('modal')) { 61 | $type = 'Modal'; 62 | } else { 63 | $type = 'Partial'; 64 | } 65 | 66 | $this->makeStub( 67 | $this->componentParser->classPath(), 68 | '/make/DummyComponent' . $type . 'Class.php' 69 | ); 70 | 71 | $this->makeStub( 72 | $this->componentParser->viewPath(), 73 | '/make/DummyComponent' . $type . '.blade.php' 74 | ); 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /resources/stubs/crud/index.blade.php: -------------------------------------------------------------------------------- 1 | @section('title', __('DummyViewTitles')) 2 | 3 |
4 |

@yield('title')

5 | 6 |
7 |
8 |
9 | 10 | 12 |
13 |
14 |
15 | 18 | 19 | 20 | @foreach($sorts as $sort) 21 | 22 | @endforeach 23 | 24 | 25 | 26 | @foreach($filters as $filter) 27 | 28 | @endforeach 29 | 30 |
31 |
32 | 33 |
34 | @forelse($DummyModelVariables as $DummyModelVariable) 35 |
36 |
37 |
38 |
    39 |
  • {{ $DummyModelVariable->name }}
  • 40 |
  • @displayDate($DummyModelVariable->created_at)
  • 41 |
42 |
43 |
44 | 46 | 47 | 49 | 50 | 52 |
53 |
54 |
55 | @empty 56 |
57 | {{ __('No results found.') }} 58 |
59 | @endforelse 60 |
61 | 62 | 63 |
64 | -------------------------------------------------------------------------------- /resources/stubs/install/resources/views/users/index.blade.php: -------------------------------------------------------------------------------- 1 | @section('title', __('Users')) 2 | 3 |
4 |

@yield('title')

5 | 6 |
7 |
8 |
9 | 10 | 12 |
13 |
14 |
15 | 18 | 19 | 20 | @foreach($sorts as $sort) 21 | 22 | @endforeach 23 | 24 | 25 | 26 | @foreach($filters as $filter) 27 | 28 | @endforeach 29 | 30 |
31 |
32 | 33 |
34 | @forelse($users as $user) 35 |
36 |
37 |
38 |
    39 |
  • {{ $user->name }}
  • 40 |
  • @displayDate($user->created_at)
  • 41 |
42 |
43 |
44 | 46 | 47 | 49 | 50 | 52 | 53 | 55 |
56 |
57 |
58 | @empty 59 |
60 | {{ __('No results found.') }} 61 |
62 | @endforelse 63 |
64 | 65 | 66 |
67 | -------------------------------------------------------------------------------- /src/Commands/MigrateCommand.php: -------------------------------------------------------------------------------- 1 | environment('production') && !$this->option('force')) { 20 | $this->warn('You must use the --force to migrate in production!'); 21 | 22 | return; 23 | } 24 | 25 | $this->runTraditionalMigrations(); 26 | $this->runAutomaticMigrations(); 27 | 28 | if ($this->option('seed')) { 29 | $this->seed(); 30 | } 31 | } 32 | 33 | public function runTraditionalMigrations() 34 | { 35 | $command = 'migrate'; 36 | 37 | if ($this->option('fresh')) { 38 | $command .= ':fresh'; 39 | } 40 | 41 | if ($this->option('force')) { 42 | $command .= ' --force'; 43 | } 44 | 45 | Artisan::call($command); 46 | } 47 | 48 | public function runAutomaticMigrations() 49 | { 50 | $path = app_path('Models'); 51 | $namespace = app()->getNamespace(); 52 | 53 | foreach ((new Finder)->in($path) as $model) { 54 | $model = $namespace . str_replace( 55 | ['/', '.php'], 56 | ['\\', ''], 57 | Str::after($model->getRealPath(), realpath(app_path()) . DIRECTORY_SEPARATOR) 58 | ); 59 | 60 | if (method_exists($model, 'migration')) { 61 | $this->migrate($model); 62 | } 63 | } 64 | 65 | $this->info('Migration complete!'); 66 | } 67 | 68 | public function migrate($model) 69 | { 70 | $model = app($model); 71 | $modelTable = $model->getTable(); 72 | 73 | if (Schema::hasTable($modelTable)) { 74 | $tempTable = 'temp_' . $modelTable; 75 | 76 | Schema::dropIfExists($tempTable); 77 | Schema::create($tempTable, function (Blueprint $table) use ($model) { 78 | $model->migration($table); 79 | }); 80 | 81 | $schemaManager = $model->getConnection()->getDoctrineSchemaManager(); 82 | $modelTableDetails = $schemaManager->listTableDetails($modelTable); 83 | $tempTableDetails = $schemaManager->listTableDetails($tempTable); 84 | $tableDiff = (new Comparator)->diffTable($modelTableDetails, $tempTableDetails); 85 | 86 | if ($tableDiff) { 87 | $schemaManager->alterTable($tableDiff); 88 | } 89 | 90 | Schema::drop($tempTable); 91 | } else { 92 | Schema::create($modelTable, function (Blueprint $table) use ($model) { 93 | $model->migration($table); 94 | }); 95 | } 96 | } 97 | 98 | public function seed() 99 | { 100 | $command = 'db:seed'; 101 | 102 | if ($this->option('force')) { 103 | $command .= ' --force'; 104 | } 105 | 106 | Artisan::call($command); 107 | 108 | $this->info('Seeding complete!'); 109 | } 110 | } 111 | -------------------------------------------------------------------------------- /src/Commands/CrudCommand.php: -------------------------------------------------------------------------------- 1 | setParsers(); 23 | 24 | if (file_exists($this->componentParser->classPath()) && !$this->option('force')) { 25 | $this->warn('CRUD already exists, use the --force to overwrite it!'); 26 | 27 | return; 28 | } 29 | 30 | $this->setStubReplaces(); 31 | $this->makeStubs(); 32 | $this->makeModel(); 33 | 34 | $this->info('CRUD components & views made!'); 35 | } 36 | 37 | public function setParsers() 38 | { 39 | $this->componentParser = new ComponentParser( 40 | config('livewire.class_namespace'), 41 | config('livewire.view_path'), 42 | Str::plural($this->argument('class')) . '\\Index', 43 | ); 44 | 45 | $this->modelParser = new ComponentParser( 46 | 'App\\Models', 47 | config('livewire.view_path'), 48 | Str::singular(Arr::last(explode('\\', $this->componentParser->classNamespace()))), 49 | ); 50 | } 51 | 52 | public function setStubReplaces() 53 | { 54 | $title = preg_replace('/(.)(?=[A-Z])/u', '$1 ', $this->modelParser->className()); 55 | $titles = Str::plural($title); 56 | $viewName = Str::replaceLast('.index', '', $this->componentParser->viewName()); 57 | 58 | $this->stubReplaces = [ 59 | 'DummyComponentNamespace' => $this->componentParser->classNamespace(), 60 | 'DummyModelNamespace' => $this->modelParser->classNamespace(), 61 | 'DummyModelClass' => $this->modelParser->className(), 62 | 'DummyModelVariables' => Str::camel($titles), 63 | 'DummyModelVariable' => Str::camel($title), 64 | 'DummyRouteUri' => Str::replace('.', '/', $viewName), 65 | 'DummyRouteName' => $viewName, 66 | 'DummyViewName' => $viewName, 67 | 'DummyViewTitles' => $titles, 68 | 'DummyViewTitle' => $title, 69 | ]; 70 | } 71 | 72 | public function makeStubs() 73 | { 74 | $componentPath = Str::replaceLast('/Index.php', '', $this->componentParser->classPath()); 75 | $viewPath = Str::replaceLast('/index.blade.php', '', $this->componentParser->viewPath()); 76 | $stubNames = ['index', 'read', 'save']; 77 | 78 | foreach ($stubNames as $stubName) { 79 | $this->makeStub( 80 | $componentPath . '/' . Str::ucfirst($stubName) . '.php', 81 | '/crud/' . Str::ucfirst($stubName) . '.php' 82 | ); 83 | 84 | $this->makeStub( 85 | $viewPath . '/' . $stubName . '.blade.php', 86 | '/crud/' . $stubName . '.blade.php' 87 | ); 88 | } 89 | } 90 | 91 | public function makeModel() 92 | { 93 | $modelClass = $this->modelParser->className(); 94 | 95 | if (!file_exists($this->modelParser->classPath()) && 96 | $this->confirm('Model ' . $modelClass . ' does not exist, make it now?')) { 97 | Artisan::call('ui:model ' . $modelClass, [], $this->getOutput()); 98 | 99 | $this->warn("Don't forget to migrate after updating the model or CRUD will throw errors."); 100 | } 101 | } 102 | } 103 | -------------------------------------------------------------------------------- /resources/stubs/install/config/livewire.php: -------------------------------------------------------------------------------- 1 | 'App\\Http\\Livewire', 19 | 20 | /* 21 | |-------------------------------------------------------------------------- 22 | | View Path 23 | |-------------------------------------------------------------------------- 24 | | 25 | | This value sets the path for Livewire component views. This affects 26 | | file manipulation helper commands like `artisan make:livewire`. 27 | | 28 | */ 29 | 30 | 'view_path' => resource_path('views'), 31 | 32 | /* 33 | |-------------------------------------------------------------------------- 34 | | Layout 35 | |-------------------------------------------------------------------------- 36 | | The default layout view that will be used when rendering a component via 37 | | Route::get('/some-endpoint', SomeComponent::class);. In this case the 38 | | the view returned by SomeComponent will be wrapped in "layouts.app" 39 | | 40 | */ 41 | 42 | 'layout' => 'layouts.app', 43 | 44 | /* 45 | |-------------------------------------------------------------------------- 46 | | Livewire Assets URL 47 | |-------------------------------------------------------------------------- 48 | | 49 | | This value sets the path to Livewire JavaScript assets, for cases where 50 | | your app's domain root is not the correct path. By default, Livewire 51 | | will load its JavaScript assets from the app's "relative root". 52 | | 53 | | Examples: "/assets", "myurl.com/app". 54 | | 55 | */ 56 | 57 | 'asset_url' => null, 58 | 59 | /* 60 | |-------------------------------------------------------------------------- 61 | | Livewire Endpoint Middleware Group 62 | |-------------------------------------------------------------------------- 63 | | 64 | | This value sets the middleware group that will be applied to the main 65 | | Livewire "message" endpoint (the endpoint that gets hit everytime 66 | | a Livewire component updates). It is set to "web" by default. 67 | | 68 | */ 69 | 70 | 'middleware_group' => 'web', 71 | 72 | /* 73 | |-------------------------------------------------------------------------- 74 | | Livewire Temporary File Uploads Endpoint Configuration 75 | |-------------------------------------------------------------------------- 76 | | 77 | | Livewire handles file uploads by storing uploads in a temporary directory 78 | | before the file is validated and stored permanently. All file uploads 79 | | are directed to a global endpoint for temporary storage. The config 80 | | items below are used for customizing the way the endpoint works. 81 | | 82 | */ 83 | 84 | 'temporary_file_upload' => [ 85 | 'disk' => null, // Example: 'local', 's3' Default: 'default' 86 | 'rules' => null, // Example: ['file', 'mimes:png,jpg'] Default: ['required', 'file', 'max:12288'] (12MB) 87 | 'directory' => null, // Example: 'tmp' Default 'livewire-tmp' 88 | 'middleware' => null, // Example: 'throttle:5,1' Default: 'throttle:60,1' 89 | 'preview_mimes' => [ // Supported file types for temporary pre-signed file URLs. 90 | 'png', 'gif', 'bmp', 'svg', 'wav', 'mp4', 91 | 'mov', 'avi', 'wmv', 'mp3', 'm4a', 92 | 'jpg', 'jpeg', 'mpga', 'webp', 'wma', 93 | ], 94 | 'max_upload_time' => 5, // Max duration (in minutes) before an upload gets invalidated. 95 | ], 96 | 97 | /* 98 | |-------------------------------------------------------------------------- 99 | | Manifest File Path 100 | |-------------------------------------------------------------------------- 101 | | 102 | | This value sets the path to the Livewire manifest file. 103 | | The default should work for most cases (which is 104 | | "/bootstrap/cache/livewire-components.php)", but for specific 105 | | cases like when hosting on Laravel Vapor, it could be set to a different value. 106 | | 107 | | Example: for Laravel Vapor, it would be "/tmp/storage/bootstrap/cache/livewire-components.php". 108 | | 109 | */ 110 | 111 | 'manifest_path' => null, 112 | 113 | ]; 114 | -------------------------------------------------------------------------------- /resources/stubs/install/config/geoip.php: -------------------------------------------------------------------------------- 1 | true, 16 | 17 | /* 18 | |-------------------------------------------------------------------------- 19 | | Include Currency in Results 20 | |-------------------------------------------------------------------------- 21 | | 22 | | When enabled the system will do it's best in deciding the user's currency 23 | | by matching their ISO code to a preset list of currencies. 24 | | 25 | */ 26 | 27 | 'include_currency' => true, 28 | 29 | /* 30 | |-------------------------------------------------------------------------- 31 | | Default Service 32 | |-------------------------------------------------------------------------- 33 | | 34 | | Here you may specify the default storage driver that should be used 35 | | by the framework. 36 | | 37 | | Supported: "maxmind_database", "maxmind_api", "ipapi" 38 | | 39 | */ 40 | 41 | 'service' => 'ipapi', 42 | 43 | /* 44 | |-------------------------------------------------------------------------- 45 | | Storage Specific Configuration 46 | |-------------------------------------------------------------------------- 47 | | 48 | | Here you may configure as many storage drivers as you wish. 49 | | 50 | */ 51 | 52 | 'services' => [ 53 | 54 | 'maxmind_database' => [ 55 | 'class' => \Torann\GeoIP\Services\MaxMindDatabase::class, 56 | 'database_path' => storage_path('app/geoip.mmdb'), 57 | 'update_url' => sprintf('https://download.maxmind.com/app/geoip_download?edition_id=GeoLite2-City&license_key=%s&suffix=tar.gz', env('MAXMIND_LICENSE_KEY')), 58 | 'locales' => ['en'], 59 | ], 60 | 61 | 'maxmind_api' => [ 62 | 'class' => \Torann\GeoIP\Services\MaxMindWebService::class, 63 | 'user_id' => env('MAXMIND_USER_ID'), 64 | 'license_key' => env('MAXMIND_LICENSE_KEY'), 65 | 'locales' => ['en'], 66 | ], 67 | 68 | 'ipapi' => [ 69 | 'class' => \Torann\GeoIP\Services\IPApi::class, 70 | 'secure' => true, 71 | 'key' => env('IPAPI_KEY'), 72 | 'continent_path' => storage_path('app/continents.json'), 73 | 'lang' => 'en', 74 | ], 75 | 76 | 'ipgeolocation' => [ 77 | 'class' => \Torann\GeoIP\Services\IPGeoLocation::class, 78 | 'secure' => true, 79 | 'key' => env('IPGEOLOCATION_KEY'), 80 | 'continent_path' => storage_path('app/continents.json'), 81 | 'lang' => 'en', 82 | ], 83 | 84 | 'ipdata' => [ 85 | 'class' => \Torann\GeoIP\Services\IPData::class, 86 | 'key' => env('IPDATA_API_KEY'), 87 | 'secure' => true, 88 | ], 89 | 90 | 'ipfinder' => [ 91 | 'class' => \Torann\GeoIP\Services\IPFinder::class, 92 | 'key' => env('IPFINDER_API_KEY'), 93 | 'secure' => true, 94 | 'locales' => ['en'], 95 | ], 96 | 97 | ], 98 | 99 | /* 100 | |-------------------------------------------------------------------------- 101 | | Default Cache Driver 102 | |-------------------------------------------------------------------------- 103 | | 104 | | Here you may specify the type of caching that should be used 105 | | by the package. 106 | | 107 | | Options: 108 | | 109 | | all - All location are cached 110 | | some - Cache only the requesting user 111 | | none - Disable cached 112 | | 113 | */ 114 | 115 | 'cache' => 'all', 116 | 117 | /* 118 | |-------------------------------------------------------------------------- 119 | | Cache Tags 120 | |-------------------------------------------------------------------------- 121 | | 122 | | Cache tags are not supported when using the file or database cache 123 | | drivers in Laravel. This is done so that only locations can be cleared. 124 | | 125 | */ 126 | 127 | 'cache_tags' => null, 128 | 129 | /* 130 | |-------------------------------------------------------------------------- 131 | | Cache Expiration 132 | |-------------------------------------------------------------------------- 133 | | 134 | | Define how long cached location are valid. 135 | | 136 | */ 137 | 138 | 'cache_expires' => 30, 139 | 140 | /* 141 | |-------------------------------------------------------------------------- 142 | | Default Location 143 | |-------------------------------------------------------------------------- 144 | | 145 | | Return when a location is not found. 146 | | 147 | */ 148 | 149 | 'default_location' => [ 150 | 'ip' => '127.0.0.0', 151 | 'iso_code' => 'US', 152 | 'country' => 'United States', 153 | 'city' => 'New Haven', 154 | 'state' => 'CT', 155 | 'state_name' => 'Connecticut', 156 | 'postal_code' => '06510', 157 | 'lat' => 41.31, 158 | 'lon' => -72.92, 159 | 'timezone' => 'America/New_York', 160 | 'continent' => 'NA', 161 | 'default' => true, 162 | 'currency' => 'USD', 163 | ], 164 | 165 | ]; 166 | -------------------------------------------------------------------------------- /resources/stubs/install/config/database.php: -------------------------------------------------------------------------------- 1 | env('DB_CONNECTION', 'mysql'), 19 | 20 | /* 21 | |-------------------------------------------------------------------------- 22 | | Database Connections 23 | |-------------------------------------------------------------------------- 24 | | 25 | | Here are each of the database connections setup for your application. 26 | | Of course, examples of configuring each database platform that is 27 | | supported by Laravel is shown below to make development simple. 28 | | 29 | | 30 | | All database work in Laravel is done through the PHP PDO facilities 31 | | so make sure you have the driver for your particular database of 32 | | choice installed on your machine before you begin development. 33 | | 34 | */ 35 | 36 | 'connections' => [ 37 | 38 | 'sqlite' => [ 39 | 'driver' => 'sqlite', 40 | 'url' => env('DATABASE_URL'), 41 | 'database' => env('DB_DATABASE', database_path('database.sqlite')), 42 | 'prefix' => '', 43 | 'foreign_key_constraints' => env('DB_FOREIGN_KEYS', true), 44 | ], 45 | 46 | 'mysql' => [ 47 | 'driver' => 'mysql', 48 | 'url' => env('DATABASE_URL'), 49 | 'host' => env('DB_HOST', '127.0.0.1'), 50 | 'port' => env('DB_PORT', '3306'), 51 | 'database' => env('DB_DATABASE', 'forge'), 52 | 'username' => env('DB_USERNAME', 'forge'), 53 | 'password' => env('DB_PASSWORD', ''), 54 | 'unix_socket' => env('DB_SOCKET', ''), 55 | 'charset' => 'utf8mb4', 56 | 'collation' => 'utf8mb4_unicode_ci', 57 | 'prefix' => '', 58 | 'prefix_indexes' => true, 59 | 'strict' => true, 60 | 'engine' => null, 61 | 'options' => extension_loaded('pdo_mysql') ? array_filter([ 62 | PDO::MYSQL_ATTR_SSL_CA => env('MYSQL_ATTR_SSL_CA'), 63 | ]) : [], 64 | 'timezone' => '+00:00', 65 | ], 66 | 67 | 'pgsql' => [ 68 | 'driver' => 'pgsql', 69 | 'url' => env('DATABASE_URL'), 70 | 'host' => env('DB_HOST', '127.0.0.1'), 71 | 'port' => env('DB_PORT', '5432'), 72 | 'database' => env('DB_DATABASE', 'forge'), 73 | 'username' => env('DB_USERNAME', 'forge'), 74 | 'password' => env('DB_PASSWORD', ''), 75 | 'charset' => 'utf8', 76 | 'prefix' => '', 77 | 'prefix_indexes' => true, 78 | 'schema' => 'public', 79 | 'sslmode' => 'prefer', 80 | ], 81 | 82 | 'sqlsrv' => [ 83 | 'driver' => 'sqlsrv', 84 | 'url' => env('DATABASE_URL'), 85 | 'host' => env('DB_HOST', 'localhost'), 86 | 'port' => env('DB_PORT', '1433'), 87 | 'database' => env('DB_DATABASE', 'forge'), 88 | 'username' => env('DB_USERNAME', 'forge'), 89 | 'password' => env('DB_PASSWORD', ''), 90 | 'charset' => 'utf8', 91 | 'prefix' => '', 92 | 'prefix_indexes' => true, 93 | ], 94 | 95 | ], 96 | 97 | /* 98 | |-------------------------------------------------------------------------- 99 | | Migration Repository Table 100 | |-------------------------------------------------------------------------- 101 | | 102 | | This table keeps track of all the migrations that have already run for 103 | | your application. Using this information, we can determine which of 104 | | the migrations on disk haven't actually been run in the database. 105 | | 106 | */ 107 | 108 | 'migrations' => 'migrations', 109 | 110 | /* 111 | |-------------------------------------------------------------------------- 112 | | Redis Databases 113 | |-------------------------------------------------------------------------- 114 | | 115 | | Redis is an open source, fast, and advanced key-value store that also 116 | | provides a richer body of commands than a typical key-value system 117 | | such as APC or Memcached. Laravel makes it easy to dig right in. 118 | | 119 | */ 120 | 121 | 'redis' => [ 122 | 123 | 'client' => env('REDIS_CLIENT', 'phpredis'), 124 | 125 | 'options' => [ 126 | 'cluster' => env('REDIS_CLUSTER', 'redis'), 127 | 'prefix' => env('REDIS_PREFIX', Str::slug(env('APP_NAME', 'laravel'), '_').'_database_'), 128 | ], 129 | 130 | 'default' => [ 131 | 'url' => env('REDIS_URL'), 132 | 'host' => env('REDIS_HOST', '127.0.0.1'), 133 | 'password' => env('REDIS_PASSWORD', null), 134 | 'port' => env('REDIS_PORT', '6379'), 135 | 'database' => env('REDIS_DB', '0'), 136 | ], 137 | 138 | 'cache' => [ 139 | 'url' => env('REDIS_URL'), 140 | 'host' => env('REDIS_HOST', '127.0.0.1'), 141 | 'password' => env('REDIS_PASSWORD', null), 142 | 'port' => env('REDIS_PORT', '6379'), 143 | 'database' => env('REDIS_CACHE_DB', '1'), 144 | ], 145 | 146 | ], 147 | 148 | ]; 149 | -------------------------------------------------------------------------------- /resources/stubs/install/config/app.php: -------------------------------------------------------------------------------- 1 | env('APP_NAME', 'Laravel'), 17 | 18 | /* 19 | |-------------------------------------------------------------------------- 20 | | Application Version 21 | |-------------------------------------------------------------------------- 22 | | 23 | | This value is the version of your application. This value is used when 24 | | serving assets to your app users. It ensures that your users are always 25 | | using the most up-to-date assets. 26 | | 27 | */ 28 | 29 | 'version' => env('APP_VERSION', '1.0.0'), 30 | 31 | /* 32 | |-------------------------------------------------------------------------- 33 | | Application Environment 34 | |-------------------------------------------------------------------------- 35 | | 36 | | This value determines the "environment" your application is currently 37 | | running in. This may determine how you prefer to configure various 38 | | services the application utilizes. Set this in your ".env" file. 39 | | 40 | */ 41 | 42 | 'env' => env('APP_ENV', 'production'), 43 | 44 | /* 45 | |-------------------------------------------------------------------------- 46 | | Application Debug Mode 47 | |-------------------------------------------------------------------------- 48 | | 49 | | When your application is in debug mode, detailed error messages with 50 | | stack traces will be shown on every error that occurs within your 51 | | application. If disabled, a simple generic error page is shown. 52 | | 53 | */ 54 | 55 | 'debug' => (bool) env('APP_DEBUG', false), 56 | 57 | /* 58 | |-------------------------------------------------------------------------- 59 | | Application URL 60 | |-------------------------------------------------------------------------- 61 | | 62 | | This URL is used by the console to properly generate URLs when using 63 | | the Artisan command line tool. You should set this to the root of 64 | | your application so that it is used when running Artisan tasks. 65 | | 66 | */ 67 | 68 | 'url' => env('APP_URL', 'http://localhost'), 69 | 70 | 'asset_url' => env('ASSET_URL', null), 71 | 72 | /* 73 | |-------------------------------------------------------------------------- 74 | | Application Timezone 75 | |-------------------------------------------------------------------------- 76 | | 77 | | Here you may specify the default timezone for your application, which 78 | | will be used by the PHP date and date-time functions. We have gone 79 | | ahead and set this to a sensible default for you out of the box. 80 | | 81 | */ 82 | 83 | 'timezone' => 'UTC', 84 | 85 | /* 86 | |-------------------------------------------------------------------------- 87 | | Application Locale Configuration 88 | |-------------------------------------------------------------------------- 89 | | 90 | | The application locale determines the default locale that will be used 91 | | by the translation service provider. You are free to set this value 92 | | to any of the locales which will be supported by the application. 93 | | 94 | */ 95 | 96 | 'locale' => 'en', 97 | 98 | /* 99 | |-------------------------------------------------------------------------- 100 | | Application Fallback Locale 101 | |-------------------------------------------------------------------------- 102 | | 103 | | The fallback locale determines the locale to use when the current one 104 | | is not available. You may change the value to correspond to any of 105 | | the language folders that are provided through your application. 106 | | 107 | */ 108 | 109 | 'fallback_locale' => 'en', 110 | 111 | /* 112 | |-------------------------------------------------------------------------- 113 | | Faker Locale 114 | |-------------------------------------------------------------------------- 115 | | 116 | | This locale will be used by the Faker PHP library when generating fake 117 | | data for your database seeds. For example, this will be used to get 118 | | localized telephone numbers, street address information and more. 119 | | 120 | */ 121 | 122 | 'faker_locale' => 'en_US', 123 | 124 | /* 125 | |-------------------------------------------------------------------------- 126 | | Encryption Key 127 | |-------------------------------------------------------------------------- 128 | | 129 | | This key is used by the Illuminate encrypter service and should be set 130 | | to a random, 32 character string, otherwise these encrypted strings 131 | | will not be safe. Please do this before deploying an application! 132 | | 133 | */ 134 | 135 | 'key' => env('APP_KEY'), 136 | 137 | 'cipher' => 'AES-256-CBC', 138 | 139 | /* 140 | |-------------------------------------------------------------------------- 141 | | Autoloaded Service Providers 142 | |-------------------------------------------------------------------------- 143 | | 144 | | The service providers listed here will be automatically loaded on the 145 | | request to your application. Feel free to add your own services to 146 | | this array to grant expanded functionality to your applications. 147 | | 148 | */ 149 | 150 | 'providers' => [ 151 | 152 | /* 153 | * Laravel Framework Service Providers... 154 | */ 155 | Illuminate\Auth\AuthServiceProvider::class, 156 | Illuminate\Broadcasting\BroadcastServiceProvider::class, 157 | Illuminate\Bus\BusServiceProvider::class, 158 | Illuminate\Cache\CacheServiceProvider::class, 159 | Illuminate\Foundation\Providers\ConsoleSupportServiceProvider::class, 160 | Illuminate\Cookie\CookieServiceProvider::class, 161 | Illuminate\Database\DatabaseServiceProvider::class, 162 | Illuminate\Encryption\EncryptionServiceProvider::class, 163 | Illuminate\Filesystem\FilesystemServiceProvider::class, 164 | Illuminate\Foundation\Providers\FoundationServiceProvider::class, 165 | Illuminate\Hashing\HashServiceProvider::class, 166 | Illuminate\Mail\MailServiceProvider::class, 167 | Illuminate\Notifications\NotificationServiceProvider::class, 168 | Illuminate\Pagination\PaginationServiceProvider::class, 169 | Illuminate\Pipeline\PipelineServiceProvider::class, 170 | Illuminate\Queue\QueueServiceProvider::class, 171 | Illuminate\Redis\RedisServiceProvider::class, 172 | Illuminate\Auth\Passwords\PasswordResetServiceProvider::class, 173 | Illuminate\Session\SessionServiceProvider::class, 174 | Illuminate\Translation\TranslationServiceProvider::class, 175 | Illuminate\Validation\ValidationServiceProvider::class, 176 | Illuminate\View\ViewServiceProvider::class, 177 | 178 | /* 179 | * Package Service Providers... 180 | */ 181 | 182 | /* 183 | * Application Service Providers... 184 | */ 185 | App\Providers\AppServiceProvider::class, 186 | App\Providers\AuthServiceProvider::class, 187 | // App\Providers\BroadcastServiceProvider::class, 188 | App\Providers\EventServiceProvider::class, 189 | App\Providers\RouteServiceProvider::class, 190 | 191 | ], 192 | 193 | /* 194 | |-------------------------------------------------------------------------- 195 | | Class Aliases 196 | |-------------------------------------------------------------------------- 197 | | 198 | | This array of class aliases will be registered when this application 199 | | is started. However, feel free to register as many as you wish as 200 | | the aliases are "lazy" loaded so they don't hinder performance. 201 | | 202 | */ 203 | 204 | 'aliases' => [ 205 | 206 | 'App' => Illuminate\Support\Facades\App::class, 207 | 'Arr' => Illuminate\Support\Arr::class, 208 | 'Artisan' => Illuminate\Support\Facades\Artisan::class, 209 | 'Auth' => Illuminate\Support\Facades\Auth::class, 210 | 'Blade' => Illuminate\Support\Facades\Blade::class, 211 | 'Broadcast' => Illuminate\Support\Facades\Broadcast::class, 212 | 'Bus' => Illuminate\Support\Facades\Bus::class, 213 | 'Cache' => Illuminate\Support\Facades\Cache::class, 214 | 'Config' => Illuminate\Support\Facades\Config::class, 215 | 'Cookie' => Illuminate\Support\Facades\Cookie::class, 216 | 'Crypt' => Illuminate\Support\Facades\Crypt::class, 217 | 'Date' => Illuminate\Support\Facades\Date::class, 218 | 'DB' => Illuminate\Support\Facades\DB::class, 219 | 'Eloquent' => Illuminate\Database\Eloquent\Model::class, 220 | 'Event' => Illuminate\Support\Facades\Event::class, 221 | 'File' => Illuminate\Support\Facades\File::class, 222 | 'Gate' => Illuminate\Support\Facades\Gate::class, 223 | 'Hash' => Illuminate\Support\Facades\Hash::class, 224 | 'Http' => Illuminate\Support\Facades\Http::class, 225 | 'Lang' => Illuminate\Support\Facades\Lang::class, 226 | 'Log' => Illuminate\Support\Facades\Log::class, 227 | 'Mail' => Illuminate\Support\Facades\Mail::class, 228 | 'Notification' => Illuminate\Support\Facades\Notification::class, 229 | 'Password' => Illuminate\Support\Facades\Password::class, 230 | 'Queue' => Illuminate\Support\Facades\Queue::class, 231 | 'Redirect' => Illuminate\Support\Facades\Redirect::class, 232 | // 'Redis' => Illuminate\Support\Facades\Redis::class, 233 | 'Request' => Illuminate\Support\Facades\Request::class, 234 | 'Response' => Illuminate\Support\Facades\Response::class, 235 | 'Route' => Illuminate\Support\Facades\Route::class, 236 | 'Schema' => Illuminate\Support\Facades\Schema::class, 237 | 'Session' => Illuminate\Support\Facades\Session::class, 238 | 'Storage' => Illuminate\Support\Facades\Storage::class, 239 | 'Str' => Illuminate\Support\Str::class, 240 | 'URL' => Illuminate\Support\Facades\URL::class, 241 | 'Validator' => Illuminate\Support\Facades\Validator::class, 242 | 'View' => Illuminate\Support\Facades\View::class, 243 | 244 | ], 245 | 246 | ]; 247 | -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | ## bastinald/ui 2 | 3 | Laravel Livewire & Bootstrap 5 UI & CRUD starter kit. This package is a modernized version of the old `laravel/ui` package for developers who prefer using Bootstrap 5 and full page Livewire components to build their projects. It also comes with a few features to boost your development speed even more. 4 | 5 | ### Requirements 6 | 7 | - Laravel 8 8 | - NPM 9 | 10 | ### Features 11 | 12 | - Bootstrap 5 pre-configured 13 | - Full auth scaffolding including login, register, forgot password, profile updating, etc. 14 | - Commands for making models, components, and CRUD 15 | - PWA capabilities 16 | - Simply app versioning 17 | - Automatic model migrations 18 | - Automatic full page component routing 19 | - Automatic attribute hashing 20 | - Automatic user timezones 21 | - Easy form data manipulation via a single property 22 | - Dynamic Livewire Bootstrap modals 23 | - Custom Blade components 24 | - Font Awesome icons 25 | - & more 26 | 27 | ### Documentation 28 | 29 | - [Installation](#installation) 30 | - [Commands](#commands) 31 | - [Automatic Migrations](#automatic-migrations) 32 | - [Automatic Routing](#automatic-routing) 33 | - [Automatic Attribute Hashing](#automatic-attribute-hashing) 34 | - [Form Data Manipulation](#form-data-manipulation) 35 | - [Dynamic Bootstrap Modals](#dynamic-bootstrap-modals) 36 | - [Blade Components](#blade-components) 37 | - [Font Awesome Icons](#font-awesome-icons) 38 | - [Publishing Assets](#publishing-assets) 39 | 40 | ## Installation 41 | 42 | This package was designed to work with fresh Laravel projects. 43 | 44 | Install Laravel via Valet, Docker, or whatever you prefer: 45 | 46 | ```console 47 | laravel new my-project 48 | ``` 49 | 50 | Configure the `.env` app, database, and mail variables: 51 | 52 | ```env 53 | APP_* 54 | DB_* 55 | MAIL_* 56 | ``` 57 | 58 | Require this package via composer: 59 | 60 | ```console 61 | composer require bastinald/ui 62 | ``` 63 | 64 | Run the `ui:install` command: 65 | 66 | ```console 67 | php artisan ui:install 68 | ``` 69 | 70 | Once the installation is complete, you should be able to visit your app URL and login with `user@example.com` as the email, and `password` as the password. This was seeded for you to test with. 71 | 72 | ## Commands 73 | 74 | ### Installing UI 75 | 76 | ```console 77 | php artisan ui:install 78 | ``` 79 | 80 | This command will create your Livewire auth components & views, update your User model & factory, migrate & seed a default User, configure Bootstrap 5 JavaScript & SCSS through NPM/Webpack, create an IDE helper file, and run the necessary NPM commands. 81 | 82 | ### Making Models 83 | 84 | ```console 85 | php artisan ui:model {class} {--force} 86 | ``` 87 | 88 | This will make a model with an automatic `migration` method included. It will also make a factory for the model whose definition points to the model `definition` method. 89 | 90 | Use the `--force` to overwrite existing models & factories. 91 | 92 | ### Making Components 93 | 94 | ```console 95 | php artisan ui:component {class} {--f|--full} {--m|--modal} {--force} 96 | ``` 97 | 98 | This will make a Livewire component and view depending on which option you pass to it. Use the `-f` option to create a full page component with a `route` method, the `-m` option to create a modal component, or neither to create a partial component. 99 | 100 | Use the `--force` to overwrite existing components & views. 101 | 102 | ### Making CRUD 103 | 104 | ```console 105 | php artisan ui:crud {path} 106 | ``` 107 | 108 | This will make CRUD components & views for a given component path/namespace. This includes an index, create, read, update, and delete. It also comes with searching, sorting, and filtering, which is easily customizable inside the index component class. 109 | 110 | For making CRUD inside of subfolders, simply use slashes or dot notation: 111 | 112 | ```console 113 | # no subfolder 114 | php artisan ui:crud Users 115 | 116 | # in an "Admin" subfolder 117 | php artisan ui:crud Admin/Users 118 | ``` 119 | 120 | If the model (e.g. `User` in the example above) does not already exist when making CRUD, it will ask if you want to make it. After generating CRUD, all you need to do is add your model fields to the component views. Check out the `Users` component & views that come with the package when you run `ui:install` for an example. 121 | 122 | Use the `--force` to overwrite existing CRUD components & views. 123 | 124 | ### Running Automatic Migrations 125 | 126 | ```console 127 | php artisan ui:migrate {--f|--fresh} {--s|--seed} {--force} 128 | ``` 129 | 130 | This command goes through your model `migration` methods and compares their schema's with the existing database table schema's. If any changes need to be made, it applies them automatically via Doctrine. 131 | 132 | This command works well alongside traditional migration files. When you run this command, it will run your traditional migrations first, and the automatic migrations after. This is useful for cases where you don't need to couple a database table with a model (pivots, etc.). 133 | 134 | Use the `-f` option to wipe the database (fresh), and the `-s` option to run your seeders after migration is complete. The `--force` is required to run migrations in production environments. 135 | 136 | ## Automatic Migrations 137 | 138 | This package promotes the usage of automatic migrations. 139 | 140 | To use automatic migrations, specify a `migration` method inside your models: 141 | 142 | ```php 143 | use Illuminate\Database\Eloquent\Model; 144 | use Illuminate\Database\Schema\Blueprint; 145 | 146 | class User extends Model 147 | { 148 | public function migration(Blueprint $table) 149 | { 150 | $table->id(); 151 | $table->string('name'); 152 | $table->string('email')->unique(); 153 | $table->timestamp('email_verified_at')->nullable(); 154 | $table->string('password'); 155 | $table->rememberToken(); 156 | $table->timestamp('created_at')->nullable(); 157 | $table->timestamp('updated_at')->nullable(); 158 | } 159 | } 160 | ``` 161 | 162 | Or, make a new model via the `ui:model` command, which will include a `migration` method for you: 163 | 164 | ```console 165 | php artisan ui:model Vehicle 166 | ``` 167 | 168 | The `migration` method uses the `$table` Blueprint variable, just like in traditional Laravel migration files. As mentioned previously, when you run the `ui:migrate` command, it will compare your existing database table schema's to your model `migration` methods and apply the necessary changes via Doctrine. With this, you'll no longer have to manage tons of migration files. 169 | 170 | ## Automatic Routing 171 | 172 | This package also promotes the usage of automatic routing. 173 | 174 | To use automatic routing, specify a `route` method inside your full page Livewire components: 175 | 176 | ```php 177 | use Illuminate\Support\Facades\Route; 178 | use Livewire\Component; 179 | 180 | class Home extends Component 181 | { 182 | public function route() 183 | { 184 | return Route::get('/home', static::class) 185 | ->name('home') 186 | ->middleware('auth'); 187 | } 188 | } 189 | ``` 190 | 191 | Or, just run the `ui:component` command with the `-f` option to quickly make a full page component including a `route` method: 192 | 193 | ```console 194 | php artisan ui:component ContactUs -f 195 | ``` 196 | 197 | The `route` method returns the Laravel `Route` facade, just like you would use in route files. This means that your component route can do anything a normal Laravel route can do. These routes are registered through the package service provider automatically, so you'll no longer have to manage messy route files. 198 | 199 | ## Automatic Attribute Hashing 200 | 201 | The `HasHashes` traits allows you to specify model attributes you want to hash automatically when they are saving to the database. 202 | 203 | To use automatic hashing, use the `HashHashes` trait and specify a `hashes` property with the attributes that should be automatically hashed: 204 | 205 | ```php 206 | use Bastinald\Ui\Traits\HasHashes; 207 | use Illuminate\Foundation\Auth\User as Authenticatable; 208 | 209 | class User extends Authenticatable 210 | { 211 | use HasHashes; 212 | 213 | protected $hashes = ['password']; 214 | } 215 | ``` 216 | 217 | This trait will only automatically hash attribute values that are not already hashed, so it will not slow down seeders. 218 | 219 | ## Form Data Manipulation 220 | 221 | The `WithModel` traits makes managing form data inside your Livewire components easy. Normally, you'd have to specify a property for every one of your form inputs. With this trait, all of your form data will be present in a `$model` property array. This trait also comes with some handy methods to get, set, and validate the data. 222 | 223 | ### Binding Model Data 224 | 225 | Please note that all of the package `x-ui` Blade components will properly map the inputs to the component `$model` property and show relevant errors. If you are using your own HTML inputs, just be sure to prepend `model.` to the `wire:model` attribute. 226 | 227 | For example, if you're using the package components, just specify the `$model` key directly via the `model` attribute: 228 | 229 | ```html 230 | 231 | ``` 232 | 233 | If you're using your own HTML inputs, make sure you prepend `model.` to the `wire:model.*` attribute: 234 | 235 | ```html 236 | 237 | 238 | @error('first_name')

{{ $message }}

@enderror 239 | ``` 240 | 241 | Notice how you don't prepend `model.` to the `@error`. Error messages use the `$model` key via the `validateModel` method, so you only need to prepend `model.` on the inputs. 242 | 243 | ### Getting Model Data 244 | 245 | Getting all model data as an array: 246 | 247 | ```php 248 | $this->getModel(); 249 | ``` 250 | 251 | Getting an array of data: 252 | 253 | ```php 254 | $this->getModel(['email', 'password']); 255 | ``` 256 | 257 | If you pass an array to the `getModel` property, it will always return an array, even if you only use a single key. This is useful for quickly updating a single model column via `create` or `update`. 258 | 259 | Getting a single value: 260 | 261 | ```php 262 | $this->getModel('first_name', 'Joe'); 263 | ``` 264 | 265 | You can specify a default value via the second parameter, or omit it entirely. 266 | 267 | ### Setting Model Data 268 | 269 | Setting an array of values: 270 | 271 | ```php 272 | $this->setModel([ 273 | 'name' => 'Joe', 274 | 'email' => 'joe@example.com', 275 | ]); 276 | ``` 277 | 278 | Setting a single value: 279 | 280 | ```php 281 | $this->setModel('name', 'Joe'); 282 | ``` 283 | 284 | ### Resetting Model Data 285 | 286 | You can reset all model data easily: 287 | 288 | ```php 289 | $this->resetModel(); 290 | ``` 291 | 292 | ### Validating Model Data 293 | 294 | The `validateModel` method works the same as the Livewire `validate` method, but will use the `$model` data for validation. 295 | 296 | You can use it alongside a `rules` method: 297 | 298 | ```php 299 | public function rules() 300 | { 301 | return [ 302 | 'email' => ['required', 'email'], 303 | 'password' => ['required'], 304 | ]; 305 | } 306 | 307 | public function login() 308 | { 309 | $this->validateModel(); 310 | 311 | // log the user in 312 | } 313 | ``` 314 | 315 | Or by itself, with rules passed directly: 316 | 317 | ```php 318 | public function login() 319 | { 320 | $this->validateModel([ 321 | 'email' => ['required', 'email'], 322 | 'password' => ['required'], 323 | ]); 324 | 325 | // log the user in 326 | } 327 | ``` 328 | 329 | ## Dynamic Bootstrap Modals 330 | 331 | This package allows you to show Livewire components as modals dynamically by emitting a simple event. No more having to manage modal components everywhere in your views. 332 | 333 | ### Making Modals 334 | 335 | Just use the `ui:component` command with the `-m` option to make a new modal component: 336 | 337 | ```console 338 | php artisan ui:component TermsOfService -m 339 | ``` 340 | 341 | This will create a partial Livewire component and a view that contains the Bootstrap modal classes. 342 | 343 | ### Showing Modals 344 | 345 | To show modals, just emit the `showModal` event. 346 | 347 | You can emit this from your component views: 348 | 349 | ```html 350 | 353 | ``` 354 | 355 | Or from the component classes themselves: 356 | 357 | ```php 358 | $this->emit('showModal', 'auth.password-change'); 359 | ``` 360 | 361 | Notice that the second parameter is using the Livewire component class alias. So in this example, `auth.password-change` actually points to the `Auth\PasswordChange` component. 362 | 363 | ### Passing Mount Parameters 364 | 365 | You can pass any parameters you want to your modal component `mount` method by specifying them in the `showModal` event: 366 | 367 | Passing parameters via component views: 368 | 369 | ```html 370 | 373 | ``` 374 | 375 | Or from a component class: 376 | 377 | ```php 378 | $this->emit('showModal', 'users.update', $user->id); 379 | ``` 380 | 381 | Now, in our component `mount` method, we can use this parameter: 382 | 383 | ```php 384 | public $user; 385 | 386 | public function mount(User $user) 387 | { 388 | $this->user = $user; 389 | } 390 | ``` 391 | 392 | Notice how even model binding works here. If you need to pass more than one parameter, just keep adding them to the `showModal` emit, separated by a comma. 393 | 394 | ### Hiding Modals 395 | 396 | Hide the currently open modal via the `hideModal` event: 397 | 398 | ```html 399 | 402 | ``` 403 | 404 | Or, through component classes: 405 | 406 | ```php 407 | $this->emit('hideModal'); 408 | ``` 409 | 410 | You can also hide the modal through regular Bootstrap `data-bs-toggle` buttons: 411 | 412 | ```html 413 | 416 | ``` 417 | 418 | ## Blade Components 419 | 420 | This package comes with some handy Blade components, ensuring that you stay DRY, while keeping your markup nice and neat. 421 | 422 | ### Input 423 | 424 | A form input: 425 | 426 | ```html 427 | 428 | ``` 429 | 430 | Available props: 431 | 432 | - `label`: the input label 433 | - `type`: the input type e.g. `text`, `email`, `file` 434 | - `model`: the key for the component `$model` property 435 | - `lazy`: bind the model value on change 436 | - `debounce="x"`: debounce the model value after `x` ms (defaults to `150`) 437 | 438 | If `lazy` and `debounce` are not used, `defer` is the default. 439 | 440 | ### Textarea 441 | 442 | A textarea input: 443 | 444 | ```html 445 | 446 | ``` 447 | 448 | Available props: 449 | 450 | - `label`: the textarea label 451 | - `model`: the key for the component `$model` property 452 | - `lazy`: bind the model value on change 453 | - `debounce="x"`: debounce the model value after `x` ms (defaults to `150`) 454 | 455 | The `lazy` and `debounce` props work the same as the `input` component. 456 | 457 | ### Select 458 | 459 | A select input: 460 | 461 | ```html 462 | 463 | ``` 464 | 465 | Available props: 466 | 467 | - `label`: the select label 468 | - `options`: an array of select options 469 | - `model`: the key for the component `$model` property 470 | - `lazy`: bind the model value on change 471 | 472 | The `options` array can be an indexed or associative array. If the array is associative, the array keys will be used for the option values, and the array values will be used for the option labels. If the array is indexed, it's values will be used for both the option values and labels. 473 | 474 | ### Radio 475 | 476 | A radio input: 477 | 478 | ```html 479 | 480 | ``` 481 | 482 | Available props: 483 | 484 | - `label`: the radio label 485 | - `options`: an array of radio options 486 | - `model`: the key for the component `$model` property 487 | - `lazy`: bind the model value on change 488 | 489 | The `options` array works the same as the `select` component. 490 | 491 | ### Checkbox 492 | 493 | A checkbox input: 494 | 495 | ```html 496 | 497 | ``` 498 | 499 | Available props: 500 | 501 | - `label`: the checkbox label 502 | - `model`: the key for the component `$model` property 503 | - `lazy`: bind the model value on change 504 | 505 | ### Dropdown 506 | 507 | A dropdown button: 508 | 509 | ```html 510 | 511 | @foreach($filters as $filter) 512 | 513 | @endforeach 514 | 515 | ``` 516 | 517 | Available props: 518 | 519 | - `icon`: the dropdown button icon (Font Awesome) 520 | - `label`: the dropdown button label 521 | - `position`: the dropdown menu position (defaults to `end`) 522 | - `slot`: the dropdown items 523 | 524 | ### Dropdown Item 525 | 526 | A dropdown item button: 527 | 528 | ```html 529 | 530 | ``` 531 | 532 | Available props: 533 | 534 | - `label`: the dropdown item button label 535 | - `route`: the route to link to 536 | - `url`: the URL to link to 537 | - `href`: the link href 538 | - `click`: the Livewire click action 539 | 540 | ### Action 541 | 542 | A CRUD action button: 543 | 544 | ```html 545 | 547 | ``` 548 | 549 | Available props: 550 | 551 | - `icon`: the action button icon (Font Awesome) 552 | - `title`: the action button title 553 | - `route`: the route to link to 554 | - `url`: the URL to link to 555 | - `href`: the link href 556 | - `click`: the Livewire click action 557 | 558 | ### Pagination 559 | 560 | Responsive pagination links: 561 | 562 | ```html 563 | 564 | ``` 565 | 566 | Available props: 567 | 568 | - `links`: the pagination link results 569 | - `count`: show the count to the left (`true` or `false`) 570 | - `justify`: the justification for the links 571 | 572 | ### Icon 573 | 574 | A Font Awesome icon: 575 | 576 | ```html 577 | 578 | ``` 579 | 580 | Available props: 581 | 582 | - `name`: the icon name 583 | - `style`: the icon style e.g. `solid`, `regular` (default set in config) 584 | 585 | ## Font Awesome Icons 586 | 587 | When running the `ui:install` command, you are given the option to install Font Awesome free or pro. If you select pro, you are required to have a global NPM token configured. 588 | 589 | For information on how to configure this token, [please see the Font Awesome documentation](https://fontawesome.com/v5.15/how-to-use/on-the-web/setup/using-package-managers#installing-pro). 590 | 591 | ## Publishing Assets 592 | 593 | Publish the package config, stubs, and views via the `vendor:publish` command: 594 | 595 | ```console 596 | php artisan vendor:publish 597 | ``` 598 | 599 | Select `ui:config`, `ui:stubs`, `ui:views`, or `ui` for all assets. 600 | 601 | ### Using Custom Stubs 602 | 603 | Once you have published the package config and stub files, the stubs will be located in the `resources/stubs/vendor/ui` folder. 604 | 605 | Update the `config/ui.php` file and point the `stub_path` to this path: 606 | 607 | ```php 608 | 'stub_path' => resource_path('stubs/vendor/ui'), 609 | ``` 610 | 611 | The commands will now use this path for the stubs. Customize them to your needs. 612 | --------------------------------------------------------------------------------