Skip to content

selezai/siteasy-case-study

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

2 Commits
 
 

Repository files navigation

SitEasy - Pet-Sitting Marketplace Case Study

A production-ready marketplace platform for South Africa's pet-sitting industry

Live Site: siteasy.co.za | Code Samples: github.com/selezai/siteasy-public

Built with Next.js 14, TypeScript, Supabase, and Paystack


Project Overview

SitEasy is a full-stack marketplace connecting pet owners with verified pet sitters in South Africa. The platform handles the complete booking lifecycle from discovery to payment, with real-time messaging, meet & greet scheduling, and comprehensive safety features.

Market Context:

  • South Africa's pet sitting market: R50.8M (2024), projected R85.3M by 2030
  • 14.1% CAGR - fastest growing segment in pet services
  • No dominant local digital marketplace - clear first-mover opportunity

Development Timeline: December 2025 - March 2026 (5 sprints)


Technical Architecture

Tech Stack

Frontend

  • Next.js 14 (App Router)
  • React 19
  • TypeScript
  • Tailwind CSS 4
  • Lucide Icons

Backend

  • Next.js API Routes
  • Supabase (PostgreSQL)
  • Supabase Auth
  • Supabase Storage
  • Supabase Realtime

Integrations

  • Paystack (Payment Processing)
  • Resend (Transactional Email)
  • Upstash Redis (Rate Limiting)
  • Sentry (Error Monitoring)
  • Vercel Analytics

Testing & DevOps

  • Playwright (E2E Testing)
  • Vercel (Deployment)
  • GitHub (Version Control)

Database Architecture

15 Core Tables:

  • profiles - User accounts with role-based access
  • sitter_profiles - Sitter-specific data (services, rates, verification)
  • client_profiles - Client home details and verification
  • agencies - Agency management
  • pets - Pet profiles with medical notes
  • bookings - Booking lifecycle management
  • meet_greets - Meet & greet scheduling
  • messages - Real-time messaging
  • conversations - Message threading
  • transactions - Payment tracking
  • reviews - Rating system
  • notifications - User notifications
  • check_ins - Daily check-in reports
  • pet_care_notes - Care activity logs
  • incident_reports - Safety incident tracking

Key Database Features:

  • Row-Level Security (RLS) policies on all tables
  • Exclusion constraints for booking overlap prevention
  • Stored procedures for complex operations
  • Automatic timestamp triggers
  • Enum types for type safety

Core Features

1. Multi-Role Authentication System

Three distinct user roles with separate dashboards:

  • Clients - Pet owners booking services
  • Sitters - Service providers
  • Agency Admins - Managing multiple sitters

Implementation Highlights:

  • Supabase Auth with email/password
  • Role-based route protection via middleware
  • Profile completion tracking
  • Document verification workflow

2. Sitter Discovery & Booking

Search & Filtering:

  • Service type (pet sitting, house sitting)
  • Pet type compatibility
  • Rate range
  • Location-based search
  • Rating filter

Booking Flow:

  1. Client selects sitter and dates
  2. System calculates pricing (platform fee: 15%)
  3. Client proposes meet & greet time slots
  4. Sitter accepts/declines booking
  5. Meet & greet scheduled and confirmed
  6. Deposit payment (50%) required
  7. Booking confirmed
  8. Final payment on completion

Technical Implementation:

// Booking overlap prevention using database exclusion constraint
CREATE EXTENSION IF NOT EXISTS btree_gist;

ALTER TABLE bookings 
ADD CONSTRAINT no_overlapping_bookings 
EXCLUDE USING gist (
  sitter_id WITH =,
  daterange(start_date, end_date, '[]') WITH &&
) WHERE (status NOT IN ('cancelled', 'completed'));

3. Real-Time Messaging

Features:

  • Conversation threading per booking
  • Real-time message delivery via Supabase Realtime
  • Unread message indicators
  • Message history

Implementation:

// Supabase Realtime subscription
const channel = supabase
  .channel('messages')
  .on(
    'postgres_changes',
    {
      event: 'INSERT',
      schema: 'public',
      table: 'messages',
      filter: `conversation_id=eq.${conversationId}`
    },
    (payload) => {
      setMessages(prev => [...prev, payload.new]);
    }
  )
  .subscribe();

4. Payment Processing

Payment Flow:

  • Deposit payment (50%) on booking confirmation
  • Final payment (50%) on completion
  • Platform fee deduction (15%)
  • Sitter payout to bank account
  • Refund processing for cancellations

Cancellation Fee Structure:

  • 7+ days before: Full refund
  • 3-6 days before: 50% refund
  • 1-2 days before: 25% refund
  • <24 hours: No refund

