Private
Public Access
1
0

Phase 8: Polish & UX — dark mode, toasts, keyboard shortcuts, responsive, accessibility

- Add dark mode with theme toggle, localStorage/cookie persistence, anti-FOUC script
- Add toast notification system with auto-dismiss and color-coded types
- Add api() fetch wrapper with auto JSON, X-Socket-ID, and error toasts
- Add keyboard shortcuts (/ search, b boards, ? help, n new card, Esc close)
- Add error page with status code and navigation
- Add focus trap utility and apply to CardModal with full ARIA dialog support
- Add hamburger menu for mobile, responsive columns, fullscreen card modal on mobile
- Add aria-labels to icon-only buttons and DnD live region
- Migrate all ~30 fetch calls to api() wrapper with success toasts
- Add dark: Tailwind variants to all 20+ pages and components

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Catherine Renelle
2026-02-26 01:09:17 -05:00
parent 6ed0349507
commit 7559102479
33 changed files with 850 additions and 374 deletions
+47
View File
@@ -0,0 +1,47 @@
import { toast } from '$lib/stores/toasts.js';
import { getSocketId } from '$lib/stores/socket.js';
export type ApiResult<T = any> = {
ok: boolean;
data: T;
status: number;
};
export async function api<T = any>(
url: string,
opts: RequestInit = {}
): Promise<ApiResult<T>> {
const headers = new Headers(opts.headers);
// Auto-set JSON content type for non-FormData bodies
if (opts.body && !(opts.body instanceof FormData) && !headers.has('Content-Type')) {
headers.set('Content-Type', 'application/json');
}
// Pass socket ID for self-dedup
const sid = getSocketId();
if (sid) {
headers.set('X-Socket-ID', sid);
}
try {
const res = await fetch(url, { ...opts, headers });
let data: any = null;
const contentType = res.headers.get('content-type');
if (contentType?.includes('application/json')) {
data = await res.json();
}
if (!res.ok) {
const msg = data?.error || data?.message || `Request failed (${res.status})`;
toast.error(msg);
return { ok: false, data, status: res.status };
}
return { ok: true, data, status: res.status };
} catch {
toast.error('Network error. Please try again.');
return { ok: false, data: null as any, status: 0 };
}
}