diff --git a/app/Http/Controllers/Admin/AdminController.php b/app/Http/Controllers/Admin/AdminController.php new file mode 100644 index 0000000..ed69827 --- /dev/null +++ b/app/Http/Controllers/Admin/AdminController.php @@ -0,0 +1,13 @@ +delete(); + + return redirect()->route('admin.card.index') + ->with('info', __("Card #:id was deleted.", [ 'id' => $card->id ])); + } +} diff --git a/app/Http/Controllers/Admin/CharacterController.php b/app/Http/Controllers/Admin/CharacterController.php new file mode 100644 index 0000000..93c2692 --- /dev/null +++ b/app/Http/Controllers/Admin/CharacterController.php @@ -0,0 +1,29 @@ +delete(); + + return redirect()->route('admin.character.index') + ->with('info', __("Character #:id was deleted.", [ 'id' => $character->id ])); + } +} diff --git a/app/Http/Controllers/Admin/RaidController.php b/app/Http/Controllers/Admin/RaidController.php new file mode 100644 index 0000000..92261a2 --- /dev/null +++ b/app/Http/Controllers/Admin/RaidController.php @@ -0,0 +1,29 @@ +delete(); + + return redirect()->route('admin.raid.index') + ->with('info', __("Raid #:id was deleted.", [ 'id' => $raid->id ])); + } +} diff --git a/app/Http/Controllers/AuthController.php b/app/Http/Controllers/AuthController.php new file mode 100644 index 0000000..2b0ae3f --- /dev/null +++ b/app/Http/Controllers/AuthController.php @@ -0,0 +1,36 @@ +logout(); + + $request->session()->invalidate(); + + $request->session()->regenerateToken(); + + return redirect('/'); + } +} diff --git a/app/Http/Kernel.php b/app/Http/Kernel.php index 83a7dfb..45f9b58 100644 --- a/app/Http/Kernel.php +++ b/app/Http/Kernel.php @@ -34,4 +34,15 @@ class Kernel extends HttpKernel \Illuminate\View\Middleware\ShareErrorsFromSession::class, ] ]; + + /** + * The application's route middleware. + * + * These middleware may be assigned to groups or used individually. + * + * @var array + */ + protected $routeMiddleware = [ + 'auth' => \App\Http\Middleware\Authenticate::class + ]; } diff --git a/app/Http/Livewire/AlertContainer.php b/app/Http/Livewire/AlertContainer.php new file mode 100644 index 0000000..310aeb2 --- /dev/null +++ b/app/Http/Livewire/AlertContainer.php @@ -0,0 +1,58 @@ + 'addMessage' + ]; + + /** + * Alert messages. + */ + public array $messages = []; + + public function mount() + { + // Load messages from session + foreach($this->_types as $type) { + if (Session::has($type)) { + $this->addMessage($type, Session::get($type)); + } + } + } + + /** + * Add a message to the container. + */ + public function addMessage(string $type, string $message) + { + $this->messages[] = [ $type, $message ]; + } + + /** + * Render the messages + */ + public function render() + { + return view('livewire.alert-container'); + } +} diff --git a/app/Http/Livewire/Datatable.php b/app/Http/Livewire/Datatable.php new file mode 100644 index 0000000..2d2df3c --- /dev/null +++ b/app/Http/Livewire/Datatable.php @@ -0,0 +1,93 @@ + ['except' => 'id'], + 'dir' => ['except' => 'asc'], + 'page' => ['except' => 1] + ]; + + /** + * Model to get data from. + */ + public $model; + + /** + * Array of columns to show + */ + public array $columns; + + /** + * What colums are sortable + */ + public array $sort_columns; + + /** + * Route for editing a record (if null, link is omitted) + */ + public $route_edit; + + /** + * Route for deleting a record (if null, link is omitted) + */ + public $route_delete; + + /** + * How many records should be displayed on one page. + */ + public int $itemsPerPage = 30; + + public function mount(string $model, array $columns, array $sort_columns = [], + $default_sort = '', $route_delete = null, $route_edit = null) + { + $this->model = app()->make($model); + $this->sort = $default_sort; + $this->sort_columns = $sort_columns; + $this->route_edit = $route_edit; + $this->route_delete = $route_delete; + } + + /** + * Render the datatable + */ + public function render() + { + $query = $this->model->query(); + + // Check colums for "dot-notated" strings. + // as we want to eager load those relationships + foreach ($this->columns as $col => $_) { + $p = strrpos($col, '.'); + if ($p !== false) { + $rel = substr($col, 0, $p); + $query->with($rel); + } + } + + // Apply sorting. + $this->sortApply($query); + + $items = $query->paginate($this->itemsPerPage); + + return view('livewire.datatable', ['items' => $items]); + } + + public function getRouteKeyProperty() + { + return Str::slug(get_class($this->model)); + } +} diff --git a/app/Http/Livewire/Form/CardForm.php b/app/Http/Livewire/Form/CardForm.php new file mode 100644 index 0000000..186db8d --- /dev/null +++ b/app/Http/Livewire/Form/CardForm.php @@ -0,0 +1,60 @@ +record = $card; + $this->characters = Character::all()->pluck('name', 'id'); + $this->raids = Raid::all()->pluck('name', 'id'); + $this->classes = Wow::$classes; + $this->exist = $card->exists; + } + + /** + * Validation rules + */ + protected function rules() + { + return [ + 'record.body' => 'required|string|min:3|max:200', + 'record.character_id' => 'exists:' . Character::class . ',id|nullable', + 'record.raid_id' => 'exists:' . Raid::class . ',id|nullable', + 'record.class' => 'in:' . collect($this->classes)->keys() . '|nullable', + ]; + } + + public function updated($property, $value) + { + // Hack to force empty value to null. + if (in_array($property, ['record.character_id', 'record.raid_id', 'record.class'])) { + if (empty($value)) { + $this->{$property} = null; + } + } + + $this->validateOnly($property); + } +} diff --git a/app/Http/Livewire/Form/CharacterForm.php b/app/Http/Livewire/Form/CharacterForm.php new file mode 100644 index 0000000..8358107 --- /dev/null +++ b/app/Http/Livewire/Form/CharacterForm.php @@ -0,0 +1,29 @@ +record = $character; + $this->exist = $character->exists; + } + + /** + * Validation rules + */ + protected function rules() + { + return [ + 'record.name' => 'required|string|min:3|max:14', + ]; + } +} diff --git a/app/Http/Livewire/Form/LoginForm.php b/app/Http/Livewire/Form/LoginForm.php new file mode 100644 index 0000000..01568cf --- /dev/null +++ b/app/Http/Livewire/Form/LoginForm.php @@ -0,0 +1,43 @@ + 'required|string|min:3', + 'password' => 'required|string', + ]; + + public function submit() + { + $this->validate(); + + $cred = [ + 'username' => $this->username, + 'password' => $this->password + ]; + + if (!Auth::attempt($cred)) { + session()->flash('message', __('auth.failed')); + return; + } + + return redirect()->intended('/admin'); + } + + /** + * Render the setup page + */ + public function render() + { + return view('form.login'); + } +} diff --git a/app/Http/Livewire/Form/ModelForm.php b/app/Http/Livewire/Form/ModelForm.php new file mode 100644 index 0000000..a44d7c9 --- /dev/null +++ b/app/Http/Livewire/Form/ModelForm.php @@ -0,0 +1,76 @@ +record), '\\'); + } + + public function updated($property, $value) + { + $this->validateOnly($property); + } + + /** + * Returns true if this record has not been stored in the database. + */ + public function isNew() : bool + { + return !$this->exist; + } + + /** + * Submit the form, create/update record. + */ + public function submit() + { + $this->validate(); + $this->record->save(); + + if ($this->isNew()) { + return redirect()->route($this->redirect_route) + ->with('info', __("{$this->getModelName()} was successfully created.")); + } + + $this->info(__("{$this->getModelName()} was successfully updated.")); + } + + /** + * Render the setup page + */ + public function render() + { + $script = Str::lower($this->getModelName()); + + return view("form.$script") + ->layout('layouts.admin'); + } +} diff --git a/app/Http/Livewire/Form/RaidForm.php b/app/Http/Livewire/Form/RaidForm.php new file mode 100644 index 0000000..831e2dc --- /dev/null +++ b/app/Http/Livewire/Form/RaidForm.php @@ -0,0 +1,29 @@ +record = $raid; + $this->exist = $raid->exists; + } + + /** + * Validation rules + */ + protected function rules() + { + return [ + 'record.name' => 'required|string|min:2|max:20', + ]; + } +} diff --git a/app/Http/Livewire/Traits/Alert.php b/app/Http/Livewire/Traits/Alert.php new file mode 100644 index 0000000..125aa22 --- /dev/null +++ b/app/Http/Livewire/Traits/Alert.php @@ -0,0 +1,46 @@ +alert('info', $message); + } + + /** + * Add an success alert + */ + public function success($message) + { + $this->alert('success', $message); + } + + /** + * Add an warning alert + */ + public function warning($message) + { + $this->alert('warning', $message); + } + + /** + * Add an danger alert + */ + public function danger($message) + { + $this->alert('danger', $message); + } + + /** + * Add an alert + */ + public function alert($type, $message) + { + $this->emit('alert', $type, $message); + } +} diff --git a/app/Http/Livewire/Traits/WithSort.php b/app/Http/Livewire/Traits/WithSort.php new file mode 100644 index 0000000..854531c --- /dev/null +++ b/app/Http/Livewire/Traits/WithSort.php @@ -0,0 +1,51 @@ +sort_columns)) { + return; + } + + if ($this->sort === $column) { + $this->dir = $this->dir === 'asc' ? 'desc' : 'asc'; + } else { + $this->sort = $column; + $this->reset('dir'); + } + } + + protected function sortApply($query) + { + // No sorting. bail out. + if (!strlen($this->sort)) { + return; + } + + $columns = $this->sort_columns[$this->sort]; + + if (!is_array($columns)) { + $columns = [ $columns ]; + } + + foreach($columns as $column) { + $query->orderBy($column, $this->dir); + } + } +} diff --git a/app/Http/Middleware/Authenticate.php b/app/Http/Middleware/Authenticate.php new file mode 100644 index 0000000..704089a --- /dev/null +++ b/app/Http/Middleware/Authenticate.php @@ -0,0 +1,21 @@ +expectsJson()) { + return route('login'); + } + } +} diff --git a/app/Models/Admin.php b/app/Models/Admin.php new file mode 100644 index 0000000..85a0b73 --- /dev/null +++ b/app/Models/Admin.php @@ -0,0 +1,31 @@ +character) { + return "Character"; + } else if ($this->class) { + return "Class"; + } else if ($this->role) { + return "Roll"; + } + return "Anyone"; + } + /** * Get cards depending on settings. */ diff --git a/app/Models/Wow.php b/app/Models/Wow.php new file mode 100644 index 0000000..4c3be78 --- /dev/null +++ b/app/Models/Wow.php @@ -0,0 +1,21 @@ + 'Warrior', + 'rogue' => 'Rogue', + 'paladin' => 'Paladin', + 'hunter' => 'Hunter', + 'mage' => 'Mage', + 'warlock' => 'Warlock', + 'shaman' => 'Shaman', + 'druid' => 'Druid', + 'priest' => 'Priest' + ]; +} diff --git a/app/View/Components/Layout.php b/app/View/Components/Layout.php new file mode 100644 index 0000000..955ece4 --- /dev/null +++ b/app/View/Components/Layout.php @@ -0,0 +1,28 @@ +name = $name; + } + + public function render() + { + return view("layouts.{$this->name}"); + } +} diff --git a/composer.json b/composer.json index 6a653e5..2056f7b 100644 --- a/composer.json +++ b/composer.json @@ -6,11 +6,13 @@ "license": "MIT", "require": { "php": "^7.3|^8.0", + "blade-ui-kit/blade-heroicons": "^1.2", "fruitcake/laravel-cors": "^2.0", "guzzlehttp/guzzle": "^7.0.1", "laravel/framework": "^8.54", "laravel/tinker": "^2.5", "livewire/livewire": "^2.5", + "pnx/laravel-ignite": "dev-master", "vinkla/hashids": "^9.1" }, "require-dev": { @@ -22,6 +24,12 @@ "nunomaduro/collision": "^5.0", "phpunit/phpunit": "^9.3.3" }, + "repositories": [ + { + "type": "vcs", + "url": "https://github.com/pnx/laravel-ignite" + } + ], "autoload": { "psr-4": { "App\\": "app/", diff --git a/composer.lock b/composer.lock index 0d75bfe..008a4f6 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "6d2513918caf2a8560710d583f142529", + "content-hash": "78c4a9878ac3ea63b726daad902f8225", "packages": [ { "name": "asm89/stack-cors", @@ -62,6 +62,156 @@ }, "time": "2021-03-11T06:42:03+00:00" }, + { + "name": "blade-ui-kit/blade-heroicons", + "version": "1.2.0", + "source": { + "type": "git", + "url": "https://github.com/blade-ui-kit/blade-heroicons.git", + "reference": "99d82616a72e36492edc9b95c6a2112d8632e9a7" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/blade-ui-kit/blade-heroicons/zipball/99d82616a72e36492edc9b95c6a2112d8632e9a7", + "reference": "99d82616a72e36492edc9b95c6a2112d8632e9a7", + "shasum": "" + }, + "require": { + "blade-ui-kit/blade-icons": "^1.0", + "illuminate/support": "^8.0", + "php": "^7.4|^8.0" + }, + "require-dev": { + "orchestra/testbench": "^6.0", + "phpunit/phpunit": "^9.0" + }, + "type": "library", + "extra": { + "laravel": { + "providers": [ + "BladeUI\\Heroicons\\BladeHeroiconsServiceProvider" + ] + } + }, + "autoload": { + "psr-4": { + "BladeUI\\Heroicons\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Dries Vints", + "homepage": "https://driesvints.com" + } + ], + "description": "A package to easily make use of Heroicons in your Laravel Blade views.", + "homepage": "https://github.com/blade-ui-kit/blade-heroicons", + "keywords": [ + "Heroicons", + "blade", + "laravel" + ], + "support": { + "issues": "https://github.com/blade-ui-kit/blade-heroicons/issues", + "source": "https://github.com/blade-ui-kit/blade-heroicons/tree/1.2.0" + }, + "funding": [ + { + "url": "https://github.com/caneco", + "type": "github" + }, + { + "url": "https://github.com/driesvints", + "type": "github" + } + ], + "time": "2021-05-05T14:59:46+00:00" + }, + { + "name": "blade-ui-kit/blade-icons", + "version": "1.1.2", + "source": { + "type": "git", + "url": "https://github.com/blade-ui-kit/blade-icons.git", + "reference": "548869bc820a25f803530c82028482aefe08115f" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/blade-ui-kit/blade-icons/zipball/548869bc820a25f803530c82028482aefe08115f", + "reference": "548869bc820a25f803530c82028482aefe08115f", + "shasum": "" + }, + "require": { + "illuminate/contracts": "^8.0", + "illuminate/filesystem": "^8.0", + "illuminate/support": "^8.0", + "illuminate/view": "^8.0", + "php": "^7.4|^8.0", + "symfony/console": "^5.3", + "symfony/finder": "^5.3" + }, + "require-dev": { + "mockery/mockery": "^1.3", + "orchestra/testbench": "^6.0", + "phpunit/phpunit": "^9.0" + }, + "bin": [ + "bin/blade-icons-generate" + ], + "type": "library", + "extra": { + "laravel": { + "providers": [ + "BladeUI\\Icons\\BladeIconsServiceProvider" + ] + } + }, + "autoload": { + "psr-4": { + "BladeUI\\Icons\\": "src" + }, + "files": [ + "src/helpers.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Dries Vints", + "homepage": "https://driesvints.com" + } + ], + "description": "A package to easily make use of icons in your Laravel Blade views.", + "homepage": "https://github.com/blade-ui-kit/blade-icons", + "keywords": [ + "blade", + "icons", + "laravel", + "svg" + ], + "support": { + "issues": "https://github.com/blade-ui-kit/blade-icons/issues", + "source": "https://github.com/blade-ui-kit/blade-icons" + }, + "funding": [ + { + "url": "https://github.com/caneco", + "type": "github" + }, + { + "url": "https://github.com/driesvints", + "type": "github" + } + ], + "time": "2021-08-11T10:09:06+00:00" + }, { "name": "brick/math", "version": "0.9.3", @@ -2323,6 +2473,55 @@ ], "time": "2021-08-28T21:27:29+00:00" }, + { + "name": "pnx/laravel-ignite", + "version": "dev-master", + "source": { + "type": "git", + "url": "https://github.com/pnx/laravel-ignite.git", + "reference": "a40324f96ad834321c434cef24a95423b77d14eb" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/pnx/laravel-ignite/zipball/a40324f96ad834321c434cef24a95423b77d14eb", + "reference": "a40324f96ad834321c434cef24a95423b77d14eb", + "shasum": "" + }, + "require": { + "php": "^7.4|^8.0" + }, + "require-dev": { + "phpunit/phpunit": "^9.3" + }, + "suggest": { + "blade-ui-kit/blade-icons": "For adding icons to form elements." + }, + "default-branch": true, + "type": "library", + "extra": { + "laravel": { + "providers": [ + "Ignite\\FormServiceProvider" + ] + } + }, + "autoload": { + "psr-4": { + "Ignite\\": "src" + } + }, + "authors": [ + { + "name": "Henrik Hautakoski", + "email": "henrik.hautakoski@gmail.com" + } + ], + "support": { + "source": "https://github.com/pnx/laravel-ignite/tree/master", + "issues": "https://github.com/pnx/laravel-ignite/issues" + }, + "time": "2022-01-02T12:50:55+00:00" + }, { "name": "psr/container", "version": "1.1.1", @@ -8361,7 +8560,9 @@ ], "aliases": [], "minimum-stability": "dev", - "stability-flags": [], + "stability-flags": { + "pnx/laravel-ignite": 20 + }, "prefer-stable": true, "prefer-lowest": false, "platform": { diff --git a/config/auth.php b/config/auth.php index e29a3f7..40def03 100644 --- a/config/auth.php +++ b/config/auth.php @@ -14,8 +14,8 @@ return [ */ 'defaults' => [ - 'guard' => 'web', - 'passwords' => 'users', + 'guard' => 'admin', + 'passwords' => 'admins', ], /* @@ -36,9 +36,9 @@ return [ */ 'guards' => [ - 'web' => [ + 'admin' => [ 'driver' => 'session', - 'provider' => 'users', + 'provider' => 'admins', ], ], @@ -60,15 +60,10 @@ return [ */ 'providers' => [ - 'users' => [ + 'admins' => [ 'driver' => 'eloquent', - 'model' => App\Models\User::class, + 'model' => App\Models\Admin::class, ], - - // 'users' => [ - // 'driver' => 'database', - // 'table' => 'users', - // ], ], /* @@ -87,8 +82,8 @@ return [ */ 'passwords' => [ - 'users' => [ - 'provider' => 'users', + 'admins' => [ + 'provider' => 'admins', 'table' => 'password_resets', 'expire' => 60, 'throttle' => 60, diff --git a/config/ignite.php b/config/ignite.php new file mode 100644 index 0000000..eda9bd2 --- /dev/null +++ b/config/ignite.php @@ -0,0 +1,9 @@ + [ + 'icon' => 'heroicon-s-chevron-down', + ] + +]; diff --git a/database/migrations/2021_11_14_154143_create_admins_table.php b/database/migrations/2021_11_14_154143_create_admins_table.php new file mode 100644 index 0000000..18ca7e8 --- /dev/null +++ b/database/migrations/2021_11_14_154143_create_admins_table.php @@ -0,0 +1,24 @@ +id(); + $table->string('username')->unique(); + $table->string('password'); + $table->rememberToken(); + $table->timestamps(); + }); + } +} diff --git a/resources/lang/en/auth.php b/resources/lang/en/auth.php index 6598e2c..1cfb622 100644 --- a/resources/lang/en/auth.php +++ b/resources/lang/en/auth.php @@ -13,7 +13,7 @@ return [ | */ - 'failed' => 'These credentials do not match our records.', + 'failed' => 'Invalid username and/or password', 'password' => 'The provided password is incorrect.', 'throttle' => 'Too many login attempts. Please try again in :seconds seconds.', diff --git a/resources/lang/se.json b/resources/lang/se.json index f2e9ce2..71fbdc4 100644 --- a/resources/lang/se.json +++ b/resources/lang/se.json @@ -1,6 +1,9 @@ { + "Name": "Namn", "Loading": "Laddar", "Save": "Spara", + "New": "Ny", + "Back": "Tillbaka", "Somebody" : "Någon", "Create shareable URL": "Skapa en delbar URL", "Sharable URL": "Delbar URL", @@ -10,12 +13,42 @@ "Go again?" : "Köra igen?", "Game over!" : "Spelet slut!", "Winning rows": "Vinstrader", + "Class": "Klass", "Classes" : "Klasser", + "Character": "Karaktär", "Characters" : "Karaktärer", + "Card": "Kort", + "Cards": "Kort", "singular_somebody" : "Någon", "plurar_somebody" : "Några", "plurar_tank": "Tanken", - "plurar_paladin" : "Paladinen" + "plurar_paladin" : "Paladinen", + + "Actions": "Åtgärd", + + "Create card": "Skapa kort", + "Edit card": "Ändra kort", + "Card was successfully created.": "Kortet har lagts till.", + "Card was successfully updated.": "Kortet har uppdaterats.", + "Card #:id was deleted.": "Kort #:id togs bort.", + + "Create character": "Skapa karaktär", + "Edit character": "Ändra karaktär", + "Character was successfully created.": "Karaktären har lagts till.", + "Character was successfully updated.": "Karaktären har uppdaterats.", + "Character #:id was deleted.": "Karaktär #:id togs bort.", + + "Create raid": "Skapa raid", + "Edit raid": "Ändra raid", + "Raid was successfully created.": "Raiden har lagts till.", + "Raid was successfully updated.": "Raiden har uppdaterats.", + "Raid #:id was deleted.": "Raid #:id togs bort.", + + "Select a character": "Välj en karaktär", + "Select a class": "Välj en klass", + "Select a raid": "Välj en raid", + + "Logout": "Logga ut" } diff --git a/resources/lang/se/auth.php b/resources/lang/se/auth.php new file mode 100644 index 0000000..6c9bae0 --- /dev/null +++ b/resources/lang/se/auth.php @@ -0,0 +1,18 @@ + 'Ogiltigt användarnamn ock/eller lösenord', + 'password' => 'Lösenordet är felaktigt', + 'throttle' => 'För många inloggningsförsök. Var vänlig försök igen om :seconds sekunder.', +]; diff --git a/resources/lang/se/pagination.php b/resources/lang/se/pagination.php new file mode 100644 index 0000000..19b7de8 --- /dev/null +++ b/resources/lang/se/pagination.php @@ -0,0 +1,17 @@ + 'Nästa »', + 'previous' => '« Föregående', +]; diff --git a/resources/lang/se/passwords.php b/resources/lang/se/passwords.php new file mode 100644 index 0000000..08666ac --- /dev/null +++ b/resources/lang/se/passwords.php @@ -0,0 +1,20 @@ + 'Lösenordet har blivit återställt!', + 'sent' => 'Lösenordspåminnelse skickad!', + 'throttled' => 'Vänligen vänta innan du försöker igen.', + 'token' => 'Koden för lösenordsåterställning är ogiltig.', + 'user' => 'Det finns ingen användare med den e-postadressen.', +]; diff --git a/resources/lang/se/validation.php b/resources/lang/se/validation.php new file mode 100644 index 0000000..b5cfee4 --- /dev/null +++ b/resources/lang/se/validation.php @@ -0,0 +1,160 @@ + ':Attribute måste accepteras.', + 'accepted_if' => 'The :attribute must be accepted when :other is :value.', + 'active_url' => ':Attribute är inte en giltig webbadress.', + 'after' => ':Attribute måste vara ett datum efter :date.', + 'after_or_equal' => ':Attribute måste vara ett datum senare eller samma dag som :date.', + 'alpha' => ':Attribute får endast innehålla bokstäver.', + 'alpha_dash' => ':Attribute får endast innehålla bokstäver, siffror och bindestreck.', + 'alpha_num' => ':Attribute får endast innehålla bokstäver och siffror.', + 'array' => ':Attribute måste vara en array.', + 'attached' => 'Denna :attribute är redan bifogad.', + 'before' => ':Attribute måste vara ett datum innan :date.', + 'before_or_equal' => ':Attribute måste vara ett datum före eller samma dag som :date.', + 'between' => [ + 'array' => ':Attribute måste innehålla mellan :min - :max objekt.', + 'file' => ':Attribute måste vara mellan :min till :max kilobyte stor.', + 'numeric' => ':Attribute måste vara en siffra mellan :min och :max.', + 'string' => ':Attribute måste innehålla :min till :max tecken.', + ], + 'boolean' => ':Attribute måste vara sant eller falskt.', + 'confirmed' => ':Attribute bekräftelsen matchar inte.', + 'current_password' => 'The password is incorrect.', + 'date' => ':Attribute är inte ett giltigt datum.', + 'date_equals' => ':Attribute måste vara ett datum lika med :date.', + 'date_format' => ':Attribute matchar inte formatet :format.', + 'declined' => 'The :attribute must be declined.', + 'declined_if' => 'The :attribute must be declined when :other is :value.', + 'different' => ':Attribute och :other får inte vara lika.', + 'digits' => ':Attribute måste vara :digits tecken.', + 'digits_between' => ':Attribute måste vara mellan :min och :max tecken.', + 'dimensions' => ':Attribute har felaktiga bilddimensioner.', + 'distinct' => ':Attribute innehåller fler än en repetition av samma element.', + 'email' => ':Attribute måste innehålla en korrekt e-postadress.', + 'ends_with' => ':Attribute måste sluta med en av följande: :values.', + 'exists' => ':Attribute är ogiltigt.', + 'file' => ':Attribute måste vara en fil.', + 'filled' => ':Attribute är obligatoriskt.', + 'gt' => [ + 'array' => ':Attribute måste innehålla fler än :value objekt.', + 'file' => ':Attribute måste vara större än :value kilobyte stor.', + 'numeric' => ':Attribute måste vara större än :value.', + 'string' => ':Attribute måste vara längre än :value tecken.', + ], + 'gte' => [ + 'array' => ':Attribute måste innehålla lika många eller fler än :value objekt.', + 'file' => ':Attribute måste vara lika med eller större än :value kilobyte stor.', + 'numeric' => ':Attribute måste vara lika med eller större än :value.', + 'string' => ':Attribute måste vara lika med eller längre än :value tecken.', + ], + 'image' => ':Attribute måste vara en bild.', + 'in' => ':Attribute är ogiltigt.', + 'in_array' => ':Attribute finns inte i :other.', + 'integer' => ':Attribute måste vara en siffra.', + 'ip' => ':Attribute måste vara en giltig IP-adress.', + 'ipv4' => ':Attribute måste vara en giltig IPv4-adress.', + 'ipv6' => ':Attribute måste vara en giltig IPv6-adress.', + 'json' => ':Attribute måste vara en giltig JSON-sträng.', + 'lt' => [ + 'array' => ':Attribute måste innehålla färre än :value objekt.', + 'file' => ':Attribute måste vara mindre än :value kilobyte stor.', + 'numeric' => ':Attribute måste vara mindre än :value.', + 'string' => ':Attribute måste vara kortare än :value tecken.', + ], + 'lte' => [ + 'array' => ':Attribute måste innehålla lika många eller färre än :value objekt.', + 'file' => ':Attribute måste vara lika med eller mindre än :value kilobyte stor.', + 'numeric' => ':Attribute måste vara lika med eller mindre än :value.', + 'string' => ':Attribute måste vara lika med eller kortare än :value tecken.', + ], + 'max' => [ + 'array' => ':Attribute får inte innehålla mer än :max objekt.', + 'file' => ':Attribute får max vara :max kilobyte stor.', + 'numeric' => ':Attribute får inte vara större än :max.', + 'string' => ':Attribute får max innehålla :max tecken.', + ], + 'mimes' => ':Attribute måste vara en fil av typen: :values.', + 'mimetypes' => ':Attribute måste vara en fil av typen: :values.', + 'min' => [ + 'array' => ':Attribute måste innehålla minst :min objekt.', + 'file' => ':Attribute måste vara minst :min kilobyte stor.', + 'numeric' => ':Attribute måste vara större än :min.', + 'string' => ':Attribute måste innehålla minst :min tecken.', + ], + 'multiple_of' => ':attribute måste vara en multipel av :value', + 'not_in' => ':Attribute är ogiltigt.', + 'not_regex' => 'Formatet för :attribute är ogiltigt.', + 'numeric' => ':Attribute måste vara en siffra.', + 'password' => 'Lösenordet är fel.', + 'present' => ':Attribute måste finnas med.', + 'prohibited' => 'Fältet :attribute är förbjudet.', + 'prohibited_if' => ':attribute är förbjudet när :other är :value.', + 'prohibited_unless' => ':attribute är förbjudet om inte :other är :values.', + 'prohibits' => 'The :attribute field prohibits :other from being present.', + 'regex' => ':Attribute har ogiltigt format.', + 'relatable' => 'Denna :attribute kanske inte är associerad med den här resursen.', + 'required' => ':Attribute är obligatoriskt.', + 'required_if' => ':Attribute är obligatoriskt när :other är :value.', + 'required_unless' => ':Attribute är obligatoriskt när inte :other finns bland :values.', + 'required_with' => ':Attribute är obligatoriskt när :values är ifyllt.', + 'required_with_all' => ':Attribute är obligatoriskt när :values är ifyllt.', + 'required_without' => ':Attribute är obligatoriskt när :values ej är ifyllt.', + 'required_without_all' => ':Attribute är obligatoriskt när ingen av :values är ifyllt.', + 'same' => ':Attribute och :other måste vara lika.', + 'size' => [ + 'array' => ':Attribute måste innehålla :size objekt.', + 'file' => ':Attribute får endast vara :size kilobyte stor.', + 'numeric' => ':Attribute måste vara :size.', + 'string' => ':Attribute måste innehålla :size tecken.', + ], + 'starts_with' => ':Attribute måste börja med en av följande: :values', + 'string' => ':Attribute måste vara en sträng.', + 'timezone' => ':Attribute måste vara en giltig tidszon.', + 'unique' => ':Attribute används redan.', + 'uploaded' => ':Attribute kunde inte laddas upp.', + 'url' => ':Attribute har ett ogiltigt format.', + 'uuid' => ':Attribute måste vara ett giltigt UUID.', + + /* + |-------------------------------------------------------------------------- + | Custom Validation Language Lines + |-------------------------------------------------------------------------- + | + | Here you may specify custom validation messages for attributes using the + | convention "attribute.rule" to name the lines. This makes it quick to + | specify a specific custom language line for a given attribute rule. + | + */ + + 'custom' => [ + //'attribute-name' => [ + // 'rule-name' => 'custom-message', + //], + ], + + /* + |-------------------------------------------------------------------------- + | Custom Validation Attributes + |-------------------------------------------------------------------------- + | + | The following language lines are used to swap our attribute placeholder + | with something more reader friendly such as "E-Mail Address" instead + | of "email". This simply helps us make our message more expressive. + | + */ + + 'attributes' => [], +]; diff --git a/resources/views/admin/admin/index.blade.php b/resources/views/admin/admin/index.blade.php new file mode 100644 index 0000000..7dc6439 --- /dev/null +++ b/resources/views/admin/admin/index.blade.php @@ -0,0 +1,20 @@ + + + @livewire('datatable', [ + 'model' => \App\Models\Admin::class, + 'default_sort' => 'id', + 'columns' => [ + 'id' => '#', + 'username' => 'Username', + 'created_at' => 'Created', + 'updated_at' => 'Updated', + ], + 'sort_columns' => [ + 'id' => 'id', + 'username' => 'username', + 'created_at' => 'created_at', + 'updated_at' => 'updated_at' + ] + ]) + + diff --git a/resources/views/admin/card/index.blade.php b/resources/views/admin/card/index.blade.php new file mode 100644 index 0000000..e0599b7 --- /dev/null +++ b/resources/views/admin/card/index.blade.php @@ -0,0 +1,25 @@ + + +
+ {{ __('New') }} +
+ + @livewire('datatable', [ + 'model' => \App\Models\Card::class, + 'default_sort' => 'id', + 'route_edit' => 'admin.card.edit', + 'route_delete' => 'admin.card.delete', + 'columns' => [ + 'id' => '#', + 'body' => 'Body', + 'subject' => 'Subject', + 'subject_type' => 'Subject Type', + 'raid.name' => 'Raid', + ], + 'sort_columns' => [ + 'id' => 'id', + 'body' => 'body', + ] + ]) + +
diff --git a/resources/views/admin/character/index.blade.php b/resources/views/admin/character/index.blade.php new file mode 100644 index 0000000..a82bf7e --- /dev/null +++ b/resources/views/admin/character/index.blade.php @@ -0,0 +1,22 @@ + + +
+ {{ __('New') }} +
+ + @livewire('datatable', [ + 'model' => \App\Models\Character::class, + 'default_sort' => 'id', + 'route_edit' => 'admin.character.edit', + 'route_delete' => 'admin.character.delete', + 'columns' => [ + 'id' => '#', + 'name' => 'Name', + ], + 'sort_columns' => [ + 'id' => 'id', + 'name' => 'name', + ] + ]) + +
diff --git a/resources/views/admin/raid/index.blade.php b/resources/views/admin/raid/index.blade.php new file mode 100644 index 0000000..d0c7c5f --- /dev/null +++ b/resources/views/admin/raid/index.blade.php @@ -0,0 +1,22 @@ + + +
+ {{ __('New') }} +
+ + @livewire('datatable', [ + 'model' => \App\Models\Raid::class, + 'default_sort' => 'id', + 'route_edit' => 'admin.raid.edit', + 'route_delete' => 'admin.raid.delete', + 'columns' => [ + 'id' => '#', + 'name' => 'Name', + ], + 'sort_columns' => [ + 'id' => 'id', + 'name' => 'name', + ] + ]) + +
diff --git a/resources/views/auth/login.blade.php b/resources/views/auth/login.blade.php new file mode 100644 index 0000000..092ee5f --- /dev/null +++ b/resources/views/auth/login.blade.php @@ -0,0 +1,3 @@ + + @livewire('form.login-form') + diff --git a/resources/views/components/button.blade.php b/resources/views/components/button.blade.php index a6f159b..3c2b58f 100644 --- a/resources/views/components/button.blade.php +++ b/resources/views/components/button.blade.php @@ -1,4 +1,4 @@ -@props(['href' => false, 'type' => 'default']) +@props(['href' => false, 'type' => 'default', 'element' => false]) @php @@ -22,6 +22,8 @@ default: if ($href) { $element = 'a'; $attributes = $attributes->merge([ 'href' => $href ]); +} else if ($element == 'input') { + $attributes = $attributes->merge([ 'type' => 'submit' ]); } else { $element = 'button'; } diff --git a/resources/views/components/header.blade.php b/resources/views/components/header.blade.php new file mode 100644 index 0000000..77b8820 --- /dev/null +++ b/resources/views/components/header.blade.php @@ -0,0 +1 @@ +

merge(["class" => "text-2xl"]) !!}>{{ $slot }}

