diff --git a/app/Livewire/Channels.php b/app/Livewire/Channels.php
index ebb7099d..795fc8b0 100644
--- a/app/Livewire/Channels.php
+++ b/app/Livewire/Channels.php
@@ -34,6 +34,14 @@ class Channels extends Component
public string $newDescription = '';
+ public ?int $editingChannelId = null;
+
+ public string $editDisplayName = '';
+
+ public ?int $editLanguageId = null;
+
+ public string $editDescription = '';
+
public function toggle(int $channelId): void
{
$channel = PlatformChannel::findOrFail($channelId);
@@ -127,6 +135,45 @@ public function createChannel(CreateChannelAction $action): void
$this->closeCreateModal();
}
+ public function openEditModal(int $channelId): void
+ {
+ $channel = PlatformChannel::findOrFail($channelId);
+
+ $this->resetErrorBag();
+ $this->editingChannelId = $channelId;
+ $this->editDisplayName = $channel->display_name;
+ $this->editLanguageId = $channel->language_id;
+ $this->editDescription = $channel->description ?? '';
+ }
+
+ public function closeEditModal(): void
+ {
+ $this->editingChannelId = null;
+ }
+
+ // The community pairing (name, channel_id, platform_instance_id) is deliberately immutable:
+ // it is the channel's remote identity, unique per instance, and re-pointing it would change
+ // the meaning of every route already attached.
+ public function updateChannel(): void
+ {
+ if ($this->editingChannelId === null) {
+ return;
+ }
+
+ $this->validate([
+ 'editDisplayName' => 'required|string|max:255',
+ 'editLanguageId' => 'nullable|integer|exists:languages,id',
+ ]);
+
+ PlatformChannel::findOrFail($this->editingChannelId)->update([
+ 'display_name' => $this->editDisplayName,
+ 'language_id' => $this->editLanguageId,
+ 'description' => $this->editDescription !== '' ? $this->editDescription : null,
+ ]);
+
+ $this->closeEditModal();
+ }
+
public function openAccountModal(int $channelId): void
{
$this->managingChannelId = $channelId;
@@ -177,6 +224,9 @@ public function render(): View
return view('livewire.channels', [
'channels' => $channels,
'managingChannel' => $managingChannel,
+ 'editingChannel' => $this->editingChannelId !== null
+ ? PlatformChannel::with('platformInstance')->find($this->editingChannelId)
+ : null,
'availableAccounts' => $availableAccounts,
'platformInstances' => PlatformInstance::where('is_active', true)->orderBy('name')->get(),
'languages' => Language::where('is_active', true)->orderBy('name')->get(),
diff --git a/app/Models/PlatformChannel.php b/app/Models/PlatformChannel.php
index 12f88fac..7b85455e 100644
--- a/app/Models/PlatformChannel.php
+++ b/app/Models/PlatformChannel.php
@@ -17,7 +17,9 @@
* @property PlatformInstance $platformInstance
* @property int $channel_id
* @property string $name
- * @property int $language_id
+ * @property string $display_name
+ * @property string|null $description
+ * @property int|null $language_id
* @property Language|null $language
* @property bool $is_active
*/
diff --git a/phpstan-baseline.neon b/phpstan-baseline.neon
index f74a15a0..93a19e71 100644
--- a/phpstan-baseline.neon
+++ b/phpstan-baseline.neon
@@ -12,12 +12,6 @@ parameters:
count: 1
path: tests/Unit/Actions/CreateChannelActionTest.php
- -
- message: '#^Call to method PHPUnit\\Framework\\Assert\:\:assertNull\(\) with int will always evaluate to false\.$#'
- identifier: method.impossibleType
- count: 1
- path: tests/Unit/Actions/CreateChannelActionTest.php
-
-
message: '#^Access to an undefined property App\\Models\\Route\:\:\$id\.$#'
identifier: property.notFound
diff --git a/resources/views/livewire/channels.blade.php b/resources/views/livewire/channels.blade.php
index cecf64f9..a29461ba 100644
--- a/resources/views/livewire/channels.blade.php
+++ b/resources/views/livewire/channels.blade.php
@@ -35,15 +35,28 @@ class="text-sm text-blue-500 hover:underline">
@endif
-
+
@if ($channel->description)
@@ -259,4 +272,82 @@ class="inline-flex justify-center rounded-md border border-transparent shadow-xs
@endif
+
+
+ @if ($editingChannel)
+
+
+
+ @endif
diff --git a/tests/Feature/Livewire/ChannelsTest.php b/tests/Feature/Livewire/ChannelsTest.php
index db8e876c..f1a1e6df 100644
--- a/tests/Feature/Livewire/ChannelsTest.php
+++ b/tests/Feature/Livewire/ChannelsTest.php
@@ -248,4 +248,166 @@ public function test_toggle_flips_active_state(): void
$this->assertFalse($channel->fresh()->is_active);
}
+
+ public function test_channel_cards_show_an_edit_action(): void
+ {
+ PlatformChannel::factory()->create(['display_name' => 'Tech Community']);
+
+ Livewire::test(Channels::class)
+ ->assertSee('Edit Tech Community');
+ }
+
+ public function test_open_edit_modal_prefills_the_current_values(): void
+ {
+ $language = Language::factory()->create();
+ $channel = PlatformChannel::factory()->create([
+ 'display_name' => 'Tech Community',
+ 'description' => 'A place for tech',
+ 'language_id' => $language->id,
+ ]);
+
+ Livewire::test(Channels::class)
+ ->assertSet('editingChannelId', null)
+ ->call('openEditModal', $channel->id)
+ ->assertSet('editingChannelId', $channel->id)
+ ->assertSet('editDisplayName', 'Tech Community')
+ ->assertSet('editDescription', 'A place for tech')
+ ->assertSet('editLanguageId', $language->id)
+ ->assertSee('Edit Channel');
+ }
+
+ public function test_open_edit_modal_prefills_a_null_description_as_blank(): void
+ {
+ $channel = PlatformChannel::factory()->create(['description' => null]);
+
+ Livewire::test(Channels::class)
+ ->call('openEditModal', $channel->id)
+ ->assertSet('editDescription', '');
+ }
+
+ public function test_update_channel_persists_the_changes_and_closes_the_modal(): void
+ {
+ $language = Language::factory()->create();
+ $channel = PlatformChannel::factory()->create([
+ 'display_name' => 'Old Name',
+ 'description' => 'Old description',
+ 'language_id' => null,
+ ]);
+
+ Livewire::test(Channels::class)
+ ->call('openEditModal', $channel->id)
+ ->set('editDisplayName', 'New Name')
+ ->set('editDescription', 'New description')
+ ->set('editLanguageId', $language->id)
+ ->call('updateChannel')
+ ->assertSet('editingChannelId', null)
+ ->assertHasNoErrors();
+
+ $channel->refresh();
+
+ $this->assertSame('New Name', $channel->display_name);
+ $this->assertSame('New description', $channel->description);
+ $this->assertSame($language->id, $channel->language_id);
+ }
+
+ public function test_update_channel_requires_a_display_name(): void
+ {
+ $channel = PlatformChannel::factory()->create(['display_name' => 'Original']);
+
+ Livewire::test(Channels::class)
+ ->call('openEditModal', $channel->id)
+ ->set('editDisplayName', '')
+ ->call('updateChannel')
+ ->assertHasErrors(['editDisplayName' => 'required']);
+
+ $this->assertSame('Original', $channel->fresh()->display_name);
+ }
+
+ public function test_update_channel_rejects_a_display_name_over_the_length_limit(): void
+ {
+ $channel = PlatformChannel::factory()->create(['display_name' => 'Original']);
+
+ Livewire::test(Channels::class)
+ ->call('openEditModal', $channel->id)
+ ->set('editDisplayName', str_repeat('a', 256))
+ ->call('updateChannel')
+ ->assertHasErrors(['editDisplayName' => 'max']);
+
+ $this->assertSame('Original', $channel->fresh()->display_name);
+ }
+
+ public function test_update_channel_rejects_an_unknown_language(): void
+ {
+ $channel = PlatformChannel::factory()->create(['display_name' => 'Original']);
+
+ Livewire::test(Channels::class)
+ ->call('openEditModal', $channel->id)
+ ->set('editLanguageId', 999999)
+ ->call('updateChannel')
+ ->assertHasErrors(['editLanguageId' => 'exists']);
+ }
+
+ public function test_update_channel_allows_clearing_the_language(): void
+ {
+ $language = Language::factory()->create();
+ $channel = PlatformChannel::factory()->create(['language_id' => $language->id]);
+
+ Livewire::test(Channels::class)
+ ->call('openEditModal', $channel->id)
+ ->set('editLanguageId', null)
+ ->call('updateChannel')
+ ->assertHasNoErrors();
+
+ $this->assertNull($channel->fresh()->language_id);
+ }
+
+ public function test_update_channel_stores_a_blank_description_as_null(): void
+ {
+ $channel = PlatformChannel::factory()->create(['description' => 'Has one']);
+
+ Livewire::test(Channels::class)
+ ->call('openEditModal', $channel->id)
+ ->set('editDescription', '')
+ ->call('updateChannel');
+
+ $this->assertNull($channel->fresh()->description);
+ }
+
+ public function test_update_channel_leaves_the_community_pairing_untouched(): void
+ {
+ $channel = PlatformChannel::factory()->create(['display_name' => 'Original']);
+ $before = $channel->only(['name', 'channel_id', 'platform_instance_id']);
+
+ Livewire::test(Channels::class)
+ ->call('openEditModal', $channel->id)
+ ->set('editDisplayName', 'Renamed')
+ ->call('updateChannel');
+
+ $this->assertSame($before, $channel->fresh()->only(['name', 'channel_id', 'platform_instance_id']));
+ }
+
+ public function test_close_edit_modal_discards_the_edit(): void
+ {
+ $channel = PlatformChannel::factory()->create(['display_name' => 'Original']);
+
+ Livewire::test(Channels::class)
+ ->call('openEditModal', $channel->id)
+ ->set('editDisplayName', 'Discarded')
+ ->call('closeEditModal')
+ ->assertSet('editingChannelId', null);
+
+ $this->assertSame('Original', $channel->fresh()->display_name);
+ }
+
+ public function test_update_channel_does_nothing_without_an_open_modal(): void
+ {
+ $channel = PlatformChannel::factory()->create(['display_name' => 'Original']);
+
+ Livewire::test(Channels::class)
+ ->set('editDisplayName', 'Should not apply')
+ ->call('updateChannel')
+ ->assertHasNoErrors();
+
+ $this->assertSame('Original', $channel->fresh()->display_name);
+ }
}