Skip to main content
The tracking screen displays a live map showing the driving route from the restaurant to the customer’s delivery location. It visualizes the Mapbox Directions API route as a polyline with pins for both endpoints.

When it activates

The tracking screen is shown after a successful delivery payment (src/app/(checkout)/payment.tsx:258):
if (serviceType === 'DELIVERY') {
  router.replace('/(delivery)/track-order');
}
Table orders do not show tracking. They return to the scanner screen after payment.

Route visualization

The screen renders a Mapbox map with three key elements:
1

Decoded polyline as route

The Mapbox Directions API returns a compressed polyline string. This is decoded into an array of coordinates and rendered as a line (src/app/(delivery)/track-order.tsx:13):
function buildRouteShape(
  coordinates: Array<{ latitude: number; longitude: number }>
): GeoJSON.Feature<GeoJSON.LineString> {
  return {
    type: 'Feature',
    properties: {},
    geometry: {
      type: 'LineString',
      // GeoJSON uses [longitude, latitude] — opposite of react-native convention
      coordinates: coordinates.map((c) => [c.longitude, c.latitude]),
    },
  };
}
The route is rendered with a ShapeSource and LineLayer:
src/app/(delivery)/track-order.tsx
{deliveryInfo && deliveryInfo.decodedRoute.length > 1 && (
  <ShapeSource id="routeSource" shape={buildRouteShape(deliveryInfo.decodedRoute)}>
    <LineLayer
      id="routeLine"
      style={{
        lineColor: Brand.primary,
        lineWidth: 4,
        lineCap: 'round',
        lineJoin: 'round',
        lineOpacity: 0.9,
      }}
    />
  </ShapeSource>
)}
Coordinate order matters: GeoJSON (used by Mapbox) expects [longitude, latitude], while React Native convention is [latitude, longitude]. The coordinate mapping in buildRouteShape handles this conversion.
2

Restaurant pin (origin)

Pin A marks the restaurant location with a primary-colored circle:
src/app/(delivery)/track-order.tsx
<PointAnnotation
  id="restaurant"
  coordinate={[restaurantLocation.longitude, restaurantLocation.latitude]}
>
  <View style={styles.pinA}>
    <MapPin size={14} color="#fff" strokeWidth={2.5} />
  </View>
</PointAnnotation>
Pin style:
  • 32×32 pixel circle
  • Primary brand color background
  • White map pin icon
  • White 3px border
  • Drop shadow for depth
3

User pin (destination)

Pin B marks the customer’s delivery location with a success-colored circle:
src/app/(delivery)/track-order.tsx
<PointAnnotation
  id="user"
  coordinate={[userLocation.longitude, userLocation.latitude]}
>
  <View style={styles.pinB}>
    <Navigation size={12} color="#fff" strokeWidth={2.5} />
  </View>
</PointAnnotation>
Pin style:
  • 32×32 pixel circle
  • Success green background
  • White navigation icon
  • White 3px border
  • Drop shadow for depth

Camera bounds

The map camera is automatically positioned to show both pins with padding (src/app/(delivery)/track-order.tsx:51):
const cameraBounds = {
  ne: [
    Math.max(userLocation.longitude, restaurantLocation.longitude) + 0.008,
    Math.max(userLocation.latitude, restaurantLocation.latitude) + 0.008,
  ] as [number, number],
  sw: [
    Math.min(userLocation.longitude, restaurantLocation.longitude) - 0.008,
    Math.min(userLocation.latitude, restaurantLocation.latitude) - 0.008,
  ] as [number, number],
  paddingTop: 60,
  paddingBottom: 220, // leave room for the info panel
  paddingLeft: 40,
  paddingRight: 40,
};
The bottom padding (220px) ensures the info panel doesn’t obscure the route.

Delivery info panel

The bottom panel displays three key metrics (src/app/(delivery)/track-order.tsx:128):
<View style={styles.panelItem}>
  <Clock size={20} color={Brand.primary} />
  <View>
    <Text style={styles.panelLabel}>Tiempo estimado</Text>
    <Text style={styles.panelValue}>{deliveryInfo.etaMinutes} min</Text>
  </View>
</View>
Calculated from Mapbox Directions API duration field (src/lib/services/mapboxService.ts:68):
const etaMinutes = Math.ceil(route.duration / 60);
The panel layout uses dividers to separate metrics:
src/app/(delivery)/track-order.tsx
<View style={styles.panelRow}>
  <View style={styles.panelItem}>{/* ETA */}</View>
  <View style={styles.panelDivider} />
  <View style={styles.panelItem}>{/* Distance */}</View>
  <View style={styles.panelDivider} />
  <View style={styles.panelItem}>{/* Shipping cost */}</View>
</View>

State management

The tracking screen depends on three pieces of state from useLocationStore (src/app/(delivery)/track-order.tsx:29):
const { userLocation, restaurantLocation, deliveryInfo, resetLocation } = useLocationStore();

DeliveryInfo structure

