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.
This commit is contained in:
@@ -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}
|
||||
|
||||
@@ -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}
|
||||
|
||||
Reference in New Issue
Block a user