← All projects

Case study

Moringa: an online fair for Brazilian makers

A marketplace with two sides. Buyers find a piece, pay and follow the order until it arrives. Makers list pieces and ship orders from one panel. Every piece carries the story of who made it.

My role
Discovery, UX and UI, development, testing and deploy
When
October 2026
Stack
Next.js 16, React 19, TypeScript, PostgreSQL (Neon), Drizzle ORM, Auth.js, Stripe (test mode), Tailwind CSS, Ipê

The problem

People who make things by hand in Brazil, like ceramists, weavers and woodworkers, mostly sell through Instagram messages, WhatsApp and local fairs. It works, but it costs them time: the same questions again and again, photos sent one by one, and no clear view of who paid and what still needs to ship. Large marketplaces solve the logistics but hide the person behind the piece.

I wrote a product brief, two proto-personas (Dona Lúcia, a ceramist, and Marina, a buyer) and a backlog with acceptance criteria before designing any screen.

What I built

Piece page: a clay jar with price, stock, shipping, the maker story and reviews.
Piece page, with the story of who made it.
Checkout on a phone: the delivery form with name, email and a postal code field that fills in the address.
Checkout on a phone.
Maker panel in the dark theme: orders to ship, sales in 30 days and a weekly revenue chart.
Maker panel, dark theme.

Decisions worth explaining

The server never charges a price the buyer did not see

The bag keeps the price from the moment a piece was added. When makers became able to edit prices, that could leave an old price on the pay button. Now the bag updates itself and says what changed, and the server refuses to create a payment if the prices differ.

Forms check on submit, then while typing, never on blur

An error that appears when a field loses focus pushes the button down while the person is clicking it, and the click misses. Validating on submit and then on each change, as GOV.UK recommends, avoids that.

Rate limits that hold on serverless

Sign-in, checkout and order lookup have limits stored in Postgres, so every server instance shares the same count. The IP is saved only as a salted hash, following the LGPD principle of collecting as little as possible. If the store fails, the request goes through: a database hiccup should not lock everyone out.

A strict Content Security Policy

Each request gets a new nonce, and only scripts carrying it, or loaded by them, can run. With the other headers, the live site scores A+ on Mozilla Observatory.

Quality, measured

Measured on the live site and on every pull request. Last checked on October 22, 2026.

AreaResultHow it is checked
PageSpeed Insights100 · 100 · 100 · 100Performance, accessibility, best practices and SEO, mobile and desktop; Lighthouse CI on every pull request
Accessibility0 axe violations · WCAG 2.2 AAaxe on every page in both themes, keyboard and focus tests in Playwright
Security headersMozilla Observatory A+Scanner run on the live site
Vulnerability scanOWASP ZAP: 0 high, 0 medium, 0 lowBaseline scan on every pull request
Tests179 unit and integration · 158 end-to-endVitest, PGlite (real Postgres in memory), Playwright on mobile and desktop

The QA cycle

After the build, I switched hats and tested Moringa as a QA analyst would, following a test plan. All 15 Must and Should stories have test cases: 58 in total, 49 automated and run on every pull request.

An exploratory session found BUG-001, rated High: after a maker edited a price, the bag and the pay button could still show the old one. It was fixed with regression tests at three levels, unit, integration and end-to-end.

The same cycle made the pipeline faster: one build is now shared by the end-to-end, Lighthouse and ZAP jobs, and the end-to-end suite runs in two parallel shards. Test summary report.

What I learned

  • Asking "why does this trust that number?" led from a harmless tampering test to a real bug.
  • A new feature can make an old assumption false. New stories now get a short "what does this make untrue?" check.
  • Writing down false alarms saves the next person the same hour of investigation.

Try it

No sign-up needed: each demo account is one click. Pay with the Stripe test card 4242 4242 4242 4242, any future date and any CVC. Open Moringa.