Internal · Team Only

Team HQ

Everything you need to build, debug, demo, and ship SondelaBiz in 24 hours. Read this before you write a single line of code.

🎉 SESSION 2 UPDATE — BACKEND + API WIRING COMPLETE
✅ server/routes/businesses.js — rewritten, Express routing bug fixed, FULLTEXT + LIKE search, PUT /me/profile
✅ server/routes/board.js — fully complete: GET, POST, respond, delete, transactions, trade confirm
✅ DEMO_MODE auto-detection — server pinged on load; pages switch automatically. No manual flag change needed.
✅ join.html — register + login wired to real /api/auth/* with loading states
✅ directory.html — fetches /api/businesses on load; live search hits server
✅ board.html — Post, Respond buttons call real API; reload after post
✅ network.html — loads real profile, trades & posts; Confirm + Remove + Save Profile all wired
All committed & pushed to main — Vercel redeploy triggered. Demo mode still works on Vercel (no MySQL). Real API kicks in automatically when you run npm run dev locally.

🏗 What We Built

SondelaBiz is a B2B network + public business directory for Mdantsane township, East London. Two products in one:

Part A — Public Directory (no login)

Anyone in South Africa can search for a Mdantsane business, see their Trusted Trader badge, rating, and services, and contact them instantly via WhatsApp. No app to install. Works on any phone.

Part B — B2B Network (login required)

Registered businesses can post "I NEED" and "I OFFER" requests on the board, respond to each other's posts, confirm trades, and build a verified reputation through the Trusted Trader system.

Trusted Trader System

BadgeRequirementHow it's awarded
🆕 New TraderJust joinedAutomatic on registration
🥉 Bronze5+ trades · 3.5★ avgMySQL trigger on each review
🥈 Silver20+ trades · 4.0★ avgMySQL trigger on each review
🥇 Gold50+ trades · 4.5★ avgMySQL trigger on each review

Tech Stack

LayerTechnologyWhy
FrontendHTML + CSS + JavaScriptWorks on any browser, no build step
AnimationsMotion One (CDN)Same physics as Framer Motion, vanilla JS
FontsUnbounded + Plus Jakarta SansPremium look, Google Fonts CDN
BackendNode.js + Express.jsSimple, fast REST API
AuthJWT + bcryptSecure, stateless tokens
DatabaseMySQLRelational, handles the tier trigger logic
DeployVercel (static) + Railway (backend)Free tiers, fast deploys

📁 File Map

Here is every file in the repo and what it does:

Frontend — public/

FileWhat it doesOwner
index.htmlHomepage: hero, category grid, featured businesses, tier explainerXolani + Siyamthanda
directory.htmlPublic business search: filters (category, zone, tier), profile modal, WhatsApp contactSiyamthanda
board.htmlI NEED / I OFFER board: two columns, respond flow, new post modalAmahle
network.htmlB2B dashboard: trade stats, trade list, my posts, edit profileAmahle
join.html2-step registration + login tabLindokuhle
pitch.htmlHackathon brief for judges: problem, solution, pricing, SDGs, teamLekgothe
team.htmlThis page — internal team guideEveryone
css/main.cssFull design system: glassmorphism, 7 colours, fonts, badges, mobile CSSXolani
js/app.jsShared utilities, mock data, scroll animations, toast. Includes window.serverCheck — auto-pings server on load and sets DEMO_MODELindokuhle
js/api.jsFull API client: Auth, Businesses (list/get/me/review/updateProfile), Board (list/create/respond/myPosts/transactions/updateTransaction/deletePost). Import or call via window.Lindokuhle

Backend — server/

FileWhat it doesOwner
index.jsExpress app entry point — starts server, mounts routes, serves public/Lindokuhle
middleware/auth.jsrequireAuth — checks JWT token on protected routesLindokuhle
routes/auth.jsPOST /api/auth/register + /api/auth/loginLindokuhle
routes/businesses.jsPublic directory, business profile, leave reviewSiyamthanda
routes/board.jsBoard posts, respond, transactions (trades)Amahle
db/schema.sqlAll 9 MySQL tables + automated tier triggerLindokuhle
db/connection.jsMySQL connection pool — reads from .envLindokuhle
db/seed.js6 demo Mdantsane businesses + board posts (run once)Lindokuhle

Config files (root)

FileWhat it does
.env.exampleTemplate for your .env — copy this, fill in your credentials
.env (not in repo)Your real secrets — NEVER commit this file
package.jsonProject dependencies and npm scripts
vercel.jsonTells Vercel: serve public/ as a static site, no build step
.gitignoreExcludes .env, node_modules, build artifacts from git
README.mdProject overview for the GitHub repo page

👥 Team Roles & Tasks

L
Lindokuhle Ntuli
Team Lead · Backend · DB
✓ server/index.js
✓ db/schema.sql + seed.js
✓ routes/auth.js
✓ js/app.js (auto-detect + mock data)
✓ js/api.js (full API client)
✓ join.html wired to real API
→ Set up local MySQL + .env
→ Test register → login → dashboard flow
X
Xolani
UI Lead · Styling
✓ css/main.css design system
✓ Navbar + hamburger menu
✓ Mobile responsive layout
✓ Scroll performance (GPU, blur reduced)
→ Final visual polish on index.html
→ Add hover micro-interactions
→ Check for any visual bugs pre-demo
S
Siyamthanda Ndabeni
Public Pages
✓ directory.html
✓ routes/businesses.js (rewritten)
✓ Search wired to real API (live params)
✓ Context-aware empty states
→ Test category + zone + tier filters
→ Test profile modal on mobile
→ Add more businesses to seed.js if needed
A
Amahle Axola
B2B Network
✓ board.html (Post + Respond wired)
✓ network.html (trades + posts + profile wired)
✓ routes/board.js (complete rewrite)
✓ Trade confirm → PATCH /transactions/:id
✓ Remove post → DELETE /board/:id
✓ Save profile → PUT /businesses/me/profile
→ Test full board post → respond → confirm flow
L
Lekgothe Ngoepe
Pitch · Deploy
✓ pitch.html
✓ Vercel deployment (static)
→ Polish pitch with screenshots
→ Rehearse the 3-min demo script
→ Deploy backend to Railway (optional)
→ Update live links if domain changes

⚙️ Local Setup (do this first)

⚠️
Everyone needs to clone the repo and run npm install. Only Lindokuhle needs to set up MySQL.

Step 1 — Clone the repo

git clone https://github.com/Nanda-Regine/SondelaBiz.git
cd SondelaBiz

Step 2 — Install dependencies

npm install

Step 3 — Create your .env file

cp .env.example .env

Then open .env and fill in your MySQL details:

PORT=3000
NODE_ENV=development
DB_HOST=localhost
DB_PORT=3306
DB_USER=root
DB_PASSWORD=your_mysql_password_here
DB_NAME=sondela_biz
JWT_SECRET=make_up_any_long_random_string_here
JWT_EXPIRES_IN=7d
🚫
NEVER commit .env to GitHub. It's already in .gitignore — but double check. If you accidentally commit it, tell Lindokuhle immediately.

Step 4 — Set up MySQL database

mysql -u root -p < server/db/schema.sql

This creates the sondela_biz database and all 9 tables.

Step 5 — Seed demo data

npm run seed

Creates 6 demo Mdantsane businesses + board posts. Demo password for all: sondela123

Step 6 — Start the server

npm run dev

Open http://localhost:3000 — you should see the homepage.

Step 7 — Start the server (auto-detection handles the rest)

You no longer need to manually change DEMO_MODE. The frontend now pings /api/health on every page load. If the server answers → it switches to real API automatically. If it doesn't → mock data. Zero config.

# In one terminal:
npm run dev          # starts Express on :3000

# Now open http://localhost:3000 in your browser.
# Check the browser console — you should see:
# [SondelaBiz] ✓ Server online — using real API
💡
Can't set up MySQL right now? No problem — open any HTML file directly in your browser (double-click it, or use Live Server). Everything works on mock data. The auto-detection just falls back silently.
🔎
To force demo mode for testing: in public/js/app.js, set window.DEMO_MODE = true after the serverCheck line (so it overrides whatever the ping found).

🌿 Git Workflow

🚫
Never push directly to main. Always create your own branch, push it, and ask someone to review before merging.

Daily git flow — do this every time

1
Pull latest changes first
git pull origin main
2
Create your branch
git checkout -b feature/xolani-mobile-nav
Use your name + what you're doing
3
Make your changes. Save. Test in browser.
4
Add and commit
git add .
git commit -m "feat: add mobile hamburger menu"
5
Push your branch
git push origin feature/xolani-mobile-nav
6
Go to GitHub → open a Pull Request → ask Lindokuhle to merge it

Good commit message examples

feat: add search filter to directory page
feat: wire register form to /api/auth/register
fix: mobile layout breaks on iPhone 12
fix: board posts not showing after refresh
style: improve button hover states
docs: update README setup instructions

What if you get a merge conflict?

1
git pull origin main — pull the latest
2
Open the conflicting file — look for <<<<<<< and >>>>>>> markers
3
Keep the code you want, delete the conflict markers, save
4
git add . then git commit -m "fix: resolve merge conflict"

⏱ 24-Hour Build Timeline

Hour 0–4

🏗 Foundation

  • Everyone: clone repo, npm install, open in browser
  • Lindokuhle: set up MySQL, run schema + seed, confirm npm run dev works
  • Xolani: open index.html, start visual polish
  • All: agree on who is doing what, no overlap
Hour 4–10

🔍 Public Pages

  • Siyamthanda: test directory.html filters, wire to real API when ready
  • Xolani: polish index.html hero, categories, mobile layout
  • Lindokuhle: test all API endpoints with Postman or curl
  • Amahle: start wiring board.html respond flow to API
Hour 10–16

🤝 B2B Network

  • Amahle: wire network.html trade confirm to real API
  • Lindokuhle: wire join.html register + login to /api/auth/*
  • Siyamthanda: add more businesses to seed.js if needed
  • Lekgothe: start polishing pitch.html
Hour 16–22

🧪 Polish & Test

  • All: go through every page, test on mobile phone
  • Xolani: fix any mobile layout issues
  • Lekgothe: rehearse the demo script, time it (aim for 3 minutes)
  • Lekgothe: deploy backend to Railway, test live API
Hour 22–24

🚀 Final prep

  • Freeze code — no more feature changes
  • Lekgothe: verify Vercel is showing latest build
  • All: do the demo script once as a team dry run
  • All: get some sleep (at least 2 hours)

🎤 Demo Script for Judges

🎯
Target: 3 minutes max. Judges see many projects. Be fast, confident, show don't tell. Always open on pitch.html first.

Opening (30 sec)

1
Open pitch.html — "This is SondelaBiz. Mdantsane has 170,000 people and a multi-billion rand township economy — but none of those businesses are findable online."
2
"We built two things: a public directory anyone can use, and a B2B trading network for registered businesses. Let me show you."

Flow 1 — Public discovery (45 sec)

1
Go to directory.html — search "plumber"
2
Click Mthembeni Plumbing — show the profile modal: "4.7 stars, Silver badge, 41 completed trades — all verified"
3
Click WhatsApp button — "One tap. No app. No sign-up. The buyer is in Sandton, the plumber is in Mdantsane, they're talking in seconds."

Flow 2 — B2B trading (90 sec)

1
Go to join.html — register as a new business (or use demo login)
2
Go to board.html — click "+ New Post" — post "I NEED: 50 bottles of shampoo. Budget R1,500. Urgent."
3
Show the need appears on the board. "Any supplier in Mdantsane can see this and respond."
4
Go to network.html — show the trade dashboard: "When someone responds, a trade record is created. Both parties confirm it here."
5
Click Confirm on a trade — "Trade complete. That's +1 to their completed trades counter. Do this 50 times at 4.5 stars — you earn the Gold badge."

Close (15 sec)

Back to pitch.html — "Free to join. Boost from R10. Premium from R49/month. Five SDGs addressed. Built in 24 hours by a team of five first-timers from Mdantsane."

📡 API Reference

💡
All endpoints are at http://localhost:3000/api/... when running locally. Send Authorization: Bearer YOUR_TOKEN header for protected routes.

Auth

MethodEndpointBodyReturns
POST/api/auth/registername, category, zone, whatsapp, password{ token, business }
POST/api/auth/loginwhatsapp, password{ token, business }

Businesses

MethodEndpointAuth?Notes
GET/api/businessesNoQuery: q, category, zone, tier, limit, offset. Short searches (<4 chars) use LIKE fallback automatically.
GET/api/businesses/:idNoIncludes offers, needs, and last 10 reviews
GET/api/businesses/me/profile✅ Yes⚠️ Must be defined BEFORE /:id in Express (already is)
PUT/api/businesses/me/profile✅ YesBody: name, tagline, description, phone, address, zone. Partial updates safe.
POST/api/businesses/review/:id✅ YesBody: rating (1–5), comment, transactionId. Triggers auto-tier update.

Board

MethodEndpointAuth?Notes
GET/api/boardNoQuery: type=need|offer, limit, offset. Urgent posts sort first.
POST/api/board✅ YesBody: type, title, description, budget, isUrgent
GET/api/board/my-posts✅ YesYour own posts (for dashboard My Posts panel)
POST/api/board/:id/respond✅ YesBody: message. Auto-creates a pending transaction record.
DELETE/api/board/:id✅ YesSoft-delete — sets deleted_at + is_active=false. Only your own posts.
GET/api/board/transactions✅ YesAll your trades (buyer + seller side)
PATCH/api/board/transactions/:id✅ YesBody: status (pending/negotiating/agreed/complete/cancelled). "complete" increments both parties' completed_deals.
⚠️
Express routing order matters. In routes/board.js and routes/businesses.js, named routes like /my-posts and /me/profile must appear before the /:id wildcard. If /:id is first, Express treats "me" and "my-posts" as IDs and you'll get wrong results. This is already correct in the repo — don't reorder these routes.

How to test with curl

# Register a new business
curl -X POST http://localhost:3000/api/auth/register \
  -H "Content-Type: application/json" \
  -d '{"name":"My Salon","category":"salon","zone":"Unit 4","whatsapp":"27700000001","password":"test1234"}'

# Login
curl -X POST http://localhost:3000/api/auth/login \
  -H "Content-Type: application/json" \
  -d '{"whatsapp":"27641234567","password":"sondela123"}'

# Get all businesses
curl http://localhost:3000/api/businesses

# Search for plumbers
curl "http://localhost:3000/api/businesses?category=plumber"

🗄 Database Tables

TablePurposeKey columns
businessesCore business profiles + authid, name, category, zone, whatsapp, password_hash, trader_tier, rating, completed_deals
usersAuth accounts (linked to businesses)id, business_id, role
categoriesCategory lookup (pre-seeded, 8 rows)id, slug, name, icon, color
offersServices each business providesid, business_id, offer_text
requestsThings each business needs to sourceid, business_id, title
board_postsPublic I NEED / I OFFER boardid, business_id, type, title, budget, is_urgent, response_count
board_responsesReplies to board postsid, post_id, business_id, message
transactionsTrade records (pending → complete)id, post_id, from_business_id, to_business_id, status, amount
reviewsRatings that auto-update tierid, business_id, author_business_id, rating, comment
⚡
MySQL trigger magic: Every time a review is inserted, a trigger automatically recalculates the average rating and updates the trader_tier. You don't need to write any code for this — the database handles it.

Soft-delete rule

We never hard-delete records. Every important table has a deleted_at column. To "delete" something:

UPDATE board_posts SET deleted_at = NOW() WHERE id = ?;

To query only active records, always filter: WHERE deleted_at IS NULL

💡 Tips & Tricks for the Hackathon

General

🏆
Working beats perfect. A page that works at 80% is worth 10x more than a perfect page that isn't finished. Ship it, then polish it.
🔄
Commit often. Every 30 minutes, git add . && git commit -m "WIP: ...". You can always clean it up later. You can't recover code you lost.
📱
Test on your actual phone. Open Chrome Dev Tools → toggle device toolbar → select "iPhone 12" or "Galaxy S20". But also send the link to your phone and click around.
🎯
Demo mode first. Make sure DEMO_MODE=true works perfectly before switching to the real API. Judges don't care if it's mock data — they care if it works.

CSS tips

🎨
Use the design tokens. Don't type #E040FB — use var(--fuchsia). All colours, spacing, and border-radius are already defined in css/main.css.
📦
Reuse classes. .glass, .btn btn-primary, .badge badge-fuchsia, .biz-card — these are all ready. Don't reinvent them.

JavaScript tips

🔧
Use window.toast(). For any user feedback, call window.toast('Your message here'). It's already wired up in app.js.
📡
Real API calls. Use the Businesses, Board, and Auth objects from api.js. Example: const data = await Businesses.list({category:'salon'})

For the presentation

🎤
Tell the story first. "A plumber in Mdantsane has no website, no Google listing, no way for a Sandton contractor to find them. SondelaBiz fixes that." — THEN show the demo.
⏱
Practice the demo script out loud. 3 minutes feels long until you're in front of judges. Rehearse it twice as a team before the competition.
📊
Lead with impact numbers. "170,000 people. R2-billion economy. Zero platforms serving them." Say the numbers first, show the product second.

🔴 Common Errors & Fixes

ErrorFix
Cannot connect to MySQL Check your .env file — is DB_PASSWORD correct? Is MySQL running? Run mysql -u root -p to test.
JWT_SECRET is not defined Your .env file is missing or not loaded. Make sure it's in the project root, not inside server/.
Table doesn't exist Run the schema again: mysql -u root -p sondela_biz < server/db/schema.sql
npm run dev: nodemon not found Run npm install first.
Page is blank / white screen Open browser Console (F12). Check for red errors. Usually a missing file path or JS syntax error.
window.renderBizCard is not a function app.js hasn't loaded yet. Make sure <script src="js/app.js" type="module"> is in the <body> before your inline scripts.
CORS error in browser console Your API call is going to the wrong URL. Check that BASE = '/api' in api.js (not localhost:3000/api).
Git: "rejected — non-fast-forward" Someone else pushed to main. Pull first: git pull origin main, fix conflicts, then push again.
Vercel build failed Check the Vercel dashboard for the error log. Usually a missing file. Push a fix commit to main and Vercel auto-redeploys.
CSS changes not showing Hard reload: Ctrl+Shift+R (Windows) or Cmd+Shift+R (Mac). Browser may be caching old CSS.
GET /api/businesses/me/profile returns "Business not found" or matches wrong ID Express routing order bug. The /me/profile route must be defined before /:id in routes/businesses.js. If /:id comes first, Express treats "me" as an ID. Already fixed in the repo — only an issue if someone reorders the routes.
Search returns no results for short words like "spa" or "car" MySQL FULLTEXT search ignores words under 4 characters by default. The route now automatically falls back to LIKE for queries under 4 chars — no action needed. If you're testing raw SQL, use: WHERE name LIKE '%spa%'
[SondelaBiz] Server online — but pages still show mock data The health ping worked but a later API call failed. Open F12 → Network tab → look for failed red /api/* requests. Usually a missing JWT token (not logged in) or a DB query error. Check the server terminal for the error message.
window.serverCheck is not a function / undefined app.js hasn't loaded yet when your inline script runs. Make sure <script src="js/app.js" type="module"></script> appears in the HTML before your page script uses await window.serverCheck.

⚠️ Hard Rules — No Exceptions

🚫
Rule 1: NEVER touch, modify, or commit the .env or .env.local files. Ever. On any project.
🚫
Rule 2: NEVER expose passwords, tokens, or API keys in client-side code (HTML, CSS, JS). These go in .env only.
🚫
Rule 3: NEVER hard-delete database records. Always use soft-delete: UPDATE table SET deleted_at = NOW() WHERE id = ?
🚫
Rule 4: NEVER commit directly to main. Always use a branch. Always.
✅
When in doubt, ask Lindokuhle. They are the team lead and have the full context of the codebase.