From c2ef6d9986da6eecdc6804647ebd829133b1b483 Mon Sep 17 00:00:00 2001 From: Doar Mihai Date: Sun, 19 Jul 2026 16:58:42 +0300 Subject: [PATCH] feat: manage cms_type and WordPress credentials in Sites UI --- app/Livewire/Sites/Index.php | 32 ++++++++++- .../views/livewire/sites/index.blade.php | 41 +++++++++++++ tests/Feature/Livewire/SitesTest.php | 57 +++++++++++++++++++ 3 files changed, 129 insertions(+), 1 deletion(-) diff --git a/app/Livewire/Sites/Index.php b/app/Livewire/Sites/Index.php index 9b9906f..b29b8e8 100644 --- a/app/Livewire/Sites/Index.php +++ b/app/Livewire/Sites/Index.php @@ -13,16 +13,25 @@ class Index extends Component public ?int $editingId = null; + public ?int $revealedId = null; + public string $name = ''; public string $url = ''; public ?string $description = null; + public string $cmsType = 'custom'; + + public string $wpUsername = ''; + + public string $wpAppPassword = ''; + public function render() { return view('livewire.sites.index', [ 'sites' => Site::orderBy('name')->get(), + 'cmsTypes' => Site::CMS_TYPES, ]); } @@ -39,6 +48,9 @@ class Index extends Component $this->name = $site->name; $this->url = $site->url; $this->description = $site->description; + $this->cmsType = $site->cms_type; + $this->wpUsername = $site->wp_username ?? ''; + $this->wpAppPassword = $site->wp_app_password ?? ''; $this->showForm = true; } @@ -48,9 +60,19 @@ class Index extends Component 'name' => ['required', 'string', 'max:255'], 'url' => ['required', 'string', 'max:255'], 'description' => ['nullable', 'string'], + 'cmsType' => ['required', 'string', 'in:' . implode(',', Site::CMS_TYPES)], + 'wpUsername' => ['required_if:cmsType,wordpress', 'nullable', 'string', 'max:255'], + 'wpAppPassword' => ['required_if:cmsType,wordpress', 'nullable', 'string', 'max:255'], ]); - Site::updateOrCreate(['id' => $this->editingId], $validated); + Site::updateOrCreate(['id' => $this->editingId], [ + 'name' => $validated['name'], + 'url' => $validated['url'], + 'description' => $validated['description'], + 'cms_type' => $validated['cmsType'], + 'wp_username' => $validated['cmsType'] === 'wordpress' ? $validated['wpUsername'] : null, + 'wp_app_password' => $validated['cmsType'] === 'wordpress' ? $validated['wpAppPassword'] : null, + ]); $this->resetForm(); $this->showForm = false; @@ -61,12 +83,20 @@ class Index extends Component Site::findOrFail($id)->delete(); } + public function toggleReveal(int $id): void + { + $this->revealedId = $this->revealedId === $id ? null : $id; + } + public function resetForm(): void { $this->editingId = null; $this->name = ''; $this->url = ''; $this->description = null; + $this->cmsType = 'custom'; + $this->wpUsername = ''; + $this->wpAppPassword = ''; $this->resetValidation(); } } diff --git a/resources/views/livewire/sites/index.blade.php b/resources/views/livewire/sites/index.blade.php index b0223dd..c21f917 100644 --- a/resources/views/livewire/sites/index.blade.php +++ b/resources/views/livewire/sites/index.blade.php @@ -10,6 +10,7 @@ Name URL Description + CMS Type @@ -19,6 +20,22 @@ {{ $site->name }} {{ $site->url }} {{ $site->description }} + + {{ ucfirst($site->cms_type) }} + @if ($site->cms_type === 'wordpress') +
+ {{ $site->wp_username }} / + @if ($revealedId === $site->id) + {{ $site->wp_app_password }} + @else + •••••••• + @endif + +
+ @endif + @@ -50,6 +67,30 @@ +
+ + + @error('cmsType') {{ $message }} @enderror +
+ + @if ($cmsType === 'wordpress') +
+ + + @error('wpUsername') {{ $message }} @enderror +
+ +
+ + + @error('wpAppPassword') {{ $message }} @enderror +
+ @endif +
diff --git a/tests/Feature/Livewire/SitesTest.php b/tests/Feature/Livewire/SitesTest.php index 8055b5b..3d48b8f 100644 --- a/tests/Feature/Livewire/SitesTest.php +++ b/tests/Feature/Livewire/SitesTest.php @@ -48,3 +48,60 @@ test('a site can be deleted', function () { expect(Site::find($site->id))->toBeNull(); }); + +test('a new WordPress site can be created with credentials', function () { + Livewire::test(Index::class) + ->set('name', 'WP Site') + ->set('url', 'https://wp.example.com') + ->set('cmsType', 'wordpress') + ->set('wpUsername', 'admin') + ->set('wpAppPassword', 'abcd 1234 efgh 5678') + ->call('save'); + + $site = Site::where('name', 'WP Site')->first(); + + expect($site) + ->cms_type->toBe('wordpress') + ->wp_username->toBe('admin') + ->wp_app_password->toBe('abcd 1234 efgh 5678'); +}); + +test('WordPress username and app password are required together when cms_type is wordpress', function () { + Livewire::test(Index::class) + ->set('name', 'WP Site') + ->set('url', 'https://wp.example.com') + ->set('cmsType', 'wordpress') + ->set('wpUsername', '') + ->set('wpAppPassword', '') + ->call('save') + ->assertHasErrors(['wpUsername', 'wpAppPassword']); +}); + +test('a site defaults to custom cms_type with no WordPress fields required', function () { + Livewire::test(Index::class) + ->set('name', 'Plain Site') + ->set('url', 'https://plain.example.com') + ->call('save') + ->assertHasNoErrors(); + + expect(Site::where('name', 'Plain Site')->first()->cms_type)->toBe('custom'); +}); + +test('editing a site loads its WordPress fields', function () { + $site = Site::factory()->wordpress()->create(['wp_username' => 'editor']); + + Livewire::test(Index::class) + ->call('edit', $site->id) + ->assertSet('cmsType', 'wordpress') + ->assertSet('wpUsername', 'editor'); +}); + +test('WordPress app password can be revealed and hidden in the table', function () { + $site = Site::factory()->wordpress()->create(); + + Livewire::test(Index::class) + ->call('toggleReveal', $site->id) + ->assertSet('revealedId', $site->id) + ->call('toggleReveal', $site->id) + ->assertSet('revealedId', null); +});