Skip to content

Repository files navigation

Marina Boat Tracking and Reservation System

A dual-sided mobile application for managing marina operations, built with React Native (Expo) and Supabase.


Overview

The Marina app serves two types of users:

  • Customers — boat owners who schedule put-ins, manage reservations, track their boat's status, and communicate with marina staff
  • Workers / Admins — marina staff who manage the slip board, approve boats, track daily operations, message customers, and (admins) generate invite codes

The system manages a fleet of 20 boats across 6 physical boat slips, with business logic that automatically handles slip assignment, overflow reservations, and end-of-day return enforcement.


Tech Stack

Layer Technology
Mobile (iOS + Android) React Native via Expo (SDK 54)
Routing Expo Router (file-based)
Backend & Database Supabase (Postgres)
Authentication Supabase Auth
Real-time sync Supabase Realtime (postgres_changes)
Scheduled jobs pg_cron (via Supabase)
Push notifications Expo Notifications + Supabase Edge Functions
Maps @rnmapbox/maps (requires dev build)
Session storage AsyncStorage

Project Structure

marina-app/
├── app/
│   ├── (auth)/
│   │   ├── login.tsx               # Shared login screen
│   │   ├── register.tsx            # Customer self-registration
│   │   └── worker-register.tsx     # Worker/admin invite-code registration
│   ├── (customer)/
│   │   ├── _layout.tsx
│   │   ├── index.tsx               # Customer dashboard
│   │   ├── boats.tsx               # My boats + add boat
│   │   ├── reservations.tsx        # Make a reservation
│   │   ├── my-reservations.tsx     # View + cancel reservations
│   │   ├── notifications.tsx       # Inbox + system notifications
│   │   └── map.tsx                 # Boat location (stubbed in Expo Go)
│   ├── (worker)/
│   │   ├── _layout.tsx
│   │   ├── index.tsx               # Worker dashboard
│   │   ├── boats.tsx               # Boat approvals + archive/delete
│   │   ├── schedule.tsx            # Slip board + daily schedule
│   │   ├── messages.tsx            # Worker message inbox
│   │   ├── chat/[customerId].tsx   # Threaded conversation with a customer
│   │   ├── invite-codes.tsx        # Admin invite code management
│   │   └── map.tsx                 # Marina map (stubbed in Expo Go)
│   └── _layout.tsx                 # Root layout + auth gate
├── src/
│   ├── lib/
│   │   ├── supabase.ts             # Supabase client
│   │   ├── boatService.ts          # Boat CRUD + location pinning
│   │   ├── inviteCodeService.ts    # Invite code generation
│   │   ├── notifications.ts        # Push token registration
│   │   ├── notificationService.ts  # Push notification sending
│   │   ├── mapbox.ts               # Mapbox init (dev build only)
│   │   └── constants.ts            # Marina coordinates + slip locations
│   ├── services/
│   │   ├── reservationService.ts   # Reservation RPC calls
│   │   └── workerService.ts        # Worker schedule + status updates (canonical file)
│   ├── context/
│   │   └── AuthContext.tsx         # Session, profile, role state + periodic role re-check
│   ├── hooks/
│   │   └── useAuth.ts
│   └── types/
│       └── database.ts
└── supabase/
    ├── functions/
    │   └── send-push-notification/ # Edge function for push delivery
    │       └── index.ts
    └── migrations/
        ├── 001_initial_schema.sql
        └── 002_customer_reservation_rpc.sql

Database Schema

Tables

Table Description
profiles Extends Supabase auth users with full name, phone, role, and push token
invite_codes One-time codes for worker/admin registration, with grants_role
boats Customer boats with approval status and archive flag
slips The 6 physical boat slips with availability status
reservations All bookings with slip assignment, return alert flag, and worker notes
boat_locations Worker-pinned GPS coordinates for boat tracking
notifications Push notification queue and history
messages Worker to customer direct messages (threaded)

Enums

user_role:           customer | worker | admin
boat_status:         in_water | on_land | maintenance
reservation_status:  pending | confirmed | in_water | returned | overdue | cancelled
notification_type:   reservation_confirmed | return_reminder | return_overdue |
                     slip_assigned | slip_released | worker_message | location_updated

Core Business Logic

Slip Assignment Rules

The marina has 6 physical slips reserved exclusively for boats staying past 5:00 PM. Early returners do not need a slip — the forklift operator handles them when they return.

