Ecommerce Template
Modern ecommerce template built with Next.js 16, React 19, Drizzle, Better Auth, Supabase, and Stripe.
Stop juggling separate distributor portals. Blended pricing and live stock across distributors, sub-second spec search, and instant BOM quoting — built for commercial and industrial solar/battery inst
CommerceTypeScript · PythonGitHub @Jay244521OnlineOwner unconfirmed
Have you tried it? One tap is enough. No sign-in needed.
No updates detected yet. GitHub releases will appear here.
Owners of public repositories under a personal GitHub account can sign in to verify ownership and manage the introduction themselves. No administrator approval is needed.
Sign in, generate a verification file, and commit it to this repository’s default branch within 30 minutes. Files on other branches or forks do not count. Then check the file below to manage the project without administrator approval. Keep the file to retain your access.
If needed, you will go to sign-in. After signing in, return here and press your chosen verification button again.
Run /nomorevibe verify in your project folder to verify ownership and manage the introduction yourself.
Verify in one minuteAsk about other verification options
Stop juggling separate distributor portals. Blended pricing and live stock across distributors, sub-second spec search, and instant BOM quoting — built for commercial and industrial solar/battery installers and EPC firms running multiple projects at once.
Taken from the repository README
Loading the full README. The saved excerpt is shown below.
Phase 1 scaffold: B2B procurement search, BOM quoting, and order routing for
solar/battery/EPC contractors, per the platform spec (v2).
Run the migration against a Postgres 15+ instance (Supabase or otherwise):
0001_init.sql creates distributors, contractors, products (JSONB
specs column with a GIN index for sub-second attribute filtering),
boms/bom_lines, orders/order_lines, and purchase_orders (one row per
distributor per order, supporting split fulfillment across a single BOM).Read the full README ↗Phase 1 scaffold: B2B procurement search, BOM quoting, and order routing for solar/battery/EPC contractors, per the platform spec (v2).
db/migrations/ Postgres schema (Supabase-flavored)
ingestion/ Python pipeline that normalizes distributor feeds into products
web/ Next.js app: spec search, BOM upload, checkout, Stripe webhook
Run the migration against a Postgres 15+ instance (Supabase or otherwise):
psql "$DATABASE_URL" -f db/migrations/0001_init.sql
psql "$DATABASE_URL" -f db/migrations/0002_auth_rls.sql
0001_init.sql creates distributors, contractors, products (JSONB
specs column with a GIN index for sub-second attribute filtering),
boms/bom_lines, orders/order_lines, and purchase_orders (one row per
distributor per order, supporting split fulfillment across a single BOM).
0002_auth_rls.sql links contractor_users to Supabase Auth
(auth_user_id) and turns on Row Level Security everywhere: a contractor can
only ever see its own boms/orders/purchase_orders, and — deliberately —
no policy grants contractors read access to distributors at all, so a
direct query can't defeat the blended-pricing display and reveal which
distributor fulfilled what (see Anti-Disintermediation Mechanics). Today all
of this is defense-in-depth: the web app's own routes already enforce
tenant scoping in application code (see below) and talk to Postgres with the
service-role key for anything that legitimately spans contractors, so
nothing currently relies on RLS alone — but it's what stops a leaked anon
key or a future direct-from-browser Supabase call from crossing a tenant
boundary.
Seed at least one distributor before ingesting a feed — the ingestion script
looks distributors up by slug:
insert into distributors (name, slug, is_pilot_partner)
values ('Acme Solar Supply', 'acme-solar-supply', true);
cd ingestion
python3 -m venv .venv && source .venv/bin/activate
pip install -r requirements.txt
python ingest.py --distributor acme-solar-supply --file sample_feeds/acme-solar-supply_2026-09-13.csv \
--database-url "$DATABASE_URL"
Each distributor gets a column-alias mapping in feed_config.py (their CSV/
Excel headers vary; we normalize to our schema rather than asking distributors
to conform). Add a new block there for each pilot partner onboarded in Phase 2.
Products are upserted on (distributor_id, distributor_sku), so re-running
ingestion on a refreshed daily feed just updates price/stock in place.
cd web
cp .env.example .env.local # fill in Supabase + Stripe keys
npm install
npm run dev
Pages/routes:
/login — email/password sign-in (Supabase Auth). There is no public
sign-up: Phase 3 onboarding is invite-only (see below)./search — spec search (/api/search), blended across distributors by
canonical_key — the UI never shows a per-distributor bidding view.
Requires auth (search access is a paid SaaS-tier feature, not public)./bom — BOM upload (/api/bom-upload), CSV/XLSX, heuristic SKU matching
against manufacturer/model name; low-confidence lines come back unmatched
for manual resolution rather than guessing. Requires auth./product/[id] — product detail, spec table, CAD/schematic/datasheet links.
Requires auth./api/checkout — requires auth; contractor_id is derived from the
session (never trusted from the request body — otherwise any caller could
place orders, or read order history, under an arbitrary contractor). Prices
the order entirely from server-side catalog data and opens a Stripe
PaymentIntent for subtotal + platform fee. GridSpec is merchant of record;
distributors are not paid via Stripe Connect transfer at charge time./api/webhooks/stripe — on payment_intent.succeeded, marks the order paid
and performs Automated PO Dispatch: splits the order into one
purchase_order per distributor with a blind packing slip, on Net-30 payout
terms. PO generation never happens before payment actually clears./ops/purchase-orders — ops-only list of purchase orders with links to the
two generated documents per PO: /ops/purchase-orders/[id]/po (names the
distributor, states what's owed to them — sent to their fulfillment desk)
and /ops/purchase-orders/[id]/packing-slip (GridSpec-branded, no
distributor identity or pricing — this is what actually ships to the job
site, replacing the distributor's own packing slip, which is what makes
drop-shipping "blind"). There's no distributor portal yet, so an operator
currently has to view/print these and forward the PO to the distributor
manually. Gated by OPS_ADMIN_EMAILS (see below) — never contractor-reachable.cd web
node --env-file=.env.local scripts/invite-contractor.mjs \
"Acme Solar Installers" [email protected] "Jordan Lee"
This creates the contractors row, sends a Supabase invite email (the
person sets their own password via the link), and links the two via
contractor_users.auth_user_id.src/proxy.ts (Next.js 16 renamed middleware.ts to proxy.ts) only
refreshes the session cookie on every request — it does not gate access.
Every route that touches contractor-owned data calls
getAuthedContractor() (src/lib/auth.ts) itself and derives
contractor_id from the session, per the Next.js proxy docs' own warning
not to rely on proxy/middleware alone for authorization.lib/supabase-server.ts /
lib/supabase-browser.ts use the anon key and a user's session (subject to
RLS); lib/supabase.ts uses the service role key (bypasses RLS) and is
only for the ingestion pipeline and the Stripe webhook, which have no user
session to act as.OPS_ADMIN_EMAILS (comma-separated) gates /ops/* — there's no real
platform-admin role yet, so lib/ops-auth.ts just checks the signed-in
Supabase Auth user's email against this env var. Set it to whichever
account(s) should be able to see distributor identity and payout amounts;
this must never be a contractor's own account./ops/purchase-orders),
but there's no distributor portal or automated email dispatch yet — an
operator has to manually forward the PO to the distributor's fulfillment
desk today.purchase_orders at their
payout_due_at — Net-30/60 payout is modeled in the schema but not executed.