From dbad41809304ad90335d0bed71701321e1337d01 Mon Sep 17 00:00:00 2001 From: pptx704 Date: Sat, 11 Apr 2026 06:18:36 +0600 Subject: [PATCH] Harden channels page: deduplicate dropdowns, add missing provider logos Consolidate three identical click-outside $effect blocks into a reusable useClickOutside helper. Extract duplicated events checkbox list into an eventsDropdownItems snippet shared by create and edit dialogs. Add brand SVG icons for Teams, Google Chat, and Matrix providers. --- .../routes/dashboard/channels/+page.svelte | 125 +++++++----------- 1 file changed, 48 insertions(+), 77 deletions(-) diff --git a/frontend/src/routes/dashboard/channels/+page.svelte b/frontend/src/routes/dashboard/channels/+page.svelte index 62a9e26..2eb60a4 100644 --- a/frontend/src/routes/dashboard/channels/+page.svelte +++ b/frontend/src/routes/dashboard/channels/+page.svelte @@ -286,39 +286,22 @@ return `${events.length} events`; } - // Click-outside handlers for dropdowns - $effect(() => { - if (!providerDropdownOpen) return; - function handleMouseDown(e: MouseEvent) { - if (providerDropdownEl && !providerDropdownEl.contains(e.target as Node)) { - providerDropdownOpen = false; + // Click-outside handler — single listener covers all dropdowns + function useClickOutside(open: () => boolean, el: () => HTMLElement | null, close: () => void) { + $effect(() => { + if (!open()) return; + function onMouseDown(e: MouseEvent) { + const container = el(); + if (container && !container.contains(e.target as Node)) close(); } - } - document.addEventListener('mousedown', handleMouseDown); - return () => document.removeEventListener('mousedown', handleMouseDown); - }); + document.addEventListener('mousedown', onMouseDown); + return () => document.removeEventListener('mousedown', onMouseDown); + }); + } - $effect(() => { - if (!eventsDropdownOpen) return; - function handleMouseDown(e: MouseEvent) { - if (eventsDropdownEl && !eventsDropdownEl.contains(e.target as Node)) { - eventsDropdownOpen = false; - } - } - document.addEventListener('mousedown', handleMouseDown); - return () => document.removeEventListener('mousedown', handleMouseDown); - }); - - $effect(() => { - if (!editEventsDropdownOpen) return; - function handleMouseDown(e: MouseEvent) { - if (editEventsDropdownEl && !editEventsDropdownEl.contains(e.target as Node)) { - editEventsDropdownOpen = false; - } - } - document.addEventListener('mousedown', handleMouseDown); - return () => document.removeEventListener('mousedown', handleMouseDown); - }); + useClickOutside(() => providerDropdownOpen, () => providerDropdownEl, () => { providerDropdownOpen = false; }); + useClickOutside(() => eventsDropdownOpen, () => eventsDropdownEl, () => { eventsDropdownOpen = false; }); + useClickOutside(() => editEventsDropdownOpen, () => editEventsDropdownEl, () => { editEventsDropdownOpen = false; }); onMount(fetchChannels); @@ -848,29 +831,7 @@ class="absolute left-0 top-full z-20 mt-1.5 w-full overflow-y-auto rounded-[var(--radius-card)] border border-[var(--color-border-mid)] bg-[var(--color-bg-2)] py-1.5 shadow-xl" style="max-height: 280px; animation: fadeUp 0.12s ease both" > - {#each Object.entries(groupedEvents) as [group, events], gi} -
{group}
- - {#each events as et} - {@const checked = createEvents.includes(et.value)} - - {/each} - - {#if gi < Object.entries(groupedEvents).length - 1} -
- {/if} - {/each} + {@render eventsDropdownItems(createEvents, (v) => { createEvents = toggleEvent(createEvents, v); })} {/if} @@ -1091,29 +1052,7 @@ class="absolute left-0 top-full z-20 mt-1.5 w-full overflow-y-auto rounded-[var(--radius-card)] border border-[var(--color-border-mid)] bg-[var(--color-bg-2)] py-1.5 shadow-xl" style="max-height: 280px; animation: fadeUp 0.12s ease both" > - {#each Object.entries(groupedEvents) as [group, events], gi} -
{group}
- - {#each events as et} - {@const checked = editEvents.includes(et.value)} - - {/each} - - {#if gi < Object.entries(groupedEvents).length - 1} -
- {/if} - {/each} + {@render eventsDropdownItems(editEvents, (v) => { editEvents = toggleEvent(editEvents, v); })} {/if} @@ -1296,13 +1235,45 @@ {/if} +{#snippet eventsDropdownItems(selected: string[], toggle: (value: string) => void)} + {#each Object.entries(groupedEvents) as [group, events], gi} +
{group}
+ + {#each events as et} + {@const checked = selected.includes(et.value)} + + {/each} + + {#if gi < Object.entries(groupedEvents).length - 1} +
+ {/if} + {/each} +{/snippet} + {#snippet providerIcon(provider: string)} {#if provider === 'discord'} {:else if provider === 'slack'} + {:else if provider === 'teams'} + + {:else if provider === 'googlechat'} + {:else if provider === 'telegram'} + {:else if provider === 'matrix'} + {:else if provider === 'webhook'} {:else}