Skip to main content

Get running in 5 minutes

This guide will get you from zero to a working TableOrder app with both modes functional.
1

Install and configure

Follow the installation guide to set up your development environment. At minimum, you need:
# Clone and install
git clone https://github.com/Diego31-10/TableOrderApp.git
cd TableOrderApp
npm install

# Configure environment
cp .env.example .env
# Add your EXPO_PUBLIC_MAPBOX_TOKEN to .env

# Generate native projects
npx expo prebuild
You must have a valid Mapbox token. Get one free at account.mapbox.com.
2

Launch the app

Start the development server and run on your device:
npx expo run:android
The app will launch and request location permissions. Grant them to continue.
3

Test scanner mode

When the app opens, you’ll see an interactive map with your current location marked. To test scanner mode:
  1. Tap a location on the map within 50 meters of your current position (simulating being inside the restaurant)
  2. The app automatically switches to Scanner Mode
  3. The QR scanner activates with camera preview
On emulators, set a custom location first:
  • iOS Simulator: Features > Location > Custom Location
  • Android Emulator: Extended Controls (⋮) > Location
4

Scan a test QR code

Use one of these test QR codes to simulate scanning a table:
Generate a QR code with this string:
TABLE_HALL_05
Expected behavior:
  • Table name: “Salón 05”
  • Menu: Full catalog (food, drinks, snacks, desserts)
  • No special effects
Use qr.io or any QR generator to create a scannable code from this string.
After scanning, the contextual menu loads automatically based on the table type.
5

Add items to cart

Browse the menu and add items:
  1. Tap any product card to view details
  2. Tap “Add to cart” to add items
  3. Adjust quantities using + and - buttons
  4. Navigate to cart using the bottom tab bar
The cart automatically calculates:
  • Subtotal
  • Discount (if birthday table)
  • Final total
6

Complete checkout

Process a test payment:
  1. Tap “Proceed to Payment” in the cart
  2. Review your order summary
  3. Tap “Pay Now” to trigger the mock payment flow
What happens next:
  • 2-second payment simulation with loading state
  • Success notification with haptic feedback
  • PDF receipt auto-generated
  • Receipt sent to Telegram (if configured)
  • Push notification delivered
The payment service is mocked for testing. To integrate real Stripe payments, add EXPO_PUBLIC_STRIPE_KEY to your .env file.
7

Test delivery mode

Return to the map and test delivery flow:
  1. Tap the home icon to return to the context switcher
  2. Tap a location on the map farther than 50 meters from your position
  3. The app automatically switches to Delivery Mode
  4. You’re redirected to the full delivery catalog
Delivery features:
  • Full product catalog (all items available)
  • Per-kilometer shipping cost calculated via Mapbox Directions API
  • Real-time distance and ETA display
  • Post-payment route tracking with polyline visualization
8

View route tracking

After completing a delivery order:
  1. Payment processes successfully
  2. App automatically navigates to Track Order screen
  3. Interactive map displays:
    • Your location (delivery destination)
    • Restaurant location (pickup point)
    • Driving route polyline
    • Distance in kilometers
    • Estimated delivery time
Route tracking uses the Mapbox Directions API. Ensure EXPO_PUBLIC_MAPBOX_TOKEN is configured for this to work.

Understanding the code

How GPS mode detection works

The app uses Haversine formula to calculate distance between user and restaurant:
src/components/location/ContextSwitcher.tsx
function calculateDistance(c1: Coordinates, c2: Coordinates): number {
  const R = 6_371_000; // Earth radius in meters
  const φ1 = (c1.latitude * Math.PI) / 180;
  const φ2 = (c2.latitude * Math.PI) / 180;
  const Δφ = ((c2.latitude - c1.latitude) * Math.PI) / 180;
  const Δλ = ((c2.longitude - c1.longitude) * Math.PI) / 180;
  const a =
    Math.sin(Δφ / 2) ** 2 +
    Math.cos(φ1) * Math.cos(φ2) * Math.sin(Δλ / 2) ** 2;
  return R * 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a));
}
When you tap the map:
src/components/location/ContextSwitcher.tsx
const distanceMeters = calculateDistance(userLocation, tapped);

