ProductionCipta Craft · Lead Developer · 2024

XFitness

Comprehensive gym management ecosystem integrating memberships, payments, QR-based access control, and trainer booking into a unified web + mobile platform. Consolidated multiple operational systems, reducing administrative overhead by 50%.

50%Reduction in admin overhead
< 3 minNew member onboarding time
0 staffRequired at entry gate

Tech Stack

Next.js 15React Native (Expo)SupabasePostgreSQLCloudflare R2RazorpayTypeScriptDocker

Stakeholders

Gym Owner (Client)

Final approval on feature scope, payment gateway selection, and brand requirements

Gym Manager

Day-to-day admin dashboard user — key tester for onboarding and reporting flows

Trainers

Primary users of the mobile app for session booking and member chat

Members

End-consumers of both web portal and mobile app; QR entry and payment flows

Zafran (Lead Dev)

System architecture, full-stack development, DevOps, payment integration

The Problem

The client operated a gym chain relying on spreadsheets, physical logbooks, and manual payment receipts. New member onboarding took 15–20 minutes per person, access control was handled by staff manually checking IDs, and trainers had no dedicated channel to communicate with members.

The Solution

Architected a multi-tenant platform across web (Next.js 15) and mobile (React Native / Expo) backed by a single Supabase + PostgreSQL database with row-level security. Integrated Revenue Monster and Razorpay for payment processing, a QR-code generation system for keycard-free gym entry, and a real-time chat layer for trainer–member messaging.

Architecture

Three-tier architecture: a Next.js 15 web dashboard (admin + member portal), a React Native Expo mobile app (trainer + member-facing), and a shared Supabase backend (PostgreSQL + Auth + Realtime + Storage). All database access is governed by Supabase Row-Level Security policies — no server-side proxy needed for read paths. Write operations that require payment or QR validation go through edge functions.

  1. 01

    Web Dashboard (Next.js 15)

    App Router with Server Components for data fetching. Admin CRUD for members, packages, and reports. Member portal for booking and payment history. Deployed on Vercel with environment-scoped Supabase keys.

  2. 02

    Mobile App (React Native / Expo)

    Expo Router for file-based navigation. Trainer dashboard for session management and real-time chat. Member app for QR entry display, class booking, and payment. OTA updates via Expo EAS.

  3. 03

    Supabase Backend

    PostgreSQL with RLS policies per user role. Supabase Auth (JWT) for session management. Supabase Realtime for chat subscriptions. Supabase Storage + Cloudflare R2 for media assets.

  4. 04

    Payment Layer

    Razorpay for card payments (web). Revenue Monster for local e-wallet support (TNG, Boost, ShopeePay). Webhook receivers validate and write to the payments table inside a DB transaction.

  5. 05

    QR Access Control

    On check-in, the member's app generates a time-scoped JWT-signed QR code. The gym's tablet scanner decodes and verifies the signature + expiry server-side before granting entry.

Dev Setup

Prerequisites

  • Node.js 20+
  • pnpm
  • Supabase CLI
  • Expo CLI
  • Docker (for local Supabase)
bash — setup
$git clone https://github.com/zafransakowi/xfitness-web && cd xfitness-web
$pnpm install
$cp .env.example .env.local

# Fill in NEXT_PUBLIC_SUPABASE_URL, SUPABASE_SERVICE_ROLE_KEY, RAZORPAY_KEY_ID, etc.

$supabase start

# Starts local Postgres + Auth + Storage

$supabase db push

# Applies migrations and seed data

$pnpm dev

# Starts Next.js dev server on localhost:3000

$# Mobile: cd ../xfitness-mobile && pnpm install && npx expo start

Challenges

  1. 01

    Multi-gateway payment reconciliation

    Revenue Monster and Razorpay have different webhook schemas and retry behaviours. A payment could be confirmed by the gateway but the webhook could arrive out-of-order or duplicated. Solved with idempotency keys stored in the DB — every webhook handler checks for an existing processed event ID before writing.

  2. 02

    RLS policy complexity at scale

    Supabase RLS policies are powerful but can become a performance liability with joins across many tables. Early policies were written naively and caused N+1 query patterns. Refactored using security definer functions and materialised role checks to keep query plans efficient.

  3. 03

    QR code replay attacks

    Early prototype used static member IDs as QR content — trivially screenshotted and reused. Replaced with HMAC-SHA256 signed tokens with a 90-second expiry window. Scanner validates signature and timestamp server-side before granting entry.

What I Learned

  • 01

    Design your RLS policies before your schema — retrofitting them is painful.

  • 02

    Payment webhooks are unreliable; always build idempotency into your write handlers.

  • 03

    Expo EAS makes OTA updates genuinely painless for iterating with clients without a full app-store review cycle.

  • 04

    Supabase Realtime is easy to get started with but needs careful channel management to avoid memory leaks in React Native.