feat: manage cms_type and WordPress credentials in Sites UI
This commit is contained in:
@@ -13,16 +13,25 @@ class Index extends Component
|
|||||||
|
|
||||||
public ?int $editingId = null;
|
public ?int $editingId = null;
|
||||||
|
|
||||||
|
public ?int $revealedId = null;
|
||||||
|
|
||||||
public string $name = '';
|
public string $name = '';
|
||||||
|
|
||||||
public string $url = '';
|
public string $url = '';
|
||||||
|
|
||||||
public ?string $description = null;
|
public ?string $description = null;
|
||||||
|
|
||||||
|
public string $cmsType = 'custom';
|
||||||
|
|
||||||
|
public string $wpUsername = '';
|
||||||
|
|
||||||
|
public string $wpAppPassword = '';
|
||||||
|
|
||||||
public function render()
|
public function render()
|
||||||
{
|
{
|
||||||
return view('livewire.sites.index', [
|
return view('livewire.sites.index', [
|
||||||
'sites' => Site::orderBy('name')->get(),
|
'sites' => Site::orderBy('name')->get(),
|
||||||
|
'cmsTypes' => Site::CMS_TYPES,
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -39,6 +48,9 @@ class Index extends Component
|
|||||||
$this->name = $site->name;
|
$this->name = $site->name;
|
||||||
$this->url = $site->url;
|
$this->url = $site->url;
|
||||||
$this->description = $site->description;
|
$this->description = $site->description;
|
||||||
|
$this->cmsType = $site->cms_type;
|
||||||
|
$this->wpUsername = $site->wp_username ?? '';
|
||||||
|
$this->wpAppPassword = $site->wp_app_password ?? '';
|
||||||
$this->showForm = true;
|
$this->showForm = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -48,9 +60,19 @@ class Index extends Component
|
|||||||
'name' => ['required', 'string', 'max:255'],
|
'name' => ['required', 'string', 'max:255'],
|
||||||
'url' => ['required', 'string', 'max:255'],
|
'url' => ['required', 'string', 'max:255'],
|
||||||
'description' => ['nullable', 'string'],
|
'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->resetForm();
|
||||||
$this->showForm = false;
|
$this->showForm = false;
|
||||||
@@ -61,12 +83,20 @@ class Index extends Component
|
|||||||
Site::findOrFail($id)->delete();
|
Site::findOrFail($id)->delete();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function toggleReveal(int $id): void
|
||||||
|
{
|
||||||
|
$this->revealedId = $this->revealedId === $id ? null : $id;
|
||||||
|
}
|
||||||
|
|
||||||
public function resetForm(): void
|
public function resetForm(): void
|
||||||
{
|
{
|
||||||
$this->editingId = null;
|
$this->editingId = null;
|
||||||
$this->name = '';
|
$this->name = '';
|
||||||
$this->url = '';
|
$this->url = '';
|
||||||
$this->description = null;
|
$this->description = null;
|
||||||
|
$this->cmsType = 'custom';
|
||||||
|
$this->wpUsername = '';
|
||||||
|
$this->wpAppPassword = '';
|
||||||
$this->resetValidation();
|
$this->resetValidation();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,6 +10,7 @@
|
|||||||
<th class="p-3">Name</th>
|
<th class="p-3">Name</th>
|
||||||
<th class="p-3">URL</th>
|
<th class="p-3">URL</th>
|
||||||
<th class="p-3">Description</th>
|
<th class="p-3">Description</th>
|
||||||
|
<th class="p-3">CMS Type</th>
|
||||||
<th class="p-3"></th>
|
<th class="p-3"></th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
@@ -19,6 +20,22 @@
|
|||||||
<td class="p-3">{{ $site->name }}</td>
|
<td class="p-3">{{ $site->name }}</td>
|
||||||
<td class="p-3"><a href="{{ $site->url }}" target="_blank" class="text-blue-600">{{ $site->url }}</a></td>
|
<td class="p-3"><a href="{{ $site->url }}" target="_blank" class="text-blue-600">{{ $site->url }}</a></td>
|
||||||
<td class="p-3">{{ $site->description }}</td>
|
<td class="p-3">{{ $site->description }}</td>
|
||||||
|
<td class="p-3">
|
||||||
|
{{ ucfirst($site->cms_type) }}
|
||||||
|
@if ($site->cms_type === 'wordpress')
|
||||||
|
<div class="text-xs text-gray-500">
|
||||||
|
{{ $site->wp_username }} /
|
||||||
|
@if ($revealedId === $site->id)
|
||||||
|
{{ $site->wp_app_password }}
|
||||||
|
@else
|
||||||
|
••••••••
|
||||||
|
@endif
|
||||||
|
<button wire:click="toggleReveal({{ $site->id }})" class="text-xs text-blue-600 ml-1">
|
||||||
|
{{ $revealedId === $site->id ? 'Hide' : 'Show' }}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
@endif
|
||||||
|
</td>
|
||||||
<td class="p-3 text-right space-x-2">
|
<td class="p-3 text-right space-x-2">
|
||||||
<button wire:click="edit({{ $site->id }})" class="text-blue-600">Edit</button>
|
<button wire:click="edit({{ $site->id }})" class="text-blue-600">Edit</button>
|
||||||
<button wire:click="delete({{ $site->id }})" wire:confirm="Delete this site?" class="text-red-600">Delete</button>
|
<button wire:click="delete({{ $site->id }})" wire:confirm="Delete this site?" class="text-red-600">Delete</button>
|
||||||
@@ -50,6 +67,30 @@
|
|||||||
<textarea wire:model="description" class="w-full border rounded p-2"></textarea>
|
<textarea wire:model="description" class="w-full border rounded p-2"></textarea>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<label class="block text-sm font-medium">CMS Type</label>
|
||||||
|
<select wire:model.live="cmsType" class="w-full border rounded p-2">
|
||||||
|
@foreach ($cmsTypes as $type)
|
||||||
|
<option value="{{ $type }}">{{ ucfirst($type) }}</option>
|
||||||
|
@endforeach
|
||||||
|
</select>
|
||||||
|
@error('cmsType') <span class="text-red-600 text-sm">{{ $message }}</span> @enderror
|
||||||
|
</div>
|
||||||
|
|
||||||
|
@if ($cmsType === 'wordpress')
|
||||||
|
<div>
|
||||||
|
<label class="block text-sm font-medium">WP Username</label>
|
||||||
|
<input type="text" wire:model="wpUsername" class="w-full border rounded p-2">
|
||||||
|
@error('wpUsername') <span class="text-red-600 text-sm">{{ $message }}</span> @enderror
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<label class="block text-sm font-medium">WP Application Password</label>
|
||||||
|
<input type="text" wire:model="wpAppPassword" class="w-full border rounded p-2">
|
||||||
|
@error('wpAppPassword') <span class="text-red-600 text-sm">{{ $message }}</span> @enderror
|
||||||
|
</div>
|
||||||
|
@endif
|
||||||
|
|
||||||
<div class="flex justify-end gap-2">
|
<div class="flex justify-end gap-2">
|
||||||
<button wire:click="$set('showForm', false)" class="px-4 py-2">Cancel</button>
|
<button wire:click="$set('showForm', false)" class="px-4 py-2">Cancel</button>
|
||||||
<button wire:click="save" class="bg-blue-600 text-white px-4 py-2 rounded">Save</button>
|
<button wire:click="save" class="bg-blue-600 text-white px-4 py-2 rounded">Save</button>
|
||||||
|
|||||||
@@ -48,3 +48,60 @@ test('a site can be deleted', function () {
|
|||||||
|
|
||||||
expect(Site::find($site->id))->toBeNull();
|
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);
|
||||||
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user