BricqsBricqs

Headless SDK: engagement components

Quizzes, spins, scratches, and predictions all share one hook: useEngagement. The hook returns the configured experience, the participant's state, and a submit handler that fires the right event with the right idempotency key.

Last updatedMay 2026

Key takeaways

Quick read
  • useEngagement loads the engagement and its components. Branch on engagement.type, or read a component with getComponent.
  • Config comes from the server. Builder edits propagate without a redeploy.
  • For interaction, use the matching activity hook — useSpinWheel, useQuiz, useForm — or the generic useActivity (validate + complete).
  • Result data (the spin segment, the quiz score) comes back from the activity hook, not from useEngagement.
  • Always render an empty state. Engagements can be paused or deleted server-side.

Quickstart

A working spin in 35 lines

components/SpinWheel.tsx·tsx
"use client";
import { useSpinWheel } from "@bricqs/sdk-react";

export function SpinWheel({ engagementId, activityId }: {
  engagementId: string; activityId: string;
}) {
  const { isSpinning, result, rotation, spin, confirmResult } = useSpinWheel({
    engagementId,
    activityId,
    config: { segments: SEGMENTS },
  });

  async function onSpin() {
    // Server draws first; the client only animates to the result.
    const outcome = await spin();
    await animateToRotation(rotation);
    await confirmResult(); // records completion + applies the reward
    if (outcome) showRevealAnimation(outcome.segment.label);
  }

  return (
    <button onClick={onSpin} disabled={isSpinning} className="px-6 py-3 rounded-xl bg-orange-500 text-white">
      {result ? "Spin again" : "Spin to win"}
    </button>
  );
}

Quiz

A 6-question quiz with branching

components/Quiz.tsx·tsx
"use client";
import { useQuiz } from "@bricqs/sdk-react";

export function Quiz({ engagementId, activityId }: {
  engagementId: string; activityId: string;
}) {
  const {
    currentQuestion, currentIndex, totalQuestions,
    selectAnswer, next, isLastQuestion,
    submit, isComplete, score,
  } = useQuiz({ engagementId, activityId, config: { questions: QUESTIONS } });

  if (isComplete) {
    window.location.href = `/quiz/result?score=${score?.correct}`;
    return null;
  }

  async function choose(optionIndex: number) {
    selectAnswer(optionIndex);
    if (isLastQuestion) await submit();
    else next();
  }

  return (
    <article>
      <p className="text-sm text-slate-500">
        Question {currentIndex + 1} of {totalQuestions}
      </p>
      <h2 className="text-xl font-bold mb-4">{currentQuestion.question}</h2>
      <ul className="grid gap-3">
        {currentQuestion.options.map((opt, i) => (
          <li key={i}>
            <button
              onClick={() => choose(i)}
              className="w-full text-left rounded-xl border p-3 hover:border-orange-500"
            >
              {opt}
            </button>
          </li>
        ))}
      </ul>
    </article>
  );
}

Scratch

Server-determined reveal

tsx
// The generic activity hook fits scratch cards: validate to learn the outcome,
// then complete to record it + apply rewards.
const { validate, complete } = useActivity({
  engagementId, activityId, activityType: "scratch_card",
});

async function reveal() {
  const { outcome } = await validate({});   // server decides the prize
  animateScratch(outcome);                   // animate the reveal to that outcome
  await complete({});                        // record completion + apply rewards
}

validate() returns the server-decided outcome so you can animate to it; complete() records it. The client never decides what was won.

Common mistakes

What goes wrong

01Mistake

Determining the prize on the client and sending it as an event. Trivially gamed.

Fix

Use validate()/spin() — the server returns the outcome; the client only animates to it.

02Mistake

Skipping confirmResult()/complete() after the animation. Rewards never get applied.

Fix

Draw with spin()/validate(), then call confirmResult()/complete() once the reveal finishes.

03Mistake

Hardcoding the question count. Builder updates break the UI.

Fix

Read totalQuestions from useQuiz. The hook reflects live config; builder edits show up without a redeploy.

04Mistake

Triggering the draw on every render. spin()/validate() fired in useEffect by mistake.

Fix

Call it from a user gesture (click, swipe, scratch). Never from useEffect.

Developer FAQ

Common questions when integrating gamification with Bricqs.

Ready to ship?

Wire it up with the Bricqs SDK or API

Headless SDK for React UIs, REST API for any backend. Same engine behind both.

1 brief to align the room2 mechanics max in version one
What happens next
01
Pick the mechanic
Choose the smallest working system for the brief.
02
Launch without rebuilds
Configure rules and rewards in one place.