Security Features:

  • Webhook signature verification
  • Amount validation on payment verification
  • Double-processing prevention
  • Idempotent transaction handling
  • Rate limiting on payment endpoints

Implementation Example:

// Payment verification with amount validation
export async function POST(request: Request) {
  const { reference } = await validateRequestBody(request, paymentVerifySchema);
  
  // Verify with Paystack
  const response = await verifyPayment(reference);
  
  if (response.data.status !== 'success') {
    return NextResponse.json({ error: 'Payment failed' }, { status: 400 });
  }
  
  // Validate amount matches expected
  if (response.data.amount !== transaction.amount) {
    console.error('Amount mismatch detected');
    return NextResponse.json({ error: 'Payment amount mismatch' }, { status: 400 });
  }
  
  // Update booking status atomically
  const { error } = await supabase
    .from('transactions')
    .update({ status: 'completed' })
    .eq('reference', reference)
    .eq('status', 'pending'); // Prevent double-processing
    
  // ... rest of logic
}

5. Meet & Greet System

Workflow:

  1. Client proposes 3 time slots during booking
  2. Sitter selects preferred slot
  3. System sends confirmation to both parties
  4. Automated reminders (24h and 2h before)
  5. Check-in confirmation on the day

Cron Job Implementation:

// Automated reminders via Vercel Cron
// /api/cron/meet-greet-reminders
export async function GET(request: Request) {
  // Verify cron secret
  const authHeader = request.headers.get('authorization');
  if (authHeader !== `Bearer ${process.env.CRON_SECRET}`) {
    return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });
  }
  
  const today = new Date().toLocaleDateString('en-CA', { 
    timeZone: 'Africa/Johannesburg' 
  });
  
  // Find meet & greets needing reminders
  const { data: meetGreets } = await supabase
    .from('meet_greets')
    .select('*, bookings(*), profiles(*)')
    .eq('date', today)
    .eq('status', 'confirmed')
    .is('24_hour_reminder_sent', false);
    
  // Send reminders via email
  for (const mg of meetGreets) {
    await sendEmail({
      to: mg.profiles.email,
      subject: 'Meet & Greet Reminder - Tomorrow',
      // ... email content
    });
  }
}

6. Review System

Features:

  • Bidirectional reviews (client reviews sitter, sitter reviews client)
  • 5-star rating system
  • Optional written feedback
  • Average rating calculation
  • Review gating (only after booking completion)

Rating Calculation:

// Atomic rating update using database function
CREATE OR REPLACE FUNCTION update_sitter_rating(sitter_uuid UUID)
RETURNS void AS $$
BEGIN
  UPDATE sitter_profiles
  SET 
    rating_average = (
      SELECT COALESCE(AVG(rating), 0)
      FROM reviews
      WHERE reviewee_id = sitter_uuid
    ),
    rating_count = (
      SELECT COUNT(*)
      FROM reviews
      WHERE reviewee_id = sitter_uuid
    )
  WHERE user_id = sitter_uuid;
END;
$$ LANGUAGE plpgsql;

7. Daily Check-Ins

Features:

  • Sitters submit daily check-in reports during active bookings
  • Photo uploads of pets
  • Care activity logging (feeding, walks, medications)
  • Clients receive real-time updates

Security Implementation

Comprehensive Security Review Conducted

All critical findings resolved:

  1. RLS Policy Hardening

    • Fixed profiles INSERT policy to prevent user impersonation
    • Verified all 15 tables have appropriate policies
    • Service-role-only tables properly isolated
  2. Payment Security

    • Amount validation on all payment verifications
    • Webhook signature verification using timing-safe comparison
    • Double-processing prevention via conditional updates
    • Idempotent transaction handling
  3. Input Validation

    • Zod schemas on all API routes
    • Type-safe request/response handling
    • SQL injection prevention via parameterized queries
  4. Rate Limiting

    • Upstash Redis for distributed rate limiting
    • Endpoint-specific limits (payments: 10/min, API: 100/min)
    • IP-based and user-based identification
  5. Authentication & Authorization

    • All API routes verify authenticated user
    • Ownership checks on all mutations
    • Role-based access control
    • No client-side database mutations
  6. Timezone Handling

    • Explicit South Africa timezone (UTC+02:00) handling
    • Consistent date parsing to prevent edge cases
    • Documented patterns for all date operations

Security Review Results:

  • 2 Critical issues: Fixed
  • 5 Warning issues: Fixed
  • 4 Info items: Documented
  • Build verification: Passed

Code Quality & Best Practices

Input Validation Example

import { z } from 'zod';

