poal98-hakim/real-time-dashboard
Responsive real-time dashboard built with Next.js 15, TailwindCSS & TypeScript. Features include API polling, customizable widgets, theme support and a scalable multi-layer architecture.
Real-Time Dashboard
A responsive, real-time dashboard built with Next.js 15, React 19, TypeScript, and Tailwind CSS. It features live data polling, a customizable widget grid, theme switching, and persistent user layout.
Deployment
- Live app: Open Vercel deployment
Features
- Real-time updates: 5-second polling with pause/resume and manual refresh
- Customizable layout: drag, resize, add/remove widgets; persistent via localStorage
- Theme switching: light/dark with cookie persistence; server-generated tokens
- Rich widgets: line and bar charts, table with pagination/selection, map with markers
- Typed boundaries: Zod validation (server-side) + Result pattern for safe error handling
- User feedback: toast notifications on failures; friendly error messages
User Journey
-
Landing
- The header shows “Reset to default”, an “Edit mode” switch, and the theme toggle.
- The subheader shows “Last updated …”, a pause/resume polling button, a manual refresh, and an “Add widget” menu when widgets are available to add.
-
Live data
- Data auto-refreshes every 5 seconds via the DashboardRepository. The subheader’s timestamp updates using a relative “from now” label.
- Users can pause/resume polling or click refresh for an immediate update.
- Errors show as toasts with a user-friendly message while keeping previous data visible.
-
Customizing the dashboard
- Toggle “Edit mode” to enable dragging/resizing widgets (react-grid-layout) and reveal remove buttons on each card.
- Remove a widget from its card; re-add it via the “Add widget” menu in the subheader.
- Layout changes persist automatically to localStorage.
-
Theme and persistence
- Toggle light/dark via the header button. Preference is stored in a cookie; CSS tokens are generated server-side and applied inline.
Architecture & Design
Layered framework-agnostic architecture for separation of concerns and testability:
-
Services
src/services/dashboard/dashboard.tscalls the Next API route (by defaultNEXT_PUBLIC_API_PATH=/api/dashboard).- HTTP communication uses native
fetchwith simple timeout handling. Service returnsResult<DashboardDTO>. - DTO type is shared from server schemas. Validation happens in the API route (see API Layer).
-
Repositories (Centralized State)
DashboardRepository(src/repositories/dashboard) owns polling, loading state, lastUpdatedAt, and normalized data (PM — Presentation Model). It publishes toasts on failures.LayoutRepository(src/repositories/layout) owns grid layout, edit mode, and persistence withzustand+persistto localStorage.NotificationsRepositorydrives toasts.
-
Presenters (Mapping to VM for UI/Business logic)
WidgetGridPresenterhydrates initial PM into theDashboardRepository, manages polling start/stop, and maps PM -> VM for widgets.HeaderPresentertoggles edit mode and layout reset;SubHeaderPresentertoggles polling, refreshes data, and computes “addable” widgets.
-
UI Components (Pure React)
- Widget grid uses
react-grid-layout(src/components/WidgetGrid). Charts userecharts, maps usereact-leaflet. - Theming: tokens generated server-side (
src/server/theme) with a small cookie-driven action layer; CSS variables injected via<style>insrc/app/layout.tsx.
- Widget grid uses
-
API Layer (Next.js Route)
src/app/api/dashboard/route.tsproxies tohttps://dashboard-api-dusky.vercel.app/api/get.- Validates upstream response with Zod (
src/server/schemas/dashboard.schema.ts) and returns a guaranteed shape. - Keeps secrets server-side and avoids CORS issues from the client.
Key design decisions:
- Result + server-side Zod: safer IO and uniform, user-friendly errors.
- Repositories as single sources of truth: polling, persistence, and view subscriptions centralized.
- Presenters isolate view-specific transforms and reduce component complexity.
- Proxied API route protects tokens and centralizes backend config.
- Server-generated theme tokens ensure consistent design with minimal runtime cost.
- Charts are dynamically imported in
WidgetGridand chart animations are disabled to reduce jank during polling - Fetch initial dashboard data on the server and
WidgetGridPresenter.setInitialData(pm)hydrates theDashboardRepositoryin auseLayoutEffectbefore first paint to avoid flicker.
Tech Stack
- Next.js 15, React 19, TypeScript
- Zustand for state; Zod for schema validation
- Native fetch for HTTP; React Grid Layout for drag/resize
- Recharts for charts; React Leaflet + Leaflet for maps
- Vitest + Testing Library for tests
Setup
Prerequisites:
- Node.js
- npm
- Install dependencies
npm install- Configure environment
Create .env.local in the project root:
NEXT_PUBLIC_API_PATH=/api/dashboard
API_PATH=https://dashboard-api-dusky.vercel.app/api/get
API_TOKEN=your_api_token_here
# Optional: return randomized stub data instead of calling upstream API
# USE_STUBS=trueNotes:
API_TOKENis used by the Next.js route to call the upstream API.- Set
USE_STUBS=trueto serve randomized demo data on each request (skips upstream fetch).
- Run the app
npm run dev- Build and start (production)
npm run build
npm start- Lint
npm run lintTesting
This project uses Vitest.
npm testCreated few tests for showcasing:
src/integration-tests/homeFlow.test.ts: black-box integration of the home flow. It verifies presenter–repository interactions, polling toggles, layout add/remove behaviors, and “last updated” timing labels.src/components/SubHeader/SubHeader.test.tsx: UI tests for the react component.