8000 GitHub - gdsuta/mai-milu: Bali Carpooling Community Β· GitHub
[go: up one dir, main page]

Skip to content
gdsutaPublic

About

Bali Carpooling Community

Resources

Stars

0 stars

Watchers

0 watching

Forks

Latest commit

Β 

History

86 Commits

Folders and files

Repository files navigation

πŸš— Mai-Milu β€” Bali Carpool Community

Mai-Milu (Balinese: "Let's Join") is a mobile-first carpooling web application that connects drivers and passengers on one-way routes across Bali, Indonesia.

Live Demo Next.js Supabase Tailwind CSS License: MIT


πŸ“– Table of Contents


🌟 Overview

Mai-Milu digitises the informal ride-sharing culture of Bali into a structured, secure, and efficient ecosystem. It reduces daily commuting costs and traffic congestion by matching verified drivers who have empty seats with passengers heading in the same direction.

The app is built with a modern, serverless stack and is fully Progressive Web App (PWA) compliant β€” users can install it directly from their mobile browser with no app store required.

"Mai Milu" literally means "Let's Join Together" in Balinese, reflecting the community spirit at the heart of the platform.


✨ Key Features

πŸ” Security & Trust

  • KTP-based identity verification β€” all users submit a national ID photo and selfie before being approved by an admin
  • Multi-layer role system β€” admin, verified user, and pending user roles with enforced Row Level Security (RLS) at the database level
  • Atomic booking transactions β€” PostgreSQL RPCs with row-locking prevent race conditions and double-booking
  • Secure password recovery β€” check_email_registered() RPC prevents email enumeration attacks on the forgot-password flow

πŸš— Ride Management

  • Post single rides or recurring weekly schedules (Mon–Fri, up to 8 weeks ahead) with a live ride-count preview
  • OpenRouteService + Nominatim integration β€” calculates driving distance and auto-suggests a fuel cost price
  • Automatic ride expiry β€” past rides are hidden from the listing via a server-side time filter and a scheduled expire_past_rides() database function
  • Driver dashboard β€” manage all rides across four status tabs: Active, Completed, Cancelled, Expired

🎫 Booking System

  • Passengers can reserve a seat directly in the app (not just via WhatsApp)
  • Booking status flow: pending β†’ accepted / rejected, with seat count updating atomically
  • Drivers see pending booking notifications on their ride cards and can approve or reject with one tap
  • Passengers can cancel pending bookings; seats are restored automatically

πŸ” Smart Ride Discovery

  • Search & filter β€” live text search by origin/destination with yellow highlight, sort by departure time or price, free-rides-only toggle
  • Ride matching algorithm β€” scores each ride 0–100 against the user's home address (token overlap), departure urgency, price, and recurring status; top 3 matches appear above the full listing

⭐ Community & Ratings

  • Passengers can leave 1–5 star ratings with optional comments after a ride
  • Ratings aggregate into a driver_ratings view for efficient display on ride cards
  • Public driver profile pages at /driver/[id] β€” stats (completion rate, avg rating), rating distribution bars, review list, upcoming rides, and ride history

πŸ“± Mobile-First PWA

  • Installable on Android and iOS via the browser ("Add to Home Screen")
  • Standalone display mode β€” no browser chrome, feels native
  • 8 icon sizes + Apple touch icon + wide/narrow screenshots
  • Client-side image compression (Canvas API, zero dependencies) before upload: selfies at 800 px/0.82 JPEG quality, KTP photos at 1400 px/0.88 quality
  • Explicit camera buttons per upload field: front camera (capture="user") for selfie, rear camera (capture="environment") for KTP

πŸ’¬ Zero-Friction Communication

  • All ride contact flows through WhatsApp deep links with pre-filled messages β€” no internal messaging system to maintain
  • WhatsApp links are automatically formatted from the driver's registered phone number

πŸ›  Tech Stack

Layer Technology Purpose
Framework Next.js 16.2 (App Router, Turbopack) SSR, routing, server actions
Language TypeScript (strict mode) Type safety throughout
UI React 19 + Tailwind CSS v4 Components and styling
Icons Phosphor Icons Consistent icon system
Forms React Hook Form + Zod Validation and form state
Backend Supabase (PostgreSQL) Database, Auth, Storage, RLS
Auth helper @supabase/ssr Cookie-based session management
Maps OpenRouteService + Nominatim (OSM) Route distance calculation
Email Resend (custom SMTP) Transactional password reset emails
Deployment Vercel CI/CD via GitHub
Unit tests Jest + React Testing Library Algorithm and component tests
E2E tests Cypress Full user flow tests

