Navy Palette (ThemeContext.tsx)
Deep (bgPage)
Mid (bgCard)
Raised
Border
Muted
Label
Gold Palette (ThemeContext.tsx)
Dark
Mid ★
Bright
Light
Surface (Light Mode)
bgPage
bgCard
bgRaised
border
Status — NO GREEN
- Success/Active/Confirmed/Paid/Verified → brandSurface (gold bg) + brandSurfaceText (#C98A1A). NEVER green.
- Error → errorBg / errorText #F87171
- Warning → warningBg / warningText #D97706
- Banned codes: #34D399 #6EE7B7 #10B981 #059669 #064E3B — all replaced with gold tokens
- Green only: WhatsApp brand icon/button where Meta requires it
Typography Scale (Both Products)
- Display: Playfair Display — headings, hero, splash
- Body: DM Sans 22/15/14/13/12/11/10px scale
- Amounts: JetBrains Mono — every ₦ figure, no exceptions
- Primary button on gold bg: #0B1520 hardcoded (not a token)
Subscription Plans
- Free: 30-day strict trial. Paywall on day 30. Cron warnings: day 7/14/23/27/28/30
- Starter: ₦8,000/mo
- Pro: ₦15,000/mo
- Growth: ₦25,000/mo
- Enterprise: custom pricing, custom limits table, never expires
- Expired plan → TRIAL_EXPIRED 403 on all API calls
Revenue Model
- 5% commission — deducted silently from merchant settlement. Never shown to buyers or sellers on marketplace UI.
- Discovery Ads: Zerrar takes 10% of spend (Spark ₦10k / Blaze ₦15k / Inferno ₦30k)
- WhatsApp broadcast costs: Meta charges merchants directly — not Zerrar
- Paystack fee: 1.5% under ₦2,500; 1.5%+₦100 capped ₦2,000 above. chargeFeeTo controls who bears it.
Zerrar
Sell everywhere.
Manage one place.
Multi-Channel Commerce OS
Sell on every channel.
Manage from one place.
Orders from your storefront, social media, WhatsApp, and walk-in customers — all in one dashboard.
Welcome back
Sign in to your store
Today's Stats
This week ›Quick Actions
Recent Orders
All ›Orders
23 today · 3 pending · /orders?status=
Order Detail (Bottom Sheet)
- Customer name, phone, full address
- Items: qty, variant, unit price, subtotal
- Payment: gateway (Paystack/Monnify/Flutterwave/COD) + status
- Delivery: courier + tracking link + ETA
- Timeline stepper: placed → confirmed → shipped → delivered
- Actions: Confirm / Mark Shipped / Delivered / Cancel
- Open in WhatsApp button → prefilled message to customer
COD vs Paystack Flow
- COD: order confirmed, delivery booked, merchant marks delivered, merchant collects cash — no Paystack lock
- Paystack: 48h hold after "delivered" confirmation, then wallet balance released
- COD bypass: zerrar.com/checkout/success COD page skips Paystack verification
- Cancelled + Paystack: auto-refund initiated
Products
48 active · 3 low stock · GET /products
Customers
142 total · 18 new this month
📣 Ads & Campaigns
Broadcasts — Meta charges you directly. Discovery Ads — Zerrar takes 10% of spend. Meta Pixel + Google Merchant on all plans.
Discovery Plan
Upgrade ›Active Campaigns
+ NewTransactions
Export CSV ›📈 Profitability
Product Margins
Sort: Margin ↓Expenses
June 2026 · 11 items
Tax & Compliance
Financial year: Jan–Dec 2026
🖥️ POS
Delivery
8 active shipments
Team
4 members · 1 invite pending
Discover Nigerian
Vendors You Can Trust
Shop verified sellers across fashion, hair, beauty, kids, wellness & home.
Trending Products
View all ›Featured Vendors
Discovery Ads · Blaze tierCart Behaviour (zerrar_cart_v1)
- React Context + localStorage, versioned zerrar_cart_v1
- Max 50 items total, 99 per item (abuse prevention)
- Client-side price sanitization, server-side price re-validation at checkout
- CartDrawer shows items grouped by merchant (multi-vendor)
- CartButton in navbar with item count badge
- Commission deducted silently — never shown to buyer
Search Page (zerrar.com/search)
- Auto-search after 2 characters, 400ms debounce
- Tabbed results: Products / Vendors / All
- Filter by category (7 categories) and state (36 states)
- LocalBusiness + Product JSON-LD schema on vendor + product pages
- GA4: G-4JJ7BEF13Y tracking on all pages
Discovery Ad Placements
- Spark ₦10k/mo — Basic placement in category pages
- Blaze ₦15k/mo — Featured Vendors section on home + category pages
- Inferno ₦30k/mo — Premium top placement + Google Ads + priority
- All tiers: Meta Pixel, Google Merchant, analytics, WhatsApp broadcasts
- Zerrar takes 10% of ad spend as platform fee
Your Cart
3 items · 2 vendorsCheckout
Step 1 of 3Delivery Information
Order Summary (3 items)
Checkout
Step 2 of 3Delivery to Surulere, Lagos
Checkout
Step 3 of 3Choose Payment Method
Checkout Backend Flow (Real)
- POST /market/checkout/rates → Fez + Sendbox parallel → lowest + 20% buffer per merchant
- POST /market/checkout/create → creates MarketOrder in DB → Paystack initialize with split (95% merchant, 5% Zerrar) → returns auth URL
- Customer redirected to Paystack → pays → redirected to /checkout/success?reference=xxx
- GET /market/checkout/verify/:ref → verifies with Paystack → creates Order per merchant in DB → merchants notified
COD Flow
- COD selected → POST /market/checkout/create with paymentMethod: COD → order created without Paystack
- /checkout/success COD page bypasses Paystack verification entirely
- Merchant sees COD order in dashboard → must confirm
- Delivery booked → customer pays on arrival → merchant marks delivered → Zerrar commission deducted at settlement
- 3 missed COD settlements → account review triggered
Buyer Protection
- All prepaid orders covered by Zerrar Buyer Protection
- Item doesn't arrive or doesn't match description → Zerrar investigates → refund
- Dispute raised by buyer → merchant has 48h to respond
- Zerrar superadmin reviews dispute via dispute management tool
- Refund: Paystack reversal to original payment method
Order Confirmed!
Payment successful. Your order has been placed with 2 vendors and they've been notified.
Order Placed — ZR-2848-TUNDE
| Screen | Requirements | Code Notes / Issues | Tag |
|---|---|---|---|
| Splash | Auth token check. Route Dashboard or Login. Z-mark animation 400ms. | Token refresh silently here if expired. Dio 401 interceptor → refresh → retry. | Required |
| Onboarding | 3 slides: multi-channel OS / business intelligence / ads+discovery. Skip button. Progress dots. | Do NOT say "WhatsApp-native" — WhatsApp is one channel. Replace placeholder images before launch. | UX Fix |
| Login | Email + password. Biometric return. Google SSO. Forgot password → zerrar.com/reset. 5 attempts → 30-min lockout. httpOnly session cookie. | DM Sans 14px uniform on all form labels across all screens. currentStep starts at 1 (whatsappDone dropped from onboarding). | Required |
| Dashboard | Wallet balance + plan badge. Revenue Forecast (useDashboardStats.ts). Reorder Alert (GET /products/low-stock + margin). Best Day to Sell (client-side order timestamps). 8-item quick actions. Recent 5 orders with channel source. Stats: revenue/orders/net profit/AOV. | FCM badge on bell. Stats from GET /orders + GET /merchants/usage. All ₦ amounts: JetBrains Mono. | Required |
| Orders | All channels. Filter: All/New/Processing/Delivered/Cancelled. Source badge per card. FCM real-time. PATCH /orders/:id/status. COD vs Paystack flow differ. | FCM token must refresh on every foreground event and sync to backend immediately. | Required |
| Products | 2-column grid, uniform height:66 body. 5 filter tabs (All/Active/Low Stock/Draft/Dead Stock). Discount price over selling price. Cost price + margin shown. | Grid: 2 full columns on all screen widths. onInput handler REQUIRED alongside onChange on all TextFields (confirmed paste bug). | Fix |
| Add Product | Step 1: name/category/desc/selling/cost/discount/images/stock/SKU. Step 2: variants (max 3 option types, 100 combos). "Boost this product" CTA → /ads?boost=[id] (only if unitsSold > 0). | costPrice: Prisma schema field Decimal? @default(0). Route /products/profitability must appear BEFORE /products/:id in controller. | Fix |
| Customers | List sorted by LTV desc. Tier badges (New/Regular/VIP/Whale). Search. Profile: orders, spend, AOV, loyalty tier (Bronze/Silver/Gold, 100pt welcome), broadcast trigger, notes. | Auto location permission: do NOT request on launch — defer to feature trigger. LTV feeds broadcast audience segmentation. | UX Fix |
| Ads | Broadcasts (free-form, 3 categories: Utility/Marketing/Conversational, Meta charges merchant directly) + Discovery (Spark/Blaze/Inferno). Wallet validation inline at Step 4. openCreateWithProduct method on useBroadcasts for Boost flow. | Wallet balance must be checked inline at budget step — disable launch if balance < budget. Broadcast modal: full-height draggable sheet. | Required |
| Wallet | Available vs pending (48h release). Subscription plan label (no per-order commission). Paystack fee transparent per txn (chargeFeeTo). All 3 gateways (Paystack/Monnify/Flutterwave) in feed. CSV export. | JetBrains Mono for ALL monetary amounts everywhere in the app — no exceptions. | Required |
| Profitability | GET /products/profitability?from=&to=. KPI: gross margin, net profit, COGS, dead stock count. No-cost-price warning if <20% products have costPrice. 5 intelligence features. Margin table. Boost CTA (unitsSold > 0). | CRITICAL: route /products/profitability must be BEFORE /products/:id in products.controller.ts — otherwise intercepted as :id lookup. | Fix |
| Expenses | P&L summary (expenses/revenue/net profit). Category filter (Overhead/Operations/Staff/Marketing/Equipment/COGS/Other). Itemised list, recurring flag. Add form: name/amount/category/date/photo/recurring/notes. | Deferred import (lazy load). Net profit = revenue − COGS − expenses — stays in sync with Profitability page calculation. | Important |
| Tax | VAT 7.5% summary, filing due date alert, company reg tab (CAC/RC/TIN/NIN pulled from profile — do NOT re-ask). CSV export. Tax Filing Service: select TIN/VAT → emails accounting@zerrar.com + hello@zerrar.com + merchant. | Pull business name, CAC, address, phone, TIN from existing merchant profile. Only ask for NIN and desired service. | Important |
| POS | 3-column grid, search, offline cache 24h, cart, checkout (discount → payment → receipt via WhatsApp/Bluetooth/skip). Cashier role = POS only. Offline badge when disconnected. | Product list cached locally 24h. Sales queued, synced on reconnect. Critical for unreliable Nigerian network. | Required |
| Delivery | Summary (active/delivered/costs). Fez primary. Sendbox fallback (only if Fez unavailable or >15% higher). Parallel quote, 20% buffer. Waybill + tracking link sent to customer. Webhook status sync. | COD "Delivered" triggers separate payment collection flow. Failed pickup → retry notification to merchant. | Required |
| Team | 4 roles: Owner/Admin/Staff/Cashier. Consistent badge colours all screens: gold=owner, blue=admin, amber=staff, purple=cashier. Invite via email+phone. Pending invites with resend/cancel. | Role badge sizes and fonts pixel-identical across all screens — inconsistent per testing feedback. | UX Fix |
| Settings | Store info, custom domain, storefront theme. Paystack/Monnify/Flutterwave toggles. chargeFeeTo (merchant/customer). Shipping settings. Notification toggles. Plan + trial days. API & Webhooks. Security/PIN. Sign out. | WebView: any settings that open web links must have back button + loading indicator. | UX Fix |
| Market Home (zerrar.com) | Hero search, 7 category grid, trending products, featured vendors (Discovery ad placement), blog teaser. Multi-vendor cart (zerrar_cart_v1 localStorage). Commission invisible to buyer. | GA4: G-4JJ7BEF13Y on all pages. Sanity CMS blog (project xstg9hlt, useCdn: false). Next.js + Cloudflare Pages. | Required |
| Vendor Profile (zerrar.com/vendors/[slug]) | LocalBusiness JSON-LD schema. Gold Verified Badge. Product grid, About tab, Reviews tab. WhatsApp vendor button. Stats: products, orders, rating. Internal /vendors/slug paths (not external storefront links). | Route: internal zerrar.com/vendors/[slug] — NOT shop.zerrar.com (slug.zerrar.com = Cloudflare Worker storefront, separate product). | Required |
| Product Detail (zerrar.com/vendors/[slug]/[productId]) | Product JSON-LD schema. Image gallery. Variant selector. Add to Cart (multi-vendor). "Ask Vendor on WhatsApp" button. Discount price over selling price. Related products from same vendor. | Client-side price sanitization. Server-side price re-validation at checkout. Max 99 per item, 50 total in cart. | Required |
| Checkout Step 1 (Address) | Name, phone, full address, state, LGA from ng_lgas_with_areas.ts. No account required. Server-side price re-validation before proceeding to rates. | ng_lgas_with_areas.ts constants file at src/constants/. Cloudflare edge runtime declarations on dynamic pages. | Required |
| Checkout Step 2 (Shipping Rates) | POST /market/checkout/rates → Fez + Sendbox parallel → lowest + 20% buffer per merchant. Grand total shown. Rate cards selectable. | Sendbox: returns data.rate (singular) not data.rates (array). No status field. Floor rates by zone to prevent ₦1,996 Kano anomalies. | Required |
| Checkout Step 3 (Payment) | POST /market/checkout/create → Paystack split (95% merchant / 5% Zerrar) → auth URL OR COD creation. Grand total confirmed. Fee shown if chargeFeeTo = customer. | Paystack fee: 1.5% under ₦2,500; 1.5%+₦100 capped ₦2,000 above. percentage_charge: 5, settlementPercentage: 95.0. | Required |
| Order Success (Paystack) | GET /market/checkout/verify/:ref. Payment confirmed → gold check. Order ref, summary, WhatsApp vendor link, continue shopping. | Redirect from Paystack: /checkout/success?reference=xxx. Verify immediately on page load. | Required |
| Order Success (COD) | COD bypass — no Paystack verification. Warm amber warning banner (pay on delivery). Order ref, delivery ETA, WhatsApp vendor link. | COD success page skips verify entirely. Merchant receives COD order notification. 3 missed COD settlements → account review. | Required |
| Order Tracking | Timeline: placed → confirmed → shipped → out for delivery → delivered. Gold dots = done. Amber = current. Fez/Sendbox webhook drives status updates. | Webhook: x-ship-signature HMAC SHA512 verification. Event types: placed, confirmed, picked_up, in_transit, delivered, failed. | Required |
| Notifications Drawer | Bell icon badge on Dashboard. Bottom sheet overlay (NOT separate screen). Types: New Order, Payment Received, Low Stock, Campaign, Delivery Status. Deep-links per notification. | Bottom sheet overlay — merchant stays in context. Do not route to separate screen. | New |
Outstanding Fixes (from Testing Feedback)
- ✅ Font consistency — DM Sans 14px uniform all form labels
- ✅ Grid tiles — 2 full columns all screen widths
- ✅ Modals — full-height draggable bottom sheets
- ✅ WebView — back button + loading indicator
- ✅ Auto location — defer to feature trigger only
- ✅ Onboarding images — replace placeholders
- ✅ TextField paste — onInput handler alongside onChange
- ✅ POS offline — 24h product cache + sync on reconnect
- ✅ Code splitting — deferred imports: Expenses/Tax/POS/Profitability
- ✅ FCM token — refresh on foreground event, sync backend
- ✅ Profitability route — /products/profitability BEFORE /products/:id
- ✅ No green — all success/active/confirmed states use gold tokens
- ✅ Positioning — "multi-channel commerce OS", not "WhatsApp-native"
- ✅ Role badge consistency — gold/blue/amber/purple, pixel-identical all screens
Performance Targets
- Cold start to Dashboard: under 2.5s
- POS screen load: under 1s (deferred import)
- Expenses/Tax/Profitability: under 800ms (code split)
- All API calls: 15s timeout + retry once on network error
- Image upload: compress to max 800px before upload
- Animations: all under 300ms, Curves.easeOutCubic
- APK size target: under 25MB
- POS offline cache: valid 24h
- Market page: Core Web Vitals green (Next.js + Cloudflare)
- zerrar.com/blog: useCdn: false (confirmed fix — never revert)
Key Backend Endpoints (Real)
- GET /merchants/profile · GET /merchants/usage
- GET /products · GET /products/low-stock · GET /products/profitability
- GET/PATCH /orders · PATCH /orders/:id/status
- GET /ads/init · POST /broadcasts · GET /broadcasts/init
- GET /merchants/settings (bundle: profile + shipping + notifications)
- POST /market/checkout/rates · /create · GET /market/checkout/verify/:ref
- POST /auth/login · /logout · /refresh
- POST /super-admin/marketers · GET /super-admin/marketers/my/
- POST /logistics/fez/webhook (unguarded, HMAC verify)
- All super-admin routes: /super-admin/ prefix (never /superadmin/)