export const createBookingSchema = z.object({
  sitterId: z.string().uuid('Invalid sitter ID'),
  serviceType: z.enum(['pet_sitting', 'drop_in_visits']),
  startDate: z.string().regex(/^\d{4}-\d{2}-\d{2}$/, 'Invalid date format'),
  endDate: z.string().regex(/^\d{4}-\d{2}-\d{2}$/, 'Invalid date format'),
  notes: z.string().max(1000).optional(),
});

// Usage in API route
export async function POST(request: Request) {
  const validation = await validateRequestBody(request, createBookingSchema);
  
  if (!validation.success) {
    return NextResponse.json({ error: validation.error }, { status: 400 });
  }
  
  const { sitterId, serviceType, startDate, endDate } = validation.data;
  // ... proceed with validated data
}

Rate Limiting Implementation

import { Ratelimit } from '@upstash/ratelimit';
import { Redis } from '@upstash/redis';

const redis = new Redis({
  url: process.env.UPSTASH_REDIS_REST_URL,
  token: process.env.UPSTASH_REDIS_REST_TOKEN,
});

export const rateLimiters = {
  payment: new Ratelimit({
    redis,
    limiter: Ratelimit.slidingWindow(10, '1 m'),
    prefix: 'rl:payment'
  }),
  api: new Ratelimit({
    redis,
    limiter: Ratelimit.slidingWindow(100, '1 m'),
    prefix: 'rl:api'
  }),
};

// Usage
export async function POST(request: Request) {
  const identifier = getClientIdentifier(request, userId);
  const { success, remaining, resetIn } = await checkRateLimit(identifier, 'payment');
  
  if (!success) {
    return rateLimitResponse(resetIn);
  }
  
  // ... proceed with request
}

Error Handling Pattern

export async function POST(request: Request) {
  try {
    // Auth check
    const { data: { user }, error: authError } = await supabase.auth.getUser();
    if (authError || !user) {
      return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });
    }
    
    // Rate limiting
    const { success } = await checkRateLimit(user.id, 'api');
    if (!success) {
      return NextResponse.json({ error: 'Too many requests' }, { status: 429 });
    }
    
    // Input validation
    const validation = await validateRequestBody(request, schema);
    if (!validation.success) {
      return NextResponse.json({ error: validation.error }, { status: 400 });
    }
    
    // Authorization check
    const { data: booking } = await supabase
      .from('bookings')
      .select('client_id')
      .eq('id', bookingId)
      .single();
      
    if (booking.client_id !== user.id) {
      return NextResponse.json({ error: 'Forbidden' }, { status: 403 });
    }
    
    // Business logic
    // ...
    
    return NextResponse.json({ success: true });
    
  } catch (error) {
    console.error('API Error:', error);
    return NextResponse.json(
      { error: 'Internal server error' },
      { status: 500 }
    );
  }
}

Testing Strategy

E2E Testing with Playwright

Test Coverage:

  • Authentication flows (signup, login, password reset)
  • Booking creation and cancellation
  • Payment processing
  • Meet & greet scheduling
  • Messaging functionality
  • Profile updates

Example Test:

test('complete booking flow', async ({ page }) => {
  // Login as client
  await page.goto('/login');
  await page.fill('[name="email"]', 'client@test.com');
  await page.fill('[name="password"]', 'password');
  await page.click('button[type="submit"]');
  
  // Search for sitter
  await page.goto('/search');
  await page.click('[data-testid="sitter-card"]:first-child');
  
  // Create booking
  await page.click('[data-testid="book-now"]');
  await page.fill('[name="startDate"]', '2026-05-01');
  await page.fill('[name="endDate"]', '2026-05-05');
  await page.click('[data-testid="submit-booking"]');
  
  // Verify booking created
  await expect(page.locator('[data-testid="booking-success"]')).toBeVisible();
});

Performance Optimizations

  1. Database Indexing

    • Composite indexes on frequently queried columns
    • GiST index for booking overlap exclusion
    • Partial indexes for active records
  2. Caching Strategy

    • Static page generation for landing pages
    • Dynamic rendering for authenticated routes
    • Supabase query caching
  3. Image Optimization

    • Next.js Image component for automatic optimization
    • Supabase Storage with CDN
    • WebP format support
  4. Code Splitting

    • Route-based code splitting via App Router
    • Dynamic imports for heavy components
    • Lazy loading for images

Deployment & DevOps

Hosting: Vercel (Production)

Environment Variables:

# Supabase
NEXT_PUBLIC_SUPABASE_URL=
NEXT_PUBLIC_SUPABASE_ANON_KEY=
SUPABASE_SERVICE_ROLE_KEY=

# Paystack
PAYSTACK_SECRET_KEY=
PAYSTACK_PUBLIC_KEY=

# Upstash Redis
UPSTASH_REDIS_REST_URL=
UPSTASH_REDIS_REST_TOKEN=

# Email
RESEND_API_KEY=

