Case studies
How the projects were built
The problem behind each product, what it needed to do, how I built it and where it ended up. Click any screenshot to see it full size.
01 / 08 · Co-founder & developer
GradeIt
An online TCG hobby store and South Africa's card-grading middleman. I built the website, a Flutter app for creating grading submissions, and the card database behind both.
gradeit.co.za →
The problem
Grading orders were managed by hand, and years of history lived in a spreadsheet of 44 batch sheets. Customers couldn't submit or track orders themselves, and nothing linked a graded slab back to the actual card, so stats and leaderboards weren't possible.
Goals
- Let collectors build and pay for submissions themselves, on the web and on their phone.
- Bring every historical order in without losing or mis-attributing anything.
- A card database so every graded item resolves to a real card, set and price.
- One place for pricing, stock and payment rules.
What I built

Submission wizard
A custom WooCommerce plugin powers a multi-step wizard: choose a grading company, card type, service level and add-ons, then search the card database to add each card. Insurance and totals are calculated as you go.

Card database & history import
A nightly catalogue sync runs in self-rescheduling batches and never deletes rows, so old orders always resolve. Classifier and language fixes were verified against ~195k catalogue rows.
An idempotent importer turned the spreadsheet into 116 real submissions. It matched 88% of items to customer accounts, linked 1,155 orders, and sent ambiguous rows to human review instead of guessing.
Flutter app on a REST API
The plugin exposes a REST API with JWT access tokens and rotating refresh tokens. The app's cart is the real store cart, so coupons, wallet and shipping behave exactly like the website. Store rules are enforced on the server, never in the app.
Payment hands off to the website's checkout through a single-use sign-in link, so Apple Pay, Google Pay, Capitec Pay, PayJustNow, EFT and wallet all work without being rebuilt. A deep link brings the customer back to the order.
PSA's free API allows about 100 calls a day, so grade reveals are cache-first and customers never call PSA directly.
Outcome
Every historical batch is now a real submission with stats and leaderboards. Uncatalogued items on the stats page dropped from about 51% to 5.5%. Collectors create, pay for and track submissions on the site, and the app is ready for its store release.
02 / 08 · Founder & sole engineer
ClubRuggas
Fixtures, results, standings, line-ups and live crowd-sourced scores for South African club rugby, on a website and a mobile app.
clubruggas.co.za →
The problem
Club rugby results in South Africa are scattered and slow to appear, and most matches never get live coverage. Supporters at the ground know the score long before anyone publishes it.
Goals
- One place for every club's fixtures, results, standings and team sheets.
- Live scores from the crowd, without letting anyone corrupt official results.
- Reward fans for contributing, and keep web and mobile in sync with one API.
What I built

Score integrity
Official scores change through a single locked, audited, revisioned path. Match events are append-only. Fan claims go through weighted voting and consensus to produce a provisional score that never overwrites the official one.
Log tables update incrementally, and a property test checks them against a full nightly rebuild.

Realtime and one API contract
Websockets (Django Channels) carry match, live and personal feeds, broadcast only after the database commit, with version-stamped caching on public reads.
The OpenAPI spec is the source of truth, and the React types and Dart models are generated from it. An undeclared filter that once silently returned unfiltered lists is now a compile error.

Data ingest and gamification
A polite nightly scraper (ETags, rate limits) brings in competitions and skips finished ones. Fans earn XP, streaks and cosmetic unlocks at every level from 1 to 50, and every uploaded image goes through a strict pipeline of validated masters, generated variants and content-hash names.
The mobile app
Outcome
Live in production on AWS (Cape Town region). A git tag builds the images and deploys with health checks, and the Flutter app ships to the Play Store's internal track. All three codebases hold a 100% coverage gate in CI.
03 / 08 · Owner & developer
PetTrek
Trusted pet care, booked in two taps. A South African marketplace where owners book walking, sitting, grooming or training, and vetted local handlers take the jobs.
pettrek.co.za →
The problem
Finding a trustworthy dog walker usually means word of mouth and WhatsApp. Owners can't check who's coming, see the walk happen or pay safely, and good handlers have no easy way to find work.
Goals
- A two-sided marketplace: owners book in a couple of taps, handlers accept jobs and get paid.
- Trust first: handlers are identity-verified before they can work.
- Live walk tracking and chat.
- Money handled on the server and never trusted from the browser.
What I built

Handler onboarding
A separate handler portal. Applicants submit a FICA document pack (SA ID, certified copy, proof of address), ID numbers are checksum-validated, and approval and verification are separate steps.

