feat(badges): goto() after print with per-device fallback toggle

Switch post-print navigation from window.location.href to goto() for
faster SvelteKit client-side transition back to Badge Search (no full
reload). A nav_to_badges() helper in print controls branches on
badges_loc.print_nav_use_goto (default true).

Badges Config page gains a "Local Device Settings" section with a
checkbox to disable goto() and fall back to hard reload — stored in
localStorage per browser, not synced to the event. Useful as an
on-site escape hatch without a code deploy.

Also fixes the Templates button on the config page: adds the standard
border border-surface-300-700 so it looks like a button.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Scott Idem
2026-06-08 20:28:33 -04:00
parent 55f3e3a5a4
commit cc04411d23
3 changed files with 58 additions and 7 deletions

View File

@@ -138,6 +138,10 @@ export interface BadgesLocState {
trusted_search_min_chars: number;
// Timestamp when the remote config was last mirrored locally
remote_cfg_last_synced_on: string | null;
// After-print navigation method. true (default) = SvelteKit goto() — faster, no full reload.
// false = window.location.href — full page reload, use as a fallback if goto causes issues.
// Per-device: stored in localStorage, not synced to the event config.
print_nav_use_goto: boolean;
}
export interface BadgesSessState {
@@ -203,7 +207,8 @@ export const badges_loc_defaults: BadgesLocState = {
auth_search_min_chars: 2,
trusted_search_result_limit: 150,
trusted_search_min_chars: 1,
remote_cfg_last_synced_on: null
remote_cfg_last_synced_on: null,
print_nav_use_goto: true
};
// In-memory badge state — resets on page load.

View File

@@ -22,6 +22,8 @@ import type { key_val } from '$lib/stores/ae_stores';
import { ae_api, ae_loc } from '$lib/stores/ae_stores';
import { events_func } from '$lib/ae_events/ae_events_functions';
import { browser } from '$app/environment';
import { goto } from '$app/navigation';
import { badges_loc } from '$lib/stores/ae_events_stores__badges.svelte';
import {
Check,
ChevronDown,
@@ -211,6 +213,19 @@ let can_print = $derived((is_public && !is_printed) || (is_trusted && is_global_
type PrintStatus = 'idle' | 'loading' | 'done' | 'error';
let print_status: PrintStatus = $state('idle');
// Navigate back to badge search after print completes.
// goto() is the default: faster SvelteKit client-side navigation, no full reload.
// Falls back to window.location.href if badges_loc.print_nav_use_goto is disabled —
// useful as an on-site escape hatch if the goto() path causes unexpected issues.
function nav_to_badges() {
if (!browser) return;
if (badges_loc.current.print_nav_use_goto) {
goto(`/events/${event_id}/badges`);
} else {
window.location.href = `/events/${event_id}/badges`;
}
}
async function handle_print_badge() {
if (!$lq__event_badge_obj?.event_badge_id) return;
print_status = 'loading';
@@ -232,7 +247,7 @@ async function handle_print_badge() {
if (browser) window.print();
print_status = 'error';
await new Promise<void>((r) => setTimeout(r, 4000));
if (browser) window.location.href = `/events/${event_id}/badges`;
nav_to_badges();
return;
}
@@ -258,7 +273,7 @@ async function handle_print_badge() {
// Hold the error state long enough for a kiosk operator to notice before
// the loop returns to search for the next attendee.
await new Promise<void>((r) => setTimeout(r, 4000));
if (browser) window.location.href = `/events/${event_id}/badges`;
nav_to_badges();
return;
}
@@ -268,13 +283,13 @@ async function handle_print_badge() {
// Brief success flash, then return to badge search
await new Promise<void>((r) => setTimeout(r, 1000));
// Full navigation back to badge search — avoids goto() lint rule in child components
if (browser) window.location.href = `/events/${event_id}/badges`;
nav_to_badges();
} catch (err) {
console.error('Badge print controls: print error:', err);
print_status = 'error';
if (browser) window.print();
await new Promise<void>((r) => setTimeout(r, 4000));
if (browser) window.location.href = `/events/${event_id}/badges`;
nav_to_badges();
}
}

View File

@@ -28,9 +28,11 @@ import {
ChevronUp,
FileText,
Lock,
Monitor,
Save,
Settings
} from '@lucide/svelte';
import { badges_loc } from '$lib/stores/ae_events_stores__badges.svelte';
interface Props {
data: any;
@@ -272,9 +274,9 @@ function toggle(key: string) {
<h1 class="text-xl font-bold">Badges Config</h1>
</div>
<a href="/events/{event_id}/templates"
class="btn btn-sm preset-tonal-surface"
class="btn btn-sm preset-tonal-surface border border-surface-300-700"
title="Manage Badge Templates">
<FileText size="1em" class="mr-1" /> Templates
<FileText size="1em" /> Templates
</a>
<div class="flex items-center gap-2">
{#if save_status === 'success'}
@@ -555,6 +557,35 @@ function toggle(key: string) {
{/if}
</section>
<!-- ================================================================ -->
<!-- LOCAL DEVICE SETTINGS (localStorage only — not saved to event) -->
<!-- ================================================================ -->
<section class="border-surface-200-800 rounded-xl border">
<div class="flex w-full items-center justify-between px-4 py-3">
<span class="flex items-center gap-2 font-semibold">
<Monitor size="1em" class="text-surface-400" />
Local Device Settings
</span>
<span class="text-xs text-surface-400 italic">saved to this browser only · not synced</span>
</div>
<div class="border-surface-200-800 space-y-3 border-t px-4 py-3">
<label class="flex items-start gap-3">
<input
type="checkbox"
class="checkbox mt-0.5 shrink-0"
bind:checked={badges_loc.current.print_nav_use_goto} />
<div>
<p class="text-sm font-medium">Use fast navigation after print</p>
<p class="text-xs text-surface-400 italic">
On by default. Uses SvelteKit client-side navigation to return to Badge Search
after printing — faster and avoids a full page reload. Turn off to fall back to
a hard page reload if the print loop behaves unexpectedly on this device.
</p>
</div>
</label>
</div>
</section>
<!-- Bottom save button -->
<div class="flex justify-end">
<button