Duro Logo

Duro: Fitness Community Marketplace

Two-sided marketplace connecting fitness communities with members through intelligent booking and event management

Next.jsSupabaseStripe ConnectMulti-TenancyRBACFirebase

Overview

Client

Personal Project → Production Platform

Role

Solo Developer & Designer

Tech Stack

Next.js, Supabase, Stripe Connect, RRULE, TypeScript

Duro is a two-sided marketplace platform that connects fitness community organizers with members. Organizers get powerful tools for event management, recurring classes, custom dashboards, and split payment processing. Members get seamless discovery, booking, and payment for fitness events.

iOS App Experience

FlutterFlow MVP - Phase 1 mobile app screenshots from the App Store listing

Swipe to see more screens →

Next.js Rebuild - Phase 2

Enterprise platform with multi-tenancy, RBAC, and advanced scheduling features

Communities Dashboard

Multi-tenant organizer view with event management and member analytics

app.joinduro.com/communities

Event Management

RRULE recurring events, real-time availability, and Calendly-like booking interface

app.joinduro.com/events

The Challenge

Fitness community organizers were using fragmented tools: Eventbrite for ticketing, Google Calendar for scheduling, Stripe for payments, and spreadsheets for member management. Each tool charged fees, none talked to each other, and the experience was clunky for both organizers and attendees.

Organizer Pain Points:

  • Multiple platforms = multiple fees (Eventbrite 3.5% + $1.79, Stripe 2.9% + $0.30)
  • No unified dashboard for events, bookings, revenue, and member management
  • Recurring classes required manual event creation every week
  • Complex payment flows (refunds, no-shows, split payments for co-hosted events)
  • Limited customization - organizers wanted branded booking pages

Member Pain Points:

  • Scattered bookings across different platforms and organizers
  • No centralized place to discover local fitness communities
  • Inconsistent booking experiences per organizer
  • Difficulty managing recurring class memberships