πŸ— Architecture

mai-milu/
β”œβ”€β”€ app/                        # Next.js App Router pages
β”‚   β”œβ”€β”€ (auth)/
β”‚   β”‚   β”œβ”€β”€ login/              # Email/password login with show/hide toggle
β”‚   β”‚   β”œβ”€β”€ register/           # KTP + selfie upload with compression
β”‚   β”‚   β”œβ”€β”€ forgot-password/    # Reset with email existence guard
β”‚   β”‚   β”œβ”€β”€ update-password/    # Post-reset password form
β”‚   β”‚   └── auth/callback/      # Supabase OAuth code exchange
β”‚   β”œβ”€β”€ home/                   # Ride listing (server component)
β”‚   β”œβ”€β”€ offer-ride/             # Post single or recurring rides
β”‚   β”œβ”€β”€ my-rides/               # Driver's ride history + booking management
β”‚   β”œβ”€β”€ my-bookings/            # Passenger's booking history
β”‚   β”œβ”€β”€ driver/[id]/            # Public driver profile
β”‚   β”œβ”€β”€ admin/                  # KTP verification + user management
β”‚   β”œβ”€β”€ verification/           # Pending verification screen
β”‚   β”œβ”€β”€ error.tsx               # Global error boundary (Bahasa Indonesia)
β”‚   └── loading.tsx             # Global loading spinner
β”‚
β”œβ”€β”€ components/
β”‚   β”œβ”€β”€ Navbar.tsx              # Sticky nav with active-link highlighting
β”‚   β”œβ”€β”€ RideList.tsx            # Search, filter, matching algorithm, rating modal
β”‚   β”œβ”€β”€ MyRidesList.tsx         # Tabbed ride manager with booking panel
β”‚   β”œβ”€β”€ BookingButton.tsx       # Seat reservation CTA with confirmation modals
β”‚   β”œβ”€β”€ DriverProfile.tsx       # Public profile with stats, ratings, ride history
β”‚   β”œβ”€β”€ StarDisplay.tsx         # Inline star rating renderer
β”‚   β”œβ”€β”€ StarRatingModal.tsx     # 1-5 star rating with comment submission
β”‚   └── ZoomableImage.tsx       # Admin KTP photo viewer
β”‚
β”œβ”€β”€ lib/supabase/
β”‚   β”œβ”€β”€ server.ts               # createServer() β€” typed server-side client
β”‚   └── client.ts               # createClient() β€” typed browser-side client
β”‚
β”œβ”€β”€ types/
β”‚   └── supabase.ts             # Auto-generated DB types via Supabase CLI
β”‚
β”œβ”€β”€ __tests__/                  # Jest unit tests
β”œβ”€β”€ cypress/                    # E2E test suites
└── public/                     # PWA icons, manifest, screenshots

πŸ—„ Database Schema

-- Core tables
profiles        -- extends auth.users with KTP, avatar, role, verification_status
rides           -- driver posts with origin, destination, time, seats, price, status
                -- is_recurring + recurring_days[] for repeating schedules
bookings        -- passenger reservations (pending β†’ accepted/rejected/cancelled)
ratings         -- 1-5 star reviews with optional comment (one per passenger per ride)

-- View
driver_ratings  -- pre-aggregated avg_score + total_ratings per driver

-- Planned (schema exists, UI not yet built)
communities     -- location-based groups
trips           -- community-linked rides with price_per_km

Supabase RPC Functions

Function Description
book_ride(ride_id, passenger_id) Atomically inserts booking + decrements seats (row-locked)
cancel_booking(booking_id, passenger_id) Cancels booking + restores seat atomically
respond_to_booking(booking_id, driver_id, action) Driver approves or rejects; reject restores seat
expire_past_rides() Marks stale rides as kadaluarsa (called by pg_cron hourly)
check_email_registered(email) Guards password reset against non-existent emails (SECURITY DEFINER)

πŸš€ Getting Started

Prerequisites

1. Clone the repository

git clone https://github.com/gdsuta/mai-milu.git
cd mai-milu

2. Install dependencies

npm install

3. Set up environment variables

cp .env.example .env.local
# Then fill in the values β€” see the section below

4. Set up the Supabase database

Run the SQL migration files in your Supabase SQL Editor in this order:

