forked from wrenn/wrenn
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.
This commit is contained in:
@ -286,39 +286,22 @@
|
|||||||
return `${events.length} events`;
|
return `${events.length} events`;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Click-outside handlers for dropdowns
|
// Click-outside handler — single listener covers all dropdowns
|
||||||
$effect(() => {
|
function useClickOutside(open: () => boolean, el: () => HTMLElement | null, close: () => void) {
|
||||||
if (!providerDropdownOpen) return;
|
$effect(() => {
|
||||||
function handleMouseDown(e: MouseEvent) {
|
if (!open()) return;
|
||||||
if (providerDropdownEl && !providerDropdownEl.contains(e.target as Node)) {
|
function onMouseDown(e: MouseEvent) {
|
||||||
providerDropdownOpen = false;
|
const container = el();
|
||||||
|
if (container && !container.contains(e.target as Node)) close();
|
||||||
}
|
}
|
||||||
}
|
document.addEventListener('mousedown', onMouseDown);
|
||||||
document.addEventListener('mousedown', handleMouseDown);
|
return () => document.removeEventListener('mousedown', onMouseDown);
|
||||||
return () => document.removeEventListener('mousedown', handleMouseDown);
|
});
|
||||||
});
|
}
|
||||||
|
|
||||||
$effect(() => {
|
useClickOutside(() => providerDropdownOpen, () => providerDropdownEl, () => { providerDropdownOpen = false; });
|
||||||
if (!eventsDropdownOpen) return;
|
useClickOutside(() => eventsDropdownOpen, () => eventsDropdownEl, () => { eventsDropdownOpen = false; });
|
||||||
function handleMouseDown(e: MouseEvent) {
|
useClickOutside(() => editEventsDropdownOpen, () => editEventsDropdownEl, () => { editEventsDropdownOpen = false; });
|
||||||
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);
|
|
||||||
});
|
|
||||||
|
|
||||||
onMount(fetchChannels);
|
onMount(fetchChannels);
|
||||||
</script>
|
</script>
|
||||||
@ -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"
|
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"
|
style="max-height: 280px; animation: fadeUp 0.12s ease both"
|
||||||
>
|
>
|
||||||
{#each Object.entries(groupedEvents) as [group, events], gi}
|
{@render eventsDropdownItems(createEvents, (v) => { createEvents = toggleEvent(createEvents, v); })}
|
||||||
<div class="px-3 py-1.5 text-badge font-semibold uppercase tracking-[0.06em] text-[var(--color-text-muted)]">{group}</div>
|
|
||||||
|
|
||||||
{#each events as et}
|
|
||||||
{@const checked = createEvents.includes(et.value)}
|
|
||||||
<label class="flex cursor-pointer items-center gap-2.5 px-3 py-2 transition-colors duration-100 hover:bg-[var(--color-bg-3)]">
|
|
||||||
<span class="flex h-3.5 w-3.5 shrink-0 items-center justify-center rounded-sm border transition-colors duration-100
|
|
||||||
{checked ? 'border-[var(--color-accent)] bg-[var(--color-accent)]' : 'border-[var(--color-border-mid)] bg-[var(--color-bg-4)]'}">
|
|
||||||
{#if checked}
|
|
||||||
<svg width="8" height="8" viewBox="0 0 24 24" fill="none" stroke="white" stroke-width="3.5" stroke-linecap="round" stroke-linejoin="round">
|
|
||||||
<polyline points="20 6 9 17 4 12" />
|
|
||||||
</svg>
|
|
||||||
{/if}
|
|
||||||
</span>
|
|
||||||
<input type="checkbox" class="sr-only" {checked} onchange={() => { createEvents = toggleEvent(createEvents, et.value); }} />
|
|
||||||
<span class="font-mono text-meta {checked ? 'text-[var(--color-text-bright)]' : 'text-[var(--color-text-secondary)]'}">{et.value}</span>
|
|
||||||
</label>
|
|
||||||
{/each}
|
|
||||||
|
|
||||||
{#if gi < Object.entries(groupedEvents).length - 1}
|
|
||||||
<div class="mx-3 my-1 border-t border-[var(--color-border)]"></div>
|
|
||||||
{/if}
|
|
||||||
{/each}
|
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
@ -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"
|
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"
|
style="max-height: 280px; animation: fadeUp 0.12s ease both"
|
||||||
>
|
>
|
||||||
{#each Object.entries(groupedEvents) as [group, events], gi}
|
{@render eventsDropdownItems(editEvents, (v) => { editEvents = toggleEvent(editEvents, v); })}
|
||||||
<div class="px-3 py-1.5 text-badge font-semibold uppercase tracking-[0.06em] text-[var(--color-text-muted)]">{group}</div>
|
|
||||||
|
|
||||||
{#each events as et}
|
|
||||||
{@const checked = editEvents.includes(et.value)}
|
|
||||||
<label class="flex cursor-pointer items-center gap-2.5 px-3 py-2 transition-colors duration-100 hover:bg-[var(--color-bg-3)]">
|
|
||||||
<span class="flex h-3.5 w-3.5 shrink-0 items-center justify-center rounded-sm border transition-colors duration-100
|
|
||||||
{checked ? 'border-[var(--color-accent)] bg-[var(--color-accent)]' : 'border-[var(--color-border-mid)] bg-[var(--color-bg-4)]'}">
|
|
||||||
{#if checked}
|
|
||||||
<svg width="8" height="8" viewBox="0 0 24 24" fill="none" stroke="white" stroke-width="3.5" stroke-linecap="round" stroke-linejoin="round">
|
|
||||||
<polyline points="20 6 9 17 4 12" />
|
|
||||||
</svg>
|
|
||||||
{/if}
|
|
||||||
</span>
|
|
||||||
<input type="checkbox" class="sr-only" {checked} onchange={() => { editEvents = toggleEvent(editEvents, et.value); }} />
|
|
||||||
<span class="font-mono text-meta {checked ? 'text-[var(--color-text-bright)]' : 'text-[var(--color-text-secondary)]'}">{et.value}</span>
|
|
||||||
</label>
|
|
||||||
{/each}
|
|
||||||
|
|
||||||
{#if gi < Object.entries(groupedEvents).length - 1}
|
|
||||||
<div class="mx-3 my-1 border-t border-[var(--color-border)]"></div>
|
|
||||||
{/if}
|
|
||||||
{/each}
|
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
@ -1296,13 +1235,45 @@
|
|||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
|
{#snippet eventsDropdownItems(selected: string[], toggle: (value: string) => void)}
|
||||||
|
{#each Object.entries(groupedEvents) as [group, events], gi}
|
||||||
|
<div class="px-3 py-1.5 text-badge font-semibold uppercase tracking-[0.06em] text-[var(--color-text-muted)]">{group}</div>
|
||||||
|
|
||||||
|
{#each events as et}
|
||||||
|
{@const checked = selected.includes(et.value)}
|
||||||
|
<label class="flex cursor-pointer items-center gap-2.5 px-3 py-2 transition-colors duration-100 hover:bg-[var(--color-bg-3)]">
|
||||||
|
<span class="flex h-3.5 w-3.5 shrink-0 items-center justify-center rounded-sm border transition-colors duration-100
|
||||||
|
{checked ? 'border-[var(--color-accent)] bg-[var(--color-accent)]' : 'border-[var(--color-border-mid)] bg-[var(--color-bg-4)]'}">
|
||||||
|
{#if checked}
|
||||||
|
<svg width="8" height="8" viewBox="0 0 24 24" fill="none" stroke="white" stroke-width="3.5" stroke-linecap="round" stroke-linejoin="round">
|
||||||
|
<polyline points="20 6 9 17 4 12" />
|
||||||
|
</svg>
|
||||||
|
{/if}
|
||||||
|
</span>
|
||||||
|
<input type="checkbox" class="sr-only" {checked} onchange={() => toggle(et.value)} />
|
||||||
|
<span class="font-mono text-meta {checked ? 'text-[var(--color-text-bright)]' : 'text-[var(--color-text-secondary)]'}">{et.value}</span>
|
||||||
|
</label>
|
||||||
|
{/each}
|
||||||
|
|
||||||
|
{#if gi < Object.entries(groupedEvents).length - 1}
|
||||||
|
<div class="mx-3 my-1 border-t border-[var(--color-border)]"></div>
|
||||||
|
{/if}
|
||||||
|
{/each}
|
||||||
|
{/snippet}
|
||||||
|
|
||||||
{#snippet providerIcon(provider: string)}
|
{#snippet providerIcon(provider: string)}
|
||||||
{#if provider === 'discord'}
|
{#if provider === 'discord'}
|
||||||
<svg width="12" height="12" viewBox="0 0 24 24" fill="currentColor"><path d="M20.317 4.37a19.791 19.791 0 0 0-4.885-1.515.074.074 0 0 0-.079.037c-.21.375-.444.864-.608 1.25a18.27 18.27 0 0 0-5.487 0 12.64 12.64 0 0 0-.617-1.25.077.077 0 0 0-.079-.037A19.736 19.736 0 0 0 3.677 4.37a.07.07 0 0 0-.032.027C.533 9.046-.32 13.58.099 18.057a.082.082 0 0 0 .031.057 19.9 19.9 0 0 0 5.993 3.03.078.078 0 0 0 .084-.028c.462-.63.874-1.295 1.226-1.994a.076.076 0 0 0-.041-.106 13.107 13.107 0 0 1-1.872-.892.077.077 0 0 1-.008-.128 10.2 10.2 0 0 0 .372-.292.074.074 0 0 1 .077-.01c3.928 1.793 8.18 1.793 12.062 0a.074.074 0 0 1 .078.01c.12.098.246.198.373.292a.077.077 0 0 1-.006.127 12.299 12.299 0 0 1-1.873.892.077.077 0 0 0-.041.107c.36.698.772 1.362 1.225 1.993a.076.076 0 0 0 .084.028 19.839 19.839 0 0 0 6.002-3.03.077.077 0 0 0 .032-.054c.5-5.177-.838-9.674-3.549-13.66a.061.061 0 0 0-.031-.03z" /></svg>
|
<svg width="12" height="12" viewBox="0 0 24 24" fill="currentColor"><path d="M20.317 4.37a19.791 19.791 0 0 0-4.885-1.515.074.074 0 0 0-.079.037c-.21.375-.444.864-.608 1.25a18.27 18.27 0 0 0-5.487 0 12.64 12.64 0 0 0-.617-1.25.077.077 0 0 0-.079-.037A19.736 19.736 0 0 0 3.677 4.37a.07.07 0 0 0-.032.027C.533 9.046-.32 13.58.099 18.057a.082.082 0 0 0 .031.057 19.9 19.9 0 0 0 5.993 3.03.078.078 0 0 0 .084-.028c.462-.63.874-1.295 1.226-1.994a.076.076 0 0 0-.041-.106 13.107 13.107 0 0 1-1.872-.892.077.077 0 0 1-.008-.128 10.2 10.2 0 0 0 .372-.292.074.074 0 0 1 .077-.01c3.928 1.793 8.18 1.793 12.062 0a.074.074 0 0 1 .078.01c.12.098.246.198.373.292a.077.077 0 0 1-.006.127 12.299 12.299 0 0 1-1.873.892.077.077 0 0 0-.041.107c.36.698.772 1.362 1.225 1.993a.076.076 0 0 0 .084.028 19.839 19.839 0 0 0 6.002-3.03.077.077 0 0 0 .032-.054c.5-5.177-.838-9.674-3.549-13.66a.061.061 0 0 0-.031-.03z" /></svg>
|
||||||
{:else if provider === 'slack'}
|
{:else if provider === 'slack'}
|
||||||
<svg width="12" height="12" viewBox="0 0 24 24" fill="currentColor"><path d="M5.042 15.165a2.528 2.528 0 0 1-2.52 2.523A2.528 2.528 0 0 1 0 15.165a2.527 2.527 0 0 1 2.522-2.52h2.52v2.52zm1.271 0a2.527 2.527 0 0 1 2.521-2.52 2.527 2.527 0 0 1 2.521 2.52v6.313A2.528 2.528 0 0 1 8.834 24a2.528 2.528 0 0 1-2.521-2.522v-6.313zM8.834 5.042a2.528 2.528 0 0 1-2.521-2.52A2.528 2.528 0 0 1 8.834 0a2.528 2.528 0 0 1 2.521 2.522v2.52H8.834zm0 1.271a2.528 2.528 0 0 1 2.521 2.521 2.528 2.528 0 0 1-2.521 2.521H2.522A2.528 2.528 0 0 1 0 8.834a2.528 2.528 0 0 1 2.522-2.521h6.312zm10.122 2.521a2.528 2.528 0 0 1 2.522-2.521A2.528 2.528 0 0 1 24 8.834a2.528 2.528 0 0 1-2.522 2.521h-2.522V8.834zm-1.268 0a2.528 2.528 0 0 1-2.523 2.521 2.527 2.527 0 0 1-2.52-2.521V2.522A2.527 2.527 0 0 1 15.165 0a2.528 2.528 0 0 1 2.523 2.522v6.312zm-2.523 10.122a2.528 2.528 0 0 1 2.523 2.522A2.528 2.528 0 0 1 15.165 24a2.527 2.527 0 0 1-2.52-2.522v-2.522h2.52zm0-1.268a2.527 2.527 0 0 1-2.52-2.523 2.526 2.526 0 0 1 2.52-2.52h6.313A2.527 2.527 0 0 1 24 15.165a2.528 2.528 0 0 1-2.522 2.523h-6.313z" /></svg>
|
<svg width="12" height="12" viewBox="0 0 24 24" fill="currentColor"><path d="M5.042 15.165a2.528 2.528 0 0 1-2.52 2.523A2.528 2.528 0 0 1 0 15.165a2.527 2.527 0 0 1 2.522-2.52h2.52v2.52zm1.271 0a2.527 2.527 0 0 1 2.521-2.52 2.527 2.527 0 0 1 2.521 2.52v6.313A2.528 2.528 0 0 1 8.834 24a2.528 2.528 0 0 1-2.521-2.522v-6.313zM8.834 5.042a2.528 2.528 0 0 1-2.521-2.52A2.528 2.528 0 0 1 8.834 0a2.528 2.528 0 0 1 2.521 2.522v2.52H8.834zm0 1.271a2.528 2.528 0 0 1 2.521 2.521 2.528 2.528 0 0 1-2.521 2.521H2.522A2.528 2.528 0 0 1 0 8.834a2.528 2.528 0 0 1 2.522-2.521h6.312zm10.122 2.521a2.528 2.528 0 0 1 2.522-2.521A2.528 2.528 0 0 1 24 8.834a2.528 2.528 0 0 1-2.522 2.521h-2.522V8.834zm-1.268 0a2.528 2.528 0 0 1-2.523 2.521 2.527 2.527 0 0 1-2.52-2.521V2.522A2.527 2.527 0 0 1 15.165 0a2.528 2.528 0 0 1 2.523 2.522v6.312zm-2.523 10.122a2.528 2.528 0 0 1 2.523 2.522A2.528 2.528 0 0 1 15.165 24a2.527 2.527 0 0 1-2.52-2.522v-2.522h2.52zm0-1.268a2.527 2.527 0 0 1-2.52-2.523 2.526 2.526 0 0 1 2.52-2.52h6.313A2.527 2.527 0 0 1 24 15.165a2.528 2.528 0 0 1-2.522 2.523h-6.313z" /></svg>
|
||||||
|
{:else if provider === 'teams'}
|
||||||
|
<svg width="12" height="12" viewBox="0 0 24 24" fill="currentColor"><path d="M20.625 8.5h-3.25V6.25a1.75 1.75 0 0 1 1.75-1.75h.002a1.75 1.75 0 1 1 0 3.5h-.002a1.7 1.7 0 0 1-.5-.074V8.5zM22.25 10h-4.375a.875.875 0 0 0-.875.875v5.25a3.375 3.375 0 0 0 3.016 3.355A3.5 3.5 0 0 0 24 16v-2.5A3.5 3.5 0 0 0 22.25 10zM9.5 7a3 3 0 1 0 0-6 3 3 0 0 0 0 6zm5.25 3H4.25A2.25 2.25 0 0 0 2 12.25V18a5.5 5.5 0 0 0 11 0v-5.75A2.25 2.25 0 0 0 14.75 10zM16 8.5a2.5 2.5 0 1 0 0-5 2.5 2.5 0 0 0 0 5z" /></svg>
|
||||||
|
{:else if provider === 'googlechat'}
|
||||||
|
<svg width="12" height="12" viewBox="0 0 24 24" fill="currentColor"><path d="M12 0C5.372 0 0 5.042 0 11.264c0 2.026.564 3.94 1.544 5.612L.05 21.932a.75.75 0 0 0 .96.932l5.112-1.7A12.4 12.4 0 0 0 12 22.528c6.628 0 12-5.042 12-11.264S18.628 0 12 0zm4.5 14.25h-9a.75.75 0 0 1 0-1.5h9a.75.75 0 0 1 0 1.5zm0-3h-9a.75.75 0 0 1 0-1.5h9a.75.75 0 0 1 0 1.5zm0-3h-9a.75.75 0 0 1 0-1.5h9a.75.75 0 0 1 0 1.5z" /></svg>
|
||||||
{:else if provider === 'telegram'}
|
{:else if provider === 'telegram'}
|
||||||
<svg width="12" height="12" viewBox="0 0 24 24" fill="currentColor"><path d="M11.944 0A12 12 0 0 0 0 12a12 12 0 0 0 12 12 12 12 0 0 0 12-12A12 12 0 0 0 12 0a12 12 0 0 0-.056 0zm4.962 7.224c.1-.002.321.023.465.14a.506.506 0 0 1 .171.325c.016.093.036.306.02.472-.18 1.898-.962 6.502-1.36 8.627-.168.9-.499 1.201-.82 1.23-.696.065-1.225-.46-1.9-.902-1.056-.693-1.653-1.124-2.678-1.8-1.185-.78-.417-1.21.258-1.91.177-.184 3.247-2.977 3.307-3.23.007-.032.014-.15-.056-.212s-.174-.041-.249-.024c-.106.024-1.793 1.14-5.061 3.345-.479.33-.913.49-1.302.48-.428-.008-1.252-.241-1.865-.44-.752-.245-1.349-.374-1.297-.789.027-.216.325-.437.893-.663 3.498-1.524 5.83-2.529 6.998-3.014 3.332-1.386 4.025-1.627 4.476-1.635z" /></svg>
|
<svg width="12" height="12" viewBox="0 0 24 24" fill="currentColor"><path d="M11.944 0A12 12 0 0 0 0 12a12 12 0 0 0 12 12 12 12 0 0 0 12-12A12 12 0 0 0 12 0a12 12 0 0 0-.056 0zm4.962 7.224c.1-.002.321.023.465.14a.506.506 0 0 1 .171.325c.016.093.036.306.02.472-.18 1.898-.962 6.502-1.36 8.627-.168.9-.499 1.201-.82 1.23-.696.065-1.225-.46-1.9-.902-1.056-.693-1.653-1.124-2.678-1.8-1.185-.78-.417-1.21.258-1.91.177-.184 3.247-2.977 3.307-3.23.007-.032.014-.15-.056-.212s-.174-.041-.249-.024c-.106.024-1.793 1.14-5.061 3.345-.479.33-.913.49-1.302.48-.428-.008-1.252-.241-1.865-.44-.752-.245-1.349-.374-1.297-.789.027-.216.325-.437.893-.663 3.498-1.524 5.83-2.529 6.998-3.014 3.332-1.386 4.025-1.627 4.476-1.635z" /></svg>
|
||||||
|
{:else if provider === 'matrix'}
|
||||||
|
<svg width="12" height="12" viewBox="0 0 24 24" fill="currentColor"><path d="M.632.55v22.9H2.28V24H0V0h2.28v.55zm7.043 7.26v1.157h.033c.309-.443.683-.784 1.117-1.024.434-.24.905-.36 1.416-.36.54 0 1.033.107 1.48.32.448.214.773.553.974 1.02.309-.4.694-.727 1.154-.98a3.1 3.1 0 0 1 1.49-.36c.434 0 .839.058 1.213.172.375.115.694.303.957.565.264.262.467.6.61 1.014.144.414.215.907.215 1.478V17.3h-2.36v-5.18c0-.312-.013-.603-.04-.873a1.84 1.84 0 0 0-.195-.685 1.08 1.08 0 0 0-.432-.45c-.187-.108-.438-.162-.754-.162-.316 0-.573.058-.77.172a1.27 1.27 0 0 0-.472.46 1.98 1.98 0 0 0-.24.672 4.4 4.4 0 0 0-.065.746V17.3H9.36v-5.07c0-.282-.006-.558-.02-.826a2.15 2.15 0 0 0-.148-.72 1.04 1.04 0 0 0-.403-.498c-.182-.126-.44-.19-.773-.19a1.55 1.55 0 0 0-.416.068c-.158.052-.31.147-.458.286a1.62 1.62 0 0 0-.36.566c-.1.238-.15.545-.15.924V17.3H4.28V7.81zm15.693 15.64V.55H21.72V0H24v24h-2.28v-.55z" /></svg>
|
||||||
{:else if provider === 'webhook'}
|
{:else if provider === 'webhook'}
|
||||||
<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71" /><path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71" /></svg>
|
<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71" /><path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71" /></svg>
|
||||||
{:else}
|
{:else}
|
||||||
|
|||||||
Reference in New Issue
Block a user