# Monitoring
SENTRY_DSN=

# Cron
CRON_SECRET=

Automated Workflows:

  • Booking status updates (daily cron)
  • Meet & greet reminders (daily cron)
  • Health check monitoring (5-minute intervals)

Key Learnings & Technical Decisions

1. Timezone Handling

Challenge: Date-only strings parsed as UTC caused booking status bugs.

Solution: Explicit timezone conversion for all date operations:

// Server-side (cron jobs)
const today = new Date().toLocaleDateString('en-CA', { 
  timeZone: 'Africa/Johannesburg' 
});

// Date parsing for comparisons
const startDate = new Date(booking.start_date + 'T00:00:00');
const endDate = new Date(booking.end_date + 'T23:59:59');

2. Payment Double-Processing Prevention

Challenge: Webhook and callback could both process the same payment.

Solution: Conditional updates with status checks:

const { error } = await supabase
  .from('transactions')
  .update({ status: 'completed' })
  .eq('reference', reference)
  .eq('status', 'pending'); // Only update if still pending

3. Booking Overlap Prevention

Challenge: Prevent double-booking sitters.

Solution: Database-level exclusion constraint:

EXCLUDE USING gist (
  sitter_id WITH =,
  daterange(start_date, end_date, '[]') WITH &&
) WHERE (status NOT IN ('cancelled', 'completed'));

4. Real-Time Updates

Challenge: Keep messaging UI in sync across devices.

Solution: Supabase Realtime subscriptions with optimistic updates:

// Optimistic update
setMessages(prev => [...prev, newMessage]);

// Send to server
const { error } = await supabase.from('messages').insert(newMessage);

// Realtime subscription handles sync across devices

Project Metrics

Development:

  • 5 sprints over 3 months
  • 27/27 tasks completed
  • 15 database tables
  • 40+ API routes
  • 50+ React components

Code Quality:

  • TypeScript strict mode enabled
  • Zero ESLint errors
  • All security findings resolved
  • Comprehensive E2E test coverage

Performance:

  • Lighthouse score: 95+ (desktop)
  • First Contentful Paint: <1.5s
  • Time to Interactive: <2.5s

Skills Demonstrated

Full-Stack Development:

  • Next.js 14 App Router architecture
  • Server Components and Server Actions
  • API route design and implementation
  • Database schema design and optimization

AI & Automation:

  • Automated workflow scheduling
  • Email automation
  • Cron job implementation

Payment Integration:

  • Paystack API integration
  • Webhook handling
  • Transaction management
  • Refund processing

Security:

  • Authentication and authorization
  • Row-Level Security policies
  • Input validation and sanitization
  • Rate limiting and DDoS prevention
  • Security audit and remediation

Database:

  • PostgreSQL schema design
  • Complex queries and joins
  • Stored procedures
  • Exclusion constraints
  • Performance optimization

DevOps:

  • Vercel deployment
  • Environment management
  • Error monitoring (Sentry)
  • Cron job scheduling

Repository Structure

siteasy/
├── src/
│   ├── app/                    # Next.js App Router
│   │   ├── (auth)/            # Auth routes
│   │   ├── (dashboard)/       # Sitter dashboard
│   │   ├── (client)/          # Client dashboard
│   │   ├── (agency)/          # Agency dashboard
│   │   └── api/               # API routes
│   ├── components/            # React components
│   ├── lib/                   # Utilities and helpers
│   │   ├── supabase/         # Supabase clients
│   │   ├── paystack.ts       # Payment integration
│   │   ├── validation.ts     # Zod schemas
│   │   └── rateLimit.ts      # Rate limiting
│   └── middleware.ts          # Auth middleware
├── supabase/
│   └── migrations/            # Database migrations
├── public/                    # Static assets
├── tests/                     # Playwright tests
└── docs/                      # Documentation
    ├── IMPLEMENTATION.md      # Sprint tracker
    ├── SECURITY_REVIEW.md     # Security audit
    └── MARKET_RESEARCH.md     # Market analysis

Conclusion

SitEasy demonstrates production-ready full-stack development with a focus on security, scalability, and user experience. The project showcases expertise in modern web technologies, payment integration, real-time features, and comprehensive security practices.

This case study represents:

  • 3 months of focused development
  • Production-grade code quality
  • Security-first architecture
  • Real-world business application
  • Market research and product strategy

Note: This is a case study of a private business venture. The actual codebase remains private to protect competitive advantage. This documentation showcases the technical architecture, implementation patterns, and engineering decisions without revealing proprietary business logic.

For employment verification or technical discussions, please contact via GitHub.

About

Case study: Production-ready pet-sitting marketplace for South Africa. Next.js, TypeScript, Supabase, Paystack.

Topics

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors