🌐 Live Links
main on GitHub auto-deploys to Vercel. You don't need to do anything — just push your code.🏗 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
| Badge | Requirement | How it's awarded |
|---|---|---|
| 🆕 New Trader | Just joined | Automatic on registration |
| 🥉 Bronze | 5+ trades · 3.5★ avg | MySQL trigger on each review |
| 🥈 Silver | 20+ trades · 4.0★ avg | MySQL trigger on each review |
| 🥇 Gold | 50+ trades · 4.5★ avg | MySQL trigger on each review |
Tech Stack
| Layer | Technology | Why |
|---|---|---|
| Frontend | HTML + CSS + JavaScript | Works on any browser, no build step |
| Animations | Motion One (CDN) | Same physics as Framer Motion, vanilla JS |
| Fonts | Unbounded + Plus Jakarta Sans | Premium look, Google Fonts CDN |
| Backend | Node.js + Express.js | Simple, fast REST API |
| Auth | JWT + bcrypt | Secure, stateless tokens |
| Database | MySQL | Relational, handles the tier trigger logic |
| Deploy | Vercel (static) + Railway (backend) | Free tiers, fast deploys |
📁 File Map
Here is every file in the repo and what it does:
Frontend — public/
| File | What it does | Owner |
|---|---|---|
| index.html | Homepage: hero, category grid, featured businesses, tier explainer | Xolani + Siyamthanda |
| directory.html | Public business search: filters (category, zone, tier), profile modal, WhatsApp contact | Siyamthanda |
| board.html | I NEED / I OFFER board: two columns, respond flow, new post modal | Amahle |
| network.html | B2B dashboard: trade stats, trade list, my posts, edit profile | Amahle |
| join.html | 2-step registration + login tab | Lindokuhle |
| pitch.html | Hackathon brief for judges: problem, solution, pricing, SDGs, team | Lekgothe |
| team.html | This page — internal team guide | Everyone |
| css/main.css | Full design system: glassmorphism, 7 colours, fonts, badges, mobile CSS | Xolani |
| js/app.js | Shared utilities, mock data, scroll animations, toast. Includes window.serverCheck — auto-pings server on load and sets DEMO_MODE | Lindokuhle |
| js/api.js | Full 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/
| File | What it does | Owner |
|---|---|---|
| index.js | Express app entry point — starts server, mounts routes, serves public/ | Lindokuhle |
| middleware/auth.js | requireAuth — checks JWT token on protected routes | Lindokuhle |
| routes/auth.js | POST /api/auth/register + /api/auth/login | Lindokuhle |
| routes/businesses.js | Public directory, business profile, leave review | Siyamthanda |
| routes/board.js | Board posts, respond, transactions (trades) | Amahle |
| db/schema.sql | All 9 MySQL tables + automated tier trigger | Lindokuhle |
| db/connection.js | MySQL connection pool — reads from .env | Lindokuhle |
| db/seed.js | 6 demo Mdantsane businesses + board posts (run once) | Lindokuhle |
Config files (root)
| File | What it does |
|---|---|
| .env.example | Template for your .env — copy this, fill in your credentials |
| .env (not in repo) | Your real secrets — NEVER commit this file |
| package.json | Project dependencies and npm scripts |
| vercel.json | Tells Vercel: serve public/ as a static site, no build step |
| .gitignore | Excludes .env, node_modules, build artifacts from git |
| README.md | Project overview for the GitHub repo page |
👥 Team Roles & Tasks
✓ 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
✓ 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
✓ 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
✓ 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
✓ 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)
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
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
public/js/app.js, set window.DEMO_MODE = true after the serverCheck line (so it overrides whatever the ping found).🌿 Git Workflow
Daily git flow — do this every time
git pull origin maingit checkout -b feature/xolani-mobile-navUse your name + what you're doing
git add .git commit -m "feat: add mobile hamburger menu"git push origin feature/xolani-mobile-navGood 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?
git pull origin main — pull the latest<<<<<<< and >>>>>>> markersgit add . then git commit -m "fix: resolve merge conflict"⏱ 24-Hour Build Timeline
🏗 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
🔍 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
🤝 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
🧪 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
🚀 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
Opening (30 sec)
Flow 1 — Public discovery (45 sec)
Flow 2 — B2B trading (90 sec)
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
http://localhost:3000/api/... when running locally. Send Authorization: Bearer YOUR_TOKEN header for protected routes.Auth
| Method | Endpoint | Body | Returns |
|---|---|---|---|
| POST | /api/auth/register | name, category, zone, whatsapp, password | { token, business } |
| POST | /api/auth/login | whatsapp, password | { token, business } |
Businesses
| Method | Endpoint | Auth? | Notes |
|---|---|---|---|
| GET | /api/businesses | No | Query: q, category, zone, tier, limit, offset. Short searches (<4 chars) use LIKE fallback automatically. |
| GET | /api/businesses/:id | No | Includes 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 | ✅ Yes | Body: name, tagline, description, phone, address, zone. Partial updates safe. |
| POST | /api/businesses/review/:id | ✅ Yes | Body: rating (1–5), comment, transactionId. Triggers auto-tier update. |
Board
| Method | Endpoint | Auth? | Notes |
|---|---|---|---|
| GET | /api/board | No | Query: type=need|offer, limit, offset. Urgent posts sort first. |
| POST | /api/board | ✅ Yes | Body: type, title, description, budget, isUrgent |
| GET | /api/board/my-posts | ✅ Yes | Your own posts (for dashboard My Posts panel) |
| POST | /api/board/:id/respond | ✅ Yes | Body: message. Auto-creates a pending transaction record. |
| DELETE | /api/board/:id | ✅ Yes | Soft-delete — sets deleted_at + is_active=false. Only your own posts. |
| GET | /api/board/transactions | ✅ Yes | All your trades (buyer + seller side) |
| PATCH | /api/board/transactions/:id | ✅ Yes | Body: status (pending/negotiating/agreed/complete/cancelled). "complete" increments both parties' completed_deals. |
/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
| Table | Purpose | Key columns |
|---|---|---|
| businesses | Core business profiles + auth | id, name, category, zone, whatsapp, password_hash, trader_tier, rating, completed_deals |
| users | Auth accounts (linked to businesses) | id, business_id, role |
| categories | Category lookup (pre-seeded, 8 rows) | id, slug, name, icon, color |
| offers | Services each business provides | id, business_id, offer_text |
| requests | Things each business needs to source | id, business_id, title |
| board_posts | Public I NEED / I OFFER board | id, business_id, type, title, budget, is_urgent, response_count |
| board_responses | Replies to board posts | id, post_id, business_id, message |
| transactions | Trade records (pending → complete) | id, post_id, from_business_id, to_business_id, status, amount |
| reviews | Ratings that auto-update tier | id, business_id, author_business_id, rating, comment |
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
git add . && git commit -m "WIP: ...". You can always clean it up later. You can't recover code you lost.CSS tips
#E040FB — use var(--fuchsia). All colours, spacing, and border-radius are already defined in css/main.css..glass, .btn btn-primary, .badge badge-fuchsia, .biz-card — these are all ready. Don't reinvent them.JavaScript tips
window.toast('Your message here'). It's already wired up in app.js.Businesses, Board, and Auth objects from api.js. Example: const data = await Businesses.list({category:'salon'})For the presentation
🔴 Common Errors & Fixes
| Error | Fix |
|---|---|
| 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
.env or .env.local files. Ever. On any project.UPDATE table SET deleted_at = NOW() WHERE id = ?main. Always use a branch. Always.