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:
@@ -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 };
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user