diff --git a/resources/views/components/notification.blade.php b/resources/views/components/notification.blade.php new file mode 100644 index 0000000..d5d6da7 --- /dev/null +++ b/resources/views/components/notification.blade.php @@ -0,0 +1,27 @@ +@props(['variant' => 'info', 'delay' => 2000]) +@php + +switch($variant) { +case 'success': + $variant_classes = 'text-green-800 bg-green-200 border-green-300'; + break; +case 'warning': + $variant_classes = 'text-yellow-800 bg-yellow-200 border-yellow-300'; + break; +case 'danger': + $variant_classes = 'text-red-800 bg-red-200 border-red-300'; + break; +case 'info': +default: + $variant_classes = 'text-blue-800 bg-blue-200 border-blue-300'; +} + +@endphp + +
merge(['class' => "w-full px-4 py-2 rounded border $variant_classes"]) !!}> + + {{ $slot }} +
diff --git a/resources/views/components/sort-link.blade.php b/resources/views/components/sort-link.blade.php new file mode 100644 index 0000000..ac52fa1 --- /dev/null +++ b/resources/views/components/sort-link.blade.php @@ -0,0 +1,5 @@ +@props(['name', 'column', 'dir']) + +@if ($name === $column) +only('class') }} /> +@endif diff --git a/resources/views/form/card.blade.php b/resources/views/form/card.blade.php new file mode 100644 index 0000000..c53df83 --- /dev/null +++ b/resources/views/form/card.blade.php @@ -0,0 +1,32 @@ + +
+ + {{ __(($this->isNew() ? 'Create' : 'Edit') . ' card') }} + +
+ +
+ {{ __('Body') }} + +
+ +
+
+ {{ __('Raid') }} + +
+
+ {{ __('Character') }} + +
+
+ {{ __('Class') }} + +
+
+ + {{ __('Save') }} + {{ __('Back') }} +
+ +
diff --git a/resources/views/form/character.blade.php b/resources/views/form/character.blade.php new file mode 100644 index 0000000..ba79495 --- /dev/null +++ b/resources/views/form/character.blade.php @@ -0,0 +1,17 @@ + +
+ + {{ __(($this->isNew() ? 'Create' : 'Edit') . ' character') }} + +
+ +
+ {{ __('Name') }} + +
+ + {{ __('Save') }} + {{ __('Back') }} +
+ +
diff --git a/resources/views/form/login.blade.php b/resources/views/form/login.blade.php new file mode 100644 index 0000000..136441e --- /dev/null +++ b/resources/views/form/login.blade.php @@ -0,0 +1,17 @@ +
+ @if (Session::has('message')) +

