Skip to main content

Documentation Index

Fetch the complete documentation index at: https://mintlify.com/Diego31-10/TableOrderApp/llms.txt

Use this file to discover all available pages before exploring further.

Delivery mode activates automatically when a customer is outside the restaurant’s geofence radius (more than 50 meters away). Customers tap the restaurant location on a map, browse the full catalog, and receive a calculated shipping cost based on driving distance.

Activation flow

1

GPS permission request

The app requests foreground location permissions using expo-location:
src/components/location/ContextSwitcher.tsx
const { status } = await Location.requestForegroundPermissionsAsync();
if (status !== 'granted') {
  setPermStatus('denied');
  return;
}
2

User location capture

The app captures the user’s current GPS coordinates with balanced accuracy:
src/components/location/ContextSwitcher.tsx
const pos = await Location.getCurrentPositionAsync({
  accuracy: Location.Accuracy.Balanced,
});
setLocations(
  { latitude: pos.coords.latitude, longitude: pos.coords.longitude },
  { latitude: 0, longitude: 0 }
);
3

Map interaction

A Mapbox GL map displays centered on the user’s location with a blue dot marker. The user taps anywhere on the map to set the restaurant location.
4

Geofence calculation

The Haversine distance formula determines whether delivery mode should activate:
src/components/location/ContextSwitcher.tsx
const distanceMeters = calculateDistance(userLocation, tapped);

if (distanceMeters > Config.restaurant.geofenceRadiusMeters) {
  setAppMode('DELIVERY');
  setServiceType('DELIVERY');
  router.push('/(delivery)/delivery-catalog');
}
Default radius: 50 meters

Route calculation

When the user reaches checkout, the app fetches a driving route from Mapbox Directions API.

Mapbox Directions API integration

The mapboxService handles route fetching and polyline decoding (src/lib/services/mapboxService.ts:36):
export async function getDeliveryRoute(
  origin: Coordinates,
  destination: Coordinates
): Promise<DeliveryInfo> {
  const coords = `${origin.longitude},${origin.latitude};${destination.longitude},${destination.latitude}`;
  const url = `${MAPBOX_BASE}/${coords}?geometries=polyline&overview=full&access_token=${token}`;

  const response = await fetch(url);
  const data: MapboxDirectionsResponse = await response.json();

  const route = data.routes[0];

  // Convert meters → km (rounded to 2 decimal places)
  const distanceKm = parseFloat((route.distance / 1000).toFixed(2));

  // Convert seconds → minutes (rounded up to nearest minute)
  const etaMinutes = Math.ceil(route.duration / 60);

  // Decode the compressed polyline into [lat, lon][] pairs
  const decodedPairs: [number, number][] = polyline.decode(route.geometry);

  const decodedRoute: Coordinates[] = decodedPairs.map(([lat, lng]) => ({
    latitude: lat,
    longitude: lng,
  }));

  return {
    distanceKm,
    etaMinutes,
    polyline: route.geometry,
    decodedRoute,
  };
}
Mapbox coordinate order: The API expects [longitude, latitude] (GeoJSON order), not [latitude, longitude] (standard geographic order).

Shipping cost calculation

Shipping cost is calculated based on distance using a per-kilometer rate:
src/lib/services/mapboxService.ts
export function calculateShippingCost(distanceKm: number): number {
  return parseFloat((distanceKm * Config.restaurant.costPerKm).toFixed(2));
}
Default rate: $2.50 per kilometer

Delivery information structure

The DeliveryInfo type stores all route metadata:
src/lib/core/types.ts
interface DeliveryInfo {
  distanceKm: number;        // 3.47
  etaMinutes: number;        // 12
  polyline: string;          // Encoded Mapbox polyline
  decodedRoute: Coordinates[]; // Array of {latitude, longitude} points
}
This data is stored in useLocationStore (src/stores/useLocationStore.ts:12):
setDeliveryRoute: (info) => set({ deliveryInfo: info })

Order summary calculation

