feat(transcription): show dismissible error banner when markAllReviewed fails
Adds markAllError state and catch block to handleMarkAllReviewed. Error banner renders below the review progress bar with role="alert" and aria-live="polite" for screen reader announcement. Dismiss button clears the error; next successful call also clears it automatically. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -1,5 +1,6 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { m } from '$lib/paraglide/messages.js';
|
import { m } from '$lib/paraglide/messages.js';
|
||||||
|
import { getErrorMessage } from '$lib/shared/errors.js';
|
||||||
import TranscriptionBlock from './TranscriptionBlock.svelte';
|
import TranscriptionBlock from './TranscriptionBlock.svelte';
|
||||||
import OcrTrigger from '$lib/ocr/OcrTrigger.svelte';
|
import OcrTrigger from '$lib/ocr/OcrTrigger.svelte';
|
||||||
import TranscribeCoachEmptyState from '$lib/shared/help/TranscribeCoachEmptyState.svelte';
|
import TranscribeCoachEmptyState from '$lib/shared/help/TranscribeCoachEmptyState.svelte';
|
||||||
@@ -49,6 +50,7 @@ let activeBlockId: string | null = $state(null);
|
|||||||
let localLabels: string[] = $derived.by(() => [...trainingLabels]);
|
let localLabels: string[] = $derived.by(() => [...trainingLabels]);
|
||||||
let listEl: HTMLElement | null = $state(null);
|
let listEl: HTMLElement | null = $state(null);
|
||||||
let markingAllReviewed = $state(false);
|
let markingAllReviewed = $state(false);
|
||||||
|
let markAllError = $state<string | null>(null);
|
||||||
|
|
||||||
const sortedBlocks = $derived([...blocks].sort((a, b) => a.sortOrder - b.sortOrder));
|
const sortedBlocks = $derived([...blocks].sort((a, b) => a.sortOrder - b.sortOrder));
|
||||||
const hasBlocks = $derived(blocks.length > 0);
|
const hasBlocks = $derived(blocks.length > 0);
|
||||||
@@ -67,8 +69,11 @@ $effect(() => {
|
|||||||
async function handleMarkAllReviewed() {
|
async function handleMarkAllReviewed() {
|
||||||
if (!onMarkAllReviewed) return;
|
if (!onMarkAllReviewed) return;
|
||||||
markingAllReviewed = true;
|
markingAllReviewed = true;
|
||||||
|
markAllError = null;
|
||||||
try {
|
try {
|
||||||
await onMarkAllReviewed();
|
await onMarkAllReviewed();
|
||||||
|
} catch (e) {
|
||||||
|
markAllError = getErrorMessage(e instanceof Error ? e.message : undefined);
|
||||||
} finally {
|
} finally {
|
||||||
markingAllReviewed = false;
|
markingAllReviewed = false;
|
||||||
}
|
}
|
||||||
@@ -217,6 +222,32 @@ async function handleLabelToggle(label: string) {
|
|||||||
style="width: {reviewProgress}%"
|
style="width: {reviewProgress}%"
|
||||||
></div>
|
></div>
|
||||||
</div>
|
</div>
|
||||||
|
{#if markAllError}
|
||||||
|
<div
|
||||||
|
role="alert"
|
||||||
|
aria-live="polite"
|
||||||
|
class="mt-1.5 flex items-center gap-2 rounded-sm border border-red-200 bg-red-50 px-3 py-2 font-sans text-xs text-red-700"
|
||||||
|
>
|
||||||
|
<span class="flex-1">{markAllError}</span>
|
||||||
|
<button
|
||||||
|
onclick={() => (markAllError = null)}
|
||||||
|
aria-label={m.comp_dismiss()}
|
||||||
|
class="flex min-h-[44px] min-w-[44px] items-center justify-center rounded text-red-500 hover:text-red-700 focus-visible:ring-2 focus-visible:ring-red-500"
|
||||||
|
>
|
||||||
|
<svg
|
||||||
|
class="h-4 w-4"
|
||||||
|
fill="none"
|
||||||
|
stroke="currentColor"
|
||||||
|
stroke-width="2"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
aria-hidden="true"
|
||||||
|
>
|
||||||
|
<path stroke-linecap="round" stroke-linejoin="round" d="M6 18L18 6M6 6l12 12" />
|
||||||
|
</svg>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
<div class="p-4">
|
<div class="p-4">
|
||||||
<!-- svelte-ignore a11y_no_static_element_interactions -->
|
<!-- svelte-ignore a11y_no_static_element_interactions -->
|
||||||
|
|||||||
Reference in New Issue
Block a user