Technical Documentation

Architecture & Implementation

Tech stack, data models, RevenueCat integration, and build configuration for Lovin' Van Life.

Overview

Lovin' Van Life is a native iOS app built with React Native and Expo (managed workflow). It uses Firebase for auth, real-time data, and storage, RevenueCat for subscription management, and Zustand for client-side state. The app targets iOS 16+ with the New Architecture (Fabric/TurboModules) enabled.

Tech Stack

LayerTechnologyPurpose
FrameworkReact Native + ExpoCross-platform mobile app (iOS primary)
NavigationExpo RouterFile-based routing with typed routes
AuthFirebase AuthEmail/password authentication
DatabaseCloud FirestoreReal-time document database with security rules
StorageFirebase Cloud StorageProfile photos and media
PaymentsRevenueCat SDKSubscription management, entitlements, paywall UI
StateZustand + AsyncStoragePersisted client-side state management
Locationexpo-locationGPS positioning and trajectory tracking
LanguageTypeScriptType safety throughout the codebase

Architecture

📱
Expo Router (File-Based Navigation)

app/(auth)/ · app/(onboarding)/ · app/(tabs)/ · app/edit-profile · app/van-build

💗
Dating Tab

Trajectory matching, swipe cards, map view, messaging

🏔
Community Tab

Interest Beacons, activity map, join/leave

🔧
Builders Tab

Expert directory, booking, consultations

👤
Profile Tab

Edit profile, van details, theme picker, settings

🧸
Zustand Stores

authStore · themeStore · locationStore · caravanStore · beaconStore

🔥
Firebase Backend

Auth · Firestore · Cloud Storage · Security Rules

File Layout

app/
  (auth)/              # Login, register, invite-code screens
    _layout.tsx
    login.tsx
    register.tsx
    invite-code.tsx
  (onboarding)/        # 3-step onboarding flow
    _layout.tsx
    activities.tsx
    van-details.tsx
    trajectory.tsx
  (tabs)/              # Main tab navigator
    _layout.tsx
    dating.tsx
    community.tsx
    builders.tsx
    profile.tsx
  _layout.tsx          # Root layout (auth gate)
  edit-profile.tsx
  van-build.tsx
  modal.tsx
stores/
  authStore.ts         # Auth state + Firebase listener
  themeStore.ts        # Theme persistence (7 themes)
  locationStore.ts     # GPS + location sharing
  caravanStore.ts      # Group travel (v2, deferred)
  beaconStore.ts       # Activity beacons state
lib/
  revenuecat.ts        # RevenueCat SDK wrapper
  geo.ts               # Geospatial utilities
  synchronicity.ts     # Match scoring algorithm
  seedDatabase.ts      # Demo data seeder
constants/
  Colors.ts            # 7 themes: palettes, buildColors(), buildMapStyle()
types/
  index.ts             # All TypeScript interfaces and types

Theming System

The app supports 7 color themes: golden-hour (default), campfire, northern-lights, crimson-night, sunset-fade, forest, and arctic. The theme system is built around a ThemePalettebuildColors()ThemeColors pipeline in constants/Colors.ts.

A Zustand store (stores/themeStore.ts) persists only the themeId string to AsyncStorage and rebuilds the full colors and mapStyle objects on rehydration. All components consume colors via useThemeStore((s) => s.colors) and use a makeStyles(colors) pattern to generate dynamic StyleSheet objects.

Data Models

All data is stored in Cloud Firestore. Below are the core document types.

User

The central document in users/{uid}. Contains profile information, travel preferences, van details, and computed fields.

interface User {
  id: string
  email: string
  displayName: string
  photoURL?: string
  photos?: string[]
  bio?: string
  inviteCode: string           // Generated on registration
  invitedBy?: string           // UID of inviter
  isPremium: boolean            // Synced from RevenueCat
  activities: Activity[]
  vanDetails?: VanDetails
  age?: number
  gender?: Gender
  currentLocation?: { lat, lng, name }
  travelPace?: TravelPace       // 'slow' | 'moderate' | 'fast'
  campingPreferences?: CampingPreference[]
  workStyle?: WorkStyle
  socialVibe?: SocialVibe
  lookingFor?: LookingFor[]
  synchronicityScore?: number  // Computed, not stored
}

Trajectory

Stored in trajectories/{id}. Each trajectory contains an array of planned destination locations with time windows.

interface Trajectory {
  id: string
  userId: string
  locations: TrajectoryLocation[] // city, state, lat, lng, startDate, endDate
  isActive: boolean
  updatedAt: Date
}

Beacon

Activity beacons for community meetups. Stored in beacons/{id}.

interface Beacon {
  id: string
  userId: string
  activity: Activity            // 'hiking' | 'climbing' | 'yoga' | ...
  title: string
  location: { lat, lng, name }
  dateTime: Date
  maxParticipants: number
  participants: string[]        // Array of UIDs
  status: BeaconStatus          // 'open' | 'full' | 'completed' | 'cancelled'
}

Builder & Consultation

interface Builder {
  id: string
  userId: string
  specialties: BuilderSpecialty[] // 'electrical' | 'solar' | 'plumbing' | ...
  hourlyRate: number
  rating: number
  reviewCount: number
  isAvailable: boolean
}

interface Consultation {
  builderId: string
  clientId: string
  status: ConsultationStatus
  price: number
  topic: string
  revenuecatTransactionId?: string  // Links to RevenueCat
}

Match & Messaging

interface Match {
  users: [string, string]
  status: 'pending' | 'matched' | 'declined'
  synchronicityScore: number
  likedBy: string[]
}

interface Conversation {
  participants: [string, string]
  lastMessage?: string
  unreadCount: number
}

