Home
Ashavid
Back to Portfolio
Built by AshaVidMarketplace / Local DiscoveryBuilt 2026

GOPLAZA

Roughly twenty thousand Iranian-owned businesses operate in Canada with no trusted Persian-language directory between them — and building one meant deciding, in code, what money is allowed to buy.

WebMobilepackages/coreSupabase
Two clients, one core: the shared package exists so both answer identically.
  1. Interface

    What the user touches

    • Next.js 16
    • Expo / React Native
    • Tailwind CSS 4
  2. Application

    Where the rules run

    • TypeScript
  3. Data

    What persists

    • Supabase
    • PostgreSQL
  4. Platform

    What it runs on

    • Stripe

A compact view of the venture

Venture
GOPLAZA
Category
Persian-first local directory
Stage
Live platform, web and mobile
Core challenge
Directory trust under a paid model
Our role
Product design and full-stack build
Focus areas
Shared core, claims and standing, Persian localisation

Why we started building this

There are roughly twenty thousand Iranian-owned businesses in Canada — lawyers, doctors, restaurants, advisers, shops, trades — and no single Persian-language reference that covers them. Discovery happens through Instagram posts, Telegram channels, and word of mouth, which works until you need someone outside your own network.

The project started as čārana and was rebranded to GOPLAZA in August 2026. The rebrand was a naming decision; the product question never moved. Listing businesses is the easy half of a directory. The hard half is keeping the data true and keeping the trust signals honest once there is a paid tier.

That second problem shaped the architecture more than anything else. If a business can pay for visibility, the platform has to be able to state — structurally, not in a policy page — that visibility is all it bought.

In a directory, the moment a paid placement can look like a trust signal, every unpaid signal on the page stops meaning anything.

What we built

A Turborepo monorepo running a Next.js 16 web app and an Expo mobile app against Supabase, with a shared core package that holds every rule both clients must answer identically.

  • Directory across four axes

    Businesses browsable by category, by city, by city-and-category, and through community channels — the ways people actually look for a local service.

  • Claim and correction flows

    Owners claim their listing through a reviewed process, and anyone can submit corrections or complaints, with an admin queue behind each.

  • Contribution standing

    A «اعتبار مشارکت» ladder that recognises people who keep listings accurate, with levels computed at read time rather than stored.

  • Owner loyalty, deliberately separated

    Tenure earns discounts and room, never a credibility badge. "Three years on Plaza" is publishable; "trusted business" is not.

  • Mobile app on the same core

    An Expo client sharing the entitlement, standing, and schema logic with the web app, so both answer identical questions identically.

  • Persian as the primary locale

    Persian digits, Iranian calendar handling, and Vazirmatn typography built into the core rather than patched at the view layer.

  • Operator tooling

    Nineteen admin surfaces covering claims, reports, reviews, corrections, listings, jobs, health checks, and data cleanup.

Why this needed to exist

  • A directory is only as valuable as its data is current, and business data decays constantly.

  • Once listings can be paid for, every visible signal becomes suspect unless the platform separates money from credibility by construction.

  • Two clients reading the same database will drift apart the moment either one implements a rule locally.

  • Stored derived state goes stale silently — an expired plan still reads as paid until something remembers to downgrade it.

  • Persian-first is not an English product with translated strings: digits, dates, and typography are all different systems.

How we framed the opportunity

Two principles ended up driving most of the technical decisions, and both came from failures we could see coming rather than ones we waited for.

The wall between money and trust

Paid features buy placement and capacity, never credibility. The loyalty module is written so that nothing it exports can be used as a trust signal, because the moment one paid signal looks earned, no unpaid signal is believable.

Derive, do not store

Standing level, plan entitlements, and owner tenure are all recomputed on read. A lapsed contributor loses their level the moment the window passes — no cron, no decay job, no stored verdict to go stale.

One core, two clients

A parity audit found the web app ordering listings randomly with a paid boost while mobile ordered by creation date: the same directory answering the same question two ways. The shared core package exists to make that class of bug impossible.

Late webhooks should cost nobody anything

Expiry is judged against the paid-through date rather than a status column, so a delayed Stripe callback neither over-charges the customer nor keeps a lapsed listing featured.

Where the existing options fell short

How discovery worked already

  • Instagram accounts and Telegram channels aggregating businesses informally.
  • General directories where Persian-language businesses are present but not findable as a community.
  • Word of mouth, which is high-trust but stops at the edge of your own network.

What none of them provided

  • Structured, current data an owner can claim and correct.
  • Coverage by city and category rather than by whoever posts most.
  • A trust model that survives the introduction of paid placement.
  • A Persian-first interface rather than an English one with Persian content inside it.

The decisions that shaped the product

Put every shared rule in one package

Entitlements, standing, schema, and formatting live in packages/core so web and mobile cannot answer the same question differently.

Compute state instead of storing it

Anything derived from time — levels, expiry, tenure — is calculated at read time, which removes an entire category of stale-data bugs.

Keep credibility unpurchasable

The paid tier is bounded to placement and capacity. Trust comes from claims, corrections, and contribution standing, none of which are for sale.

How it came together

  1. Phase 1

    Directory foundation

    Model businesses, categories, cities, and channels on Supabase, and ship the browsing surfaces across each axis.

  2. Phase 2

    Trust and data quality

    Build the claim, correction, complaint, review, and report flows with the admin queues that adjudicate them.

  3. Phase 3

    Shared core, mobile, and monetisation

    Extract the shared rules into packages/core, ship the Expo client against it, and add entitlements, standing, and loyalty with the money-trust separation enforced in code.

Where the product stands today

  • A live Persian-first directory browsable by category, city, city-and-category, and channel
  • Owner claim flow with corrections, complaints, reviews, and reports, each with an admin queue
  • A shared core package keeping web and mobile answers identical
  • An Expo mobile client running on that same core
  • Contribution standing and owner loyalty as separate systems, with tenure barred from acting as a trust signal
  • Read-time computation of standing level, plan entitlements, and tenure
  • Nineteen admin surfaces including health checks, logs, and data cleanup tooling

What building this taught us

  • In a paid directory, the trust model is the product decision that matters most.
  • Derived state should be computed on read; stored copies drift in whichever direction hurts.
  • Two clients without a shared rule package will disagree, and you will find out from users.
  • Persian-first means digits, calendars, and type — not a translation layer.
  • Write down why a boundary exists, or the next change quietly crosses it.

We build the products we would want to operate ourselves.

GOPLAZA is one of the ventures we design, build, and run in-house at AshaVid.