Enhance image upload experience in ReceiptForm and +page components by integrating a loading state with a spinner icon during image processing. Update styles for better accessibility and user feedback, ensuring a smoother interaction when converting images to WebP format.
CI / build (push) Has been skipped
CI / deploy (push) Successful in 1m33s

This commit is contained in:
eewing
2026-02-19 11:33:57 -06:00
parent 8af1ee8bd4
commit d06e585bb5
18 changed files with 184 additions and 145 deletions
+20 -6
View File
@@ -3,9 +3,13 @@
import { Label } from "$lib/components/ui/label";
import { convertImageToWebP } from "$lib/convert-image-to-webp";
import { enhance } from "$app/forms";
import Loader2Icon from "@lucide/svelte/icons/loader-2";
const TYPE_OPTIONS = ["Purchase", "Gas"] as const;
const SCAN_BUTTON_CLASS =
"border-input bg-background hover:bg-accent hover:text-accent-foreground flex h-9 w-full cursor-pointer items-center justify-center gap-2 rounded-md border px-4 py-2 text-sm font-medium shadow-xs transition-colors";
let {
action,
submitLabel,
@@ -32,6 +36,8 @@
}
imageConverting = true;
imageFileName = null;
// Let the UI paint "Processing image…" before we block the main thread
await new Promise((r) => setTimeout(r, 0));
try {
const webpFile = await convertImageToWebP(file);
const dt = new DataTransfer();
@@ -86,12 +92,20 @@
tabindex="-1"
aria-label="Scan receipt"
/>
<label
for="Image"
class="border-input bg-background hover:bg-accent hover:text-accent-foreground flex h-9 w-full cursor-pointer items-center justify-center rounded-md border px-4 py-2 text-sm font-medium shadow-xs transition-colors"
>
Scan receipt
</label>
{#if imageConverting}
<div
class="{SCAN_BUTTON_CLASS} pointer-events-none opacity-80"
aria-busy="true"
aria-live="polite"
>
<Loader2Icon class="size-4 shrink-0 animate-spin" aria-hidden />
<span>Processing image…</span>
</div>
{:else}
<label for="Image" class={SCAN_BUTTON_CLASS}>
Scan receipt
</label>
{/if}
{#if imageConverting}
<p class="text-muted-foreground text-xs">Converting to WebP…</p>
{:else if imageFileName}
+25 -6
View File
@@ -9,6 +9,7 @@
import { enhance } from "$app/forms";
import ChevronDownIcon from "@lucide/svelte/icons/chevron-down";
import ChevronUpIcon from "@lucide/svelte/icons/chevron-up";
import Loader2Icon from "@lucide/svelte/icons/loader-2";
import type { StackqReceipt } from "$lib/types/receipts";
import type { ReceiptLineItem } from "$lib/types/receipts";
@@ -122,6 +123,7 @@
let imageFileName = $state<string | null>(null);
let selectedImageFile = $state<File | null>(null);
let imageError = $state(false);
let imageConverting = $state(false);
/** On small screens image is collapsed by default; on lg+ expanded. */
let imageOpen = $state(false);
@@ -141,6 +143,10 @@
imageFileName = null;
return;
}
imageConverting = true;
selectedImageFile = null;
imageFileName = null;
await new Promise((r) => setTimeout(r, 0));
try {
const webpFile = await convertImageToWebP(file);
selectedImageFile = webpFile;
@@ -149,6 +155,8 @@
selectedImageFile = null;
imageFileName = null;
if (input) input.value = "";
} finally {
imageConverting = false;
}
}
function imageLoadError() {
@@ -247,12 +255,23 @@
tabindex="-1"
aria-label={receipt.Image ? "Replace image" : "Add image"}
/>
<label
for="receipt-image-input"
class="border-input bg-background hover:bg-accent hover:text-accent-foreground inline-flex h-8 cursor-pointer items-center justify-center rounded-md border px-3 text-sm font-medium shadow-xs transition-colors"
>
{receipt.Image ? "Replace image" : "Add image"}
</label>
{#if imageConverting}
<div
class="border-input bg-background inline-flex h-8 cursor-not-allowed items-center justify-center gap-2 rounded-md border px-3 text-sm font-medium opacity-80"
aria-busy="true"
aria-live="polite"
>
<Loader2Icon class="size-4 shrink-0 animate-spin" aria-hidden />
<span>Processing image…</span>
</div>
{:else}
<label
for="receipt-image-input"
class="border-input bg-background hover:bg-accent hover:text-accent-foreground inline-flex h-8 cursor-pointer items-center justify-center rounded-md border px-3 text-sm font-medium shadow-xs transition-colors"
>
{receipt.Image ? "Replace image" : "Add image"}
</label>
{/if}
{#if imageFileName}
<span class="text-muted-foreground text-xs">New: {imageFileName}</span>
{/if}