Bookings, realtime and hardening
Prices are calculated on the server, job acceptance is row-locked so two handlers can't take the same booking, and status and payment fields are protected from the client.
A dedicated ASGI service runs chat and live location, with a tracking toggle for handlers and a live map for owners.
Before launch I ran a full security audit, fixed every finding with regression tests, and set a backend coverage floor of 94%. TLS auto-renews with a fallback certificate so a renewal problem never takes the site down.
Outcome
The web platform is live at pettrek.co.za, with a Flutter app alongside. Next up: switching on payments and a design-system refresh.
04 / 08 · Owner & developer
ClaimsaleBot
A WhatsApp bot for collector communities. It runs claim sales, keeps a vouch-based reputation for every member and tracks moderation per group, with a dashboard for hosts and group admins.
whatsappclaimbot.co.za →
The problem
Collectible sales in WhatsApp groups run by hand. The host scrolls back through hundreds of messages to work out who claimed first, chases winners and keeps a spreadsheet of orders. Trust is word of mouth too: "is this seller legit?" gets asked in every group, and the answer is buried in old chats.
Goals
- Award every slot instantly and fairly, and track claims, auctions and orders.
- Give every member a reputation that can't be easily gamed.
- Let group admins track warnings and manage their own group.
- Contact winners without getting the host's number banned.
Claim sales
Auction · R50 increments · ends 20:00
Illustration with fictional names
First claim wins, automatically
A small Node bridge (Baileys) holds the WhatsApp session and forwards every message to a Django + Celery engine. Listing captions are auto-parsed for price, condition and quantity. The first claim gets 👍, later ones 👎, and ❌ marks a listing sold out. Auctions support reserves, increments and end times.
When a winner deletes their claim, the next-oldest claim is promoted automatically. At the end, an orders dashboard tracks payment, collection or PUDO delivery and tracking numbers, with bulk updates for hosts.
The bot never cold-DMs anyone, because that gets numbers soft-banned. An end-of-sale shout-out invites winners to message first, which unlocks a DM menu with their tally, payment details and a PUDO locker search.
Vouches & reputation
Pos: 12 (10 unique), Neg: 0 (0 unique)
15 more people to 💎 Elite
Repped by (their own rep in brackets):
👍 Lerato ⭐ ×2 (Pos: 6, Neg: 0)
great seller, fast shipping
👍 Dan (Pos: 2, Neg: 0)
…and 8 more
Illustration with fictional names. The reply formats are the bot's own.
Reputation that's hard to game
Any member can vouch for another with @user +rep or -rep, with an optional comment, for up to five people in one message. Vouches are append-only, self-reps are blocked by a database constraint, and nobody can rep the same person twice in a row. Someone else has to vouch in between.
Ranks are scored on unique people, not the raw total, so two friends trading reps can't farm a crown. The ladder runs 5 ⭐ Rising, 10 🔥 Trusted, 25 💎 Elite, 50 🏆 Legendary, 100 👑 King Repper, and only upward milestones are announced.
A lookup shows each voucher's own standing in brackets, so a vouch from someone credible reads differently to one from a brand-new account. History is kept when a member leaves the group, and a typed number can still find it.
Commands only count when the message starts with the tag. A real early bug: "Are your negative reps gonna be there forever @jared" posted Jared's review list to the group.
Group tracking & moderation
1. 🔥 @Sipho (Pos: 8, Neg: 0)
2. ⭐ @Lerato (Pos: 5, Neg: 0)
3. @Dan (Pos: 2, Neg: 0)
Illustration with fictional names
Every group runs its own way
Groups opt into features: claim sales, reputation or both. A reviews-only group ignores claims entirely, and reputation is off until an admin switches it on, so the bot never surprises a group.
Group admins are assigned per group with per-capability permissions, and get a scoped dashboard for their own groups only. Warnings are issued in chat (@user warn reason) or from the dashboard, and are revoked rather than deleted, so the record stays intact.
Leaderboards show top reppers all-time, this week, month or year, and hosts get stats, revenue trends and leaderboards for their sales.
Outcome
Running in production on AWS Lightsail with daily database backups to S3, checked by a CI canary. Buyer identity is keyed on WhatsApp's newer private IDs, so it's ready for WhatsApp usernames. When the bridge once stayed "connected" while silently receiving nothing, I added a nightly restart that keeps the session, and it's been stable since.
05 / 08 · Owner & developer · Built for Starke Ayres sales staff
Stark Notes
A field app built for Starke Ayres sales staff to keep notes on their areas and the seed conditions each one needs, track their test plants, and reach seed documents and information quickly.
The problem
Seed sales reps cover several areas, and each has its own conditions that decide which varieties will do well. Their knowledge of those areas lived in their heads, notebooks and chat messages. Variety details (maturity, sunlight, water, soil) sat in separate PDF fact sheets and growing guides on the website. Test plants sent to nurseries and farms were tracked by hand, so results were easy to lose.
Goals
- Notes per area and per variety, with photos, so field knowledge isn't lost.
- Track every test plant from delivery to results in one place.
- Every fact sheet, comparison table and growing guide one tap away.
- A private account per rep, so each person sees only their own trials and notes.
What I built
Catalogue import
A scraper turns the public Starke Ayres catalogue into structured JSON, and an idempotent Django management command imports it: 25 categories and 125 varieties with fact sheets, comparative tables and production guidelines. The API offers search and filters by maturity days, sunlight, water and soil.
One data model for varieties
I refactored separate variation, sub-variation and three media models into a single hierarchical CropItem (sub-items via a parent link) and one MediaAsset model. I wrote a migration guide so the app could move to the new endpoints and field names.
Test plant tracking
Each trial moves through Awaiting delivery → Delivered (to a nursery or farm) → Planted → Complete → Archived, with a date stamped at every stage, plus the farm contact. Trial varieties are reusable across trials, and dated results record each observation with its value and unit.
Notes, favourites and the app
Notes are private to each rep, can be linked to a variety, and carry photos, files or links, with archiving, so observations about an area and what grows there stay together. Favourite varieties are one tap away. The Flutter app stores JWT tokens in secure storage, signs back in automatically, and switches between local, tunnel and production APIs per build.
In the app
Screens captured from the app running against a local copy of the backend with the full catalogue and demo data. Farm names and notes are fictional.
Outcome
Deployed with Docker Compose (Django, Postgres 15 and nginx) on AWS EC2 with TLS. It has been developed over more than 60 commits since August 2025, and is moving to its own server and domain next.
06 / 08 · Developer · Squarespace → WooCommerce
Move360
A gym, Pilates, physio and recovery studio in Durbanville. They asked me to move their Squarespace site to WooCommerce while keeping the original styling. I converted it and added a shop, member sign-up and a booking system.
move360.co.za →
The problem
The site was locked into Squarespace, bookings went through a paid third-party embed, and there was no way to sell packages or memberships online.
Goals
- Look exactly like the existing site, so customers notice nothing.
- Don't lose any search rankings in the move.
- Add a shop, sign-up and an in-house booking system with online payment.
- Let the owner edit everything without code.
What I built

