Tixflow
Find concerts, conferences, parties and more. Pay with M-Pesa or card and get your ticket instantly.
SpazaLink links township spaza shops to verified wholesalers, tracks their stock, and turns their trading history into a Financial Health Report they can take to a bank.
CommerceTypeScript · JavaScriptGitHub @danielsnyashadzashe-boopOnlineOwner 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
The maker's introduction consists of the line above. Read the repository README for more details. Repository README ↗
Loading the full README.
Read the full README ↗Full shelves. Safe stock. Bank ready.
SpazaLink is a B2B web app for South African township spaza shops. It links shops to verified wholesalers, tracks their stock, and turns their trading history into a Financial Health Report they can take to a bank.
The idea comes from Rethabile Madonsela (founder and idea owner).
It's a live app that processes real user data against MongoDB Atlas, with sign-in handled by Clerk. Nothing in it is a static mock-up.
| Screen | What it does |
|---|---|
Inventory Dashboard (/dashboard) | Compliance score that updates as CIPC, BEE and tax documents are uploaded or removed. Colour-coded stock tracker. A low-stock alert slides down when an item drops into Red. Quick Fix adds the missing quantity to the cart and opens checkout. |
Ordering & Checkout (/order) | A catalogue of verified products only, each with SABS / Verified Supplier badges. The cart shows bulk discounts as a separate line. Payment toggles are PayShap, Instant EFT and Cash on Delivery. A live delivery timeline follows each order. |
Financial Portal (/finance) | Monthly spend chart and bank scorecard (total spent, on-time payments, credit health), plus a one-tap Financial Health Report PDF with the SpazaLink logo. |
npm install
cp .env.example .env.local # fill in MONGODB_URI and REPORT_SIGNING_SECRET
npx clerk@latest env pull # writes the Clerk keys for the linked "Spaza Link" app
npm run seed # wholesalers, catalogue, two demo shops + their Clerk users, sample PDFs
npm run dev # http://localhost:3000
Before you submit, run the security checks against a production build:
npm run build && npm start # terminal 1
npm run test:brief # terminal 2: 80 checks, one per item in the project brief (writes BRIEF_TEST_REPORT.md)
npm run test:security # terminal 2: 66 security, role, demo shop and data wall checks
Windows clock. Clerk rejects session tokens that look "issued in the future". If your PC clock is more than a few seconds off, sign-in loops back to the sign-in page. Sync it under Settings → Time & language → Date & time → Sync now.
| Password | ||
|---|---|---|
| Demo shop (Thandi's Spaza, Soweto) | [email protected] | Spaza@2026 |
| Second shop, for showing the data walls (Sipho's Corner Store) | [email protected] | Spaza@2026 |
| Platform admin (admin portal) | [email protected] | Spaza@2026 |
If Clerk asks for a verification code, enter 424242. That code works for any +clerk_test address on a development instance. The same accounts are shown on the sign-in page with copy buttons.
Thandi's shop is preloaded with stock in all three colours, six months of orders, and CIPC + BEE documents (85% Ready). Sipho's shop has no documents, so you can demo uploads live with samples/sample_cipc_certificate.pdf and samples/sample_bee_affidavit.pdf. Run npm run seed again at any time to reset both demo shops. It also resets the demo users' passwords in Clerk.
Thandi's Spaza is the shared demo shop (isDemo: true in the seed).
DEMO_READ_ONLY./admin)Open to the Platform admin role only. The role is stored in the Clerk user's publicMetadata.role and checked on the server for every admin request. Everyone else gets an "Admins only" screen, and the API answers 403.
| Page | What it shows | Forms and actions |
|---|---|---|
| Overview | Key figures, registrations per month, newest shops, order value, orders, deliveries by status, payment mix, best sellers, sales by category, stock health per shop, credit health, compliance, critical stock, recent orders | Platform report download (PDF or PowerPoint) |
| Shops | Every shop with joined date, compliance, a stock-health bar, orders, spend, on-time rate and credit health. Includes credit health and compliance charts | Select a shop for its monthly spend chart |
| Orders | Every order with payment outcome. Includes a status donut and payment outcome chart | Status tabs, search, Move on (advances delivery) |
| Products | The catalogue, with a status donut and products per category | Add product, with a live price check chart against its category. Edit price, bulk deal and verification |
| Suppliers | Every wholesaler, with a status donut, wholesalers per province and order value per wholesaler | Add supplier, with a live province chart. Change verification inline |
| Guide | A plain-language explanation of how SpazaLink works |
All charts are shadcn/ui charts (src/components/charts.tsx).
When anyone signs in, a one-time notice explains that access is role based today, and that full RBAC is coming: named permissions per role, with an audit trail.
Each report offers a choice of PDF document or PowerPoint slides (native, editable charts):
/finance): GET /api/report?format=pdf|pptx/admin): GET /api/admin/report?format=pdf|pptx/finance, this month's point on the chart now includes the new order.Payments run in sandbox mode: no money moves. State this on the slides.
Palette (taken from the logo): navy #062654 for trust and primary actions, pink #F2366D for energy and highlights, and mustard #F4AF23 for attention, savings and Quick Fix, all on a soft blush background. Stock status keeps its own green, amber and red.
Components: shadcn/ui (Radix) in src/components/ui, wrapped by the app's kit in src/components/kit.tsx: Button, Card, Input, Badge, Select, AlertDialog for destructive confirmations, and Sonner toasts. Clerk's screens use @clerk/ui's shadcn theme in the same colours.
Logo: the master artwork is brand/spaza-link.png (a copy is in public/brand/). npm run brand cuts out the white background and generates:
favicon.ico, icon.png, apple-icon.pngopengraph-image.png)The logo appears in the site header, the app header, the sign-in panel, the About section, the footer and the PDF report.
Copy: the interface avoids dashes by design.
src/lib/rules.ts).Shop owner (phone / desktop, installable PWA)
│ HTTPS
▼
Next.js 16 app (App Router, Tailwind CSS 4, shadcn/ui)
├─ Clerk: sign-in / sign-up pages, UserButton, session cookies
├─ proxy.ts: clerkMiddleware(), app pages require a session
├─ Inventory Dashboard ─┐
├─ Ordering & Checkout ─┼─ client components → /api/* route handlers
├─ Financial Portal ────┘
└─ API routes: Clerk auth() → owner's shop → shop-walled data layer → MongoDB
▼
MongoDB Atlas (collections + GridFS "documents" bucket)
| Layer | Choice |
|---|---|
| Frontend | Next.js 16 (App Router), Tailwind CSS 4, shadcn/ui (Radix), lucide icons, Recharts |
| Backend | Next.js API route handlers (Node.js runtime) |
| Database | MongoDB Atlas, with application-level row security (src/lib/walled.ts) |
| Auth | Clerk (@clerk/nextjs): email/password, Google, email codes, device verification, bot and brute-force protection |
| File storage | MongoDB GridFS (documents bucket) |
| PDF reports | pdf-lib, generated server-side |
| Hosting | Vercel (set the env vars below) |
| Build file | Installable PWA (public/manifest.webmanifest); can be wrapped as a signed APK with Bubblewrap / PWABuilder |
shops.ownerUserId is unique.withShop() (src/lib/api.ts). It calls Clerk's auth(), looks up that user's shop, and hands the handler a walled(db, shopId) accessor.walled() merges shopId into every filter, insert, update, delete and aggregation. It's written last, so request data can never override it.NO_SHOP, and the app sends them to /onboarding.| Collection | Holds | Key fields | Walled by shop |
|---|---|---|---|
shops | One per spaza shop | ownerUserId (Clerk user id, unique), name, location, phone | Yes (owner = session user) |
compliance_documents | CIPC / BEE / TAX uploads | shopId, type, fileId → GridFS, filename, mime, size, uploadedAt | Yes |
suppliers | Wholesalers | name, region, verificationStatus, badges | No (read-only) |
products | Wholesaler catalogue | supplierId, name, unit, price, bulkRule, badges, verificationStatus | No (read-only) |
stock_items | The shop's own stock | shopId, productId, quantity, fullCapacity | Yes |
orders | Each order placed | shopId, orderNumber, subtotal, discount, total, paymentMethod, status | Yes |
order_items | Lines in an order | shopId, orderId, productId, quantity, unitPrice, discount, lineTotal | Yes |
order_status_events | Delivery timeline steps | shopId, orderId, status, changedAt | Yes |
payments | Payment records | shopId, orderId, method, amount, status, paidAt, paidOnTime, sandbox | Yes |
documents.files / .chunks | GridFS file storage | metadata.shopId, metadata.type | Yes (served only via a walled lookup) |
User accounts, passwords and sessions live in Clerk, not in MongoDB.
| Stage | What we do | Evidence / notes |
|---|---|---|
| Requirements | Protect shop documents, orders, payments, financial reports and identities | Walled collections are listed in src/lib/walled.ts; identities are delegated to Clerk |
| Design | Threat-model the three screens; one data-access path per shop | Clerk session → owner's shop → walled(); read-only catalogue; server-side pricing |
| Implementation | Input cleaning, upload filters, secrets out of code, HTTPS only | src/lib/validation.ts (zod + text cleaning), src/lib/uploads.ts (extension + MIME + magic bytes + 5 MB), .env.local ignored by git, HSTS and security headers in next.config.ts |
| Testing | Cross-account access, roles, bad input and upload tests; every brief item | npm run test:security (55 checks) and npm run test:brief (77 checks), all passing against real Clerk sessions and MongoDB |
| Deployment | Environment variables for keys; least-privilege database user | Env vars in Vercel; Atlas user with readWrite on spazalink only; Clerk production instance via npx clerk@latest deploy |
| Maintenance | Dependency updates, error logging, access review | npm audit; server errors logged as [api] without leaking details; Clerk dashboard for sessions and users |
walled.ts + test suite)auth() in each route).exe, a renamed .exe and a 6 MB PDF are each refused with a clear messagenosniff / X-Frame-Options: DENY / HSTS, and Cache-Control: no-store on documents and reports| Name | Purpose |
|---|---|
MONGODB_URI, MONGODB_DB | Atlas connection |
NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY, CLERK_SECRET_KEY | Clerk (pulled with npx clerk@latest env pull) |
REPORT_SIGNING_SECRET | Signs the integrity code printed on Financial Health Reports |
npx clerk@latest deploy and swap in production keys. The current keys are for a development instance.Vercel limits request bodies to 4.5 MB, so on Vercel the practical upload limit is about 4.4 MB. The server still enforces the 5 MB rule.
brand/spaza-link.png master logo artwork
src/
proxy.ts clerkMiddleware page protection
components/ui/ shadcn/ui components
components/kit.tsx SpazaLink UI kit on top of shadcn
components/logo.tsx Logo (lockup / mark / full) + BrandStripe
lib/
rules.ts business rules shared by client and server
walled.ts application-level row security
api.ts withShop() / withUser() on Clerk auth(), errors, origin check
clerk-appearance.ts Clerk theme in SpazaLink colours
validation.ts, uploads.ts, services.ts, db.ts, ...
app/
page.tsx landing + About
(auth)/sign-in, sign-up, onboarding
(app)/dashboard, order, finance
api/... route handlers
scripts/
seed.mjs demo data + Clerk demo users
security-check.mjs SSDL test evidence
brand-assets.mjs logo, favicon and share image generation