Customer books with return before 5:00 PM
  -> No slip assigned
  -> Forklift pulls boat on return

Customer books with return at or after 5:00 PM
  -> System checks: how many slips are held by other late stayers?
  -> If a slip is available -> assigned automatically
  -> If all 6 slips are taken by late stayers -> overflow reservation
     -> needs_return_alert = true
     -> Customer must return by 4:45 PM

The RPC public.create_customer_reservation uses row-level locking (FOR UPDATE SKIP LOCKED) to prevent race conditions during concurrent bookings, and includes optimistic-concurrency checks so a status or slip change is rejected (with a clear message) if the underlying row changed since it was last loaded.

Time Slot Blocking

  • Time slots are in 15-minute increments from 9:00 AM to 5:00 PM
  • One boat per time slot (the forklift handles one boat at a time)
  • A reservation blocks both its put-in time slot and its return time slot
  • Taken slots are hidden from the time picker for other customers
  • The RPC rejects bookings for already-taken slots as a server-side safeguard

Overdue Auto-Trigger

A pg_cron job runs every 15 minutes and marks no-slip in_water reservations as overdue if their expected return has passed or it's past 4:45 PM. Slip boats are never marked overdue (they stay overnight by design).

4:45 PM Alert

A pg_cron job runs at exactly 4:45 PM daily and notifies all in_water no-slip customers to return immediately.


Authentication & Roles

Customer Registration

Self-service via the registration screen. Email confirmation disabled for development.

Worker / Admin Registration

Invite-code gated. The invite code carries a grants_role so a code can create either a worker or an admin. Codes are redeemed atomically via a row-locked database function to prevent two people redeeming the same code at once, and single-use codes expire after 7 days.

Admin Capabilities

Admins can generate, copy, and deactivate invite codes from inside the app (Invite Codes screen). To create the first admin, manually update a profile's role to admin in SQL.

Role-Based Routing

The root layout reads the user's role after login and routes:

  • customer -> /(customer)/
  • worker or admin -> /(worker)/

