From be1f238f8963c9c5b10916812d80724710ca6779 Mon Sep 17 00:00:00 2001 From: Dmitry Gammel Date: Wed, 12 Aug 2026 10:52:04 +0500 Subject: [PATCH] =?UTF-8?q?=D0=9F=D0=B5=D1=80=D0=B5=D0=B2=D0=BE=D0=B4=20?= =?UTF-8?q?=D0=B7=D0=B0=D1=89=D0=B8=D1=89=D1=91=D0=BD=D0=BD=D0=BE=D0=B9=20?= =?UTF-8?q?=D1=87=D0=B0=D1=81=D1=82=D0=B8=20=D0=BF=D1=80=D0=B8=D0=BB=D0=BE?= =?UTF-8?q?=D0=B6=D0=B5=D0=BD=D0=B8=D1=8F=20=D0=BD=D0=B0=20Vue.js=20+=20In?= =?UTF-8?q?ertia?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Дашборд, зоны, устройства, правила автоматизации и профиль теперь Vue 3 + Inertia вместо Blade+Alpine — гостевые страницы Breeze (логин/регистрация) сознательно оставлены на Blade, чтобы не трогать уже протестированную аутентификацию. Контроллеры возвращают Inertia::render() вместо view(), переиспользуют существующие API Resources и FormRequest. Ключевой нюанс: Resource/ResourceCollection, переданные напрямую в проп Inertia, заворачиваются в ключ "data" (Inertia вызывает toResponse() у Responsable-объектов) — везде используется ->resolve() и явное резолвление вложенных ресурсов (zone/device_type внутри Device и т.п.), иначе вложенные поля тоже задваивались бы обёрткой. Alpine.js и все Blade-вьюхи защищённой части удалены как мёртвый код. Тесты (ZoneControllerTest/DeviceControllerTest) переведены на assertInertia(). UserFactory теперь явно задаёт role=owner — без этого Eloquent не подтягивает DB-default обратно в модель после create(), что уронило бы общий auth.user проп на любой странице. --- .../Controllers/AutomationRuleController.php | 34 +- .../app/Http/Controllers/DeviceController.php | 32 +- .../Http/Controllers/ProfileController.php | 9 +- .../app/Http/Controllers/ZoneController.php | 12 +- .../Http/Middleware/HandleInertiaRequests.php | 36 ++ .../Http/Resources/AutomationRuleResource.php | 8 +- .../app/Http/Resources/DeviceResource.php | 9 +- .../app/Services/DeviceControlClient.php | 3 +- laravel-app/app/View/Components/AppLayout.php | 17 - laravel-app/bootstrap/app.php | 5 +- laravel-app/composer.json | 1 + laravel-app/composer.lock | 74 +++- .../database/factories/UserFactory.php | 5 + laravel-app/package-lock.json | 390 ++++++++++++++---- laravel-app/package.json | 6 +- .../js/Components/ApplicationLogo.vue | 11 + .../resources/js/Components/NavLink.vue | 29 ++ .../resources/js/Components/StatusBadge.vue | 15 + .../resources/js/Layouts/AppLayout.vue | 143 +++++++ .../js/Pages/AutomationRules/Create.vue | 34 ++ .../js/Pages/AutomationRules/Edit.vue | 36 ++ .../js/Pages/AutomationRules/Form.vue | 176 ++++++++ .../js/Pages/AutomationRules/Index.vue | 78 ++++ laravel-app/resources/js/Pages/Dashboard.vue | 41 ++ .../resources/js/Pages/Devices/Create.vue | 25 ++ .../resources/js/Pages/Devices/Edit.vue | 26 ++ .../resources/js/Pages/Devices/Form.vue | 108 +++++ .../resources/js/Pages/Devices/Index.vue | 73 ++++ .../resources/js/Pages/Devices/Show.vue | 132 ++++++ .../resources/js/Pages/Profile/Edit.vue | 40 ++ .../Pages/Profile/Partials/DeleteUserForm.vue | 88 ++++ .../Profile/Partials/UpdatePasswordForm.vue | 82 ++++ .../Partials/UpdateProfileInformationForm.vue | 69 ++++ .../resources/js/Pages/Zones/Create.vue | 20 + laravel-app/resources/js/Pages/Zones/Edit.vue | 24 ++ laravel-app/resources/js/Pages/Zones/Form.vue | 62 +++ .../resources/js/Pages/Zones/Index.vue | 66 +++ laravel-app/resources/js/app.js | 20 +- laravel-app/resources/js/routes.js | 44 ++ .../views/{layouts => }/app.blade.php | 21 +- .../views/automation-rules/_form.blade.php | 116 ------ .../views/automation-rules/create.blade.php | 15 - .../views/automation-rules/edit.blade.php | 15 - .../views/automation-rules/index.blade.php | 72 ---- .../views/components/danger-button.blade.php | 3 - .../views/components/dropdown-link.blade.php | 1 - .../views/components/dropdown.blade.php | 35 -- .../views/components/modal.blade.php | 78 ---- .../views/components/nav-link.blade.php | 11 - .../components/responsive-nav-link.blade.php | 11 - .../components/secondary-button.blade.php | 3 - .../resources/views/dashboard.blade.php | 28 -- .../resources/views/devices/_form.blade.php | 56 --- .../resources/views/devices/create.blade.php | 15 - .../resources/views/devices/edit.blade.php | 15 - .../resources/views/devices/index.blade.php | 77 ---- .../resources/views/devices/show.blade.php | 105 ----- .../views/layouts/navigation.blade.php | 118 ------ .../resources/views/profile/edit.blade.php | 29 -- .../partials/delete-user-form.blade.php | 55 --- .../partials/update-password-form.blade.php | 48 --- .../update-profile-information-form.blade.php | 64 --- .../resources/views/zones/_form.blade.php | 23 -- .../resources/views/zones/create.blade.php | 15 - .../resources/views/zones/edit.blade.php | 15 - .../resources/views/zones/index.blade.php | 60 --- laravel-app/routes/web.php | 3 +- laravel-app/tailwind.config.js | 1 + .../tests/Feature/DeviceControllerTest.php | 8 +- .../tests/Feature/ZoneControllerTest.php | 6 +- laravel-app/vite.config.js | 15 + 71 files changed, 1988 insertions(+), 1232 deletions(-) create mode 100644 laravel-app/app/Http/Middleware/HandleInertiaRequests.php delete mode 100644 laravel-app/app/View/Components/AppLayout.php create mode 100644 laravel-app/resources/js/Components/ApplicationLogo.vue create mode 100644 laravel-app/resources/js/Components/NavLink.vue create mode 100644 laravel-app/resources/js/Components/StatusBadge.vue create mode 100644 laravel-app/resources/js/Layouts/AppLayout.vue create mode 100644 laravel-app/resources/js/Pages/AutomationRules/Create.vue create mode 100644 laravel-app/resources/js/Pages/AutomationRules/Edit.vue create mode 100644 laravel-app/resources/js/Pages/AutomationRules/Form.vue create mode 100644 laravel-app/resources/js/Pages/AutomationRules/Index.vue create mode 100644 laravel-app/resources/js/Pages/Dashboard.vue create mode 100644 laravel-app/resources/js/Pages/Devices/Create.vue create mode 100644 laravel-app/resources/js/Pages/Devices/Edit.vue create mode 100644 laravel-app/resources/js/Pages/Devices/Form.vue create mode 100644 laravel-app/resources/js/Pages/Devices/Index.vue create mode 100644 laravel-app/resources/js/Pages/Devices/Show.vue create mode 100644 laravel-app/resources/js/Pages/Profile/Edit.vue create mode 100644 laravel-app/resources/js/Pages/Profile/Partials/DeleteUserForm.vue create mode 100644 laravel-app/resources/js/Pages/Profile/Partials/UpdatePasswordForm.vue create mode 100644 laravel-app/resources/js/Pages/Profile/Partials/UpdateProfileInformationForm.vue create mode 100644 laravel-app/resources/js/Pages/Zones/Create.vue create mode 100644 laravel-app/resources/js/Pages/Zones/Edit.vue create mode 100644 laravel-app/resources/js/Pages/Zones/Form.vue create mode 100644 laravel-app/resources/js/Pages/Zones/Index.vue create mode 100644 laravel-app/resources/js/routes.js rename laravel-app/resources/views/{layouts => }/app.blade.php (53%) delete mode 100644 laravel-app/resources/views/automation-rules/_form.blade.php delete mode 100644 laravel-app/resources/views/automation-rules/create.blade.php delete mode 100644 laravel-app/resources/views/automation-rules/edit.blade.php delete mode 100644 laravel-app/resources/views/automation-rules/index.blade.php delete mode 100644 laravel-app/resources/views/components/danger-button.blade.php delete mode 100644 laravel-app/resources/views/components/dropdown-link.blade.php delete mode 100644 laravel-app/resources/views/components/dropdown.blade.php delete mode 100644 laravel-app/resources/views/components/modal.blade.php delete mode 100644 laravel-app/resources/views/components/nav-link.blade.php delete mode 100644 laravel-app/resources/views/components/responsive-nav-link.blade.php delete mode 100644 laravel-app/resources/views/components/secondary-button.blade.php delete mode 100644 laravel-app/resources/views/dashboard.blade.php delete mode 100644 laravel-app/resources/views/devices/_form.blade.php delete mode 100644 laravel-app/resources/views/devices/create.blade.php delete mode 100644 laravel-app/resources/views/devices/edit.blade.php delete mode 100644 laravel-app/resources/views/devices/index.blade.php delete mode 100644 laravel-app/resources/views/devices/show.blade.php delete mode 100644 laravel-app/resources/views/layouts/navigation.blade.php delete mode 100644 laravel-app/resources/views/profile/edit.blade.php delete mode 100644 laravel-app/resources/views/profile/partials/delete-user-form.blade.php delete mode 100644 laravel-app/resources/views/profile/partials/update-password-form.blade.php delete mode 100644 laravel-app/resources/views/profile/partials/update-profile-information-form.blade.php delete mode 100644 laravel-app/resources/views/zones/_form.blade.php delete mode 100644 laravel-app/resources/views/zones/create.blade.php delete mode 100644 laravel-app/resources/views/zones/edit.blade.php delete mode 100644 laravel-app/resources/views/zones/index.blade.php diff --git a/laravel-app/app/Http/Controllers/AutomationRuleController.php b/laravel-app/app/Http/Controllers/AutomationRuleController.php index ae492a0..309245a 100644 --- a/laravel-app/app/Http/Controllers/AutomationRuleController.php +++ b/laravel-app/app/Http/Controllers/AutomationRuleController.php @@ -2,12 +2,18 @@ namespace App\Http\Controllers; +use App\Enums\ConditionOperator; use App\Enums\DeviceCategory; use App\Http\Requests\AutomationRuleRequest; +use App\Http\Resources\AutomationRuleResource; +use App\Http\Resources\DeviceResource; +use App\Http\Resources\ZoneResource; use App\Models\AutomationRule; use App\Models\Device; use App\Models\DeviceType; use App\Models\Zone; +use Illuminate\Support\Collection; +use Inertia\Inertia; class AutomationRuleController extends Controller { @@ -19,14 +25,16 @@ class AutomationRuleController extends Controller ->orderByDesc('id') ->get(); - return view('automation-rules.index', ['rules' => $rules]); + return Inertia::render('AutomationRules/Index', [ + 'rules' => AutomationRuleResource::collection($rules)->resolve(), + ]); } public function create() { $this->authorize('create', AutomationRule::class); - return view('automation-rules.create', $this->formOptions()); + return Inertia::render('AutomationRules/Create', $this->formOptions()); } public function store(AutomationRuleRequest $request) @@ -42,8 +50,19 @@ class AutomationRuleController extends Controller { $this->authorize('update', $automation_rule); - return view('automation-rules.edit', [ - 'rule' => $automation_rule, + return Inertia::render('AutomationRules/Edit', [ + 'rule' => [ + 'id' => $automation_rule->id, + 'zone_id' => $automation_rule->zone_id, + 'condition_source_device_id' => $automation_rule->condition_source_device_id, + 'condition_sensor_type' => $automation_rule->condition_sensor_type, + 'condition_operator' => $automation_rule->condition_operator->value, + 'condition_value' => $automation_rule->condition_value, + 'target_device_id' => $automation_rule->target_device_id, + 'action_type' => $automation_rule->action_type, + 'level' => $automation_rule->action_params['level'] ?? '', + 'is_active' => $automation_rule->is_active, + ], ...$this->formOptions(), ]); } @@ -91,10 +110,11 @@ class AutomationRuleController extends Controller $devices = Device::with(['zone', 'deviceType'])->orderBy('name')->get(); return [ - 'zones' => Zone::orderBy('name')->get(), - 'devices' => $devices, + 'zones' => ZoneResource::collection(Zone::orderBy('name')->get())->resolve(), + 'devices' => DeviceResource::collection($devices)->resolve(), 'actionTypeOptions' => $this->capabilityOptions(DeviceCategory::Actuator), 'sensorTypeOptions' => $this->capabilityOptions(DeviceCategory::Sensor), + 'conditionOperatorOptions' => array_map(fn ($case) => $case->value, ConditionOperator::cases()), ]; } @@ -102,7 +122,7 @@ class AutomationRuleController extends Controller * Distinct capability values across device_types of the given category — * used as friendly select/datalist suggestions, not a hardcoded list. * - * @return \Illuminate\Support\Collection + * @return Collection */ private function capabilityOptions(DeviceCategory $category) { diff --git a/laravel-app/app/Http/Controllers/DeviceController.php b/laravel-app/app/Http/Controllers/DeviceController.php index 1ad3fb2..524eeb9 100644 --- a/laravel-app/app/Http/Controllers/DeviceController.php +++ b/laravel-app/app/Http/Controllers/DeviceController.php @@ -3,6 +3,9 @@ namespace App\Http\Controllers; use App\Http\Requests\DeviceRequest; +use App\Http\Resources\DeviceResource; +use App\Http\Resources\DeviceTypeResource; +use App\Http\Resources\ZoneResource; use App\Models\Device; use App\Models\DeviceType; use App\Models\Zone; @@ -10,6 +13,7 @@ use App\Services\ClickHouseClient; use App\Services\DeviceControlClient; use App\Services\DeviceShadow; use Illuminate\Http\Request; +use Inertia\Inertia; class DeviceController extends Controller { @@ -19,8 +23,11 @@ class DeviceController extends Controller $devices = Device::with(['zone', 'deviceType'])->orderBy('name')->get(); $statuses = $shadow->statuses($devices->pluck('external_id')->all()); + $devices->each(fn (Device $d) => $d->live_status = $statuses[$d->external_id] ?? 'unknown'); - return view('devices.index', compact('devices', 'statuses')); + return Inertia::render('Devices/Index', [ + 'devices' => DeviceResource::collection($devices)->resolve(), + ]); } public function show(Device $device, DeviceShadow $shadow, ClickHouseClient $clickHouse) @@ -28,6 +35,8 @@ class DeviceController extends Controller $this->authorize('view', $device); $device->load(['zone', 'deviceType']); + $snapshot = $shadow->snapshot($device->external_id); + $device->live_status = $snapshot['status']; $telemetry = []; if ($device->deviceType->category->value === 'sensor') { @@ -39,9 +48,13 @@ class DeviceController extends Controller ); } - return view('devices.show', [ - 'device' => $device, - 'shadow' => $shadow->snapshot($device->external_id), + return Inertia::render('Devices/Show', [ + 'device' => (new DeviceResource($device))->resolve(), + 'shadow' => [ + 'last_seen' => $snapshot['last_seen'], + 'desired_state' => $snapshot['desired_state'], + 'reported_state' => $snapshot['reported_state'], + ], 'telemetry' => $telemetry, ]); } @@ -50,7 +63,7 @@ class DeviceController extends Controller { $this->authorize('create', Device::class); - return view('devices.create', $this->formOptions()); + return Inertia::render('Devices/Create', $this->formOptions()); } public function store(DeviceRequest $request) @@ -66,7 +79,10 @@ class DeviceController extends Controller { $this->authorize('update', $device); - return view('devices.edit', ['device' => $device, ...$this->formOptions()]); + return Inertia::render('Devices/Edit', [ + 'device' => $device->only(['id', 'name', 'zone_id', 'device_type_id', 'external_id', 'protocol']), + ...$this->formOptions(), + ]); } public function update(DeviceRequest $request, Device $device) @@ -137,8 +153,8 @@ class DeviceController extends Controller private function formOptions(): array { return [ - 'zones' => Zone::orderBy('name')->get(), - 'deviceTypes' => DeviceType::orderBy('code')->get(), + 'zones' => ZoneResource::collection(Zone::orderBy('name')->get())->resolve(), + 'deviceTypes' => DeviceTypeResource::collection(DeviceType::orderBy('code')->get())->resolve(), ]; } } diff --git a/laravel-app/app/Http/Controllers/ProfileController.php b/laravel-app/app/Http/Controllers/ProfileController.php index a48eb8d..6506879 100644 --- a/laravel-app/app/Http/Controllers/ProfileController.php +++ b/laravel-app/app/Http/Controllers/ProfileController.php @@ -7,17 +7,18 @@ use Illuminate\Http\RedirectResponse; use Illuminate\Http\Request; use Illuminate\Support\Facades\Auth; use Illuminate\Support\Facades\Redirect; -use Illuminate\View\View; +use Inertia\Inertia; +use Inertia\Response; class ProfileController extends Controller { /** * Display the user's profile form. */ - public function edit(Request $request): View + public function edit(Request $request): Response { - return view('profile.edit', [ - 'user' => $request->user(), + return Inertia::render('Profile/Edit', [ + 'user' => $request->user()->only(['id', 'name', 'email']), ]); } diff --git a/laravel-app/app/Http/Controllers/ZoneController.php b/laravel-app/app/Http/Controllers/ZoneController.php index b29d829..d5d0b2c 100644 --- a/laravel-app/app/Http/Controllers/ZoneController.php +++ b/laravel-app/app/Http/Controllers/ZoneController.php @@ -3,7 +3,9 @@ namespace App\Http\Controllers; use App\Http\Requests\ZoneRequest; +use App\Http\Resources\ZoneResource; use App\Models\Zone; +use Inertia\Inertia; class ZoneController extends Controller { @@ -13,14 +15,16 @@ class ZoneController extends Controller $zones = Zone::withCount('devices')->orderBy('name')->get(); - return view('zones.index', compact('zones')); + return Inertia::render('Zones/Index', [ + 'zones' => ZoneResource::collection($zones)->resolve(), + ]); } public function create() { $this->authorize('create', Zone::class); - return view('zones.create'); + return Inertia::render('Zones/Create'); } public function store(ZoneRequest $request) @@ -34,7 +38,9 @@ class ZoneController extends Controller { $this->authorize('update', $zone); - return view('zones.edit', compact('zone')); + return Inertia::render('Zones/Edit', [ + 'zone' => (new ZoneResource($zone))->resolve(), + ]); } public function update(ZoneRequest $request, Zone $zone) diff --git a/laravel-app/app/Http/Middleware/HandleInertiaRequests.php b/laravel-app/app/Http/Middleware/HandleInertiaRequests.php new file mode 100644 index 0000000..054bf18 --- /dev/null +++ b/laravel-app/app/Http/Middleware/HandleInertiaRequests.php @@ -0,0 +1,36 @@ + */ + public function share(Request $request): array + { + return [ + ...parent::share($request), + 'auth' => [ + // ->resolve() instead of passing the Resource directly: Inertia + // calls ->toResponse() on Responsable props (Resources are + // Responsable), which triggers Laravel's `data`-key wrapping — + // resolve() returns the plain array Inertia pages actually want. + 'user' => $request->user() ? (new UserResource($request->user()))->resolve() : null, + ], + 'flash' => [ + 'status' => fn () => $request->session()->get('status'), + 'error' => fn () => $request->session()->get('error'), + ], + ]; + } +} diff --git a/laravel-app/app/Http/Resources/AutomationRuleResource.php b/laravel-app/app/Http/Resources/AutomationRuleResource.php index cc3eb10..f020b11 100644 --- a/laravel-app/app/Http/Resources/AutomationRuleResource.php +++ b/laravel-app/app/Http/Resources/AutomationRuleResource.php @@ -14,9 +14,11 @@ class AutomationRuleResource extends JsonResource { return [ 'id' => $this->id, - 'zone' => new ZoneResource($this->whenLoaded('zone')), - 'target_device' => new DeviceResource($this->whenLoaded('targetDevice')), - 'condition_source_device' => new DeviceResource($this->whenLoaded('conditionSourceDevice')), + // See DeviceResource for why nested resources are ->resolve()'d + // rather than returned as bare Resource instances. + 'zone' => $this->whenLoaded('zone', fn ($zone) => (new ZoneResource($zone))->resolve()), + 'target_device' => $this->whenLoaded('targetDevice', fn ($device) => (new DeviceResource($device))->resolve()), + 'condition_source_device' => $this->whenLoaded('conditionSourceDevice', fn ($device) => (new DeviceResource($device))->resolve()), 'condition_sensor_type' => $this->condition_sensor_type, 'condition_operator' => $this->condition_operator->value, 'condition_value' => $this->condition_value, diff --git a/laravel-app/app/Http/Resources/DeviceResource.php b/laravel-app/app/Http/Resources/DeviceResource.php index aab193d..330c62c 100644 --- a/laravel-app/app/Http/Resources/DeviceResource.php +++ b/laravel-app/app/Http/Resources/DeviceResource.php @@ -25,8 +25,13 @@ class DeviceResource extends JsonResource 'external_id' => $this->external_id, 'protocol' => $this->protocol, 'status' => $this->live_status ?? $this->status, - 'zone' => new ZoneResource($this->whenLoaded('zone')), - 'device_type' => new DeviceTypeResource($this->whenLoaded('deviceType')), + // Nested resources are resolved (not returned as bare Resource + // instances) — a Resource embedded as a value gets wrapped in its + // own `data` key when the parent response goes through Laravel's + // Responsable pipeline (Inertia props, ->response() in the API), + // which would double up with the outer collection's own wrapping. + 'zone' => $this->whenLoaded('zone', fn ($zone) => (new ZoneResource($zone))->resolve()), + 'device_type' => $this->whenLoaded('deviceType', fn ($type) => (new DeviceTypeResource($type))->resolve()), 'created_at' => $this->created_at, ]; } diff --git a/laravel-app/app/Services/DeviceControlClient.php b/laravel-app/app/Services/DeviceControlClient.php index d3d2d7d..459a637 100644 --- a/laravel-app/app/Services/DeviceControlClient.php +++ b/laravel-app/app/Services/DeviceControlClient.php @@ -2,6 +2,7 @@ namespace App\Services; +use Illuminate\Http\Client\ConnectionException; use Illuminate\Support\Facades\Http; use Illuminate\Support\Facades\Log; @@ -43,7 +44,7 @@ class DeviceControlClient { try { $response = Http::timeout(5)->post("{$this->baseUrl}{$path}", $body); - } catch (\Illuminate\Http\Client\ConnectionException $e) { + } catch (ConnectionException $e) { Log::error('device-control-service unreachable', ['path' => $path, 'error' => $e->getMessage()]); return ['success' => false, 'error' => 'device-control-service недоступен']; diff --git a/laravel-app/app/View/Components/AppLayout.php b/laravel-app/app/View/Components/AppLayout.php deleted file mode 100644 index de0d46f..0000000 --- a/laravel-app/app/View/Components/AppLayout.php +++ /dev/null @@ -1,17 +0,0 @@ -withMiddleware(function (Middleware $middleware): void { - // + $middleware->web(append: [ + HandleInertiaRequests::class, + ]); }) ->withExceptions(function (Exceptions $exceptions): void { $exceptions->shouldRenderJsonWhen( diff --git a/laravel-app/composer.json b/laravel-app/composer.json index c5601a4..6c60e36 100644 --- a/laravel-app/composer.json +++ b/laravel-app/composer.json @@ -7,6 +7,7 @@ "license": "MIT", "require": { "php": "^8.3", + "inertiajs/inertia-laravel": "^3.3", "laravel/framework": "^13.8", "laravel/sanctum": "^4.0", "laravel/tinker": "^3.0", diff --git a/laravel-app/composer.lock b/laravel-app/composer.lock index 6fbe296..fdea44f 100644 --- a/laravel-app/composer.lock +++ b/laravel-app/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": "483f1bf17688fcc178b8c1849ff31ddc", + "content-hash": "3c4f6c9d2bddd0693e70d6396ea65c43", "packages": [ { "name": "brick/math", @@ -1057,6 +1057,78 @@ ], "time": "2026-07-17T13:53:03+00:00" }, + { + "name": "inertiajs/inertia-laravel", + "version": "v3.3.1", + "source": { + "type": "git", + "url": "https://github.com/inertiajs/inertia-laravel.git", + "reference": "7bfd75e352938b703180574b943d81963555a0aa" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/inertiajs/inertia-laravel/zipball/7bfd75e352938b703180574b943d81963555a0aa", + "reference": "7bfd75e352938b703180574b943d81963555a0aa", + "shasum": "" + }, + "require": { + "ext-json": "*", + "laravel/framework": "^11.35|^12.0|^13.0", + "php": "^8.2.0", + "symfony/console": "^7.0|^8.0" + }, + "conflict": { + "laravel/boost": "<2.5.0" + }, + "require-dev": { + "guzzlehttp/guzzle": "^7.15.2|^8.0", + "larastan/larastan": "^3.0", + "laravel/pint": "^1.16", + "mockery/mockery": "^1.3.3", + "orchestra/testbench": "^9.2|^10.0|^11.0", + "phpunit/phpunit": "^11.5|^12.0" + }, + "suggest": { + "ext-pcntl": "Recommended when running the Inertia SSR server via the `inertia:start-ssr` artisan command." + }, + "type": "library", + "extra": { + "laravel": { + "providers": [ + "Inertia\\ServiceProvider" + ] + } + }, + "autoload": { + "files": [ + "./helpers.php" + ], + "psr-4": { + "Inertia\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Jonathan Reinink", + "email": "jonathan@reinink.ca", + "homepage": "https://reinink.ca" + } + ], + "description": "The Laravel adapter for Inertia.js.", + "keywords": [ + "inertia", + "laravel" + ], + "support": { + "issues": "https://github.com/inertiajs/inertia-laravel/issues", + "source": "https://github.com/inertiajs/inertia-laravel/tree/v3.3.1" + }, + "time": "2026-08-04T21:58:51+00:00" + }, { "name": "laravel/framework", "version": "v13.22.0", diff --git a/laravel-app/database/factories/UserFactory.php b/laravel-app/database/factories/UserFactory.php index c4ceb07..dc86ede 100644 --- a/laravel-app/database/factories/UserFactory.php +++ b/laravel-app/database/factories/UserFactory.php @@ -2,6 +2,7 @@ namespace Database\Factories; +use App\Enums\UserRole; use App\Models\User; use Illuminate\Database\Eloquent\Factories\Factory; use Illuminate\Support\Facades\Hash; @@ -30,6 +31,10 @@ class UserFactory extends Factory 'email_verified_at' => now(), 'password' => static::$password ??= Hash::make('password'), 'remember_token' => Str::random(10), + // Matches the `role` column's DB default — create() doesn't + // hydrate DB-side defaults back into the in-memory model, so an + // omitted role here would leave $user->role null in-process. + 'role' => UserRole::Owner, ]; } diff --git a/laravel-app/package-lock.json b/laravel-app/package-lock.json index b4635ee..929ea22 100644 --- a/laravel-app/package-lock.json +++ b/laravel-app/package-lock.json @@ -4,10 +4,14 @@ "requires": true, "packages": { "": { + "dependencies": { + "@inertiajs/vue3": "^3.6.1", + "@vitejs/plugin-vue": "^6.0.8", + "vue": "^3.5.41" + }, "devDependencies": { "@tailwindcss/forms": "^0.5.2", "@tailwindcss/vite": "^4.0.0", - "alpinejs": "^3.4.2", "autoprefixer": "^10.4.2", "concurrently": "^9.0.1", "laravel-vite-plugin": "^3.1", @@ -29,11 +33,56 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/@babel/helper-string-parser": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.29.7.tgz", + "integrity": "sha512-Pb5ijPrZ89GDH8223L4UP8i6QApWxs04RbPQJTeWDV0/keR2E36MeKnyr6LYmUUvqRRI+Iv87SuF1W6ErINzYw==", + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-identifier": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.29.7.tgz", + "integrity": "sha512-qehxGkRj55h/ff8EMaJ+cYhyaKlHIxqYDn682wQD7RNp9UujOQsHog2uS0r2vzr4pW+sXf90NeeayjcNaX3fFg==", + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/parser": { + "version": "7.29.8", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.8.tgz", + "integrity": "sha512-E8lTAYNB1KW+FH+VGJuZM1ioAx2E6oVlvQFRrf5P8ZZmsiJXYAD9vTFV7yyEURNzgh1dFqMZuO6tUwcARbqFCA==", + "license": "MIT", + "dependencies": { + "@babel/types": "^7.29.8" + }, + "bin": { + "parser": "bin/babel-parser.js" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/types": { + "version": "7.29.8", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.29.8.tgz", + "integrity": "sha512-Vj1jF3cPfxg7OAfoI7QnVKLoILlm2JF9pnVHrX8qx7AHMiYWT+NDAA7jChlNgRS4WTLc/fD1lXLmPixluj+3Gg==", + "license": "MIT", + "dependencies": { + "@babel/helper-string-parser": "^7.29.7", + "@babel/helper-validator-identifier": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, "node_modules/@emnapi/core": { "version": "1.11.1", "resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.11.1.tgz", "integrity": "sha512-RSvbQmHzdKzNsLYa/wHrbc3KN4sYLKAdPZxqiM2HATqv/SBk2/ENSHpvXGaLOMcsAyz0poEGqkmmKYG3OWiJEQ==", - "dev": true, "license": "MIT", "optional": true, "dependencies": { @@ -45,7 +94,6 @@ "version": "1.11.1", "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.11.1.tgz", "integrity": "sha512-vgj7R3y3Wgx24IQaGPA/R6YFXLHVMOZ0uVEyIQPaWs+rd1AzfEMXlAC22FYwO1XkKR6NPsq7mUandH8oIRdZFw==", - "dev": true, "license": "MIT", "optional": true, "dependencies": { @@ -56,13 +104,45 @@ "version": "1.2.2", "resolved": "https://registry.npmjs.org/@emnapi/wasi-threads/-/wasi-threads-1.2.2.tgz", "integrity": "sha512-c95qOXkHdydNKhscBTebqEC1CVAZpyqOfVfBzQ1qgzyl3gfeldUjIggDbIZgDKsHLgnsM+igH7TJ/eAasaVuMA==", - "dev": true, "license": "MIT", "optional": true, "dependencies": { "tslib": "^2.4.0" } }, + "node_modules/@inertiajs/core": { + "version": "3.6.1", + "resolved": "https://registry.npmjs.org/@inertiajs/core/-/core-3.6.1.tgz", + "integrity": "sha512-h6+qqkKfpcoZvxWENy/F5yyiD00PIm8lK+ruQkt6HGk4d3N0LMS9GeUbWVQ4+TDZzIxP/vQLurPktnYvDhYeew==", + "license": "MIT", + "dependencies": { + "@jridgewell/trace-mapping": "^0.3.31", + "es-toolkit": "^1.33.0", + "laravel-precognition": "^2.0.0" + }, + "peerDependencies": { + "axios": "^1.15.2" + }, + "peerDependenciesMeta": { + "axios": { + "optional": true + } + } + }, + "node_modules/@inertiajs/vue3": { + "version": "3.6.1", + "resolved": "https://registry.npmjs.org/@inertiajs/vue3/-/vue3-3.6.1.tgz", + "integrity": "sha512-7M76W7uw1DqxWR5O+OV7Yg23yITTDcRxuO7KCdQhI58rp9+OTv2BZId7ePSBmqahYOeXUZbTfZfVoS2O6/hijA==", + "license": "MIT", + "dependencies": { + "@inertiajs/core": "3.6.1", + "es-toolkit": "^1.33.0", + "laravel-precognition": "^2.0.0" + }, + "peerDependencies": { + "vue": "^3.0.0" + } + }, "node_modules/@jridgewell/gen-mapping": { "version": "0.3.13", "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", @@ -89,7 +169,6 @@ "version": "3.1.2", "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", - "dev": true, "license": "MIT", "engines": { "node": ">=6.0.0" @@ -99,14 +178,12 @@ "version": "1.5.5", "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", - "dev": true, "license": "MIT" }, "node_modules/@jridgewell/trace-mapping": { "version": "0.3.31", "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz", "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==", - "dev": true, "license": "MIT", "dependencies": { "@jridgewell/resolve-uri": "^3.1.0", @@ -117,7 +194,6 @@ "version": "1.1.6", "resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-1.1.6.tgz", "integrity": "sha512-ZLv/JdUfkvOy9eCnnBaGfiO+XimbjebAeO+MRQqD/B+FR1tnRN0tpKSJHRbE8sFfS6aqsXZ67TQjfwfsxULVbg==", - "dev": true, "license": "MIT", "optional": true, "dependencies": { @@ -174,7 +250,6 @@ "version": "0.139.0", "resolved": "https://registry.npmjs.org/@oxc-project/types/-/types-0.139.0.tgz", "integrity": "sha512-r9gHphtCs+1M7J0pw6Sn/hh/Wpa/iQrOOkrNAlVLF/gHq+/CJmHIWKKUUhdWjcD6CIa8idarspCsASiXCXvFUw==", - "dev": true, "license": "MIT", "funding": { "url": "https://github.com/sponsors/Boshen" @@ -187,7 +262,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -204,7 +278,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -221,7 +294,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -238,7 +310,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -255,7 +326,6 @@ "cpu": [ "arm" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -272,7 +342,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -289,7 +358,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -306,7 +374,6 @@ "cpu": [ "ppc64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -323,7 +390,6 @@ "cpu": [ "s390x" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -340,7 +406,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -357,7 +422,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -374,7 +438,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -391,7 +454,6 @@ "cpu": [ "wasm32" ], - "dev": true, "license": "MIT", "optional": true, "dependencies": { @@ -410,7 +472,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -427,7 +488,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -441,7 +501,6 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.1.tgz", "integrity": "sha512-2j9bGt5Jh8hj+vPtgzPtl72j0yRxHAyumoo6TNfAjsLB04UtpSvPbPcDcBMxz7n+9CYB0c1GxQFxYRg2jimqGw==", - "dev": true, "license": "MIT" }, "node_modules/@tailwindcss/forms": { @@ -747,40 +806,171 @@ "version": "0.10.3", "resolved": "https://registry.npmjs.org/@tybys/wasm-util/-/wasm-util-0.10.3.tgz", "integrity": "sha512-F3fo1MYrRJYL3zER0OUOmkutjr1Vp23m7OsSgp7nq4SP6OqX6C/56XFIPAl5bt3zaBRjmW7SGz3u/6LwFpYcOg==", - "dev": true, "license": "MIT", "optional": true, "dependencies": { "tslib": "^2.4.0" } }, - "node_modules/@vue/reactivity": { - "version": "3.1.5", - "resolved": "https://registry.npmjs.org/@vue/reactivity/-/reactivity-3.1.5.tgz", - "integrity": "sha512-1tdfLmNjWG6t/CsPldh+foumYFo3cpyCHgBYQ34ylaMsJ+SNHQ1kApMIa8jN+i593zQuaw3AdWH0nJTARzCFhg==", - "dev": true, + "node_modules/@vitejs/plugin-vue": { + "version": "6.0.8", + "resolved": "https://registry.npmjs.org/@vitejs/plugin-vue/-/plugin-vue-6.0.8.tgz", + "integrity": "sha512-0ZjgOg7oO6farnNGup7yvoM/YXZV84OZxHAwtflItNa/6zzQyVb5LNxyea3FEKEX2XlagIKzrlH7wwxkKgtiew==", "license": "MIT", "dependencies": { - "@vue/shared": "3.1.5" + "@rolldown/pluginutils": "^1.0.1" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + }, + "peerDependencies": { + "vite": "^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0", + "vue": "^3.2.25" } }, - "node_modules/@vue/shared": { - "version": "3.1.5", - "resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.1.5.tgz", - "integrity": "sha512-oJ4F3TnvpXaQwZJNF3ZK+kLPHKarDmJjJ6jyzVNDKH9md1dptjC7lWR//jrGuLdek/U6iltWxqAnYOu8gCiOvA==", - "dev": true, + "node_modules/@vue/compiler-core": { + "version": "3.5.41", + "resolved": "https://registry.npmjs.org/@vue/compiler-core/-/compiler-core-3.5.41.tgz", + "integrity": "sha512-q0Xtv/F9w2YO/7htQhtiL+Ev2WCJbe5N2hc+XfgyKkEKqWpSxknmT8QOuGdEKNdjPq0c3F7rNpFkTo3Kfrm7pg==", + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.29.8", + "@vue/shared": "3.5.41", + "entities": "^7.0.1", + "estree-walker": "^2.0.2", + "source-map-js": "^1.2.1" + } + }, + "node_modules/@vue/compiler-core/node_modules/@vue/shared": { + "version": "3.5.41", + "resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.5.41.tgz", + "integrity": "sha512-IOnwSCma8j+9xJT6b8H0dEYidC80NsYmNMlZxRsukYcSoGaDBohog5hDxzeUXdFeGWFA++vWvxqOmrr96VlqMA==", "license": "MIT" }, - "node_modules/alpinejs": { - "version": "3.15.12", - "resolved": "https://registry.npmjs.org/alpinejs/-/alpinejs-3.15.12.tgz", - "integrity": "sha512-nJvPAQVNPdZZ0NrExJ/kzQco3ijR8LwvCOadQecllESiqT4NyZ/57sN9V2XyvhlBGAbmlKYgeWZvYdKq99ij/Q==", - "dev": true, + "node_modules/@vue/compiler-dom": { + "version": "3.5.41", + "resolved": "https://registry.npmjs.org/@vue/compiler-dom/-/compiler-dom-3.5.41.tgz", + "integrity": "sha512-oKacVfNglLvGjnS6BXOlGL7EyG2h8X03pqXCjzotRZUaXGjbrTJUnVAQjrCqUnS+lyu31nwQjZY/d817GmCnfw==", "license": "MIT", "dependencies": { - "@vue/reactivity": "~3.1.1" + "@vue/compiler-core": "3.5.41", + "@vue/shared": "3.5.41" } }, + "node_modules/@vue/compiler-dom/node_modules/@vue/shared": { + "version": "3.5.41", + "resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.5.41.tgz", + "integrity": "sha512-IOnwSCma8j+9xJT6b8H0dEYidC80NsYmNMlZxRsukYcSoGaDBohog5hDxzeUXdFeGWFA++vWvxqOmrr96VlqMA==", + "license": "MIT" + }, + "node_modules/@vue/compiler-sfc": { + "version": "3.5.41", + "resolved": "https://registry.npmjs.org/@vue/compiler-sfc/-/compiler-sfc-3.5.41.tgz", + "integrity": "sha512-XJhip7R2wy6vX3knCxdZN4KracFaZUef58s1KYewqluedHIJaPIVfXoYT7MF1F8nCvv6k8bWWxDC8opMkg1VTQ==", + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.29.8", + "@vue/compiler-core": "3.5.41", + "@vue/compiler-dom": "3.5.41", + "@vue/compiler-ssr": "3.5.41", + "@vue/shared": "3.5.41", + "estree-walker": "^2.0.2", + "magic-string": "^0.30.21", + "postcss": "^8.5.19", + "source-map-js": "^1.2.1" + } + }, + "node_modules/@vue/compiler-sfc/node_modules/@vue/shared": { + "version": "3.5.41", + "resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.5.41.tgz", + "integrity": "sha512-IOnwSCma8j+9xJT6b8H0dEYidC80NsYmNMlZxRsukYcSoGaDBohog5hDxzeUXdFeGWFA++vWvxqOmrr96VlqMA==", + "license": "MIT" + }, + "node_modules/@vue/compiler-ssr": { + "version": "3.5.41", + "resolved": "https://registry.npmjs.org/@vue/compiler-ssr/-/compiler-ssr-3.5.41.tgz", + "integrity": "sha512-U3v5OejKEGqOI0Wy0+Sz7hGuIFZHA4LSXzrNM3IMIeDyJEBBfTpX26n3SDgToRpP2bLc9FfI2j/kSgcJ8Emq5A==", + "license": "MIT", + "dependencies": { + "@vue/compiler-dom": "3.5.41", + "@vue/shared": "3.5.41" + } + }, + "node_modules/@vue/compiler-ssr/node_modules/@vue/shared": { + "version": "3.5.41", + "resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.5.41.tgz", + "integrity": "sha512-IOnwSCma8j+9xJT6b8H0dEYidC80NsYmNMlZxRsukYcSoGaDBohog5hDxzeUXdFeGWFA++vWvxqOmrr96VlqMA==", + "license": "MIT" + }, + "node_modules/@vue/runtime-core": { + "version": "3.5.41", + "resolved": "https://registry.npmjs.org/@vue/runtime-core/-/runtime-core-3.5.41.tgz", + "integrity": "sha512-Vcry58hiAKwGen9Z1jUZE0feFsNArPCMOImYI8el48A9Idf6DuQYD0U05zZIF2Iad1hGhPSvcbBbAOhNr55fhg==", + "license": "MIT", + "dependencies": { + "@vue/reactivity": "3.5.41", + "@vue/shared": "3.5.41" + } + }, + "node_modules/@vue/runtime-core/node_modules/@vue/reactivity": { + "version": "3.5.41", + "resolved": "https://registry.npmjs.org/@vue/reactivity/-/reactivity-3.5.41.tgz", + "integrity": "sha512-rznsqKM0np0x18EjzF8x88MpEhdNsffbvFbckLL5+oUKz1BxAImEmO7J1ArRYSyo6aQaVoBDp7jEkT91OOxydA==", + "license": "MIT", + "dependencies": { + "@vue/shared": "3.5.41" + } + }, + "node_modules/@vue/runtime-core/node_modules/@vue/shared": { + "version": "3.5.41", + "resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.5.41.tgz", + "integrity": "sha512-IOnwSCma8j+9xJT6b8H0dEYidC80NsYmNMlZxRsukYcSoGaDBohog5hDxzeUXdFeGWFA++vWvxqOmrr96VlqMA==", + "license": "MIT" + }, + "node_modules/@vue/runtime-dom": { + "version": "3.5.41", + "resolved": "https://registry.npmjs.org/@vue/runtime-dom/-/runtime-dom-3.5.41.tgz", + "integrity": "sha512-3vVBahVBS9+U6cmXBLyb8nE6/yYo4J/CGI9eVFs3KiMc0YHuudwKyShTD65jtJy/L9PUUxNAFu4cj4LiJ0UFbw==", + "license": "MIT", + "dependencies": { + "@vue/reactivity": "3.5.41", + "@vue/runtime-core": "3.5.41", + "@vue/shared": "3.5.41", + "csstype": "^3.2.3" + } + }, + "node_modules/@vue/runtime-dom/node_modules/@vue/reactivity": { + "version": "3.5.41", + "resolved": "https://registry.npmjs.org/@vue/reactivity/-/reactivity-3.5.41.tgz", + "integrity": "sha512-rznsqKM0np0x18EjzF8x88MpEhdNsffbvFbckLL5+oUKz1BxAImEmO7J1ArRYSyo6aQaVoBDp7jEkT91OOxydA==", + "license": "MIT", + "dependencies": { + "@vue/shared": "3.5.41" + } + }, + "node_modules/@vue/runtime-dom/node_modules/@vue/shared": { + "version": "3.5.41", + "resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.5.41.tgz", + "integrity": "sha512-IOnwSCma8j+9xJT6b8H0dEYidC80NsYmNMlZxRsukYcSoGaDBohog5hDxzeUXdFeGWFA++vWvxqOmrr96VlqMA==", + "license": "MIT" + }, + "node_modules/@vue/server-renderer": { + "version": "3.5.41", + "resolved": "https://registry.npmjs.org/@vue/server-renderer/-/server-renderer-3.5.41.tgz", + "integrity": "sha512-n6hx/pNFfbD6SuyeuMVkvqox8bwf/ET9JlA/kAz/imw8sw++wkqKe2mHX5KutjPpbKE4Z56yTHszoOjGMI9igQ==", + "license": "MIT", + "dependencies": { + "@vue/compiler-ssr": "3.5.41", + "@vue/runtime-dom": "3.5.41", + "@vue/shared": "3.5.41" + } + }, + "node_modules/@vue/server-renderer/node_modules/@vue/shared": { + "version": "3.5.41", + "resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.5.41.tgz", + "integrity": "sha512-IOnwSCma8j+9xJT6b8H0dEYidC80NsYmNMlZxRsukYcSoGaDBohog5hDxzeUXdFeGWFA++vWvxqOmrr96VlqMA==", + "license": "MIT" + }, "node_modules/ansi-regex": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", @@ -1127,11 +1317,16 @@ "node": ">=4" } }, + "node_modules/csstype": { + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz", + "integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==", + "license": "MIT" + }, "node_modules/detect-libc": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz", "integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==", - "dev": true, "license": "Apache-2.0", "engines": { "node": ">=8" @@ -1179,6 +1374,18 @@ "node": ">=10.13.0" } }, + "node_modules/entities": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/entities/-/entities-7.0.1.tgz", + "integrity": "sha512-TWrgLOFUQTH994YUyl1yT4uyavY5nNB5muff+RtWaqNVCAK408b5ZnnbNAUEWLTCpum9w6arT70i1XdQ4UeOPA==", + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.12" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, "node_modules/es-errors": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", @@ -1189,6 +1396,17 @@ "node": ">= 0.4" } }, + "node_modules/es-toolkit": { + "version": "1.50.0", + "resolved": "https://registry.npmjs.org/es-toolkit/-/es-toolkit-1.50.0.tgz", + "integrity": "sha512-OyZKhUVvEep9ITEiwHn8GKnMRQIVqoSIX7WnRbkWgJkllCujilqP2rD0u979tkl8wqyc8ICwlc1UBVv/Sl1G6w==", + "license": "MIT", + "workspaces": [ + "docs", + "benchmarks", + "tests/types" + ] + }, "node_modules/escalade": { "version": "3.2.0", "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", @@ -1199,6 +1417,12 @@ "node": ">=6" } }, + "node_modules/estree-walker": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz", + "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==", + "license": "MIT" + }, "node_modules/fast-glob": { "version": "3.3.3", "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.3.tgz", @@ -1270,7 +1494,6 @@ "version": "2.3.3", "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", - "dev": true, "hasInstallScript": true, "license": "MIT", "optional": true, @@ -1420,12 +1643,29 @@ "version": "2.7.0", "resolved": "https://registry.npmjs.org/jiti/-/jiti-2.7.0.tgz", "integrity": "sha512-AC/7JofJvZGrrneWNaEnJeOLUx+JlGt7tNa0wZiRPT4MY1wmfKjt2+6O2p2uz2+skll8OZZmJMNqeke7kKbNgQ==", - "dev": true, + "devOptional": true, "license": "MIT", "bin": { "jiti": "lib/jiti-cli.mjs" } }, + "node_modules/laravel-precognition": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/laravel-precognition/-/laravel-precognition-2.0.0.tgz", + "integrity": "sha512-dmA4HGc9m+TsVNsJs9/XQBI8u6j7coilN+qKkBuhuXQzH3HypwS/c5dFQ4UqUGjBbcxIM7zdk91kM/SRZwIvWQ==", + "license": "MIT", + "dependencies": { + "es-toolkit": "^1.32.0" + }, + "peerDependencies": { + "axios": "^1.4.0" + }, + "peerDependenciesMeta": { + "axios": { + "optional": true + } + } + }, "node_modules/laravel-vite-plugin": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/laravel-vite-plugin/-/laravel-vite-plugin-3.1.3.tgz", @@ -1457,7 +1697,6 @@ "version": "1.32.0", "resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.32.0.tgz", "integrity": "sha512-NXYBzinNrblfraPGyrbPoD19C1h9lfI/1mzgWYvXUTe414Gz/X1FD2XBZSZM7rRTrMA8JL3OtAaGifrIKhQ5yQ==", - "dev": true, "license": "MPL-2.0", "dependencies": { "detect-libc": "^2.0.3" @@ -1490,7 +1729,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MPL-2.0", "optional": true, "os": [ @@ -1511,7 +1749,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MPL-2.0", "optional": true, "os": [ @@ -1532,7 +1769,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "MPL-2.0", "optional": true, "os": [ @@ -1553,7 +1789,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "MPL-2.0", "optional": true, "os": [ @@ -1574,7 +1809,6 @@ "cpu": [ "arm" ], - "dev": true, "license": "MPL-2.0", "optional": true, "os": [ @@ -1595,7 +1829,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MPL-2.0", "optional": true, "os": [ @@ -1616,7 +1849,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MPL-2.0", "optional": true, "os": [ @@ -1637,7 +1869,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "MPL-2.0", "optional": true, "os": [ @@ -1658,7 +1889,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "MPL-2.0", "optional": true, "os": [ @@ -1679,7 +1909,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MPL-2.0", "optional": true, "os": [ @@ -1700,7 +1929,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "MPL-2.0", "optional": true, "os": [ @@ -1738,7 +1966,6 @@ "version": "0.30.21", "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz", "integrity": "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==", - "dev": true, "license": "MIT", "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.5" @@ -1791,10 +2018,9 @@ } }, "node_modules/nanoid": { - "version": "3.3.16", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.16.tgz", - "integrity": "sha512-bzlKTyNJ7+LdGIIwy8ijFpIqEQIvafahV7eYykJ8Cvh42EdJeODoJ6gUJXpQJvej1BddH8OqTXZNE/KfbWAu8Q==", - "dev": true, + "version": "3.3.18", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.18.tgz", + "integrity": "sha512-DTg4MJbGMWkfi6VZFdNt2/caMbQy4Ou+Op/hJQvGEWcnVfoA1QA+xzRKAzw9jD6+GVOOeYr/mIcuDSdug6F6+w==", "funding": [ { "type": "github", @@ -1860,7 +2086,6 @@ "version": "1.1.1", "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", - "dev": true, "license": "ISC" }, "node_modules/picomatch": { @@ -1900,7 +2125,6 @@ "version": "8.5.23", "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.23.tgz", "integrity": "sha512-g50586zr4bZmwFiTlflMu8E0bDTb5I5gertgwAKmsdUlTQIhZtunzUlD1WSzwcVWPoAVpsrA6vlfCD7oXvRwgg==", - "dev": true, "funding": [ { "type": "opencollective", @@ -2150,7 +2374,6 @@ "version": "1.1.5", "resolved": "https://registry.npmjs.org/rolldown/-/rolldown-1.1.5.tgz", "integrity": "sha512-t9z29cJjXf/vxQ8dyhCSpt6H6aSwHTk8cT5I3iy6SMXuFpk5mB6PL6XfC8PCwrPTx93udwKUm9HRteAlTGBLiA==", - "dev": true, "license": "MIT", "dependencies": { "@oxc-project/types": "=0.139.0", @@ -2231,7 +2454,6 @@ "version": "1.2.1", "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", - "dev": true, "license": "BSD-3-Clause", "engines": { "node": ">=0.10.0" @@ -2406,7 +2628,6 @@ "version": "0.2.17", "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.17.tgz", "integrity": "sha512-wXR/dYpcqKmfWpEdZjiKJOwCNFndD0DMnrW/cYjVGttEkBfVgcLFHoNrlj47mjOVic9yyNu65alsgF4NQyTa2g==", - "dev": true, "license": "MIT", "dependencies": { "fdir": "^6.5.0", @@ -2423,7 +2644,6 @@ "version": "6.5.0", "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", - "dev": true, "license": "MIT", "engines": { "node": ">=12.0.0" @@ -2441,7 +2661,6 @@ "version": "4.0.5", "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.5.tgz", "integrity": "sha512-RvwwcruNjI1ncT5xRakeyS9Lf8lcItv34KD+aif+VH9kduAyfYBipGh12274xtenIPZ119/R9BdTBa8gAwSh0A==", - "dev": true, "license": "MIT", "engines": { "node": ">=12" @@ -2484,7 +2703,7 @@ "version": "2.8.1", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", - "dev": true, + "devOptional": true, "license": "0BSD" }, "node_modules/update-browserslist-db": { @@ -2529,7 +2748,6 @@ "version": "8.1.5", "resolved": "https://registry.npmjs.org/vite/-/vite-8.1.5.tgz", "integrity": "sha512-7ULLwsCdYx/nRyrpiEwvqb5TFHrMVZyBt+rg/OAXT7rgj/z+DtTDyKFeLAdDkubDVDKD8jOsndmy7m55XcfUsw==", - "dev": true, "license": "MIT", "dependencies": { "lightningcss": "^1.32.0", @@ -2618,7 +2836,6 @@ "version": "4.0.5", "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.5.tgz", "integrity": "sha512-RvwwcruNjI1ncT5xRakeyS9Lf8lcItv34KD+aif+VH9kduAyfYBipGh12274xtenIPZ119/R9BdTBa8gAwSh0A==", - "dev": true, "license": "MIT", "engines": { "node": ">=12" @@ -2627,6 +2844,33 @@ "url": "https://github.com/sponsors/jonschlinkert" } }, + "node_modules/vue": { + "version": "3.5.41", + "resolved": "https://registry.npmjs.org/vue/-/vue-3.5.41.tgz", + "integrity": "sha512-2laE0p+aK+/AOPG/XL/WepOs/GlK755LJ1XECi9kDUrz1FKNw8rb2Xzlw9JS1rqEV55nb0ttsKxVlTCcd+R5cg==", + "license": "MIT", + "dependencies": { + "@vue/compiler-dom": "3.5.41", + "@vue/compiler-sfc": "3.5.41", + "@vue/runtime-dom": "3.5.41", + "@vue/server-renderer": "3.5.41", + "@vue/shared": "3.5.41" + }, + "peerDependencies": { + "typescript": "*" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/vue/node_modules/@vue/shared": { + "version": "3.5.41", + "resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.5.41.tgz", + "integrity": "sha512-IOnwSCma8j+9xJT6b8H0dEYidC80NsYmNMlZxRsukYcSoGaDBohog5hDxzeUXdFeGWFA++vWvxqOmrr96VlqMA==", + "license": "MIT" + }, "node_modules/wrap-ansi": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", diff --git a/laravel-app/package.json b/laravel-app/package.json index eea878d..ffa326c 100644 --- a/laravel-app/package.json +++ b/laravel-app/package.json @@ -9,12 +9,16 @@ "devDependencies": { "@tailwindcss/forms": "^0.5.2", "@tailwindcss/vite": "^4.0.0", - "alpinejs": "^3.4.2", "autoprefixer": "^10.4.2", "concurrently": "^9.0.1", "laravel-vite-plugin": "^3.1", "postcss": "^8.4.31", "tailwindcss": "^3.1.0", "vite": "^8.0.0" + }, + "dependencies": { + "@inertiajs/vue3": "^3.6.1", + "@vitejs/plugin-vue": "^6.0.8", + "vue": "^3.5.41" } } diff --git a/laravel-app/resources/js/Components/ApplicationLogo.vue b/laravel-app/resources/js/Components/ApplicationLogo.vue new file mode 100644 index 0000000..feb5e10 --- /dev/null +++ b/laravel-app/resources/js/Components/ApplicationLogo.vue @@ -0,0 +1,11 @@ + + + diff --git a/laravel-app/resources/js/Components/NavLink.vue b/laravel-app/resources/js/Components/NavLink.vue new file mode 100644 index 0000000..a7e4697 --- /dev/null +++ b/laravel-app/resources/js/Components/NavLink.vue @@ -0,0 +1,29 @@ + + + diff --git a/laravel-app/resources/js/Components/StatusBadge.vue b/laravel-app/resources/js/Components/StatusBadge.vue new file mode 100644 index 0000000..cbbb11e --- /dev/null +++ b/laravel-app/resources/js/Components/StatusBadge.vue @@ -0,0 +1,15 @@ + + + diff --git a/laravel-app/resources/js/Layouts/AppLayout.vue b/laravel-app/resources/js/Layouts/AppLayout.vue new file mode 100644 index 0000000..f2d37c3 --- /dev/null +++ b/laravel-app/resources/js/Layouts/AppLayout.vue @@ -0,0 +1,143 @@ + + + diff --git a/laravel-app/resources/js/Pages/AutomationRules/Create.vue b/laravel-app/resources/js/Pages/AutomationRules/Create.vue new file mode 100644 index 0000000..1d2f62b --- /dev/null +++ b/laravel-app/resources/js/Pages/AutomationRules/Create.vue @@ -0,0 +1,34 @@ + + + diff --git a/laravel-app/resources/js/Pages/AutomationRules/Edit.vue b/laravel-app/resources/js/Pages/AutomationRules/Edit.vue new file mode 100644 index 0000000..b66e3e0 --- /dev/null +++ b/laravel-app/resources/js/Pages/AutomationRules/Edit.vue @@ -0,0 +1,36 @@ + + + diff --git a/laravel-app/resources/js/Pages/AutomationRules/Form.vue b/laravel-app/resources/js/Pages/AutomationRules/Form.vue new file mode 100644 index 0000000..378c87c --- /dev/null +++ b/laravel-app/resources/js/Pages/AutomationRules/Form.vue @@ -0,0 +1,176 @@ + + + diff --git a/laravel-app/resources/js/Pages/AutomationRules/Index.vue b/laravel-app/resources/js/Pages/AutomationRules/Index.vue new file mode 100644 index 0000000..718f908 --- /dev/null +++ b/laravel-app/resources/js/Pages/AutomationRules/Index.vue @@ -0,0 +1,78 @@ + + + diff --git a/laravel-app/resources/js/Pages/Dashboard.vue b/laravel-app/resources/js/Pages/Dashboard.vue new file mode 100644 index 0000000..4c8d080 --- /dev/null +++ b/laravel-app/resources/js/Pages/Dashboard.vue @@ -0,0 +1,41 @@ + + + diff --git a/laravel-app/resources/js/Pages/Devices/Create.vue b/laravel-app/resources/js/Pages/Devices/Create.vue new file mode 100644 index 0000000..380505c --- /dev/null +++ b/laravel-app/resources/js/Pages/Devices/Create.vue @@ -0,0 +1,25 @@ + + + diff --git a/laravel-app/resources/js/Pages/Devices/Edit.vue b/laravel-app/resources/js/Pages/Devices/Edit.vue new file mode 100644 index 0000000..9f58e13 --- /dev/null +++ b/laravel-app/resources/js/Pages/Devices/Edit.vue @@ -0,0 +1,26 @@ + + + diff --git a/laravel-app/resources/js/Pages/Devices/Form.vue b/laravel-app/resources/js/Pages/Devices/Form.vue new file mode 100644 index 0000000..0b00ca7 --- /dev/null +++ b/laravel-app/resources/js/Pages/Devices/Form.vue @@ -0,0 +1,108 @@ + + + diff --git a/laravel-app/resources/js/Pages/Devices/Index.vue b/laravel-app/resources/js/Pages/Devices/Index.vue new file mode 100644 index 0000000..43adda8 --- /dev/null +++ b/laravel-app/resources/js/Pages/Devices/Index.vue @@ -0,0 +1,73 @@ + + + diff --git a/laravel-app/resources/js/Pages/Devices/Show.vue b/laravel-app/resources/js/Pages/Devices/Show.vue new file mode 100644 index 0000000..4872766 --- /dev/null +++ b/laravel-app/resources/js/Pages/Devices/Show.vue @@ -0,0 +1,132 @@ + + + diff --git a/laravel-app/resources/js/Pages/Profile/Edit.vue b/laravel-app/resources/js/Pages/Profile/Edit.vue new file mode 100644 index 0000000..23b7bd3 --- /dev/null +++ b/laravel-app/resources/js/Pages/Profile/Edit.vue @@ -0,0 +1,40 @@ + + + diff --git a/laravel-app/resources/js/Pages/Profile/Partials/DeleteUserForm.vue b/laravel-app/resources/js/Pages/Profile/Partials/DeleteUserForm.vue new file mode 100644 index 0000000..03e87ed --- /dev/null +++ b/laravel-app/resources/js/Pages/Profile/Partials/DeleteUserForm.vue @@ -0,0 +1,88 @@ + + + diff --git a/laravel-app/resources/js/Pages/Profile/Partials/UpdatePasswordForm.vue b/laravel-app/resources/js/Pages/Profile/Partials/UpdatePasswordForm.vue new file mode 100644 index 0000000..7ae4afb --- /dev/null +++ b/laravel-app/resources/js/Pages/Profile/Partials/UpdatePasswordForm.vue @@ -0,0 +1,82 @@ + + + diff --git a/laravel-app/resources/js/Pages/Profile/Partials/UpdateProfileInformationForm.vue b/laravel-app/resources/js/Pages/Profile/Partials/UpdateProfileInformationForm.vue new file mode 100644 index 0000000..d11b47b --- /dev/null +++ b/laravel-app/resources/js/Pages/Profile/Partials/UpdateProfileInformationForm.vue @@ -0,0 +1,69 @@ + + + diff --git a/laravel-app/resources/js/Pages/Zones/Create.vue b/laravel-app/resources/js/Pages/Zones/Create.vue new file mode 100644 index 0000000..74bda31 --- /dev/null +++ b/laravel-app/resources/js/Pages/Zones/Create.vue @@ -0,0 +1,20 @@ + + + diff --git a/laravel-app/resources/js/Pages/Zones/Edit.vue b/laravel-app/resources/js/Pages/Zones/Edit.vue new file mode 100644 index 0000000..beb2cd4 --- /dev/null +++ b/laravel-app/resources/js/Pages/Zones/Edit.vue @@ -0,0 +1,24 @@ + + + diff --git a/laravel-app/resources/js/Pages/Zones/Form.vue b/laravel-app/resources/js/Pages/Zones/Form.vue new file mode 100644 index 0000000..395b9ca --- /dev/null +++ b/laravel-app/resources/js/Pages/Zones/Form.vue @@ -0,0 +1,62 @@ + + +