Next JSPostgreSQLReduxTailwind CSSShadcn UICOMPLETED

Tumaini GYM

gym.tumainifitness is both a website and a management system for Tumaini Fitness, providing users with information about the gym, programs, and services while helping manage memberships, classes, and

Tumaini GYM

Tumaini Fitness — Gym Management Platform

Case Study · Full-Stack Web Application

Client / ProjectTumaini Fitness Centre, Kasarani, Nairobi
Live Sitegym.tumainifitness.co.ke
TypeCustom gym management & public marketing platform
Timeline[Add your project timeline, e.g. Jan 2025 – Jul 2025]
Role[Add your role, e.g. Full-Stack Developer / Solo Developer]
StatusProduction

Overview

Tumaini Fitness is a full-stack gym management platform built for a real fitness centre in Nairobi, Kenya. The system combines a public-facing marketing website with a secure admin dashboard that handles member registration, membership plans, payment processing, and reporting — including native M-Pesa (Safaricom STK Push) integration for mobile money payments.

The platform replaces manual record-keeping and fragmented payment tracking with a single, modern web application that staff can use daily and members can discover online.

[Hero image: landing page hero section showing gym branding, headline, and CTA]


The Problem

Before this platform, Tumaini Fitness relied on manual processes to manage members and payments:

  • Member records were scattered across paper forms and spreadsheets
  • Payment tracking (cash, M-Pesa, bank transfer) had no central audit trail
  • Membership expiry and renewals were hard to monitor at scale
  • There was no professional online presence tied to live membership pricing
  • Staff had no unified dashboard for day-to-day operations

The business needed a system that was practical for Kenyan operations — especially mobile money — while still presenting a polished public website to attract new members.


Project Goals

  1. Centralize operations — One dashboard for members, plans, and payments
  2. Support local payments — Integrate M-Pesa STK Push alongside cash, card, and bank transfer
  3. Automate membership lifecycle — Track subscriptions, expiry, and renewals automatically after payment
  4. Enable self-service registration — Let prospective members sign up online without admin intervention
  5. Build for discoverability — SEO, structured data, and a responsive marketing site
  6. Secure by default — Role-based access for admins vs. members

My Role & Responsibilities

[Customize this section. Example below.]

  • End-to-end product design and full-stack development
  • Database schema design and API architecture
  • M-Pesa Daraja API integration (STK Push + callback handling)
  • Admin dashboard UI with analytics and reporting
  • Authentication, authorization, and route protection
  • Deployment and production configuration

Tech Stack

Frontend

TechnologyPurpose
Next.js 15 (App Router)Full-stack React framework, SSR/SSG, API routes
React 19UI rendering and component architecture
TypeScriptEnd-to-end type safety
Tailwind CSS 4Utility-first styling
Radix UI + shadcn/uiAccessible, composable UI components
TanStack React QueryServer state, caching, and data fetching
TanStack React TableSortable, filterable data tables
RechartsDashboard analytics charts
Framer MotionPage and component animations
React Hook Form + ZodForm handling and validation

Backend

TechnologyPurpose
Next.js API RoutesRESTful backend endpoints
Prisma ORMDatabase access and migrations
PostgreSQLRelational data storage
NextAuth.jsAuthentication (JWT + credentials)
bcryptjsPassword hashing

Integrations & Tools

TechnologyPurpose
Safaricom M-Pesa Daraja APISTK Push mobile payments
ResendTransactional email (contact form)
React EmailEmail template components
jsPDFPDF receipt generation
date-fnsDate formatting and calculations
ESLint + PrettierCode quality and formatting

[Tech stack graphic: Next.js, React, TypeScript, PostgreSQL, Prisma, M-Pesa, Tailwind logos in a grid]


System Architecture

The application follows a monolithic Next.js architecture — a single codebase serving both the public website and the authenticated dashboard, with API routes acting as the backend layer and Prisma connecting to PostgreSQL.

