Skip to main content
Locations are individual checkpoints within a quest. Each quest consists of multiple locations you must visit in sequential order, proving your presence through photo verification.

Location Structure

Every location includes specific details to guide you to the right spot:

Identification

  • Name: Location checkpoint title
  • Description: Details about what to find
  • Quest ID: Parent quest reference

Navigation Data

  • Coordinates: GPS latitude and longitude
  • Order: Sequential position in quest
  • Map Display: Interactive map view

GPS Coordinates

Each location has precise GPS coordinates stored as an object with two properties:
coordinates: {
  latitude: number,
  longitude: number
}
The coordinates are defined in the schema (convex/schema.ts:29-32) and displayed on an interactive map when you visit the location.
Coordinates are shown on a map component that helps you navigate to the exact spot. You don’t need to manually enter latitude/longitude values.

Sequential Ordering

Locations must be completed in a specific order:
  • Each location has an order field (starting from 1)
  • You cannot skip locations or complete them out of sequence
  • The next location becomes available only after completing the current one
  • Resume functionality automatically finds your next incomplete location
The resume logic is implemented in app/(tabs)/(quests)/[id]/index.tsx:74-79:
const resumeLocationId = (() => {
  if (!isInProgress) return firstLocationId;
  const completedIds = new Set(completedLocations.map((cl) => cl.locationId));
  return (locations.find((l) => !completedIds.has(l._id)) ?? locations[0])._id;
})();

Photo Verification Process

Photo verification ensures you physically visited each location. Here’s how it works:

Step 1: Arrive at the Location

When you navigate to a location screen:
  1. View the location name and description
  2. See an interactive map with GPS coordinates
  3. Navigate to the physical location

Step 2: Take a Photo

Once you arrive:
  1. Tap the “Foto aufnehmen” (Take Photo) button
  2. Grant camera permissions if prompted
  3. Capture a photo of the location
Camera permissions are required for photo verification. The app will request access when you first attempt to take a photo.

Step 3: Upload and Store

The photo verification process (components/location/location-action-bar.tsx:38-85):
  1. Generate Upload URL: Request a secure upload URL from the backend
  2. Convert to Blob: Fetch the image URI and convert to a blob
  3. Upload Photo: POST the image to Convex storage
  4. Store Reference: Save the photoStorageId in the database
  5. Mark Complete: Update userLocations with completion timestamp
const uploadUrl = await generateUploadUrl();

const blob = await fetch(result.assets[0].uri).then((r) => r.blob());
const uploadResponse = await fetch(uploadUrl, {
  method: "POST",
  headers: { "Content-Type": result.assets[0].mimeType ?? "image/jpeg" },
  body: blob,
});

const { storageId } = await uploadResponse.json();

await completeLocation({
  questId: questId as Id<"quests">,
  locationId: locationId as Id<"locations">,
  photoStorageId: storageId,
});

Step 4: Verification

The backend validates your completion (convex/locations.ts:61-103):
  • Ensures the quest is started but not yet completed
  • Verifies the location belongs to the specified quest
  • Checks you haven’t already completed this location
  • Confirms the photo exists in storage
  • Creates a userLocations record with the photo reference
Each location can only be completed once per user. If you try to complete the same location again, you’ll receive an error.

User Location Records

When you complete a location, a record is created in the userLocations table:
userLocations: {
  userId: Id<"users">,
  questId: Id<"quests">,
  locationId: Id<"locations">,
  photoStorageId: Id<"_storage">,
  completedAt: number  // Unix timestamp
}
This record tracks:
  • Who completed it (userId)
  • Which quest it belongs to (questId)
  • Which location was completed (locationId)
  • Proof of visit (photoStorageId)
  • When it was completed (completedAt)

Database Indexes

Locations are efficiently queried using two database indexes:

by_quest Index

Retrieve all locations for a specific quest:
const locations = await ctx.db
  .query("locations")
  .withIndex("by_quest", (q) => q.eq("questId", questId))
  .collect();

by_quest_order Index

Fetch locations sorted by their sequential order:
const locations = await ctx.db
  .query("locations")
  .withIndex("by_quest_order", (q) => q.eq("questId", questId))
  .collect();
This ensures locations are always displayed and completed in the correct sequence.

Location Screen Features

When viewing a location (app/(tabs)/(quests)/[id]/location/[locationId].tsx):
A map component displays the exact GPS coordinates, helping you navigate to the location. The map uses the coordinates from the database to center on the checkpoint.
Detailed text explaining what to look for, historical context, or specific instructions for finding the checkpoint.
  • Not Completed: Shows “Foto aufnehmen” button to capture verification photo
  • Completed: Shows “Weiter” button to proceed to next location or “Quest abschliessen” for final location
Hardware back button and header close icon open a dialog to cancel the quest. Your progress is saved, allowing you to resume later.
After the final location, a dialog appears to confirm quest completion and award XP.
The complete navigation flow through locations:
  1. Start Quest → Navigate to first location (order: 1)
  2. View Location → See map, description, and photo button
  3. Take Photo → Upload and verify presence
  4. Location Completed → Button changes to “Weiter” (Next)
  5. Next Location → Navigate to location with order + 1
  6. Repeat → Continue until all locations complete
  7. Final Location → Button shows “Quest abschliessen”
  8. Complete Quest → Receive XP reward
You can pause and resume at any time. The app remembers which locations you’ve completed and takes you to the next incomplete one.

Error Handling

The location system includes robust error handling:
Error ScenarioMessageResolution
Quest not started”Quest not started”Start the quest from the quest detail page
Quest already completed”Quest already completed”Quest is finished, cannot modify locations
Location not found”Location not found”Invalid location ID, contact support
Wrong quest”Location does not belong to this quest”Data inconsistency, contact support
Already completed”Location already completed”Use “Next” button to proceed
Photo upload failed”Photo not found in storage”Retry photo capture
Camera denied”Kamera nicht verfügbar”Grant camera permissions in settings

Best Practices

Check Your Position

Use the interactive map to ensure you’re at the correct GPS coordinates before taking your photo.

Clear Photos

Take clear, well-lit photos that show recognizable features of the location. This helps verify your visit.

Stable Connection

Ensure you have internet connectivity when uploading photos. The app needs to upload to Convex storage.

Follow the Order

Always complete locations sequentially. The app enforces this, but planning your route accordingly saves time.
  • Quests - Learn about quest categories and difficulty levels
  • User Progress - View your completed locations and quests
  • Leaderboard - Compare your location completion stats

Build docs developers (and LLMs) love