Hazrat Ali
Web & ecommerce2024live

Bogura Theke — bilingual yogurt storefront for Dhaka same-day delivery

A bilingual (Bangla + English) ecommerce storefront for a Bogura yogurt shop selling clay-pot doi into Dhaka. OTP phone login, six BD payment rails, conditional cash-on-delivery, and a Dhaka-metro-only delivery zone keyed off a pre-loaded postal graph.

Role
Solo — design, full-stack Next.js, Express API, admin panel, BD payments
Year
2024

Problem

Bogura's doi — sweet yogurt set in unglazed clay pots — is a regional product with a cult buyer base in Dhaka, four hours away by road. The shop already had the product and the brand. What it didn't have was a way to take orders that wasn't a Facebook page and a bKash number. Every order was a screenshot. Inventory was a notebook. And the product itself is the hard part: clay-pot dairy is perishable, fragile, and only worth shipping into addresses that a same-day courier can actually reach. A generic Shopify dupe wasn't going to work — the storefront had to encode the delivery zone, the payment habits, and the language of the buyer or it wasn't worth shipping.

Approach

Three apps, one stack: a Next.js 14 customer storefront, a separate Next.js admin panel, and an Express + TypeScript API on top of MongoDB. The split is deliberate — the customer site is public and SEO-served, the admin runs behind Firebase auth and isn't indexed at all, and the API is the single source of truth for both. Customer site is bilingual (Bangla / English) end-to-end with a cookie-driven toggle in the nav. Bootstrap 5 + SCSS for the storefront because the client wanted a brand palette they could tweak through a single variables file without touching components — portfolio-me would pick Tailwind, client-me picks the tool that survives handoff.

The shape of the build is intentionally boring everywhere except three places where Bangladesh-market ecommerce actually breaks: the delivery zone, the payment options, and how trust gets earned with a first-time COD buyer.

Technical deep-dive

  • Bilingual at the data layer. Product documents carry name.en / name.bn, description.en / description.en, and so on. The catalog endpoint resolves the field matching the active locale on the server, so the client never ships both strings down the wire. UI copy goes through a <Language data={{ en, bn }} /> component that reads from a Redux locale slice. Toggling language flips a cookie and re-resolves — same URLs, same product IDs, two languages.
  • Dhaka-metro delivery zone, baked into the address picker. Yogurt only ships inside Dhaka Metropolitan. Rather than freeform an address that ops would have to triage, the checkout loads a pre-built JSON graph of every postal area inside the metro — adabor, banani, bashundhara, mirpur, every entry tagged with its Bangla name and postal code — and the area dropdown is constrained to that list. If your address isn't in the graph, you can't check out. That's the feature, not a bug: it pushes refusals out of the courier loop and into the cart.
  • OTP-only login. Almost every buyer enters on mobile and doesn't want another password. Login is phone number → 6-digit code with a 5-minute expiry. Tokens are issued as cookies on verify, the storefront accepts the code through react-otp-input, and the API rate-limits per phone + IP. Unauthenticated checkout is also supported — there's an explicit "Checkout as Guest" path on the account step — because forcing an account on first-time yogurt buyers loses sales.
  • Six payment rails, picked at the payment step. The payments route handles bKash, Nagad, Rocket, Upay, bank transfer, and a generic "other" rail for offline reconciliation. Each method is its own controller — the bKash flow takes a transaction ID, the bank flow takes a deposit slip reference, the COD flow just records intent. Method selection is dynamic at the cart level: the payment step renders only the rails enabled for that specific order based on the customer's COD eligibility.
  • Conditional cash-on-delivery. First-time buyers can't pick full COD. The order document carries an eligable_cash_on_delivery boolean evaluated server-side from the customer's order history — refused parcels and returns lock the flag down, clean deliveries unlock it. The payment page reads the flag and filters the COD option in or out of the rails it shows. Same anti-fraud logic the shopkeeper would already apply at the counter, just compiled into the checkout state machine.
  • Three-app split with one source of truth. The admin panel (Next.js 14 + Tailwind + Firebase auth) is a separate deploy that hits the same Express API. Mongoose models are shared via the API — admin doesn't touch Mongo directly. Order detail in admin shows the per-customer history, the delivery outcomes, and the COD eligibility flag inline, so ops can see why a buyer is locked to prepay and decide whether to override.

Outcome

Shipped as the client's first real storefront — moved order-taking off Facebook DMs onto a real cart with real receipts. The Dhaka-metro-only address picker collapsed the courier-refusal rate by design: out-of-zone buyers can't get to the payment step, so the courier never round-trips a clay-pot order to an unreachable address. The conditional-COD pattern that later became the spine of Bismillah Agro shipped here first.

Stack & handoff

Next.js 14 storefront (Bootstrap 5 + SCSS, Redux Toolkit), Next.js 14 admin panel (Tailwind, Firebase auth), Express + TypeScript API on Node, MongoDB with Mongoose, payment integrations for bKash / Nagad / Rocket / Upay / bank transfer / COD. Three deploys, one API, one schema — the admin team operates the shop without ever touching the customer codebase.

Stack

Next.js 14ReactJavaScriptRedux ToolkitBootstrap 5SCSSExpressTypeScriptMongoDBMongooseFirebasebKashNagadRocket