Coming soon!
Work / Fitt — Case Study
Launching on App Store Mobile App · 2026

Fitt —
Built for the designer.

A complete business management mobile app built from scratch for fashion designers — covering orders, customers, deadlines, PDF receipts, and push notifications. I designed, engineered, and deployed everything.

Role
Sole Engineer
Platform
iOS + Android
Stack
RN · Supabase · TS
Status
Launching 2026
The Problem

Fashion designers in Nigeria — and across Africa — run complex businesses entirely from WhatsApp, phone notes, and memory. Orders come in through DMs, deadlines are tracked mentally, customers have no receipts, and money management is guesswork. Existing business apps (QuickBooks, Wave, generic order managers) were built for retailers and accountants. None spoke the language of a fashion designer.

Fitt was built to fill that exact gap. A purpose-built tool for designers who take orders, sew to deadline, and need to manage customers, track payments, and generate professional receipts — all from their phone.

What I Built
Order Management
Full order lifecycle — create, track status, update, complete. Linked to customer records and deadlines.
Customer Profiles
Customer database with order history, measurements, and contact details. All scoped by RLS.
Deadline Tracking
order_deadlines table — deadline per order, tracked independently for complex multi-piece orders.
PDF Receipt Generation
In-app PDF generation with business logo (base64), order details, payment breakdown, and branding.
6-Type Notification Engine
Due-today, overdue, delivery confirmation, payment reminder, custom, and system notifications.
Onboarding & Auth
Smooth onboarding flow, Supabase auth, profile setup, and business configuration on first launch.
Marketing Website
fittapp.net — built from scratch with waitlist form, editorial dark design, and App Store CTA.
App Store Pipeline
EAS Build configuration, Apple Developer certificates, app.json, screenshots, and submission.
Technical Stack
React Native Expo Supabase TypeScript EAS Build PostgreSQL Row Level Security Expo Push Notifications AsyncStorage React Navigation PDF Generation Formspree
Key Technical Challenges
⚠ Challenge — Deadline Architecture
The original data model stored deadlines directly on orders. This worked until designers needed multiple deadlines per order (e.g. fabric delivery, first fitting, final delivery). A schema migration mid-development was necessary without losing live data.
✓ Solution
Retired the fitting_date column and migrated to a dedicated order_deadlines table with a foreign key to orders. The migration was written to safely backfill existing data, preserving all records. This is an example of thinking about data evolution from the start — not just initial builds.
⚠ Challenge — PDF Generation on Mobile
Generating professional-quality PDF receipts on a React Native device without a backend is non-trivial. The logo needed to be embedded, the layout needed to be pixel-consistent, and file access required proper Expo file system handling.
✓ Solution
Built a custom PDF generation pipeline — business logo fetched and converted to base64 at receipt generation time, injected into an HTML template, rendered to PDF via an Expo-compatible library, and saved to the device's documents directory with proper URI handling for sharing.
⚠ Challenge — RLS Without Data Leakage
In a multi-user Supabase setup, ensuring one designer's orders, customers, and data are never accessible to another — even with a misconfigured query — requires careful RLS policy design at the database level.
✓ Solution
Every table has a user_id column with a Row Level Security policy enforcing auth.uid() = user_id. Policies are tested explicitly against different auth contexts. No reliance on application-layer filtering — security is at the data layer.
Code Snapshot
// Order creation — production Supabase call export async function createOrder( data: OrderInput, userId: string ): Promise<Order> { const { data: order, error } = await supabase .from('orders') .insert({ ...data, user_id: userId }) .select('*, customer(*), order_deadlines(*)') .single() if (error) throw error return order as Order } // RLS Policy (SQL) -- CREATE POLICY "Users can only see own orders" -- ON orders FOR ALL -- USING (auth.uid() = user_id);
Lessons Learned
01
Schema design is product design. Every field decision — naming, types, relationships — affects the product years later. Taking time to design the schema properly is the highest-leverage engineering decision.
02
Test RLS policies explicitly. Application code can hide data access issues. The only way to know security is correct is to test it directly against the database with different auth contexts.
03
Build the marketing site early. Having fittapp.net live before the app gave a real URL for App Store review, press outreach, and waitlist signups. Don't leave it as an afterthought.
04
EAS Build configuration is its own project. Certificates, provisioning profiles, app.json settings, and the EAS pipeline took more time than expected. Start this process well before submission.
Visit fittapp.net
Work/ Impala — Case Study
In Development Backend + 2 Mobile Apps · 2025–2026

