Skip to content

feat: error boundary, empty state, jwt interceptors, protected routes , admin shell#128

Merged
johdanike merged 1 commit into
SwiftChainn:mainfrom
Ogstevyn:frontend
Jun 1, 2026
Merged

feat: error boundary, empty state, jwt interceptors, protected routes , admin shell#128
johdanike merged 1 commit into
SwiftChainn:mainfrom
Ogstevyn:frontend

Conversation

@Ogstevyn
Copy link
Copy Markdown

@Ogstevyn Ogstevyn commented Jun 1, 2026

Summary

Closes #8,
closes #11,
closes #12,
closes #16

This PR implements four foundational frontend features following the strict Component → Hook → Service layered architecture throughout.


#8 — Empty State & Error Boundary

components/ui/EmptyState.tsx
Reusable component accepting a Lucide icon, title, optional description, and an optional CTA action callback. Designed to be dropped into any list view that may return zero results.

app/error.tsx
Next.js App Router global error boundary (Client Component). Catches render errors anywhere in the component tree, logs them to the console, and renders a full-screen recovery UI with a "Try again" button that calls reset() to re-render the failed segment.


#11 — JWT Axios Interceptor Setup

lib/api.ts
Singleton Axios instance configured with two interceptors:

  • Request: reads authToken from localStorage and injects it as Authorization: Bearer <token> on every outgoing request.
  • Response: intercepts 401 Unauthorized responses, removes the stale token from storage, and redirects the browser to /login.

services/auth.service.ts
Thin auth service built on top of lib/api.ts. Exposes login, logout, and getCurrentUser — all requests automatically carry the bearer token via the shared interceptor. Imports shared types from the existing services/authService.ts to avoid duplication.


#12 — Protected Route Wrapper

hooks/useAuth.ts
On mount, reads authToken from localStorage. If present, calls GET /auth/me via the interceptor-enabled client to verify the token and populate the Zustand authStore. On 401 or missing token, clears the store. Returns { isAuthenticated, isLoading, user }.

components/auth/ProtectedRoute.tsx
Client component wrapper that:

  1. Shows a centered spinner while useAuth resolves.
  2. Redirects unauthenticated visitors to /login via router.replace.
  3. Redirects authenticated users with the wrong role to /.
  4. Renders children only when auth and role checks pass.
    Accepts an optional requiredRole prop for role-gated pages.

#16 — Admin Dashboard Shell

services/adminService.ts
Service that calls GET /admin/stats (via the JWT-bearing lib/api.ts client) and returns platform-wide metrics: total users, active deliveries, revenue, active drivers, pending KYC, and escrow locked.

hooks/useAdminDashboard.ts
TanStack Query hook wrapping adminService.getStats(). Caches for 30 seconds, retries once on failure, exposes { stats, isLoading, isError, refetch }.

components/admin/AdminSidebar.tsx
Fixed 256 px sidebar exclusively for admin pages. Contains navigation links (Overview, Users, Deliveries, KYC Review, Settings) with active-path highlighting and a sign-out button that clears the auth store and redirects to /login. Completely separate from the standard AppLayout sidebar.

app/(admin)/layout.tsx
Route-group layout wrapping all pages under (admin)/ with ProtectedRoute requiredRole="Admin". Admins who are not authenticated are redirected to /login; authenticated non-admins are redirected to /. Renders AdminSidebar + a <main> content area.

app/(admin)/admin/overview/page.tsx → URL: /admin/overview
Admin overview page with six metric cards (Total Users, Active Deliveries, Total Revenue, Active Drivers, Pending KYC, Escrow Locked). Shows skeleton loaders while data is in flight and a dismissible error banner on fetch failure. Data is sourced from the backend API via useAdminDashboard.

app/(dashboard)/admin/page.tsx — updated to redirect('/admin/overview') so legacy /admin links forward to the new isolated admin shell.

middleware.ts — updated to redirect unauthenticated requests to /login for both /dashboard/* and /admin/* routes.

Routing note: The (admin) route group layout is isolated from the (dashboard) group. The admin overview is served at /admin/overview (inside the (admin) group) rather than at / to avoid a Next.js build conflict with the existing landing page at app/page.tsx. The old /admin entry point is preserved as a redirect.

@drips-wave
Copy link
Copy Markdown

drips-wave Bot commented Jun 1, 2026

@Ogstevyn Great news! 🎉 Based on an automated assessment of this PR, the linked Wave issue(s) no longer count against your application limits.

You can now already apply to more issues while waiting for a review of this PR. Keep up the great work! 🚀

Learn more about application limits

@johdanike johdanike merged commit d925de0 into SwiftChainn:main Jun 1, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Frontend: Admin Dashboard Shell Frontend: Protected Route Wrapper Frontend: JWT Axios Interceptor Setup Frontend: Empty State & Error Boundary

2 participants