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);
+});
|