This PR addresses two frontend issues:
- Accessibility (a11y) audit -- Adds ARIA labels, roles, keyboard navigation, focus management, and WCAG-compliant touch targets across checkout and dashboard components.
- Checkout: Real-time payment status via SSE -- Replaces polling with Server-Sent Events (EventSource) for instant payment confirmation, with automatic fallback to the existing 3-second polling if SSE is unavailable.
Checkout components:
PaymentStatus.tsx-- Addedrole="status",aria-live="polite",aria-label, andaria-hiddenon decorative icons.PaymentTimer.tsx-- Addedrole="timer",aria-live="off", human-readablearia-label, andmin-h-[44px]touch target.PaymentQRCode.tsx-- Addedrole="img"with descriptivearia-labelon QR container,aria-labelledbylinking address to its label.pay/[payment_id]/page.tsx-- Addedrole="status"androle="alert"on state containers,aria-hiddenon all decorative icons,aria-labelon payment instructions, and responsive padding for mobile.
Dashboard components:
Sidebar.tsx-- Addedaria-labelon sidebar and nav elements,aria-current="page"on active link,aria-label="Close menu"on mobile close button,aria-hiddenon icons, andmin-h-[44px]touch targets on all nav links.DashboardShell.tsx-- Addedrole="main"on main content area,aria-hiddenon overlay backdrop, and Escape key handler to close mobile sidebar.Modal.tsx-- Addedrole="dialog",aria-modal="true",aria-labelledbypointing to modal title, focus trap (Tab key cycling), Escape key to close, focus restoration on close, and 44px minimum close button touch target.
- New file:
api/payments/[payment_id]/stream/route.ts-- SSE endpoint usingReadableStreamthat pushes payment status every 2 seconds. Automatically closes the stream on terminal states (confirmed, expired, failed). Cleans up on client disconnect viaAbortSignal. - Modified:
hooks/usePaymentStatus.ts-- Connects viaEventSourcefirst for instant updates. On SSE error or connection failure, automatically falls back to the existing 3-second polling. ExposesconnectionType('sse' | 'polling') for observability. Properly cleans up both EventSource and polling intervals on unmount or terminal state.
All CI pipeline checks pass:
| Check | Result |
|---|---|
ESLint (npx eslint . --max-warnings=0) |
Pass, 0 warnings |
Build (npm run build) |
Pass, exit 0. SSE stream route registered as dynamic route |
Unit tests (npm test) |
15/15 tests pass across 4 test files |
No new dependencies were added. All existing tests (PaymentStatus, StatsCards, LoginForm, SignUpForm) continue to pass without modification.
src/components/checkout/PaymentStatus.tsxsrc/components/checkout/PaymentTimer.tsxsrc/components/checkout/PaymentQRCode.tsxsrc/components/Modal.tsxsrc/app/pay/[payment_id]/page.tsxsrc/features/dashboard/components/Sidebar.tsxsrc/features/dashboard/layout/DashboardShell.tsxsrc/hooks/usePaymentStatus.tssrc/app/api/payments/[payment_id]/stream/route.ts[NEW]