Technical Challenges:

  • Two-sided marketplace requiring different user roles and permissions (RBAC)
  • Multi-tenancy: Each organizer needs isolated data with row-level security
  • Stripe Connect marketplace integration (onboarding, split payments, platform fees)
  • RRULE implementation for recurring events (like Google Calendar's "every Tuesday at 6pm")
  • Calendly-like booking UX with real-time availability and conflict detection
  • Notion-like dashboard creator so organizers can customize their workspace

The Solution

I built Duro in two phases: Flutterflow MVP for validation, then Next.js rebuild for complex marketplace features.

Phase 1: Flutterflow MVP

Built initial marketplace with Flutterflow + Firebase for speed. Validated product-market fit with TruFusion South Austin (10+ events). Proved organizers wanted unified booking + payments.

Phase 2: Next.js Rebuild

Hit Flutterflow limitations with RBAC, multi-tenancy, and RRULE. Rebuilt from scratch with Next.js + Supabase for complex features:

  • Multi-Tenant Architecture: Supabase Row-Level Security (RLS) for data isolation per organizer
  • RBAC: Organizers, staff, members, admins with granular permissions
  • Stripe Connect: Automated onboarding, split payments, platform fees
  • RRULE Events: Recurring classes with complex schedules (every Tuesday + Thursday)
  • Calendly-like Booking: Real-time availability, conflict detection, waitlists
  • Dashboard Creator: Notion-like drag-and-drop widgets for organizers

Technical Approach

Multi-Tenancy with Supabase RLS

Each organizer is a tenant with isolated data. Used Supabase Row-Level Security policies to enforce data boundaries at the database level:

-- Row-Level Security policy for organizers
CREATE POLICY "Organizers can only see their events"
ON events
FOR SELECT
USING (organizer_id = auth.uid());

-- Members can see events they've booked
CREATE POLICY "Members can see their bookings"
ON bookings
FOR SELECT
USING (user_id = auth.uid());

-- RBAC: Staff members inherit organizer permissions
CREATE POLICY "Staff can manage organizer events"
ON events
FOR ALL
USING (
  EXISTS (
    SELECT 1 FROM team_members
    WHERE team_members.user_id = auth.uid()
    AND team_members.organizer_id = events.organizer_id
    AND team_members.role IN ('staff', 'admin')
  )
);

Stripe Connect Marketplace Integration

Implemented Stripe Connect Express for organizer onboarding and split payment flows:

// Organizer onboarding to Stripe Connect
async function onboardOrganizer(organizerId: string) {
  // Create Stripe Connect account
  const account = await stripe.accounts.create({
    type: 'express',
    country: 'US',
    email: organizerEmail,
    capabilities: {
      card_payments: { requested: true },
      transfers: { requested: true },
    },
  });

  // Generate onboarding link
  const accountLink = await stripe.accountLinks.create({
    account: account.id,
    refresh_url: `${baseUrl}/dashboard/stripe/refresh`,
    return_url: `${baseUrl}/dashboard/stripe/success`,
    type: 'account_onboarding',
  });

  return accountLink.url;
}

// Split payment: Platform fee + Organizer payout
async function processBooking(booking: Booking) {
  const platformFee = booking.price * 0.05; // 5% platform fee
  const organizerPayout = booking.price - platformFee;

  const paymentIntent = await stripe.paymentIntents.create({
    amount: booking.price * 100,
    currency: 'usd',
    customer: booking.customerId,
    application_fee_amount: platformFee * 100,
    transfer_data: {
      destination: booking.organizerStripeAccountId,
    },
  });

  return paymentIntent;
}

RRULE Recurring Events

Implemented RRULE (iCalendar recurrence rules) for complex recurring class schedules:

import { RRule } from 'rrule';

// Example: Every Tuesday and Thursday at 6pm for 12 weeks
const rule = new RRule({
  freq: RRule.WEEKLY,
  byweekday: [RRule.TU, RRule.TH],
  dtstart: new Date(2024, 0, 1, 18, 0),
  count: 24, // 12 weeks × 2 days
});

// Generate all event instances
const instances = rule.all();
// [
//   2024-01-02T18:00:00 (Tue),
//   2024-01-04T18:00:00 (Thu),
//   2024-01-09T18:00:00 (Tue),
//   ...
// ]

// Store as event series with individual instance overrides
await supabase.from('event_series').insert({
  organizer_id: organizerId,
  rrule: rule.toString(),
  base_event: { name, price, capacity },
});

// Allow canceling individual instances
await supabase.from('event_instance_overrides').insert({
  series_id: seriesId,
  instance_date: '2024-01-09',
  status: 'canceled', // Cancel one occurrence
});

Notion-like Dashboard Creator

Built a drag-and-drop dashboard system where organizers could customize their workspace with widgets (revenue charts, upcoming events, member lists):

// Dashboard schema stored in Supabase
interface DashboardLayout {
  organizer_id: string;
  widgets: Widget[];
}

interface Widget {
  id: string;
  type: 'revenue' | 'events' | 'members' | 'analytics';
  position: { x: number; y: number; w: number; h: number };
  config: Record<string, any>;
}

// React Grid Layout for drag-and-drop
import GridLayout from 'react-grid-layout';

function OrganizerDashboard({ layout }: { layout: DashboardLayout }) {
  return (
    <GridLayout
      layout={layout.widgets.map(w => w.position)}
      onLayoutChange={saveLayout}
    >
      {layout.widgets.map(widget => (
        <div key={widget.id}>
          {renderWidget(widget)}
        </div>
      ))}
    </GridLayout>
  );
}

Results

Platform Impact:

  • TruFusion South Austin partnership with 10+ successful events
  • Multi-organizer marketplace supporting multiple fitness communities
  • 5% platform fee vs. Eventbrite's 5.3% (lower cost for organizers)

Technical Achievements:

  • Built MVP with Flutterflow for rapid validation, then rebuilt as complex platform with Next.js
  • Implemented production-grade multi-tenancy with Supabase RLS (data isolation at database level)
  • Successfully integrated Stripe Connect marketplace (onboarding, split payments, platform fees)
  • RRULE implementation supporting complex recurring class schedules
  • Notion-like dashboard creator with drag-and-drop customization
  • Calendly-like booking UX with real-time availability and conflict detection

Key Learnings

What Worked Well:

  • Flutterflow for MVP Validation: Built and launched rapidly, validated product-market fit with real partner before investing in complex rebuild
  • Supabase RLS for Multi-Tenancy: Row-Level Security policies enforced data isolation at database level - no application logic bugs could leak data
  • Stripe Connect for Marketplaces: Stripe's marketplace APIs handled all compliance (KYC, tax forms, onboarding) - would've been complex to build manually
  • RRULE Library: Used battle-tested RFC 5545 standard for recurring events instead of building custom solution
  • Partner-First Launch: TruFusion South Austin partnership provided real-world feedback before scaling

What I'd Do Differently:

  • Start with Next.js: While Flutterflow was great for MVP, I knew I'd hit limits. Could've avoided the rebuild by starting with Next.js + simplified feature set
  • Earlier Stripe Connect Testing: Stripe Connect onboarding has edge cases (international organizers, tax forms) - should've tested earlier in MVP
  • Simpler Dashboard Initially: Notion-like dashboard creator was over-engineered. Most organizers wanted 2-3 preset layouts, not full customization
  • Better RRULE Exception Handling: Canceling individual recurring event instances created UX complexity - needed clearer UI patterns upfront