All projects

CMSAntino Labs

Content Management Dashboard

Admin portal that took NeoZAP's app content out of the database and away from developers. Banners, offers, FAQs, notifications and app config, edited by the teams that own them.

Client work under NDA
Role
Full Stack Developer
Team
NeoFinity product team
Timeline
Status
Completed
ops.neozap.in/offers
NeoZAP Ops offers console showing live offer count, claims today and redemption rate, a table of offers with category, value, claim progress and status, a new offer draft form, and a spin wheel payout panel

UI mockup from the original design, not an exact replica. The shipped interface is client IP and cannot be shown.

Ops creates the offer, the app shows it1 / 4

01

The problem

Changing anything in NeoZAP's app meant asking a developer. Banners, offers, FAQs, notification copy: all of it lived in the database, and updating it meant someone writing SQL by hand against production. Marketing could not run a campaign without pulling an engineer off whatever they were doing, and every hand-written query was a chance to get it wrong.

02

The approach

A React admin portal over REST APIs, with the app reading content at runtime instead of a developer editing rows. Each content type gets a schema-driven form so adding a type does not mean building another screen, and access is scoped by team: marketing owns offers and banners, the content team owns FAQs and app copy.

Overview

5
content types, one portal
0
SQL statements to change a banner
10
technologies

03

Key decisions

Content served from an API at runtime

over A developer updating rows directly

Every copy change was a hand-written SQL statement against production, which meant an engineer was involved in work that was not engineering, and a typo landed straight on live data. Moving content behind an API and a form took both problems away at once.

Schema-driven dynamic forms

over One hand-built form per content type

Five content types with different shapes would have been five screens to maintain, drifting apart as fields were added to one and not the others. Driving react-hook-form from a schema meant a new content type is a schema entry rather than a new screen.

Access scoped by team

over One admin role with access to everything

Marketing owns offers and banners; the content team owns FAQs and app copy. Scoping the portal to match meant each team could work without stepping on the other, and nobody had to be careful around sections that were not theirs.

PrimeReact for the table and form layer

over Building the data grid from scratch

The portal is a list view and a long validated form repeated across five content types. Using a component library for that left the effort on the content schemas and the access rules, which is where the actual domain was.

04

What I built

Content CRUD for five types

React · PrimeReact

Banners, offers, FAQs, notifications and app config all managed from one portal.

Schema-driven dynamic forms

react-hook-form · Validation

New content types need a schema entry, not a new screen, with validation declared alongside the fields.

Content APIs

Node.js · Express · PostgreSQL · Sequelize

The app reads live content at runtime, so updating a banner is a form submission rather than a SQL statement.

Team-scoped access

React · Express middleware

Marketing sees offers and banners, the content team sees FAQs and app copy, each editing only their own sections.

05

The outcome

Marketing and the content team manage their own sections without an engineer in the loop, and nobody writes SQL against production to change a banner any more. Adding a new content type became a schema entry rather than a new screen.

Stack

  • React
  • TypeScript
  • PrimeReact
  • react-hook-form
  • Validation
  • Node.js
  • Express
  • REST API
  • PostgreSQL
  • Sequelize