The checkout screen displays a comprehensive order summary (src/app/(checkout)/payment.tsx:88):
const subtotal = items.reduce(
  (s, i) => s + i.product.price * i.quantity, 
  0
);

State management

App mode switching

src/stores/useLocationStore.ts
interface LocationState {
  appMode: AppMode;  // 'CHECKING' | 'SCANNER' | 'DELIVERY'
  userLocation: Coordinates | null;
  restaurantLocation: Coordinates | null;
  deliveryInfo: DeliveryInfo | null;
}
The mode determines which UI the app displays:
ModeTriggerUI
CHECKINGInitial loadGPS permission prompt / loading
SCANNERDistance ≤ 50mQR code scanner
DELIVERYDistance > 50mRestaurant map + catalog

Cart configuration

src/stores/useCartStore.ts
interface CartState {
  serviceType: 'TABLE' | 'DELIVERY';
  shippingCost: number;
  setServiceType: (type: 'TABLE' | 'DELIVERY') => void;
  setShippingCost: (cost: number) => void;
}
The serviceType is set when the app mode switches (src/components/location/ContextSwitcher.tsx:92):
setServiceType('DELIVERY');

Map rendering

The app uses @rnmapbox/maps (Mapbox GL Native) for map rendering:
src/components/location/ContextSwitcher.tsx
<MapView
  style={StyleSheet.absoluteFillObject}
  onPress={onMapPress}
  logoEnabled={false}
  attributionEnabled={false}
  scaleBarEnabled={false}
>
  <Camera
    defaultSettings={{
      centerCoordinate: [userLocation.longitude, userLocation.latitude],
      zoomLevel: 15,
    }}
  />

  <UserLocation visible androidRenderMode="compass" />

  {restaurant && (
    <PointAnnotation
      id="restaurant-pin"
      coordinate={[restaurant.longitude, restaurant.latitude]}
    >
      <View style={styles.restaurantPin}>
        <View style={styles.restaurantPinDot} />
      </View>
    </PointAnnotation>
  )}
</MapView>
The user’s location is displayed as a built-in blue dot with compass orientation on Android.

Post-payment tracking

After a successful delivery payment, the user is redirected to the tracking screen (src/app/(checkout)/payment.tsx:258):
if (serviceType === 'DELIVERY') {
  router.replace('/(delivery)/track-order');
}
See Tracking for details on the route visualization.

Configuration

Delivery mode behavior is configured in src/lib/core/config.ts:
export const Config = {
  restaurant: {
    name: 'TableOrder Restaurant',
    geofenceRadiusMeters: 50,
    costPerKm: 2.5,
  },
  mapbox: {
    token: process.env.EXPO_PUBLIC_MAPBOX_TOKEN,
  },
};
Required environment variables:
  • EXPO_PUBLIC_MAPBOX_TOKEN — Mapbox public token for map rendering and Directions API
  • RNMAPBOX_MAPS_DOWNLOAD_TOKEN — Mapbox secret token for native SDK download at build time
The app will throw an error at runtime if these are not configured (src/lib/services/mapboxService.ts:42).

Error handling

Permission denied

If location permission is denied, the app displays an error state with a link to system settings (src/components/location/ContextSwitcher.tsx:102):
<ErrorState
  icon={<Navigation size={60} color={Brand.primary} />}
  title="Ubicación bloqueada"
  message="TableOrder necesita acceso a tu ubicación..."
  primaryAction={{
    label: 'Abrir configuración',
    onPress: () => Linking.openSettings(),
  }}
/>

Route calculation failure

If the Mapbox API returns an error, the service throws (src/lib/services/mapboxService.ts:58):
if (data.code !== 'Ok' || data.routes.length === 0) {
  throw new Error('No route found between the two points.');
}
This error should be caught and displayed to the user in production.

Tracking

Post-payment route visualization with ETA

Table Mode

Alternative mode for customers inside the restaurant

Payments

Checkout flow with shipping cost calculation

Contextual Menus

Menu filtering (delivery shows full catalog)

Build docs developers (and LLMs) love