NatureRestorer is a portfolio-ready Next.js dashboard for exploring restoration sites, biodiversity performance, and map-based location context.
It now includes authentication, role-based access control, a Supabase-backed PostgreSQL database via Prisma, and admin tooling for managing sites and metrics.
This project was built to satisfy a demanding Next.js take-home assignment with emphasis on full-stack architecture, state management, navigation UX, persistence and production readiness.
- Live demo: https://nature-restorer.vercel.app/dashboard
- Browse all sites from
/dashboard. - Open detailed site pages at
/dashboard/[id]. - View biodiversity metrics and map context per site.
- Log in at
/login. - Access admin controls at
/dashboard/admin(admin role only). - Use sidebar profile dropdown actions (
Settings,Logout).
viewer- Can read sites and metrics.
admin- Can create, update, and delete sites and metrics.
- Can access
/dashboard/admin.
Protected API routes enforce this behavior server-side.
- Next.js 16 (App Router)
- React 19
- TypeScript 5
- TailwindCSS 4
- shadcn/ui primitives (including Tooltip and Dropdown Menu)
- Radix UI (
@radix-ui/react-tooltip,@radix-ui/react-dropdown-menu) - Prisma 7 +
@prisma/adapter-pg - Supabase PostgreSQL
- Mapbox GL (
react-map-gl) - Bun (package manager + scripts)
- Reusable
BrandLogocomponent insrc/shared/ui/brand-logo.tsx. - New leaf logo asset (
leaf-logo.svg) replaces the previous logo. - Custom Montserrat Alternates font integration.
- Login page includes demo-credentials tooltip and footer attribution.
src/app: App Router routes, layouts, API handlerssrc/pages-layer: Route-level page compositionsrc/widgets: Composite UI blocks (sidebar, map, grids)src/features: Interaction logicsrc/entities: Domain models/services (sites, metrics)src/shared: Shared UI, utilities, assetsprisma: Prisma schema/config/seed
Feature-Sliced Design (FSD) is just a way to keep files organized by responsibility, so the app stays easy to grow.
Think of it like building a house:
shared= common tools/materials used everywhere (buttons, helpers, logo, auth utils)entities= core business objects (in this app:Site,SiteMetrics, and their data services)features= user actions and behavior (for example selection state)widgets= bigger UI blocks made from entities/features (sidebar, map, site grid)pages-layer= how a full page is assembled from widgets/features/entitiesapp= Next.js routing, layouts, and API endpoints (the entry points)
- User opens
/dashboard. approute/layout checks session and role.- Page calls
entitiesservices to fetch sites/metrics. entitiesservices call protectedapp/apiroutes.- API routes use
sharedauth guards (requireAuth,requireAdmin). - API routes read/write data through Prisma (
shared/lib/prisma.ts) into Supabase Postgres. - Data returns to page components.
widgetsrender the UI (sidebar, cards, map) andfeatureshandle interactions.
This separation helps because:
- UI changes usually stay in
widgets/pages-layer. - Business/data changes usually stay in
entities+app/api. - Shared code stays reusable in
shared. - Auth/RBAC stays centralized and consistent.
Auth:
POST /api/auth/loginPOST /api/auth/logout
Sites:
GET /api/sites(authenticated)POST /api/sites(admin)GET /api/sites/:id(authenticated)PUT /api/sites/:id(admin)DELETE /api/sites/:id(admin)GET /api/sites/:id/metrics(authenticated)PUT /api/sites/:id/metrics(admin)
Prisma schema defines:
User(email,username,passwordHash,role)SiteSiteMetrics
RBAC roles are stored as Prisma enum UserRole (ADMIN, VIEWER).
Use .env.local (or .env.development) with:
NEXT_PUBLIC_SITE_URL="http://localhost:3000"
NEXT_PUBLIC_MAPBOX_TOKEN="your_mapbox_token"
# Runtime DB connection (used by app server / Prisma adapter)
DATABASE_URL="postgresql://..."
# Optional direct DB URL for Prisma CLI operations (db push, migrations)
DIRECT_URL="postgresql://..."
# Seed credentials override (optional)
SEED_ADMIN_PASSWORD="Admin123!"
SEED_VIEWER_PASSWORD="Viewer123!"
# Session signing secret
AUTH_SECRET="your_random_secret"bun install
bun run prisma:generate
bun run prisma:push
bun run prisma:seed
bun devOpen: http://localhost:3000
If seeded with defaults:
-
username: admin -
password: Admin123! -
username: viewer -
password: Viewer123!
bun devbun buildbun startbun lintbun run prisma:generatebun run prisma:pushbun run prisma:seedbun run prisma:studio