┌─────────────────────────────────────────────────────────────┐ │                     Client (Browser)                        │ ├──────────────────────────┬──────────────────────────────────┤ │   Public Website         │   Admin / Member Dashboard       │ │   /  /about  /contact    │   /dashboard/*                   │ │   /register  /nutrition  │   (Protected routes)             │ └────────────┬─────────────┴──────────────┬───────────────────┘              │                            │              ▼                            ▼ ┌─────────────────────────────────────────────────────────────┐ │              Next.js 15 App Router + API Routes             │ │  ┌─────────────┐  ┌──────────────┐  ┌─────────────────────┐ │ │  │ Middleware  │  │ NextAuth.js  │  │ React Query Hooks   │ │ │  │ (Auth/RBAC) │  │ (JWT Session)│  │ (Data Fetching)     │ │ │  └─────────────┘  └──────────────┘  └─────────────────────┘ │ └────────────┬───────────────────────────────┬────────────────┘              │                               │              ▼                               ▼ ┌────────────────────────┐    ┌──────────────────────────────┐ │   PostgreSQL (Prisma)  │    │   External Services          │ │   Users, Members,      │    │   • M-Pesa Daraja API        │ │   Plans, Payments,     │    │   • Resend (Email)           │ │   Subscriptions        │    │   • Cloudinary (Images)      │ └────────────────────────┘    └──────────────────────────────┘

[Architecture diagram: the flow above rendered visually with icons for each layer]

Application Structure

src/ ├── app/ │   ├── (main)/          # Public pages — home, about, contact, nutrition │   ├── (auth)/          # Sign-in │   ├── dashboard/       # Protected admin & member dashboard │   ├── register/        # Public member registration │   └── api/             # REST API endpoints ├── components/          # UI, dashboard, payments, members, etc. ├── hooks/               # React Query data hooks ├── lib/                 # Auth, Prisma, M-Pesa, validations, SEO └── types/               # Shared TypeScript types

Database Design

The data model is built around six core business entities, with NextAuth tables for session management.

ModelDescription
UserAuthentication identity with role (ADMIN or MEMBER)
MemberExtended profile — health info, emergency contact, membership status
MembershipPlanConfigurable plans with duration, price, and feature list
MembershipSubscriptionActive/expired subscription linked to a payment
PaymentTransaction record with method, status, and M-Pesa reference
Account / SessionNextAuth OAuth and session storage

Key Relationships

  • Every Member belongs to one User (1:1)
  • A Payment can activate a MembershipSubscription
  • Subscriptions automatically extend if a member renews before expiry
  • Payment methods: CASH, CARD, BANK_TRANSFER, MOBILE_MONEY

Membership Status Flow

INACTIVE → (payment completed) → ACTIVE → (subscription expires) → EXPIRED                 ↓             SUSPENDED (admin action)

[ERD image: Users, Members, MembershipPlans, MembershipSubscriptions, and Payments with relationships]


Key Features

1. Public Marketing Website

A responsive, SEO-optimized landing experience for Tumaini Fitness:

  • Hero section with gym imagery and primary CTA
  • Services overview (strength training, cardio, nutrition, kids karate)
  • Dynamic membership pricing pulled from the live database
  • Member testimonials and gym statistics
  • Dedicated pages: About, Contact, Nutrition
  • JSON-LD structured data, sitemap, and robots.txt for search engines

[Landing page screenshot — hero and services section]
[Pricing section screenshot — membership plan cards]

2. Online Member Registration

Prospective members can register through a public form at /register without needing admin credentials. The flow:

  1. User fills in personal details, health info, and fitness goals
  2. Form is validated client-side (Zod) and server-side
  3. A User + Member record is created in a single transaction
  4. A success screen confirms registration and guides next steps

The registration endpoint is intentionally public (POST /api/members) while all other member APIs require admin authentication.

[Registration form screenshot — desktop or mobile]

3. Admin Dashboard

Role-aware dashboard at /dashboard with real-time business metrics:

Key metrics

  • Total, active, expired, suspended, and inactive members
  • Total and monthly revenue (KES)
  • Pending and failed payment counts

Dashboard sections

  • Overview with quick actions and upcoming renewals
  • Recent activity feed (new members, completed payments)
  • Analytics tab with revenue and membership charts

[Dashboard overview screenshot — metric cards]
[Analytics tab screenshot — revenue and membership charts]

4. Member Management

Full CRUD for gym members with:

  • Searchable, filterable member table
  • Individual member profiles with subscription history
  • Suspend / reactivate membership status
  • Member reports with summary statistics and export
  • Days-remaining calculation for active subscriptions

[Members table screenshot — filters and status badges]
[Member profile page screenshot — subscription and payment history]

5. Membership Plans

Admins can create and manage flexible membership plans:

  • Name, description, duration (days), and price (KES)
  • Feature list (stored as a string array)
  • Active/inactive toggle
  • Plans appear dynamically on the public homepage pricing section

[Plan management screenshot]

6. Payment Processing

A comprehensive payment module supporting multiple methods:

MethodFlow
CashManual entry, instant completion
CardManual entry with reference
Bank TransferManual entry with reference
M-Pesa (Mobile Money)STK Push to member's phone, async callback confirmation

Payment features

  • New payment form with member lookup and plan selection
  • Real-time M-Pesa status tracker in the UI
  • Payment history table with filters, sorting, and pagination
  • PDF receipt generation (branded with gym logo and colors)
  • CSV export for accounting
  • Payment reports page with date-range filtering

[New payment form screenshot]
[Payments table screenshot]
[PDF receipt preview]

7. M-Pesa Integration

Native integration with Safaricom's Daraja API using STK Push (Lipa na M-Pesa Online):

Admin initiates payment         │         ▼ POST /api/payments/push         │         ├── Create PENDING payment in database         ├── Send STK Push to member's phone         └── Store CheckoutRequestID as transactionRef                 │                 ▼         Member enters M-Pesa PIN on phone                 │                 ▼ POST /api/payments/callback  (Safaricom webhook)         │         ├── ResultCode 0 → COMPLETED         │       ├── Update payment with M-Pesa receipt number         │       └── Create/extend MembershipSubscription         │         └── ResultCode ≠ 0 → FAILED

Implementation highlights

  • Dedicated MpesaService class with token caching and environment-aware config
  • Phone number validation and formatting for Kenyan numbers (07… / 254…)
  • Prisma transactions ensure payment + subscription updates are atomic
  • Smart subscription extension: renewals stack onto existing active subscriptions
  • Graceful error handling with health-check endpoints

[M-Pesa STK Push modal screenshot — phone input and status tracker]

8. Authentication & Authorization

Authentication

  • NextAuth.js with credentials provider (email + password)
  • JWT session strategy (24-hour expiry)
  • bcrypt password hashing
  • Rate limiting on login attempts (5 attempts per 15 minutes per IP/email)

Authorization

  • Two roles: ADMIN and MEMBER
  • Next.js middleware protects /dashboard and /api/* routes
  • Admin-only routes: members, plans, payments, dashboard stats
  • Members see a simplified dashboard (extensible for future self-service features)

[Sign-in page screenshot]

9. Contact Form & Email

The contact page sends inquiries via Resend with:

  • Server-side validation and XSS sanitization
  • React Email template for branded HTML emails
  • Rate limiting and error handling

[Contact page screenshot]


UI / UX Design

Design System

  • Primary brand color: Yellow/Gold (#F0B100) — energy, fitness, optimism
  • Dark mode: Full theme support via next-themes
  • Component library: shadcn/ui built on Radix UI primitives
  • Typography: Inter (Google Fonts)
  • Layout: Collapsible sidebar navigation in the dashboard
  • Responsive: Mobile-first public site; adaptive dashboard with sidebar trigger

UX Decisions

  • Skeleton loaders on dashboard and data tables to reduce perceived wait time
  • Toast notifications (Sonner) for action feedback
  • Breadcrumb navigation in the dashboard for wayfinding
  • Currency formatting in Kenyan Shillings (KES) throughout
  • Duration formatting that reads naturally (e.g. "1 month 5 days" instead of "35 days")

[Mobile responsive view screenshot]
[Dark mode screenshot]


Development Approach

Phase 1 — Foundation

  • Project scaffolding with Next.js 15 App Router and TypeScript
  • Prisma schema design and PostgreSQL setup
  • NextAuth authentication with role-based sessions
  • Middleware for route protection

Phase 2 — Core Admin Features

  • Member CRUD with validation schemas (Zod)
  • Membership plan management
  • Payment recording (manual methods)
  • Dashboard statistics API with parallel Prisma queries

Phase 3 — M-Pesa & Automation

  • M-Pesa Daraja API service layer
  • STK Push initiation and callback webhook
  • Automatic subscription creation/extension on successful payment
  • Payment status polling in the UI

Phase 4 — Public Website & Polish

  • Marketing homepage with dynamic pricing
  • Public member registration flow
  • SEO (metadata, JSON-LD, sitemap, robots.txt)
  • Contact form with Resend email integration
  • PDF receipt generation and CSV export
  • UI refinements, dark mode, and responsive polish

Code Quality Practices

  • TypeScript strict mode across frontend and API routes
  • Zod schemas shared between client forms and server validation
  • React Query hooks for consistent data fetching patterns
  • Component co-location (feature folders for payments, members, plans)
  • ESLint + Prettier with Tailwind class sorting

API Overview

EndpointMethodAccessDescription
/api/membersGETAdminList all members with subscription info
/api/membersPOSTPublicRegister a new member
/api/members/[id]GET/PATCH/DELETEAdminMember CRUD
/api/members/[id]/suspendPOSTAdminSuspend/reactivate member
/api/members/reportsGETAdminMember reports data
/api/membership-plansGET/POSTAdminList/create plans
/api/membership-plans/[id]GET/PATCH/DELETEAdminPlan CRUD
/api/paymentsGET/POSTAdminList/create payments
/api/payments/pushPOSTAdminInitiate M-Pesa STK Push
/api/payments/callbackPOSTPublic (M-Pesa)M-Pesa webhook
/api/payments/[id]/receiptGETAdminReceipt data for PDF
/api/dashboard/statsGETAdminDashboard metrics
/api/contactPOSTPublicContact form submission
/api/auth/[...nextauth]*PublicNextAuth handlers

Challenges & Solutions

Challenge 1 — M-Pesa Callback Reliability

Problem: M-Pesa callbacks can arrive before the UI updates, or fail silently if the server returns an error.

Solution: Always respond with ResultCode: 0 to Safaricom (prevent retries), process callbacks inside Prisma transactions, and provide a client-side status polling endpoint (/api/payments/status) so admins see real-time payment confirmation.

Challenge 2 — Subscription Overlap on Renewal

Problem: When a member pays for a new plan while still active, naively starting a new subscription would waste remaining days.

Solution: Before creating a subscription, check for an active one. If remaining days exist, the new subscription starts from the current end date — effectively stacking renewals.

Challenge 3 — Role-Based Route Protection

Problem: Both UI routes and API routes need consistent authorization; exposing admin APIs would be a security risk.

Solution: Centralized Next.js middleware with an explicit allowlist for public routes (POST /api/members, M-Pesa callback, auth endpoints) and role checks for admin-only paths.

Challenge 4 — Receipt Generation Performance

Problem: Generating PDF receipts with embedded logos was slow and bloated file sizes.

Solution: Client-side PDF generation with jsPDF, logo compression/caching (window.__TUMAINI_LOGO_CACHE__), and optimized PDF options (compress: true, putOnlyUsedFonts: true).


Results & Impact

[Customize with real numbers where possible.]

MetricBeforeAfter
Member record keepingManual / spreadsheetsCentralized database
Payment trackingFragmentedFull audit trail with receipts
M-Pesa paymentsManual confirmationAutomated STK Push + subscription activation
Online presenceNone / basicFull marketing site with live pricing
Membership renewalsManual trackingAutomated expiry and renewal alerts

Qualitative outcomes

  • Staff can process a payment and activate membership in under a minute
  • Every completed payment generates a downloadable branded PDF receipt
  • Membership plans on the website stay in sync with admin-configured pricing
  • The platform is built to scale with additional member self-service features

[Before/after comparison image — optional]


What I Learned

  • Designing for local context matters — M-Pesa isn't optional in Kenya; building first-class mobile money support changed how usable the product is for the client
  • Webhook-driven flows need defensive coding — Async payment confirmation requires idempotent handlers, transaction safety, and UI polling
  • Monolithic Next.js is productive for SMB tools — One codebase for marketing site + admin dashboard + API reduced complexity for a solo/small-team build
  • Schema design upfront saves refactoring — Modeling subscriptions separately from payments made renewal logic and reporting much cleaner

Future Roadmap

  • Expand member dashboard (view own subscription, payment history, renewal)
  • Automated email/SMS reminders for expiring memberships
  • Attendance / check-in tracking
  • Multi-branch support
  • Admin notification system for pending payments and new registrations

Project Links

ResourceURL
Live Applicationgym.tumainifitness.co.ke
Source Code[Add your GitHub repo URL if public]
Portfolio[Add your portfolio URL]