Badges: template controls cfg, collapsible form sections, navigation polish
- badge_template_form: fix default field visibility (location off render, pronouns/leads excluded from controls); fix duplicate QR checkboxes by removing orphan show_qr_front/back state vars; reorganize Advanced cfg_json into labeled sub-groups; make all 5 non-Advanced sections collapsible (general starts open, rest collapsed) - print_controls: add DEFAULT_SHOWN constant so field_shown() uses explicit whitelist fallback instead of showing all fields when no controls_cfg is set - badges config +page: add Templates navigation button in header (FileText icon) - templates +page: add back-nav header with ArrowLeft to badges/config, Settings icon, page title Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -153,11 +153,16 @@ const DEFAULT_AUTH_EDITABLE = [
|
||||
'pronouns'
|
||||
];
|
||||
|
||||
// Default shown fields in the controls panel when the template has no explicit controls_cfg.
|
||||
// Pronouns and lead scanning are off by default — most events don't expose them.
|
||||
// WHY: prevents clutter for attendees at the badge table; events that need them must opt in via template config.
|
||||
const DEFAULT_SHOWN = ['name', 'title', 'affiliations', 'location'];
|
||||
|
||||
/** Is this field card shown in the panel at all? trusted+edit always sees all fields. */
|
||||
function field_shown(field: string): boolean {
|
||||
if (is_trusted && is_global_edit_mode) return true;
|
||||
const cfg = template_controls_cfg;
|
||||
if (!cfg?.shown) return true;
|
||||
if (!cfg?.shown) return DEFAULT_SHOWN.includes(field);
|
||||
return cfg.shown.includes(field);
|
||||
}
|
||||
|
||||
|
||||
@@ -26,6 +26,7 @@ import {
|
||||
Check,
|
||||
ChevronDown,
|
||||
ChevronUp,
|
||||
FileText,
|
||||
Lock,
|
||||
Save,
|
||||
Settings
|
||||
@@ -270,6 +271,11 @@ function toggle(key: string) {
|
||||
<Settings size="1.2em" class="text-primary-500" />
|
||||
<h1 class="text-xl font-bold">Badges Config</h1>
|
||||
</div>
|
||||
<a href="/events/{event_id}/templates"
|
||||
class="btn btn-sm preset-tonal-surface"
|
||||
title="Manage Badge Templates">
|
||||
<FileText size="1em" class="mr-1" /> Templates
|
||||
</a>
|
||||
<div class="flex items-center gap-2">
|
||||
{#if save_status === 'success'}
|
||||
<span class="badge preset-tonal-success flex items-center gap-1">
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<script lang="ts">
|
||||
import { liveQuery } from 'dexie';
|
||||
import { Pencil, Plus, Trash2 } from '@lucide/svelte';
|
||||
import { ArrowLeft, Pencil, Plus, Settings, Trash2 } from '@lucide/svelte';
|
||||
import { events_func } from '$lib/ae_events/ae_events_functions';
|
||||
import { ae_api } from '$lib/stores/ae_stores';
|
||||
import { Modal } from 'flowbite-svelte';
|
||||
@@ -78,16 +78,23 @@ async function delete_template(template_id: string) {
|
||||
</svelte:head>
|
||||
|
||||
<section class="p-4">
|
||||
<h1 class="h1">Badge Templates</h1>
|
||||
|
||||
<div class="my-4 flex justify-end">
|
||||
<header class="mb-4 flex items-center justify-between gap-4">
|
||||
<div class="flex items-center gap-2">
|
||||
<a href="/events/{event_id}/badges/config"
|
||||
class="btn btn-sm preset-tonal-surface"
|
||||
title="Back to Badges Config">
|
||||
<ArrowLeft size="1em" />
|
||||
</a>
|
||||
<Settings size="1.2em" class="text-primary-500" />
|
||||
<h1 class="text-xl font-bold">Badge Templates</h1>
|
||||
</div>
|
||||
<button
|
||||
type="button"
|
||||
class="btn btn-primary"
|
||||
onclick={() => (show_create_template_modal = true)}>
|
||||
<Plus size="1em" class="mr-2" /> Add New Template
|
||||
</button>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
{#if $lq__badge_templates}
|
||||
{#if $lq__badge_templates.length > 0}
|
||||
|
||||
@@ -38,8 +38,7 @@ let header_row_1 = $state('');
|
||||
let header_row_2 = $state('');
|
||||
let secondary_header_path = $state('');
|
||||
let footer_text = $state('');
|
||||
let show_qr_front = $state(true);
|
||||
let show_qr_back = $state(true);
|
||||
// show_qr_front / show_qr_back: removed — UI now binds directly to cfg_show_qr_front/cfg_show_qr_back
|
||||
let wireless_ssid = $state('');
|
||||
let wireless_password = $state('');
|
||||
let ticket_1_text = $state('');
|
||||
@@ -56,10 +55,11 @@ let cfg_show_qr_back = $state(true);
|
||||
// Per-field hide toggles
|
||||
let cfg_hide_title = $state(false);
|
||||
let cfg_hide_affiliations = $state(false);
|
||||
let cfg_hide_location = $state(false);
|
||||
let cfg_hide_location = $state(true);
|
||||
// Controls menu config (per-template) — which fields the controls panel shows
|
||||
// and which fields authenticated users may edit. Stored under cfg_json.controls_cfg
|
||||
let cfg_controls_shown: string[] = $state([]);
|
||||
// Defaults match DEFAULT_SHOWN in ae_comp__badge_print_controls.svelte (pronouns/leads off by default).
|
||||
let cfg_controls_shown: string[] = $state(['name', 'title', 'affiliations', 'location']);
|
||||
let cfg_controls_auth_editable: string[] = $state([]);
|
||||
// Body text color (hex)
|
||||
let cfg_body_text_color = $state('#000000');
|
||||
@@ -73,6 +73,15 @@ let cfg_qr_alignment_back = $state('center');
|
||||
|
||||
let submit_status = $state('idle'); // idle, loading, success, error
|
||||
|
||||
// Section collapse state — General open by default (required name field); rest closed.
|
||||
let sections_open = $state({
|
||||
general: true,
|
||||
branding: false,
|
||||
footer: false,
|
||||
qr: false,
|
||||
tickets: false
|
||||
});
|
||||
|
||||
// Load template data if in edit mode
|
||||
$effect(() => {
|
||||
if (template_id) {
|
||||
@@ -155,10 +164,6 @@ async function load_template(id: string) {
|
||||
cfg_qr_alignment_front = parsed_cfg?.qr_alignment?.front ?? parsed_cfg.qr_alignment_front ?? 'center';
|
||||
cfg_qr_alignment_back = parsed_cfg?.qr_alignment?.back ?? parsed_cfg.qr_alignment_back ?? 'center';
|
||||
|
||||
// Keep top-level fields in sync for backward compatibility
|
||||
show_qr_front = cfg_show_qr_front;
|
||||
show_qr_back = cfg_show_qr_back;
|
||||
|
||||
submit_status = 'idle';
|
||||
} else {
|
||||
submit_status = 'error';
|
||||
@@ -291,94 +296,119 @@ function toggle_cfg_controls_auth_editable(key: string) {
|
||||
<form onsubmit={prevent_default(handle_submit)} class="space-y-4 p-4">
|
||||
<h3 class="h3">{template_id ? 'Edit' : 'Create New'} Badge Template</h3>
|
||||
|
||||
<section class="space-y-3">
|
||||
<h4 class="font-semibold">General</h4>
|
||||
<label class="label">
|
||||
<span>Template Name</span>
|
||||
<input type="text" bind:value={name} class="input" required />
|
||||
</label>
|
||||
<label class="label">
|
||||
<span>Background Image Path (URL) — full-badge background, replaces header</span>
|
||||
<input type="text" bind:value={background_image_path} class="input" />
|
||||
</label>
|
||||
{#if background_image_path}
|
||||
<p class="text-xs text-amber-600 dark:text-amber-400">
|
||||
⚠ When a background image is set, the header path and logo/text header are hidden on the badge front — the background image covers the full badge.
|
||||
</p>
|
||||
<section class="border-t pt-3">
|
||||
<button type="button" class="text-sm text-surface-500" onclick={() => (sections_open.general = !sections_open.general)}>
|
||||
General {sections_open.general ? '▲' : '▼'}
|
||||
</button>
|
||||
{#if sections_open.general}
|
||||
<div class="space-y-3 pt-2">
|
||||
<label class="label">
|
||||
<span>Template Name</span>
|
||||
<input type="text" bind:value={name} class="input" required />
|
||||
</label>
|
||||
<label class="label">
|
||||
<span>Background Image Path (URL) — full-badge background, replaces header</span>
|
||||
<input type="text" bind:value={background_image_path} class="input" />
|
||||
</label>
|
||||
{#if background_image_path}
|
||||
<p class="text-xs text-amber-600 dark:text-amber-400">
|
||||
⚠ When a background image is set, the header path and logo/text header are hidden on the badge front — the background image covers the full badge.
|
||||
</p>
|
||||
{/if}
|
||||
</div>
|
||||
{/if}
|
||||
</section>
|
||||
|
||||
<section class="space-y-3">
|
||||
<h4 class="font-semibold">Header & Branding</h4>
|
||||
<label class="label">
|
||||
<span>Header Path (URL) — top banner image (used when no background image)</span>
|
||||
<input type="text" bind:value={header_path} class="input" />
|
||||
</label>
|
||||
<label class="label">
|
||||
<span>Logo Path (URL, if no Header Path)</span>
|
||||
<input type="text" bind:value={logo_path} class="input" />
|
||||
</label>
|
||||
<label class="label">
|
||||
<span>Header Row 1 Text (HTML allowed)</span>
|
||||
<textarea bind:value={header_row_1} class="textarea" rows="2"
|
||||
></textarea>
|
||||
</label>
|
||||
<label class="label">
|
||||
<span>Header Row 2 Text (HTML allowed)</span>
|
||||
<textarea bind:value={header_row_2} class="textarea" rows="2"
|
||||
></textarea>
|
||||
</label>
|
||||
<label class="label">
|
||||
<span>Secondary Header Path (URL, back of badge)</span>
|
||||
<input type="text" bind:value={secondary_header_path} class="input" />
|
||||
</label>
|
||||
<section class="border-t pt-3">
|
||||
<button type="button" class="text-sm text-surface-500" onclick={() => (sections_open.branding = !sections_open.branding)}>
|
||||
Header & Branding {sections_open.branding ? '▲' : '▼'}
|
||||
</button>
|
||||
{#if sections_open.branding}
|
||||
<div class="space-y-3 pt-2">
|
||||
<label class="label">
|
||||
<span>Header Path (URL) — top banner image (used when no background image)</span>
|
||||
<input type="text" bind:value={header_path} class="input" />
|
||||
</label>
|
||||
<label class="label">
|
||||
<span>Logo Path (URL, if no Header Path)</span>
|
||||
<input type="text" bind:value={logo_path} class="input" />
|
||||
</label>
|
||||
<label class="label">
|
||||
<span>Header Row 1 Text (HTML allowed)</span>
|
||||
<textarea bind:value={header_row_1} class="textarea" rows="2"></textarea>
|
||||
</label>
|
||||
<label class="label">
|
||||
<span>Header Row 2 Text (HTML allowed)</span>
|
||||
<textarea bind:value={header_row_2} class="textarea" rows="2"></textarea>
|
||||
</label>
|
||||
<label class="label">
|
||||
<span>Secondary Header Path (URL, back of badge)</span>
|
||||
<input type="text" bind:value={secondary_header_path} class="input" />
|
||||
</label>
|
||||
</div>
|
||||
{/if}
|
||||
</section>
|
||||
|
||||
<section class="space-y-3">
|
||||
<h4 class="font-semibold">Footer</h4>
|
||||
<label class="label">
|
||||
<span>Footer Text (HTML allowed)</span>
|
||||
<textarea bind:value={footer_text} class="textarea" rows="2"></textarea>
|
||||
</label>
|
||||
<section class="border-t pt-3">
|
||||
<button type="button" class="text-sm text-surface-500" onclick={() => (sections_open.footer = !sections_open.footer)}>
|
||||
Footer {sections_open.footer ? '▲' : '▼'}
|
||||
</button>
|
||||
{#if sections_open.footer}
|
||||
<div class="space-y-3 pt-2">
|
||||
<label class="label">
|
||||
<span>Footer Text (HTML allowed)</span>
|
||||
<textarea bind:value={footer_text} class="textarea" rows="2"></textarea>
|
||||
</label>
|
||||
</div>
|
||||
{/if}
|
||||
</section>
|
||||
|
||||
<section class="space-y-3">
|
||||
<h4 class="font-semibold">QR & Wireless</h4>
|
||||
<label class="label flex items-center gap-2">
|
||||
<input type="checkbox" bind:checked={show_qr_front} class="checkbox" />
|
||||
<span>Show QR Code on Front</span>
|
||||
</label>
|
||||
<label class="label flex items-center gap-2">
|
||||
<input type="checkbox" bind:checked={show_qr_back} class="checkbox" />
|
||||
<span>Show QR Code on Back</span>
|
||||
</label>
|
||||
<label class="label">
|
||||
<span>Wireless SSID</span>
|
||||
<input type="text" bind:value={wireless_ssid} class="input" />
|
||||
</label>
|
||||
<label class="label">
|
||||
<span>Wireless Password</span>
|
||||
<input type="text" bind:value={wireless_password} class="input" />
|
||||
</label>
|
||||
<section class="border-t pt-3">
|
||||
<button type="button" class="text-sm text-surface-500" onclick={() => (sections_open.qr = !sections_open.qr)}>
|
||||
QR & Wireless {sections_open.qr ? '▲' : '▼'}
|
||||
</button>
|
||||
{#if sections_open.qr}
|
||||
<div class="space-y-3 pt-2">
|
||||
<label class="label flex items-center gap-2">
|
||||
<input type="checkbox" bind:checked={cfg_show_qr_front} class="checkbox" />
|
||||
<span>Show QR Code on Front</span>
|
||||
</label>
|
||||
<label class="label flex items-center gap-2">
|
||||
<input type="checkbox" bind:checked={cfg_show_qr_back} class="checkbox" />
|
||||
<span>Show QR Code on Back</span>
|
||||
</label>
|
||||
<label class="label">
|
||||
<span>Wireless SSID</span>
|
||||
<input type="text" bind:value={wireless_ssid} class="input" />
|
||||
</label>
|
||||
<label class="label">
|
||||
<span>Wireless Password</span>
|
||||
<input type="text" bind:value={wireless_password} class="input" />
|
||||
</label>
|
||||
</div>
|
||||
{/if}
|
||||
</section>
|
||||
|
||||
<section class="space-y-3">
|
||||
<h4 class="font-semibold">Tickets</h4>
|
||||
<label class="label">
|
||||
<span>Ticket 1 Text (HTML allowed)</span>
|
||||
<textarea bind:value={ticket_1_text} class="textarea" rows="2"
|
||||
></textarea>
|
||||
</label>
|
||||
<label class="label">
|
||||
<span>Ticket 2 Text (HTML allowed)</span>
|
||||
<textarea bind:value={ticket_2_text} class="textarea" rows="2"
|
||||
></textarea>
|
||||
</label>
|
||||
<label class="label">
|
||||
<span>Ticket 3 Text (HTML allowed)</span>
|
||||
<textarea bind:value={ticket_3_text} class="textarea" rows="2"
|
||||
></textarea>
|
||||
</label>
|
||||
<section class="border-t pt-3">
|
||||
<button type="button" class="text-sm text-surface-500" onclick={() => (sections_open.tickets = !sections_open.tickets)}>
|
||||
Tickets {sections_open.tickets ? '▲' : '▼'}
|
||||
</button>
|
||||
{#if sections_open.tickets}
|
||||
<div class="space-y-3 pt-2">
|
||||
<label class="label">
|
||||
<span>Ticket 1 Text (HTML allowed)</span>
|
||||
<textarea bind:value={ticket_1_text} class="textarea" rows="2"></textarea>
|
||||
</label>
|
||||
<label class="label">
|
||||
<span>Ticket 2 Text (HTML allowed)</span>
|
||||
<textarea bind:value={ticket_2_text} class="textarea" rows="2"></textarea>
|
||||
</label>
|
||||
<label class="label">
|
||||
<span>Ticket 3 Text (HTML allowed)</span>
|
||||
<textarea bind:value={ticket_3_text} class="textarea" rows="2"></textarea>
|
||||
</label>
|
||||
</div>
|
||||
{/if}
|
||||
</section>
|
||||
|
||||
<section class="border-t pt-3">
|
||||
@@ -386,90 +416,99 @@ function toggle_cfg_controls_auth_editable(key: string) {
|
||||
Advanced (cfg_json) {advanced_open ? '▲' : '▼'}
|
||||
</button>
|
||||
{#if advanced_open}
|
||||
<div class="grid grid-cols-1 gap-2 pt-2">
|
||||
<label class="label flex items-center gap-2">
|
||||
<input type="checkbox" bind:checked={cfg_hide_badge_header} class="checkbox" />
|
||||
<span>Hide badge header</span>
|
||||
</label>
|
||||
<label class="label flex items-center gap-2">
|
||||
<input type="checkbox" bind:checked={cfg_hide_badge_footer} class="checkbox" />
|
||||
<span>Hide badge footer</span>
|
||||
</label>
|
||||
<label class="label flex items-center gap-2">
|
||||
<input type="checkbox" bind:checked={cfg_show_qr_front} class="checkbox" />
|
||||
<span>Show QR on Front (cfg_json)</span>
|
||||
</label>
|
||||
<label class="label flex items-center gap-2">
|
||||
<input type="checkbox" bind:checked={cfg_show_qr_back} class="checkbox" />
|
||||
<span>Show QR on Back (cfg_json)</span>
|
||||
</label>
|
||||
<div class="grid grid-cols-2 gap-2">
|
||||
<label class="label flex items-center gap-2">
|
||||
<input type="checkbox" bind:checked={cfg_hide_title} class="checkbox" />
|
||||
<span>Hide Title</span>
|
||||
</label>
|
||||
<label class="label flex items-center gap-2">
|
||||
<input type="checkbox" bind:checked={cfg_hide_affiliations} class="checkbox" />
|
||||
<span>Hide Affiliations</span>
|
||||
</label>
|
||||
<label class="label flex items-center gap-2">
|
||||
<input type="checkbox" bind:checked={cfg_hide_location} class="checkbox" />
|
||||
<span>Hide Location</span>
|
||||
</label>
|
||||
<label class="label">
|
||||
<span>Name Alignment</span>
|
||||
<select bind:value={cfg_align_name} class="input">
|
||||
<option value="left">Left</option>
|
||||
<option value="center">Center</option>
|
||||
<option value="right">Right</option>
|
||||
<option value="justify">Justify</option>
|
||||
</select>
|
||||
</label>
|
||||
<label class="label">
|
||||
<span>Title Alignment</span>
|
||||
<select bind:value={cfg_align_title} class="input">
|
||||
<option value="left">Left</option>
|
||||
<option value="center">Center</option>
|
||||
<option value="right">Right</option>
|
||||
<option value="justify">Justify</option>
|
||||
</select>
|
||||
</label>
|
||||
<label class="label">
|
||||
<span>Affiliations Alignment</span>
|
||||
<select bind:value={cfg_align_affiliations} class="input">
|
||||
<option value="left">Left</option>
|
||||
<option value="center">Center</option>
|
||||
<option value="right">Right</option>
|
||||
<option value="justify">Justify</option>
|
||||
</select>
|
||||
</label>
|
||||
<label class="label">
|
||||
<span>Location Alignment</span>
|
||||
<select bind:value={cfg_align_location} class="input">
|
||||
<option value="left">Left</option>
|
||||
<option value="center">Center</option>
|
||||
<option value="right">Right</option>
|
||||
<option value="justify">Justify</option>
|
||||
</select>
|
||||
</label>
|
||||
<label class="label">
|
||||
<span>QR Alignment (Front)</span>
|
||||
<select bind:value={cfg_qr_alignment_front} class="input">
|
||||
<option value="left">Left</option>
|
||||
<option value="center">Center</option>
|
||||
<option value="right">Right</option>
|
||||
<option value="justify">Justify</option>
|
||||
</select>
|
||||
</label>
|
||||
<label class="label">
|
||||
<span>QR Alignment (Back)</span>
|
||||
<select bind:value={cfg_qr_alignment_back} class="input">
|
||||
<option value="left">Left</option>
|
||||
<option value="center">Center</option>
|
||||
<option value="right">Right</option>
|
||||
<option value="justify">Justify</option>
|
||||
</select>
|
||||
</label>
|
||||
<div class="space-y-4 pt-2">
|
||||
|
||||
<!-- Visibility -->
|
||||
<div>
|
||||
<p class="text-xs font-semibold text-surface-500 uppercase tracking-wide mb-1">Visibility</p>
|
||||
<div class="grid grid-cols-2 gap-2">
|
||||
<label class="label flex items-center gap-2">
|
||||
<input type="checkbox" bind:checked={cfg_hide_badge_header} class="checkbox" />
|
||||
<span>Hide badge header</span>
|
||||
</label>
|
||||
<label class="label flex items-center gap-2">
|
||||
<input type="checkbox" bind:checked={cfg_hide_badge_footer} class="checkbox" />
|
||||
<span>Hide badge footer</span>
|
||||
</label>
|
||||
<label class="label flex items-center gap-2">
|
||||
<input type="checkbox" bind:checked={cfg_hide_title} class="checkbox" />
|
||||
<span>Hide Title</span>
|
||||
</label>
|
||||
<label class="label flex items-center gap-2">
|
||||
<input type="checkbox" bind:checked={cfg_hide_affiliations} class="checkbox" />
|
||||
<span>Hide Affiliations</span>
|
||||
</label>
|
||||
<label class="label flex items-center gap-2">
|
||||
<input type="checkbox" bind:checked={cfg_hide_location} class="checkbox" />
|
||||
<span>Hide Location</span>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Alignment -->
|
||||
<div>
|
||||
<p class="text-xs font-semibold text-surface-500 uppercase tracking-wide mb-1">Alignment</p>
|
||||
<div class="grid grid-cols-2 gap-2">
|
||||
<label class="label">
|
||||
<span>Name</span>
|
||||
<select bind:value={cfg_align_name} class="input">
|
||||
<option value="left">Left</option>
|
||||
<option value="center">Center</option>
|
||||
<option value="right">Right</option>
|
||||
<option value="justify">Justify</option>
|
||||
</select>
|
||||
</label>
|
||||
<label class="label">
|
||||
<span>Title</span>
|
||||
<select bind:value={cfg_align_title} class="input">
|
||||
<option value="left">Left</option>
|
||||
<option value="center">Center</option>
|
||||
<option value="right">Right</option>
|
||||
<option value="justify">Justify</option>
|
||||
</select>
|
||||
</label>
|
||||
<label class="label">
|
||||
<span>Affiliations</span>
|
||||
<select bind:value={cfg_align_affiliations} class="input">
|
||||
<option value="left">Left</option>
|
||||
<option value="center">Center</option>
|
||||
<option value="right">Right</option>
|
||||
<option value="justify">Justify</option>
|
||||
</select>
|
||||
</label>
|
||||
<label class="label">
|
||||
<span>Location</span>
|
||||
<select bind:value={cfg_align_location} class="input">
|
||||
<option value="left">Left</option>
|
||||
<option value="center">Center</option>
|
||||
<option value="right">Right</option>
|
||||
<option value="justify">Justify</option>
|
||||
</select>
|
||||
</label>
|
||||
<label class="label">
|
||||
<span>QR (Front)</span>
|
||||
<select bind:value={cfg_qr_alignment_front} class="input">
|
||||
<option value="left">Left</option>
|
||||
<option value="center">Center</option>
|
||||
<option value="right">Right</option>
|
||||
<option value="justify">Justify</option>
|
||||
</select>
|
||||
</label>
|
||||
<label class="label">
|
||||
<span>QR (Back)</span>
|
||||
<select bind:value={cfg_qr_alignment_back} class="input">
|
||||
<option value="left">Left</option>
|
||||
<option value="center">Center</option>
|
||||
<option value="right">Right</option>
|
||||
<option value="justify">Justify</option>
|
||||
</select>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Appearance -->
|
||||
<div>
|
||||
<p class="text-xs font-semibold text-surface-500 uppercase tracking-wide mb-1">Appearance</p>
|
||||
<label class="label">
|
||||
<span>Body Text Color (hex)</span>
|
||||
<div class="flex items-center gap-2">
|
||||
|
||||
Reference in New Issue
Block a user