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.
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