if (distanceMeters <= Config.restaurant.geofenceRadiusMeters) {
  // IN-RESTAURANT: Activate scanner mode
  setAppMode('SCANNER');
  setServiceType('TABLE');
} else {
  // DELIVERY: Show full catalog
  setAppMode('DELIVERY');
  setServiceType('DELIVERY');
  router.push('/(delivery)/delivery-catalog');
}
The geofence radius (default: 50m) is configurable in src/lib/core/config.ts:20.

How contextual menus work

Tables have different types that filter the available menu:
src/lib/core/types.ts
export type MenuType = 'FULL' | 'DRINKS_ONLY';

export interface TableData {
  id: string;
  displayName: string;
  status: TableStatus;
  menuType: MenuType;
  specialEvent: SpecialEvent;
  discount?: number;
  animation?: 'cake';
}
The menu logic hook filters products based on table type:
src/lib/modules/menu/useMenuLogic.ts
export function useMenuLogic() {
  const { activeTable } = useTableStore();
  const allProducts = PRODUCTS;
  
  if (!activeTable) return { products: allProducts };
  
  // Filter drinks-only tables
  if (activeTable.menuType === 'DRINKS_ONLY') {
    return {
      products: allProducts.filter(p => p.category === 'DRINK'),
      discount: activeTable.discount
    };
  }
  
  // Full menu for dining tables
  return {
    products: allProducts,
    discount: activeTable.discount
  };
}

How state management works

TableOrder uses Zustand for global state with three stores:
src/stores/useLocationStore.ts
interface LocationState {
  appMode: AppMode;
  userLocation: Coordinates | null;
  restaurantLocation: Coordinates | null;
  deliveryInfo: DeliveryInfo | null;
  setAppMode: (mode: AppMode) => void;
  setLocations: (user: Coordinates, restaurant: Coordinates) => void;
  setDeliveryInfo: (info: DeliveryInfo | null) => void;
}

export const useLocationStore = create<LocationState>((set) => ({
  appMode: 'CHECKING',
  userLocation: null,
  restaurantLocation: null,
  deliveryInfo: null,
  setAppMode: (mode) => set({ appMode: mode }),
  setLocations: (user, restaurant) => 
    set({ userLocation: user, restaurantLocation: restaurant }),
  setDeliveryInfo: (info) => set({ deliveryInfo: info }),
}));
Manages app context mode and GPS coordinates.
Zustand provides zero-boilerplate state management without Provider components. Components re-render only when their selected slice changes.

Test QR codes reference

All available test codes for scanner mode:
QR CodeTable NameMenu TypeSpecial Effect
TABLE_BAR_01Barra 01Drinks onlyNone
TABLE_HALL_05Salón 05Full menuNone
TABLE_BDAY_99Mesa EspecialFull menuBirthday banner + 15% discount
These codes are defined in src/lib/core/mockData.ts. You can add more tables by extending the TABLES array.

Common workflows

Adding new products

Edit the products array in src/lib/core/mockData.ts:
src/lib/core/mockData.ts
export const PRODUCTS: Product[] = [
  {
    id: 'p01',
    name: 'Hamburguesa Clásica',
    price: 12.99,
    category: 'FOOD',
    image: 'https://images.unsplash.com/photo-...',
    description: 'Carne 200g, lechuga, tomate, queso cheddar',
  },
  // Add more products here
];

Adjusting geofence radius

Modify the configuration in src/lib/core/config.ts:
src/lib/core/config.ts
export const Config = {
  restaurant: {
    name: 'TableOrder Restaurant',
    costPerKm: 1.0,
    geofenceRadiusMeters: 100, // Increase to 100 meters
  },
} as const;

Customizing shipping costs

Update the cost multiplier in src/lib/core/config.ts:
src/lib/core/config.ts
export const Config = {
  restaurant: {
    costPerKm: 2.5, // $2.50 per kilometer
  },
} as const;

Next steps

Architecture guide

Understand the feature-based structure and design patterns

API reference

Explore services, stores, and utility functions

Core features

Learn about table mode and delivery mode

Configuration

Configure Mapbox, Stripe, and Telegram