{{ Session::get('message') }}

+ @endif + +
+ Username + +
+ +
+ + +
+ + + diff --git a/resources/views/form/raid.blade.php b/resources/views/form/raid.blade.php new file mode 100644 index 0000000..f09b4ee --- /dev/null +++ b/resources/views/form/raid.blade.php @@ -0,0 +1,17 @@ + +
+ + {{ __(($this->isNew() ? 'Create' : 'Edit') . ' raid') }} + +
+ +
+ {{ __('Name') }} + +
+ + {{ __('Save') }} + {{ __('Back') }} +
+ +
diff --git a/resources/views/layouts/admin.blade.php b/resources/views/layouts/admin.blade.php new file mode 100644 index 0000000..6bb84ca --- /dev/null +++ b/resources/views/layouts/admin.blade.php @@ -0,0 +1,37 @@ +@php + $item_classes = "flex items-center inline-block w-full px-4 py-2 rounded-lg hover:bg-blue-800"; +@endphp + + + + diff --git a/resources/views/layouts/app.blade.php b/resources/views/layouts/app.blade.php index a18a1c4..9ef3ea9 100644 --- a/resources/views/layouts/app.blade.php +++ b/resources/views/layouts/app.blade.php @@ -6,11 +6,15 @@ Bingo! @livewireStyles + - + + + {{ $slot }} @livewireScripts +@igniteScripts diff --git a/resources/views/layouts/auth.blade.php b/resources/views/layouts/auth.blade.php new file mode 100644 index 0000000..9803195 --- /dev/null +++ b/resources/views/layouts/auth.blade.php @@ -0,0 +1,5 @@ + +
+ {{ $slot }} +
+
diff --git a/resources/views/livewire/alert-container.blade.php b/resources/views/livewire/alert-container.blade.php new file mode 100644 index 0000000..dc6726e --- /dev/null +++ b/resources/views/livewire/alert-container.blade.php @@ -0,0 +1,5 @@ +
+ @foreach($messages as $message) + {{ $message[1] }} + @endforeach +
diff --git a/resources/views/livewire/datatable.blade.php b/resources/views/livewire/datatable.blade.php new file mode 100644 index 0000000..db46489 --- /dev/null +++ b/resources/views/livewire/datatable.blade.php @@ -0,0 +1,59 @@ +
+ + + + + @foreach($columns as $key => $name) + + @endforeach + + @if($route_delete || $route_edit) + + @endif + + + + @foreach($items as $item) + + @foreach(array_keys($columns) as $key) + + @endforeach + + @if ($route_edit || $route_delete) + + @endif + + @endforeach + +
+ @if (isset($sort_columns[$key])) +
+ {{ __($name) }} + +
+ @else + {{ $name }} + @endif +
{{ __('Actions') }}
{{ Arr::get($item, $key) }} + @if($route_edit) + + + + @endif + + @if($route_delete) + + + + + + @endif +
+ + @if ($items->hasPages()) +
+ {{ $items->links() }} +
+ @endif + +
diff --git a/routes/web.php b/routes/web.php index b7b5974..a15ca45 100644 --- a/routes/web.php +++ b/routes/web.php @@ -5,6 +5,12 @@ use Illuminate\Support\Facades\Route; use App\Http\Livewire\Game; use App\Http\Livewire\Setup; use App\Http\Controllers\SettingController; +use App\Http\Controllers\AuthController; + +use App\Http\Controllers\Admin\AdminController; +use App\Http\Controllers\Admin\CardController; +use App\Http\Controllers\Admin\CharacterController; +use App\Http\Controllers\Admin\RaidController; use Illuminate\Support\Facades\Session; @@ -28,3 +34,33 @@ Route::get('/setup', Setup::class)->name('setup'); route::get('/setup/{setting}', [ SettingController::class, "hash" ])->name('setup-hash'); Route::get('/game', Game::class)->name('game'); + +// Admin section +Route::prefix('admin')->group(function() { + + // Auth + Route::get('/login', [AuthController::class, 'create'])->name('login'); + Route::post('/login', [AuthController::class, 'store']); + + Route::middleware(['auth'])->group(function() { + + Route::get('/', [AdminController::class, 'index']); + + Route::get('/cards', [CardController::class, 'index'])->name('admin.card.index'); + Route::get('/cards/new', CardController::getForm())->name('admin.card.create'); + Route::get('/cards/{card}', CardController::getForm())->name('admin.card.edit'); + Route::delete('/cards/{card}', [CardController::class, 'destroy'])->name('admin.card.delete'); + + Route::get('/characters', [CharacterController::class, 'index'])->name('admin.character.index'); + Route::get('/characters/new', CharacterController::getForm())->name('admin.character.create'); + Route::get('/characters/{character}', CharacterController::getForm())->name('admin.character.edit'); + Route::delete('/characters/{character}', [CharacterController::class, 'destroy'])->name('admin.character.delete'); + + Route::get('/raids', [RaidController::class, 'index'])->name('admin.raid.index'); + Route::get('/raids/new', RaidController::getForm())->name('admin.raid.create'); + Route::get('/raids/{raid}', RaidController::getForm())->name('admin.raid.edit'); + Route::delete('/raids/{raid}', [RaidController::class, 'destroy'])->name('admin.raid.delete'); + + Route::post('/logout', [AuthController::class, 'destroy'])->name('logout'); + }); +}); diff --git a/tailwind.config.js b/tailwind.config.js index 47b44b9..84e67e2 100644 --- a/tailwind.config.js +++ b/tailwind.config.js @@ -24,7 +24,9 @@ module.exports = { }, }, variants: { - extend: {}, + extend: { + backgroundColor: ['odd'], + }, }, plugins: [ plugin(function({ addUtilities }) {