feat(ui): add global feedback and agent menu

This commit is contained in:
JB
2026-07-26 01:40:19 -04:00
parent 0e93aa25bd
commit d4ddfe3b80
11 changed files with 299 additions and 15 deletions
+2
View File
@@ -1,6 +1,7 @@
<script lang="ts">
import '../app.css';
import AppShell from '$lib/components/AppShell.svelte';
import ToastViewport from '$lib/components/ToastViewport.svelte';
let { data, children } = $props();
</script>
@@ -9,3 +10,4 @@
</svelte:head>
<AppShell user={data.user} isacAccess={data.isacAccess} notificationSummary={data.notifications}>{@render children()}</AppShell>
<ToastViewport/>
+3
View File
@@ -2,6 +2,7 @@
import PageBreadcrumb from '$lib/components/PageBreadcrumb.svelte';
import PendingForm from '$lib/components/PendingForm.svelte';
import ImageUploadInput from '$lib/components/ImageUploadInput.svelte';
import { toast } from '$lib/toast';
import { browserSupportsWebAuthn, startRegistration } from '@simplewebauthn/browser';
import { onMount } from 'svelte';
let { data, form } = $props();
@@ -64,9 +65,11 @@
const result = await verification.json();
if (!verification.ok || !result.verified) throw new Error(result.message || 'Passkey registration failed.');
passkeyMessage = 'Passkey registered. Reloading account methods…';
toast.success('Passkey registered', 'Your new sign-in method is ready.');
location.reload();
} catch (error) {
passkeyClientError = error instanceof Error ? error.message : 'Passkey registration was cancelled.';
toast.error('Passkey not registered', passkeyClientError);
passkeyPending = false;
}
}
@@ -81,6 +81,9 @@ export const actions: Actions = {
in_app_enabled=excluded.in_app_enabled,browser_enabled=excluded.browser_enabled,email_frequency=excluded.email_frequency,updated_at=excluded.updated_at`))
.run(user.id, category, inApp ? 1 : 0, browser ? 1 : 0, emailFrequency, now);
}
return { notificationPreferencesSaved: true };
return {
notificationPreferencesSaved: true,
notificationPreferenceSaveToken: crypto.randomUUID()
};
}
};
@@ -6,6 +6,7 @@
import SegmentedChoice from '$lib/components/SegmentedChoice.svelte';
let { data, form } = $props();
let settingsOpen = $state(false);
let consumedPreferenceSaveToken = $state<string | null>(null);
let selected = $state<any>(null);
let browserPermission = $state(typeof Notification === 'undefined' ? 'unsupported' : Notification.permission);
const labels: Record<string, string> = { isac: 'ISAC', contributions: 'CONTRIBUTIONS', comments: 'COMMENTS', builds: 'BUILDS', guides: 'GUIDES', system: 'SYSTEM' };
@@ -45,6 +46,13 @@
function applyFilters() {
document.querySelector<HTMLFormElement>('.notification-filter-console')?.requestSubmit();
}
$effect(() => {
const token = form?.notificationPreferenceSaveToken;
if (token && token !== consumedPreferenceSaveToken) {
consumedPreferenceSaveToken = token;
settingsOpen = false;
}
});
</script>
<svelte:head><title>Notification inbox — SHD.build</title></svelte:head>
+3 -2
View File
@@ -7,6 +7,7 @@
import RangeValueControl from '$lib/components/RangeValueControl.svelte';
import SegmentedChoice from '$lib/components/SegmentedChoice.svelte';
import PendingForm from '$lib/components/PendingForm.svelte';
import { toast } from '$lib/toast';
import {
blankRoll, clamp, createDraft, gearBaseStatRange, gearSlots, levelScaledRange, remapRangeValue, scaled, weaponBaseRange,
type LoadoutDraft, type Roll
@@ -326,8 +327,8 @@
});
function saveDraft() {
localStorage.setItem('shd-build-loadout-draft-v1', JSON.stringify(draft));
saved = true; message = 'Draft saved on this device.';
setTimeout(() => (message = ''), 2200);
saved = true;
toast.success('Draft saved locally', 'This loadout is available on this device.');
}
</script>