src/lib/core/types.ts
interface DeliveryInfo {
  distanceKm: number;        // 3.47
  etaMinutes: number;        // 12
  polyline: string;          // Encoded Mapbox polyline (not used in tracking)
  decodedRoute: Coordinates[]; // Array of {latitude, longitude} points for rendering
}
This data is set when the delivery catalog calculates the route (before checkout).

Loading state

If location or delivery info is missing, a loading indicator displays (src/app/(delivery)/track-order.tsx:39):
if (!userLocation || !restaurantLocation) {
  return (
    <SafeAreaView style={styles.container}>
      <View style={styles.centered}>
        <ActivityIndicator size="large" color={Brand.primary} />
        <Text style={styles.loadingText}>Cargando mapa de ruta...</Text>
      </View>
    </SafeAreaView>
  );
}
This handles edge cases like:
  • Direct navigation to tracking screen without going through delivery flow
  • Store hydration delays
  • Network issues during route calculation
The header displays order status and restaurant name (src/app/(delivery)/track-order.tsx:69):
<View style={styles.header}>
  <PackageCheck size={24} color={Brand.success} strokeWidth={1.8} />
  <View style={styles.headerText}>
    <Text style={styles.headerTitle}>Pedido en camino</Text>
    <Text style={styles.headerSub}>{Config.restaurant.name}</Text>
  </View>
</View>
The green checkmark icon provides visual confirmation that payment succeeded.

Done button

The “Finalizar” button resets all state and returns to the home screen (src/app/(delivery)/track-order.tsx:32):
const handleDone = () => {
  resetCart();
  resetLocation();
  router.replace('/(tabs)');
};
This clears:
  • Cart items and totals
  • Delivery route and ETA
  • Restaurant and user locations
  • App mode (resets to CHECKING)
The user can now start a new order (either table or delivery).

Map styling

The map container has rounded corners and margin for visual polish (src/app/(delivery)/track-order.tsx:194):
mapContainer: {
  flex: 1,
  marginHorizontal: 16,
  marginVertical: 12,
  borderRadius: 20,
  overflow: 'hidden',
}
Mapbox branding is disabled for a cleaner look:
<MapView
  logoEnabled={false}
  attributionEnabled={false}
  scaleBarEnabled={false}
>
Mapbox Terms of Service: If deploying to production, ensure you comply with Mapbox attribution requirements. The disabled attribution is acceptable for demos and portfolio projects.

Route line styling

The polyline uses rounded caps and joins for smooth rendering (src/app/(delivery)/track-order.tsx:92):
<LineLayer
  id="routeLine"
  style={{
    lineColor: Brand.primary,    // Primary brand color
    lineWidth: 4,                 // 4px line
    lineCap: 'round',             // Rounded line ends
    lineJoin: 'round',            // Rounded corners
    lineOpacity: 0.9,             // Slightly transparent
  }}
/>
This creates a visually distinct route that stands out against the map tiles.

Performance considerations

Polyline decoding

The polyline is decoded once during route calculation (in mapboxService) and stored in the deliveryInfo object. The tracking screen reads the pre-decoded coordinates, avoiding duplicate decoding work.

GeoJSON rendering

Mapbox GL Native renders GeoJSON features efficiently using GPU acceleration. Large routes (100+ points) render smoothly without performance degradation.

Map tile caching

Mapbox SDK caches map tiles locally. Revisiting the same area (e.g., testing multiple deliveries) loads almost instantly.

Real-time tracking (future enhancement)

The current implementation shows a static route. To add real-time driver tracking:
1

Backend location updates

Stream driver GPS coordinates via WebSocket or polling:
const driverLocation = await fetch('/api/driver/location');
2

Animated pin

Add a third pin for the driver that moves along the route:
<PointAnnotation
  id="driver"
  coordinate={[driverLocation.longitude, driverLocation.latitude]}
>
  <View style={styles.driverPin}>
    <Car size={16} color="#fff" />
  </View>
</PointAnnotation>
3

Route progress

Highlight the completed portion of the route in a different color using two LineLayer components.
4

ETA updates

Recalculate ETA based on current driver position and remaining distance.

Error handling

Missing delivery info

If deliveryInfo is null, the polyline doesn’t render but the map still displays with pins:
{deliveryInfo && deliveryInfo.decodedRoute.length > 1 && (
  <ShapeSource ...>
)}
The info panel shows a loading state:
{deliveryInfo ? (
  <View style={styles.panelRow}>{/* Metrics */}</View>
) : (
  <View style={styles.panelItem}>
    <ActivityIndicator size="small" color={Brand.primary} />
    <Text style={styles.panelLabel}>Calculando ruta...</Text>
  </View>
)}

Invalid coordinates

If coordinates are invalid (e.g., {latitude: 0, longitude: 0}), the map will center on the Atlantic Ocean. Always validate coordinates before storing:
if (latitude === 0 && longitude === 0) {
  throw new Error('Invalid GPS coordinates');
}

Delivery Mode

Complete delivery ordering workflow

Payments

Checkout flow that redirects to tracking

Table Mode

Alternative mode (no tracking)