A periodic re-check keeps an already-logged-in session in sync if a role changes elsewhere (e.g. an admin promotes or demotes someone while they're still signed in).


Features Completed

Phase 1 — Foundation

  • Expo project with TypeScript and expo-router
  • Full Supabase schema with RLS on all tables
  • Role-based auth (customer self-register, worker/admin invite-code)
  • Automatic profile creation via database trigger
  • Role-based navigation gate
  • Login, customer registration, and worker registration screens

Phase 2 — Core Booking

  • Customer boat registration with name, make/model, and boat number
  • Worker boat approval and rejection flow with reason
  • Reservation system with correct 6-slip business logic
  • Calendar date picker with past-date blocking
  • Customer reservations list (today + upcoming)
  • Cancel reservation with confirmation (race-guarded against concurrent worker updates)
  • Multi-boat support per customer

Phase 3 — Worker Operations

  • Worker daily schedule with date navigation (all dates)
  • Slip board with 6 slips (available / occupied / overdue / unavailable)
  • Worker can mark slips unavailable and restore them
  • No-slip boats section with 4:45 PM urgent alert
  • Summary bar (slips used, no-slip boats, overdue)
  • Reservation action sheet with full boat/customer details
  • Mark boats in water / returned / overdue, with undo paths in both directions
  • Expandable "Returned (N)" section so returned boats stay reachable for undo
  • Slip reassignment
  • Overdue auto-trigger via pg_cron
  • Real-time sync between worker devices, including live refresh of an open action sheet
  • Boat archive (worker) / restore / permanent delete (admin)
  • Archived boats show as inactive to the customer

Phase 4 — Map & Location

  • Mapbox map screens written (worker pin-drop + customer view)
  • Slip and yard location markers defined
  • Requires a development build to run (stubbed with placeholder screens in Expo Go for now)

Phase 5 — Notifications & Messaging

  • Push token registration and saving
  • Edge function delivering push notifications to device (working)
  • Worker messages customer from action sheet, in a threaded conversation view
  • Customer inbox with unread badge
  • Customer can reply to worker messages
  • Worker message inbox with unread badge and reply
  • Customers can message staff once they have at least one approved boat
  • Reservation confirmed notification
  • Boat status update notifications (non-blocking)
  • 4:45 PM deadline alert scheduled
  • Keyboard-avoiding inputs on all message screens

Phase 6 — Polish & Edge Case Hardening

  • 15-minute time slots with forklift buffer blocking
  • Admin invite code generation (auto + custom codes, worker/admin roles)
  • Status flow undo paths (return -> undo, overdue -> back to in water)
  • Archive warning when a boat has upcoming reservations
  • Flag reservations for review when their boat is archived
  • Non-blocking notifications so push failures never crash core actions
  • Concurrency hardening: optimistic concurrency checks on status/slip changes, live action-sheet refresh via realtime, race guard on customer cancellations
  • Account/auth hardening: periodic role re-check while logged in, atomic invite-code redemption, an 8-second timeout safeguard for an auth cold-start hang
  • Boat & reservation integrity edge cases (customer with zero boats reaching the reservation screen, duplicate boat resubmission, reservation date/timezone edge cases)
  • Messaging edge cases (a demoted/deleted worker's existing conversations, a customer's last approved boat archived before staff reply)
  • Notification edge cases (a stale push token after reinstall or a new device)

Phases Remaining

Phase Feature
6 Remaining edge case sweeps (boat/reservation integrity, messaging, notifications — see above)
6 Development build to enable Mapbox
6 Test maps with real marina coordinates
6 Final QA
6 App Store and Play Store builds

Environment Setup

Prerequisites

  • Node.js 18+
  • Expo CLI and EAS CLI (npm install -g expo-cli eas-cli)
  • Supabase account
  • Expo account (expo.dev)
  • Docker Desktop (required for deploying edge functions)
  • Expo Go app on a physical device

Installation

git clone https://github.com/bqhorsfall/marina-app.git
cd marina-app
npm install --legacy-peer-deps

Environment Variables

Create a .env file in the project root (gitignored — never commit this file):

SUPABASE_URL=https://your-project.supabase.co
SUPABASE_ANON_KEY=your-anon-key-here
MAPBOX_SECRET_TOKEN=your-mapbox-secret-token-here

Reference these via process.env inside app.config.ts rather than hardcoding any key or token as a literal string in source.

Database Setup

  1. Run the migrations in the Supabase SQL editor
  2. Seed the slips and an invite code:
insert into public.slips (slip_number) values (1),(2),(3),(4),(5),(6);

insert into public.invite_codes (code, grants_role, expires_at)
values ('MARINA-WORKER-2025', 'worker', now() + interval '365 days');
  1. Schedule the cron jobs:
select cron.schedule('mark-overdue-reservations', '*/15 * * * *',
  'select public.mark_overdue_reservations()');

select cron.schedule('send-445-alerts', '45 16 * * *',
  'select public.send_445_alerts()');

Edge Function (requires Docker running)

supabase login
supabase link --project-ref your-project-ref
supabase functions deploy send-push-notification --no-verify-jwt

If the function serves stale code after a CLI deploy, edit it directly in the Supabase dashboard editor and save there — this has been the reliable fallback in this project.

Running the App

npx expo start --clear

Scan the QR code with Expo Go. Both devices must be on the same WiFi network.


Known Limitations

  • Map-based location tracking requires a development build (not available in Expo Go).
  • Email confirmation is disabled in development — enable before production.
  • Apple App Store distribution requires a $99/year Apple Developer account.

Security Notes

  • Never commit .env to version control.
  • Row-level security is enabled on all tables.
  • Invite codes are redeemed atomically (row-locked) and expire after 7 days.
  • Admin-only actions check role via the profiles table in RLS policies rather than a self-referencing policy on profiles itself, which avoids infinite recursion.
  • The service role key lives only in the edge function — never expose it to the client.
  • Never hardcode a secret token (Mapbox secret token, service role key, etc.) directly in a source file such as app.config.ts — reference it via an environment variable instead.
  • If a secret is ever accidentally committed, rotate it immediately in the provider's dashboard and remove it from git history (e.g. with git filter-repo) before pushing again — deleting it from the current file alone is not sufficient, since GitHub's push protection scans full commit history.

Built with React Native, Expo, and Supabase.

About

Mobile app for marina operations — customers book boat put-ins and reservations while staff manage a 6-slip board, approvals, messaging, and 4:45 PM return enforcement. Built with React Native (Expo) and Supabase.

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages