Soft Beans Palace
A Next.js ordering flow for a Port Harcourt food business — meal configuration, persisted cart, and checkout that hands off to WhatsApp.
- 3
- Menu categories
- 0
- Backend services
- 1
- Source of truth for price
- 100%
- Type-checked forms
- Role
- Designer & engineer
- Year
- 2026
- Status
- Live
- Type
- Ordering experience
01Overview
Soft Beans Palace is a food business in Port Harcourt. This is its storefront: customers browse the menu, configure meals with sides and proteins, review the order and send it over WhatsApp.
It is the smallest project here and the one with the tightest constraints. No payment gateway, no backend, no accounts — the business confirms payment manually from a transfer receipt.
That made it purely an interface problem, which is why I like it. Everything the product does is in how it feels to use.
02The problem
The business already took orders on WhatsApp. Replacing that would have meant asking the owner to learn a dashboard and asking customers to trust a payment form. Both were the wrong trade.
So the site had to do the hard part — browsing, configuring, pricing, capturing details — and then hand a clean, complete, unambiguous order into the channel that already worked.
The configuration itself is deceptively awkward. A meal is a base dish plus any number of sides and proteins, each with its own quantity and its own instructions, and it has to stay legible on a phone.
03Architecture
- 01
Next.js App Router
TypeScript throughout, Tailwind for styling, shadcn/ui primitives on Base UI, Motion for animation that has a reason to exist, and Lucide for icons.
- 02
Cart as persisted client state
Zustand with localStorage persistence. A customer can configure four meals, close the tab, and come back to them.
- 03
Focused configuration picker
Sides and proteins open in a bottom sheet on mobile and a dialog on desktop — the same component, the right shape for the device. Each addition carries its own quantity.
- 04
Single source of truth for the menu
Dishes, descriptions, categories and numeric prices live in one data file. Prices flow to the menu, detail page, cart, checkout and the WhatsApp message through one currency formatter. No component ever hardcodes a formatted price.
- 05
Validated checkout
React Hook Form with Zod for customer, delivery and manual bank-transfer details, generating a unique reference per order — SBP-20260904-A82F7C — assigned once at submission.
- 06
Configuration over hardcoding
WhatsApp number, phone, socials, location, hours and bank details all read from environment variables through one site config, with Nigerian local-format numbers normalised into international format automatically.
04Key decisions
WhatsApp as the checkout target
Meeting the business and its customers where they already are beat introducing a payment gateway neither party had asked for. The site's job is to make the message perfect.
"Price on request" instead of invented prices
Prices were unconfirmed at build time, so the UI says so. Placeholder numbers on a live commerce site are a defect waiting for a customer to find.
One order reference, generated once
Generated at submission, not on render — so a re-render can never produce a second reference for the same order.
05Stack
- Next.js
- TypeScript
- Tailwind CSS
- shadcn/ui
- Zustand
- React Hook Form
- Zod
- Motion