Files
shd.build/src/lib/components/ContentReport.svelte
T
jb dd84dff055
Validate and deploy production / validate (push) Successful in 23s
Validate and deploy staging / validate (push) Successful in 50s
Validate and deploy production / deploy (push) Failing after 40s
Validate and deploy staging / deploy (push) Failing after 7s
feat: add community publishing and news syndication
2026-08-01 21:42:09 -04:00

79 lines
4.9 KiB
Svelte

<script lang="ts">
import PendingForm from '$lib/components/PendingForm.svelte';
let {
contentType,
contentId,
label = 'REPORT',
result,
plain = false,
menuItem = false,
itemId = ''
}: {
contentType: 'comment' | 'profile_bio' | 'guide' | 'avatar';
contentId: string;
label?: string;
result?: Record<string, unknown> | null;
plain?: boolean;
menuItem?: boolean;
itemId?: string;
} = $props();
const categories = [
{ value: 'spam', label: 'SPAM OR PROMOTION' },
{ value: 'harassment', label: 'HARASSMENT OR BULLYING' },
{ value: 'hate', label: 'HATEFUL CONTENT' },
{ value: 'sexual', label: 'SEXUAL CONTENT' },
{ value: 'violence', label: 'VIOLENCE OR THREATS' },
{ value: 'self_harm', label: 'SELF-HARM' },
{ value: 'privacy', label: 'PRIVATE INFORMATION' },
{ value: 'other', label: 'OTHER POLICY CONCERN' }
];
const matchesResult = $derived(
String(result?.userReportContentId ?? '') === contentId
&& String(result?.userReportContentType ?? '') === contentType
);
let reportOpen = $state(false);
function closeReport() {
reportOpen = false;
}
</script>
<details bind:open={reportOpen} class="content-report" class:plain class:menu-item={menuItem}>
<summary role={menuItem ? 'button' : undefined}>{label}</summary>
{#if reportOpen}
<div class="content-report-panel">
<strong>REQUEST OVERSIGHT REVIEW</strong>
<p>Send this content to Community Oversight. A report does not remove it automatically.</p>
<PendingForm action="?/reportContent" toast={false}>
{#snippet children(pending)}
<input type="hidden" name="contentType" value={contentType}/>
<input type="hidden" name="contentId" value={contentId}/>
{#if itemId}<input type="hidden" name="itemId" value={itemId}/>{/if}
<label class="field">CONCERN
<select name="category" required>
{#each categories as category}<option value={category.value}>{category.label}</option>{/each}
</select>
</label>
<label class="field">DETAILS — OPTIONAL
<textarea name="details" maxlength="500" rows="3" placeholder="Give Oversight useful context without repeating sensitive information."></textarea>
</label>
{#if matchesResult && result?.userReportError}<p class="error">{String(result.userReportError)}</p>{/if}
{#if matchesResult && result?.userReportSaved}<p class="success">Report sent to Community Oversight.</p>{/if}
<div class="content-report-actions">
<button type="button" class="secondary" onclick={closeReport}>CANCEL</button>
<button class="primary" disabled={pending} aria-busy={pending}>{pending ? 'SENDING…' : 'SEND REPORT →'}</button>
</div>
{/snippet}
</PendingForm>
</div>
{/if}
</details>
<style>
.content-report{position:relative}.content-report summary{list-style:none;min-height:32px;display:inline-flex;align-items:center;padding:0 7px;border:1px solid var(--shd-border);color:var(--shd-text-muted);font-size:var(--shd-type-telemetry);font-weight:900;letter-spacing:.7px;cursor:pointer}.content-report summary::-webkit-details-marker{display:none}.content-report summary:hover,.content-report summary:focus-visible{border-color:var(--shd-danger);color:var(--shd-danger)}.content-report[open] summary{border-color:var(--shd-orange);color:var(--shd-orange)}.content-report.plain summary{min-height:32px;padding:0;border:0;background:transparent}.content-report.plain summary:hover,.content-report.plain summary:focus-visible{color:var(--shd-danger);text-decoration:underline;text-underline-offset:3px}.content-report.plain[open] summary{color:var(--shd-orange)}.content-report.menu-item summary{border:0}.content-report.menu-item summary:hover,.content-report.menu-item summary:focus-visible{color:var(--shd-orange);text-decoration:none}.content-report-panel{position:absolute;z-index:8;top:calc(100% + 6px);right:0;width:min(380px,calc(100vw - 32px));display:grid;gap:8px;padding:10px;border:1px solid var(--shd-border-strong);background:var(--shd-surface);box-shadow:0 18px 46px #000a}.content-report.plain:not(.menu-item) .content-report-panel{right:auto;left:0}.content-report-panel>strong{color:var(--shd-orange);font-size:var(--shd-type-telemetry);letter-spacing:.8px}.content-report-panel>p{margin:0;color:var(--shd-text-muted);font-size:var(--shd-type-supporting)}.content-report-panel :global(form fieldset){display:grid;gap:8px}.content-report-panel :global(.field){margin:0}.content-report-panel textarea{min-height:80px}.content-report-actions{display:flex;justify-content:flex-end;gap:7px}.content-report-actions button{min-height:40px}
@media(max-width:650px){.content-report-panel{position:fixed;top:auto;right:16px;bottom:16px;left:16px;width:auto}.content-report summary{min-height:44px}.content-report-actions{display:grid;grid-template-columns:1fr 1fr}.content-report-actions button{width:100%;min-height:44px}}
@media(prefers-reduced-motion:reduce){.content-report summary{transition:none}}
</style>