Ecommerce Template
Modern ecommerce template built with Next.js 16, React 19, Drizzle, Better Auth, Supabase, and Stripe.
SAAHAA — one circle for every service and every shop in your neighbourhood. Pros keep 100% of what they quote. Shops pay 3–5%, not 25%.
CommerceJavaScript · HTMLGitHub @OhhksOnlineOwner unconfirmed
Have you tried it? One tap is enough. No sign-in needed.
Hyperlocal services + retail marketplace. Pros keep 100% of their quote; the platform charges 8% on top. Verified partners, escrow, sealed-rate auctions, real maps, single-owner admin. Vanilla ES modules, no build step.
Taken from the repository README
Loading the full README. The saved excerpt is shown below.
A plumber in Kukatpally has no digital existence — he cannot build or maintain
a website, and the apps that would list him take 25% of his price. A kirana
owner cannot list her products without a developer, and quick-commerce apps
take more than her whole margin. Customers pay padded prices to strangers
with no way to verify them.Read the full README ↗A plumber in Kukatpally has no digital existence — he cannot build or maintain a website, and the apps that would list him take 25% of his price. A kirana owner cannot list her products without a developer, and quick-commerce apps take more than her whole margin. Customers pay padded prices to strangers with no way to verify them. SAAHAA gives local professionals and shops a professional digital presence they never build or maintain, lets them keep 100% of their quote (the platform's 8% is laid on top and paid by the customer), verifies every pro mechanically — and promotes them on the network's own evidence, not the owner's phone calls — holds the money until the work is confirmed, and runs itself for a single owner. Everyone pays SAAHAA and SAAHAA pays everyone: every rupee enters and leaves through one door, everyone has a wallet, and the company's own position is read from the books. Full write-up: docs/PROBLEM.md.
python tools/serve.py 8772
(tools/serve.py sends Cache-Control: no-cache; a plain http.server lets the
browser keep stale modules after an update.)
| App | http://localhost:8772 |
| Self-tests | http://localhost:8772/?selftest=1 |
| Owner console | http://localhost:8772/admin.html |
| Neighbourhood (map) | http://localhost:8772/#/nearby |
| Example roster (local only) | http://localhost:8772/?demo=1 |
| Single-file build | python tools/build.py → dist/saahaa.html |
The console is its own page, not a route in the app: typing #/admin in the
customer app goes nowhere on purpose, so there is no door for a customer to
find. tools/build.py --site emits dist/admin.html beside dist/index.html.
A new install starts empty. No customers, no pros, no shops, no products —
the first accounts on a device are the ones somebody signs up. ?demo=1 loads
an example roster and turns the market simulation on for that page load
only, for local walkthroughs and for the deck capture
(python tools/shots.py, which opens ?shot=<scene>&demo=1). Nothing about
it reaches a real device without that query string — and a device that was
once opened with it drops the example roster at its next normal boot
(src/domain/fresh.js, audited as data.demoPurged). The owner can also
wipe a device on purpose: Admin → System & audit → Fresh start — type
FRESH, re-enter the password; a snapshot is taken first, the credential and
the dials are kept, and the counts are audited.
Every account carries an ID: C20262001 for a customer, P20262001 for
a pro, S20262001 for a shop owner — what the account is for, the year it
was opened, then a sequence. It is a name, not a password: it identifies, the
password authenticates.
One person may hold more than one account on the same number. A pro who also
buys groceries has a C… and a P…, each with its own password, wallet and
history. What they cannot have is two accounts of the same kind. Sign-in takes
either a mobile number or an ID; a number holding two accounts asks which one.
Accounts are the platform's, not the device's. Signing up asks the server for the code and the server keeps the account, so a customer who signs up on her phone appears in the owner console on a different machine, and the same number cannot open two accounts of one kind from two devices. Sign-in works from any device: the password is checked against the database (bcrypt), and the account is adopted onto that device the first time. What does not travel yet is history — orders, wallet and ledger are still per device.
Accounts that existed before IDs keep their internal key — every order, partner
row and ledger leg still points where it did — and were assigned an ID by
migration, oldest first. See src/domain/identity.js.
The admin username is siidhartha12. The password is the owner's own and
is not written down in this repository or in any document — what ships in
src/core/config.js (ADMIN_BOOTSTRAP) is a PBKDF2-SHA256 hash over a random
salt, 250,000 rounds, which is what a login is checked against.
To change it, either:
Admin → System & audit → Change admin password — writes a new hash into the device's state and takes effect immediately; or
rotate the shipped bootstrap for every fresh device:
node tools/admin-cred.mjs '<new password>'
and paste the printed {salt, hash, iterations} block into
ADMIN_BOOTSTRAP in src/core/config.js, bumping its version.
Login is rate-limited (backoff after 3 attempts, lockout after 5), the session
lives in sessionStorage with a 15-minute idle and 8-hour absolute cap, money
movements above ₹5,000 need step-up re-auth, and every attempt is written to
the audit log. See docs/SECURITY.md for the honest scope of
what client-side auth can and cannot do.
Nothing is stored in Cloudflare. Cloudflare runs two Workers and holds no records at all.
saahaa | the site. Static files out of dist/, plus a router (worker-site/index.js) that hands /api/* to the API. Holds no secret. |
saahaa-api | the only thing that may write. Holds SUPABASE_SERVICE_KEY. |
| Supabase Postgres | every record — accounts, orders, payments, the ledger, payouts. |
| the browser | a working copy, so the app renders and works offline. Not the source of truth. |
The two Workers are joined by a service binding, not a URL: /api/* goes
to saahaa-api over Cloudflare's internal RPC, so it never leaves their
network, never resolves DNS and never crosses an origin. That is why the site
needs no CORS and why its connect-src does not have to name another host.
Secrets are not bindings and do not appear in that panel — they are under
Settings → Variables and Secrets on saahaa-api. The service-role key is
there and only there: the browser never holds it, and neither does the site
Worker. That separation is what makes it safe to publish the owner console as
a static file.
An account is written by create_account() in Postgres, which allocates the
code under a row lock and stores a bcrypt hash — the password is never stored,
never logged, and never sent back. See supabase/schema.sql.
docs/design/mockup-2026-09-08/; the rules are in
docs/design/DESIGN-SPEC.md; the system itself is src/ui/tokens.css./vendor/leaflet — no
CDN, because the CSP allows scripts from self only and a tradesperson's
phone should not depend on a third party's uptime. Enrolment works anywhere
in the world: a partner or shop sets a place by search, by pin, or from the
device's own location.src/domain/settings.js.src/core/money.js is the only module allowed to do currency
arithmetic and the build lints for float maths outside it.auto with its evidence. The owner keeps a
kill switch and the dials (Admin → Approvals → Automation) and can still
approve or suspend by hand. Rules in full: docs/AUTOMATION.md.src/core/gateway.js is the only module that
touches a payment rail: collect() in, payout() out. Today it runs in
MODE = 'sim' — a sandbox UPI that moves no real money and says so on
every screen and on every ledger leg (via: 'upi-sim'). Wiring Razorpay
means replacing that one file (docs/PRODUCTION.md §3).| The problem, and the answer | docs/PROBLEM.md |
| How this is built | docs/WORKFLOW.md |
| Architecture and safe-update rules | docs/ARCHITECTURE.md |
| First-time setup | docs/SETUP.md |
| Go-live checklist | docs/LAUNCH.md |
| Step-by-step to production | docs/PRODUCTION-PROCESS.md |
| Deploying, in one command | docs/DEPLOY.md |
| What it costs to run (₹0) | docs/FREE-TIER.md |
| What runs itself | docs/AUTOMATION.md |
| When something breaks | docs/RUNBOOK.md |
| Release test report | docs/TEST-REPORT.md |
| Security scope | docs/SECURITY.md |
SAAHAA is built with an uncompromising commitment to clean engineering and production integrity:
We welcome PRs for local language translations, additional offline map optimizations, and edge-case testing. See docs/ARCHITECTURE.md and docs/WORKFLOW.md before submitting a PR.
| Deploys and rollback | docs/DEVOPS.md |