Impala —
Two apps. One backend.

A multi-vendor Nigerian marketplace with a vendor app, a buyer app, and a shared Supabase backend. I engineered the complete backend and both mobile applications — wallet system, Flutterwave payments, escrow hold periods, sub-order architecture, and full RLS security.

My Role
Backend + Both Apps
Platforms
iOS + Android
Payments
Flutterwave
Architecture
Multi-vendor
The Product

Impala is a marketplace that connects Nigerian vendors to buyers — locally and internationally. Two separate mobile applications share a single Supabase backend: a Vendor App for sellers to manage their storefronts, products, and orders; and a Buyer App for customers to browse, purchase, and track deliveries.

The complexity comes from coordinating two separate user types, separate apps, and a shared backend with strict data isolation — all while handling real money through Flutterwave's Nigerian payment rails.

Architecture — What I Built
Multi-Vendor Order Splitting
One buyer order fans out to multiple vendor sub-orders (IMP-YYYYMMDD-XXXXXX-V1 format). Each vendor only sees their sub-orders.
Wallet + Escrow System
Buyer funds held in escrow on payment. Released to vendor wallet after delivery confirmation. Hold period logic built in Supabase Edge Functions.
Flutterwave Integration
Full payment flow — initiate, webhook verification, transaction recording, and payout to vendor wallets in Naira.
Commission Engine
10% platform commission automatically calculated and deducted on every completed transaction before vendor payout.
Kobo / Naira System
All monetary values stored in kobo (smallest unit). Displayed in Naira. Eliminates floating point errors entirely.
Vendor App
Storefront management, product listings with image uploads, order management, wallet dashboard, and payout history.
Buyer App
Product browsing, cart, checkout, wallet top-up, order tracking, delivery confirmation, and purchase history.
RLS Security Layer
Vendors see only their products and sub-orders. Buyers see only their orders. All enforced at the database level.
Technical Stack
React Native Supabase Flutterwave TypeScript PostgreSQL Row Level Security Edge Functions Expo EAS Build Supabase Storage Push Notifications
Key Engineering Decisions
⚠ Challenge — Kobo vs Naira
JavaScript floating point arithmetic makes currency handling unreliable. 8500.10 + 1499.90 in JavaScript does not always equal 10000.00. For a payments platform this is critical.
✓ Solution — Store everything in kobo
Every monetary value in the database is stored in kobo (integers only). ₦8,500 is stored as 850000. Display conversion happens at the UI layer. No float arithmetic on money, anywhere.
⚠ Challenge — Multi-vendor Order Routing
A buyer adds products from three different vendors to their cart. One checkout. But three vendors need to receive their specific orders with their own fulfilment, tracking, and payout — completely isolated from each other.
✓ Solution — Sub-order Architecture
A parent orders table contains the buyer's full order. A sub_orders table contains one row per vendor, linked to the parent. Vendors see only their sub-order. RLS enforces vendor_id = auth.uid() on sub_orders. Commission is calculated per sub-order at completion time.
⚠ Challenge — Escrow Release Timing
Funds must be held until the buyer confirms delivery — but what if the buyer never confirms? The hold period needs a timeout with automatic release to prevent vendor funds being locked indefinitely.
✓ Solution — Edge Function + Hold Period Logic
A Supabase Edge Function runs on a schedule checking for unconfirmed deliveries past the hold period. Automatic release is triggered after N days, with the transaction recorded and vendor notified. Buyer confirmation triggers immediate release.
Lessons Learned
01
Two apps means two codebases of context. Keeping the Vendor and Buyer apps architecturally consistent — same patterns, same data models, same naming — reduces cognitive load dramatically.
02
Design RLS before writing any app code. The data access model is the foundation. Every query in both apps relies on the policies being correct. Getting this wrong at the start creates cascading bugs.
03
Flutterwave webhooks need idempotency. Webhooks can fire more than once. Every webhook handler must check if the transaction was already processed before updating wallet balances.
04
Never store money as a float. Kobo-first from day one. This decision saved countless hours of debugging edge cases in payment calculations.