Auth & Invite System

Authentication Flow

  1. User receives an invite code from an existing member
  2. Enters code on invite-code.tsx — validated against Firestore invites/{code} document
  3. Registers with email/password via Firebase Auth
  4. Three-step onboarding: select activities, enter van details, set trajectory destinations
  5. User document created in Firestore with a freshly generated invite code for them to share

Invite Code Architecture

Each invite code is a Firestore document at invites/{code}. Validation uses Firestore transactions to atomically check availability and mark the code as consumed, preventing race conditions. Security rules allow any authenticated user to read (validate) codes, but only the system can write to them.

interface Invite {
  code: string              // 8-char alphanumeric
  createdBy: string         // UID of the member who generated it
  usedBy?: string           // UID of the person who redeemed it
  createdAt: Date
  usedAt?: Date
}

RevenueCat Implementation

All subscription management flows through RevenueCat. The app uses react-native-purchases and react-native-purchases-ui for SDK integration and native paywall presentation.

SDK Configuration

RevenueCat is initialized in lib/revenuecat.ts with a singleton pattern. Configuration is deferred until first use and supports both anonymous and identified users.

// lib/revenuecat.ts — initialization
import Purchases from 'react-native-purchases';

const configureRevenueCat = async (appUserID?: string) => {
  const configured = await Purchases.isConfigured();
  if (configured) return;

  await Purchases.setLogLevel(LOG_LEVEL.DEBUG);
  Purchases.configure({ apiKey, appUserID });
};

Products & Entitlements

ConceptIdentifierDescription
EntitlementLovin Van life ProGrants access to all premium features
Product (Monthly)monthly$14.99/month auto-renewing subscription
Product (Annual)annual$99.99/year auto-renewing subscription

Paywall Presentation

The app uses RevenueCatUI to present native paywalls. Two presentation modes are used:

// Present paywall unconditionally (e.g., from profile settings)
import RevenueCatUI from 'react-native-purchases-ui';
const result = await RevenueCatUI.presentPaywall();

// Present only if user lacks the Pro entitlement
const result = await RevenueCatUI.presentPaywallIfNeeded({
  requiredEntitlementIdentifier: 'Lovin Van life Pro',
});

Entitlement Checking

Premium status is checked via checkPremiumStatus() which queries the RevenueCat SDK for the active "Lovin Van life Pro" entitlement. This is called on app launch and after subscription events, with the result stored in the authStore Zustand store so all screens can gate features consistently.

export const checkPremiumStatus = async (): Promise<boolean> => {
  const customerInfo = await Purchases.getCustomerInfo();
  return typeof customerInfo.entitlements.active['Lovin Van life Pro'] !== 'undefined';
};

Feature Gating

Premium features are gated at the point of action, not at the tab level. Free users can browse and discover but are prompted to upgrade when they:

  • Exceed daily match limits on the Dating tab
  • Try to book a builder consultation
  • Attempt to create more than the free-tier beacon limit
  • Try to view profile visitors

Additional RevenueCat APIs Used

  • Purchases.logIn(userId) — Associates Firebase UID with RevenueCat customer on login
  • Purchases.logOut() — Resets to anonymous on sign-out
  • Purchases.getOfferings() — Fetches configured offerings for custom paywall scenarios
  • Purchases.purchasePackage(pkg) — Direct package purchase (used for builder consultations)
  • Purchases.restorePurchases() — Restore purchases flow
  • RevenueCatUI.presentCustomerCenter() — Native subscription management screen

Feature Breakdown

Trajectory Matching

Trajectories are stored as arrays of discrete destinations (city, state, lat/lng, date window). Matching compares overlapping destinations within time windows between two users. A synchronicity score (computed in lib/synchronicity.ts) weights proximity, temporal overlap, and shared interests to rank matches. Firestore doesn't support native geospatial intersection queries, so matching is computed client-side by comparing destination documents.

Interest Beacons

Beacons are Firestore documents with geolocation, activity type, time, and a participants array. The Community tab renders beacons on an interactive map using react-native-maps. Users can join/leave beacons, and real-time Firestore listeners keep participant counts live.

Builder Marketplace

Builder profiles are stored in a builders collection linked to user documents. Consultations are separate documents tracking status, pricing, scheduling, and an optional revenuecatTransactionId for linking payments. Builder booking is gated behind the Pro entitlement.

Van Build Tracker

Each user's van build is tracked through vanDetails.buildFeatures — an array of categorized features (electrical, kitchen, bathroom, climate, water, bed, storage, tech, exterior) with status tracking (wishlist, planned, in-progress, installed), cost, specs, and photos.

Build Configuration

Expo Config

SettingValue
Expo SDKManaged workflow
New Architecturetrue (Fabric/TurboModules)
iOS Deployment Target16.0
Bundle ID (iOS)com.vanlife.lovin
Package (Android)com.sunnysparyowstudios.lovinvanlife
Typed Routestrue

iOS-Specific Build Configuration

Running @react-native-firebase with Expo's New Architecture requires specific build settings:

// expo-build-properties plugin config
{
  ios: {
    useFrameworks: "static",         // Required for Firebase on iOS
    buildReactNativeFromSource: true,
    deploymentTarget: "16.0",
    cppFlags: "-DFOLLY_NO_CONFIG -DFOLLY_MOBILE=1 ..." // Folly compat
  }
}

// Custom plugin: plugins/withFirebaseFix.js
// Patches Xcode project for Firebase + New Architecture compatibility

Hosting

The landing page is deployed to Firebase Hosting at lovin-van-life.web.app (aliased to lovinvanlife.com). The app binary is distributed via TestFlight for iOS beta testing.