Same design, new platform
A custom theme, screenshot-compared against the original page by page. Every page is native Gutenberg blocks with ready-made Move360 section patterns, so the owner can add and edit sections without a page builder.
The move was SEO-safe: identical URLs verified against the old sitemap, 301s for legacy links, titles and descriptions carried over, plus local-business structured data and alt text the old site never had.

Booking system, shop and sign-up
A Calendly-style booking plugin replaced the paid embed: recurring and one-off slots, per-slot capacity, blackout dates, member and non-member rates priced on the server, and an admin week calendar.
PayFast checkout is shared by shop and bookings. Unpaid spots are held for 20 minutes, and a validated payment webhook confirms them. Session-credit packages and memberships only ask customers to sign in when they buy one, and credits are refunded on cancellation.


Outcome
Live at move360.co.za with the same look and URLs as before, a working shop, and bookings taken directly on the site instead of through a paid embed.
07 / 08 · Lead developer
Hobby Huyss
A TCG and hobby store selling Pokémon, One Piece, MTG and more, plus board games and accessories, with in-store events and leagues.
hobbyhuyss.co.za →
The problem
The existing site was half-configured: placeholder menus, no proper catalogue structure and no link to the distributor's stock. Most of the range is sourced on demand, so the store couldn't show what it was actually able to sell.
Goals
- A store organised the way TCG players shop: game, then era, then set.
- Sell distributor stock on back-order safely, without overselling.
- Local shipping and payments, legal pages and a hardened admin.
What I built

Catalogue and distributor sync
A Game › Era › Set brand tree drives the mega-menus and filters. In-stock, out-of-stock, back-order and pre-order states stay in sync automatically.
A custom sync plugin reads the distributor's live stock through their store's JSON endpoints, with no scraping. Customers can back-order up to a safe share of the distributor's stock, and prices are calculated from cost, VAT and markup, rounded and capped at RRP.
Checkout covers Courier Guy, PUDO and in-store collection, with PayFast, Stitch and PayJustNow. Security-hardening and migration plugins mean large backups restore without paid tools.
Outcome
Live at hobbyhuyss.co.za with a structured catalogue, distributor-backed back-orders and local payment and shipping options.
08 / 08 · Developer
Mi Adventure Tours
A themed WooCommerce site built as a portfolio and booking page for Mizo's Egypt tours, showing off the team, past trips and tour options, with a booking form on every tour.

The problem
Mizo needed a professional home for the tours: somewhere to show the team and past trips, explain each itinerary, and take bookings directly instead of over messages.
Goals
- A polished travel site, focused on Egypt only.
- Tour pages with itineraries, route maps, galleries and pricing.
- A booking form on every tour, with date and group-size selection.
What I built

Tours and booking
A travel theme with a tour-booking plugin. The 8-day and 11-day Egypt journeys were rebuilt to a consistent layout, with custom Google My Maps route embeds, activity highlights, and a booking panel with date, group size and live pricing.

Real content, update-safe
All theme demo content was replaced with real About, Privacy and Terms copy. Text, style and mobile layout fixes live in a must-use plugin, so theme updates never undo them. A verification script checks every page for the required content and flags any leftover demo text.


Outcome
A complete portfolio and booking site with tour listings, team and gallery pages, and a booking flow from date selection to checkout.