Overview
The Quests API provides endpoints for retrieving quest information, tracking quest progress, and managing quest completion. All endpoints require authentication.
Queries
get
Retrieves a single quest by ID.
import { api } from "@/convex/_generated/api" ;
import { useQuery } from "convex/react" ;
const quest = useQuery ( api . quests . get , { questId });
The unique identifier of the quest to retrieve
The quest object with all fields
Quest Object Structure:
Detailed quest description
Estimated time to complete in minutes
difficulty
'einfach' | 'mittel' | 'schwer'
Quest difficulty level (easy, medium, hard in German)
Experience points awarded for completion
category
'abenteuer' | 'geschichte' | 'kultur' | 'natur' | 'essen' | 'trinken'
Quest category (adventure, history, culture, nature, food, drinks in German)
URL to the quest’s cover image
Errors:
“Quest not found” - The specified quest ID doesn’t exist
“Not authenticated” - User is not logged in
listRecommended
Returns all quests that the user hasn’t completed yet.
import { api } from "@/convex/_generated/api" ;
import { useQuery } from "convex/react" ;
const recommendedQuests = useQuery ( api . quests . listRecommended );
Array of quests the user hasn’t completed
Example Response:
[
{
_id: "quest123" ,
name: "Historical Berlin" ,
description: "Explore the rich history of Berlin" ,
estimatedTime: 120 ,
difficulty: "mittel" ,
xp: 100 ,
category: "geschichte" ,
imageUrl: "https://..."
},
// ... more quests
]
Errors:
“Not authenticated” - User is not logged in
listNew
Returns quests created within the last 7 days that the user hasn’t completed.
import { api } from "@/convex/_generated/api" ;
import { useQuery } from "convex/react" ;
const newQuests = useQuery ( api . quests . listNew );
Array of new quests (created in the last 7 days) that the user hasn’t completed
Implementation:
const SEVEN_DAYS_MS = 7 * 24 * 60 * 60 * 1000 ;
const cutoff = Date . now () - SEVEN_DAYS_MS ;
return allQuests . filter (
( quest ) =>
quest . _creationTime >= cutoff && ! completedQuestIds . has ( quest . _id ),
);
Errors:
“Not authenticated” - User is not logged in
listFinished
Returns all quests the user has completed.
import { api } from "@/convex/_generated/api" ;
import { useQuery } from "convex/react" ;
const finishedQuests = useQuery ( api . quests . listFinished );
Array of quests the user has completed
Errors:
“Not authenticated” - User is not logged in
listInProgress
Returns quest IDs for all quests the user has started but not completed.
import { api } from "@/convex/_generated/api" ;
import { useQuery } from "convex/react" ;
const inProgressQuestIds = useQuery ( api . quests . listInProgress );
Array of quest IDs currently in progress
Example Response:
[ "quest123" , "quest456" , "quest789" ]
Implementation:
const userQuests = await ctx . db
. query ( "userQuests" )
. withIndex ( "by_user" , ( q ) => q . eq ( "userId" , user . _id ))
. filter (( q ) => q . eq ( q . field ( "completedAt" ), undefined ))
. collect ();
return userQuests . map (( uq ) => uq . questId );
Errors:
“Not authenticated” - User is not logged in
getStatus
Retrieves the user’s progress status for a specific quest.
import { api } from "@/convex/_generated/api" ;
import { useQuery } from "convex/react" ;
const questStatus = useQuery ( api . quests . getStatus , { questId });
The quest ID to check status for
The user’s quest progress, or null if not started
UserQuest Object:
Unique user quest record identifier
Unix timestamp when the quest was started
Unix timestamp when the quest was completed (undefined if in progress)
Example Response:
{
_id : "userQuest123" ,
userId : "user456" ,
questId : "quest789" ,
startedAt : 1709481600000 ,
completedAt : undefined // or timestamp if completed
}
Errors:
“Not authenticated” - User is not logged in
Mutations
start
Starts a quest for the current user.
import { api } from "@/convex/_generated/api" ;
import { useMutation } from "convex/react" ;
const startQuest = useMutation ( api . quests . start );
await startQuest ({ questId });
The ID of the quest to start
The ID of the newly created user quest record
Implementation:
return await ctx . db . insert ( "userQuests" , {
userId: user . _id ,
questId ,
startedAt: Date . now (),
});
Errors:
“Quest not found” - The specified quest ID doesn’t exist
“Quest already in progress” - User has already started this quest
“Quest already completed” - User has already completed this quest
“Not authenticated” - User is not logged in
complete
Marks a quest as completed for the current user. Validates that all locations have been visited.
import { api } from "@/convex/_generated/api" ;
import { useMutation } from "convex/react" ;
const completeQuest = useMutation ( api . quests . complete );
await completeQuest ({ questId });
The ID of the quest to complete
Validation Logic:
The mutation performs comprehensive validation:
Checks if the quest has been started
Checks if the quest is already completed
Retrieves all locations for the quest
Retrieves all user-completed locations
Verifies that every required location has been visited
const requiredIds = new Set ( questLocations . map (( l ) => l . _id ));
const completedIds = new Set ( completedLocations . map (( cl ) => cl . locationId ));
if ([ ... requiredIds ]. some (( id ) => ! completedIds . has ( id )))
throw new ConvexError ( "Quest not finished" );
Errors:
“Quest not started” - User hasn’t started this quest yet
“Quest already completed” - User has already completed this quest
“Quest not finished” - Not all required locations have been visited
“Not authenticated” - User is not logged in
Example Usage:
React Component
Error Handling
import { api } from "@/convex/_generated/api" ;
import { useMutation , useQuery } from "convex/react" ;
function QuestCompletion ({ questId }) {
const completeQuest = useMutation ( api . quests . complete );
const locations = useQuery ( api . locations . listByQuest , { questId });
const completed = useQuery ( api . locations . listCompleted , { questId });
const allLocationsVisited =
locations ?. length === completed ?. length ;
const handleComplete = async () => {
try {
await completeQuest ({ questId });
// Show success message
} catch ( error ) {
// Handle errors: "Quest not finished", etc.
}
};
return (
< button
onClick = { handleComplete }
disabled = {! allLocationsVisited }
>
Complete Quest
</ button >
);
}