Bluemap Citadel
Property marketplace and managed-rentals platform for Nigeria

Overview
A full-stack Next.js platform for the Nigerian property market covering listings and sales, a managed-rentals module (units, applications, leases, recurring rent payments), Paystack-powered escrow and wallets, a referral-rewards program, and KYC. It unifies buying, renting, and property management behind a fine-grained, role-gated admin panel.
My role
Full-stack developer, solo. Scope spans the entire stack: Prisma/Postgres data modelling (~40 models), Next.js App Router UI and API route handlers, NextAuth auth, a 12-role RBAC + admin panel, the wallet/escrow/withdrawals money layer, the rentals module, Paystack + Cloudinary + Mapbox + Resend + web-push integrations, migrations, and scheduled cron jobs.
The problem
Nigeria's property market is fragmented and low-trust: buyers and renters lack a single, verifiable place to transact, informal rentals have no structured application/lease/payment trail, and payments carry counterparty risk. There was no unified platform tying together listings, identity verification, escrowed payments, and managed rentals for tenants, landlords, agents, and operators.
The solution
A single Next.js platform that unifies listing/sale discovery with a full managed-rentals pipeline (unit → application with document review → generated lease → recurring rent payments) and a money layer built on Paystack. Payments flow through escrow, wallets, and 24-hour-hold withdrawals; a referral program credits referrers 5% of base rent on a tenant's first lease payment. Everything sensitive sits behind a fine-grained, role-gated admin panel with audit logging.
Key features
- Property listings & sales with a multi-step listing wizard (location, features, amenities, media, documents, pricing) and a DRAFT→REVIEW→PUBLISHED moderation workflow with per-listing audit trail.
- Managed rentals module: rentable units, tenant applications with document upload/review, admin lease generation, and recurring rent payments (online via Paystack or manual proof-of-payment), plus cron-driven payment generation and overdue marking.
- Wallet & payments: BigInt-kobo balances, transaction ledger, card/bank payment methods, and withdrawals with a 24h hold settled via Paystack Transfers and confirmed by webhook.
- Referral-rewards program: one idempotent source of truth credits a referrer 5% of a unit's base rent to their wallet on the referred tenant's first payment / lease activation.
- Paystack escrow with disputes and admin resolution (release, refund, partial refund), plus KYC identity verification with an admin approval workflow.
- Role-based admin panel: 12+ roles and permission keys enforced in three layers (layout gate, sidebar visibility, per-page/per-route permission checks), with audit logs; realtime messaging (Socket.IO) and in-app/email/push notifications.
Architecture & engineering
Next.js 15 App Router with route groups (landing, auth, dashboard, admin, api). Auth resolved in server components via NextAuth; RBAC enforced in depth: a coarse admin-layout redirect, sidebar visibility, and the real gate of requireAdminPage(...) (pages) / requireAdmin(...) (API routes) checked against flattened permission keys. Money is modelled as BigInt kobo end-to-end, serialised to strings in JSON and rendered via Number(x)/100 + a naira formatter to avoid precision loss. Prisma migrations follow a strict fix-forward, idempotent discipline against a Supabase Postgres, and the build runs prisma migrate deploy before next build. Three cron jobs (generate rent payments, mark overdue, process withdrawals) plus a Paystack webhook drive the async money flows; Cloudinary handles signed media/document uploads; next.config.ts sets a full security-header set (HSTS, X-Frame-Options, Permissions-Policy, etc.).
The hardest problem
Two documented, real production issues. (1) Referral-reward crediting had been copy-pasted across two call sites (online-payment verify and admin manual lease activation) and drifted: different rounding, and the admin path lacked an idempotency guard while running inside the activation transaction, so a duplicate credit would roll back the entire lease activation. It was centralized into one idempotent creditReferralReward() helper. (2) A migration recreated the Unit/RentalApplication/Lease tables with pre-refactor column names after a prior refactor, and because it sorted later by timestamp it silently reverted the refactor on every DB in order; production's Unit ended up with priceKobo instead of baseRentKobo, causing a runtime error. Fixed forward with an idempotent reconciliation migration and a rule to always diff migrations against the real DB after structural changes.
Key learnings
Money-mutating logic belongs in a single idempotent source of truth; copy-pasted crediting code drifts and breaks under concurrency/retries. Migration discipline matters as much as schema design: recreating tables in a later-timestamped migration can silently undo earlier refactors, so always diff migrations against the real database. And RBAC must be enforced per-page and per-route on the server; hiding sidebar links is visibility, not access control.
Want to see it in action?
Explore the live product, or reach out to talk through the build.