sql/01_schema.sql           -- tables and enums
sql/02_rls_policies.sql     -- row-level security
sql/03_storage_policies.sql -- storage bucket policies
sql/04_rpc_functions.sql    -- book_ride, cancel_booking, respond_to_booking, etc.

Create two storage buckets in your Supabase dashboard:

  • avatars β€” set to Public
  • identity_docs β€” set to Private

5. Run the development server

npm run dev

Open http://localhost:3000 in your browser.


πŸ”‘ Environment Variables

Create a .env.local file in the project root:

# Supabase
NEXT_PUBLIC_SUPABASE_URL=https://your-project-id.supabase.co
NEXT_PUBLIC_SUPABASE_ANON_KEY=your-anon-key

# OpenRouteService (for route distance calculation)
NEXT_PUBLIC_ORS_API_KEY=your-ors-api-key

Never commit .env.local to version control. It is already listed in .gitignore.


πŸ§ͺ Running Tests

Unit tests (Jest)

npm test              # run once
npm run test:watch    # watch mode
npm run test:coverage # with coverage report

The unit test suite covers:

  • Ride matching algorithm β€” tokenize(), routeScore(), timeScore(), formatBytes() (25 assertions)
  • StarDisplay component β€” empty state, score display, count, size variants (7 assertions)

End-to-end tests (Cypress)

# Start the dev server first
npm run dev

# Then in a separate terminal:
npm run cy:open    # interactive UI mode (recommended for development)
npm run cy:run     # headless mode (for CI)

Create a cypress.env.json file with your test account credentials (already gitignored):

{
  "TEST_USER_EMAIL": "your-test-user@gmail.com",
  "TEST_USER_PASSWORD": "your-password",
  "TEST_ADMIN_EMAIL": "your-admin@gmail.com",
  "TEST_ADMIN_PASSWORD": "your-admin-password"
}

E2E suites cover: authentication flows, home page search/filter/matching, offer-ride form (single + recurring), and the Tumpangan Saya driver dashboard.


πŸ“ Project Structure Highlights

Ride Matching Algorithm (components/RideList.tsx)

Every ride is scored 0–100 against the current user before rendering:

Route score  (0–100)  β€” word overlap between user's home_address and ride origin/destination
Time score   (5–40)   β€” departing in ≀2h = 40pts, today = 30pts, this week = 10pts
Price bonus  (0–10)   β€” free rides +10pts, ≀Rp10.000 +5pts
Recurring    (+8)     β€” reliable repeating drivers get a trust boost

Top 3 rides scoring above 5 appear in the "Cocok untuk Anda" section above the main listing.

Atomic Booking (book_ride RPC)

BEGIN
  SELECT * FROM rides WHERE id = p_ride_id FOR UPDATE; -- row lock
  -- validate: ride is active, passenger β‰  driver, no duplicate, seats > 0
  INSERT INTO bookings ...
  UPDATE rides SET available_seats = available_seats - 1 ...
COMMIT

This guarantees seat counts never go negative even under concurrent requests.

Image Compression (app/register/page.tsx)

// Uses browser Canvas API β€” zero npm dependencies
// Selfie: max 800px wide, JPEG 0.82 quality β†’ ~150–250 KB
// KTP:    max 1400px wide, JPEG 0.88 quality β†’ ~300–500 KB
//         (wider + higher quality so admin can read the ID text)

🀝 Contributing

Contributions, bug reports, and feature requests are welcome! Here are the best areas to contribute:

Area Description
In-app chat Replace WhatsApp deep links with Supabase Realtime WebSocket chat
Payment gateway Integrate Midtrans or Xendit for in-app transactions
Push notifications Web Push API for new ride alerts
Automated ride matching ML-based route similarity beyond keyword overlap
E2E test coverage Expand Cypress suite to cover booking and rating flows

How to contribute

  1. Fork the repository
  2. Create a feature branch: git checkout -b feat/your-feature-name
  3. Make your changes and write tests where applicable
  4. Commit using Conventional Commits: git commit -m "feat: add push notifications"
  5. Push and open a Pull Request

Please read through the existing issues before opening a new one.


πŸ‘€ Author

Gede Suta Pinatih

Built as a real-world solution to the daily commuting challenges faced by people across Bali, combining modern web technology with local cultural context.


Made with ❀️ in Bali, Indonesia

Mai Milu β€” Bali Carpool Community

About

Bali Carpooling Community

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages

0