Files
shd.build/src/routes/isac/avatar-oversight/+page.svelte
T
2026-07-25 20:55:13 -04:00

66 lines
6.4 KiB
Svelte
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<script lang="ts">
import PendingForm from '$lib/components/PendingForm.svelte';
import RegistryPagination from '$lib/components/RegistryPagination.svelte';
import KpiFilters from '$lib/components/KpiFilters.svelte';
let { data, form } = $props();
let reviewing = $state<any>(null);
let decision = $state<'approve' | 'remove'>('approve');
let handledToken = $state('');
const date = (value: unknown) => value ? new Intl.DateTimeFormat(undefined, { dateStyle: 'medium', timeStyle: 'short' }).format(new Date(String(value))) : '—';
const openReview = (review: any) => { reviewing = review; decision = 'approve'; };
$effect(() => {
if (form?.avatarReviewSaved && form.avatarReviewToken && form.avatarReviewToken !== handledToken) {
handledToken = form.avatarReviewToken;
reviewing = null;
}
});
</script>
<svelte:head><title>Avatar Oversight — ISAC</title></svelte:head>
<div class="module-toolbar moderation-toolbar">
<div><p class="eyebrow">COMMUNITY SAFETY / IMAGE CONTROL</p><h2>AVATAR OVERSIGHT</h2><p>Inspect custom agent portraits without delaying their use. Approve acceptable images or remove policy violations.</p></div>
<div class="queue-readout"><span>PENDING REVIEW</span><strong>{data.summary.pending ?? 0}</strong><small>CUSTOM AVATARS</small></div>
</div>
<KpiFilters parameter="status" active={data.status} items={[
{value:'pending',label:'PENDING',count:data.summary.pending ?? 0,description:'Awaiting image review'},
{value:'approved',label:'APPROVED',count:data.summary.approved ?? 0,description:'Accepted by Oversight'},
{value:'removed',label:'REMOVED',count:data.summary.removed ?? 0,description:'Removed for policy concerns'},
{value:'withdrawn',label:'WITHDRAWN',count:data.summary.withdrawn ?? 0,description:'User selected another avatar'},
{value:'superseded',label:'SUPERSEDED',count:data.summary.superseded ?? 0,description:'Replaced by a newer upload'}
]}/>
<form class="audit-filter avatar-review-filter"><input name="search" value={data.search} placeholder="AGENT OR LOGIN"/><input type="hidden" name="status" value={data.status}/><button class="secondary">SEARCH QUEUE →</button></form>
<RegistryPagination pagination={data.pagination}/>
<div class="avatar-review-grid">
{#each data.reviews as review}
<article class="avatar-review-card">
<div class="avatar-review-image">
{#if review.is_current}<img src={`/avatars/user/${review.user_id}?v=${encodeURIComponent(review.avatar_updated_at)}`} alt={`Custom avatar uploaded by ${review.display_name}`}/>{:else}<span>IMAGE<br/>NO LONGER ACTIVE</span>{/if}
<b class:pending={review.status === 'pending'} class:approved={review.status === 'approved'} class:declined={review.status === 'removed'}>{String(review.status).toUpperCase()}</b>
</div>
<div class="avatar-review-details"><strong>{review.display_name}</strong><small>LOGIN: {review.login_username ?? 'NOT CONFIGURED'}</small><dl><div><dt>SUBMITTED</dt><dd>{date(review.submitted_at)}</dd></div><div><dt>SIZE</dt><dd>{Math.ceil(Number(review.byte_size) / 1024)} KB</dd></div>{#if review.reviewer_name}<div><dt>REVIEWER</dt><dd>{review.reviewer_name}</dd></div>{/if}</dl>{#if review.review_note}<p>{review.review_note}</p>{/if}<button class="secondary" onclick={() => openReview(review)}>{review.status === 'pending' ? 'REVIEW IMAGE →' : 'VIEW EVIDENCE →'}</button></div>
</article>
{:else}<div class="empty-dashboard"><strong>NO {data.status.toUpperCase()} AVATARS</strong><span>Adjust the queue filters to inspect another review state.</span></div>{/each}
</div>
{#if reviewing}
<div class="isac-modal-backdrop" role="presentation">
<div class="isac-confirm-modal avatar-review-modal" role="dialog" aria-modal="true" aria-labelledby="avatar-review-title">
<div class="editor-title"><div><p class="eyebrow">AVATAR REVIEW / {String(reviewing.status).toUpperCase()}</p><h3 id="avatar-review-title">{reviewing.display_name}</h3></div><button aria-label="Close avatar review" onclick={() => reviewing = null}>×</button></div>
<div class="avatar-review-preview">{#if reviewing.is_current}<img src={`/avatars/user/${reviewing.user_id}?v=${encodeURIComponent(reviewing.avatar_updated_at)}`} alt={`Custom avatar uploaded by ${reviewing.display_name}`}/>{:else}<span>THE REVIEWED IMAGE IS NO LONGER ACTIVE</span>{/if}</div>
<div class="moderation-metadata"><span>SUBMITTED <b>{date(reviewing.submitted_at)}</b></span><span>NORMALIZED SIZE <b>{Math.ceil(Number(reviewing.byte_size) / 1024)} KB</b></span><span>AGENT <b>{reviewing.display_name}</b></span></div>
{#if reviewing.status === 'pending'}
<div class="decision-tabs avatar-decision-tabs"><button class:active={decision === 'approve'} onclick={() => decision = 'approve'}><b>✓</b><span>APPROVE<small>KEEP IMAGE ACTIVE</small></span></button><button class:active={decision === 'remove'} onclick={() => decision = 'remove'}><b>×</b><span>REMOVE<small>RESET TO DEFAULT</small></span></button></div>
<PendingForm action={decision === 'approve' ? '?/approve' : '?/remove'}>
{#snippet children(pending)}
<input type="hidden" name="id" value={reviewing.id}/>
<label class="field">{decision === 'remove' ? 'REMOVAL REASON — REQUIRED' : 'OPTIONAL OVERSIGHT NOTE'}<textarea name="note" required={decision === 'remove'} placeholder={decision === 'remove' ? 'Explain why this image is unacceptable…' : 'Record any useful review context…'}></textarea></label>
{#if form?.avatarReviewError}<p class="error">{form.avatarReviewError}</p>{/if}
<div class="moderation-warning" class:rejecting={decision === 'remove'}><b>{decision === 'approve' ? '!' : '×'}</b><span>{decision === 'approve' ? 'APPROVAL RECORDS THE REVIEW. THE AVATAR IS ALREADY ACTIVE.' : 'REMOVAL DELETES THE CUSTOM FILE AND RESETS THE AGENT TO THE DEFAULT SHD PORTRAIT.'}</span></div>
<div class="form-actions"><button type="button" class="secondary" onclick={() => reviewing = null} disabled={pending}>CANCEL</button><button class={decision === 'approve' ? 'primary' : 'danger'} disabled={pending} aria-busy={pending}>{pending ? 'PROCESSING…' : decision === 'approve' ? 'CONFIRM APPROVAL →' : 'CONFIRM REMOVAL →'}</button></div>
{/snippet}
</PendingForm>
{:else}<div class="review-note"><span>OVERSIGHT EVIDENCE</span><p>{reviewing.review_note || 'No review note was recorded.'}</p></div><div class="form-actions"><button class="primary" onclick={() => reviewing = null}>CLOSE EVIDENCE</button></div>{/if}
</div>
</div>
{/if}