Predictor

Season-long football score predictor. Two templates: the default multi-tab interface (Play, Leaderboard, Private Leagues, Rules, Prizes) for a dedicated predictor page, and embed — a lightweight single-match card designed to sit inside an article body. Includes consent gating, custom Play-tab banners, and first-class Betslip integration.

Import

import { Predictor } from "fansunited-frontend-components";
import {
  PredictorProps,
  PredictorTab,
	PredictorTemplateType,
  PredictorEmbedConfig,
  PredictorBetslipConfig,
  PlayTabBanner,
  ConsentDef,
} from "fansunited-frontend-core";

Required props

PropTypeDescription
entityIdstringPredictor template identifier.
sdkFansUnitedSDKModelSDK instance.
languageLanguageTypeDisplay language.

Optional props

PropTypeDefaultDescription
templatePredictorTemplateType ("standard" | "embed")"standard"Which presentation to render. See Templates below.
embedPredictorEmbedConfigOnly read when template === "embed". See Embed template.
themeOptionsCustomThemeOptionsSee Theming.
userIsLoggedInbooleanfalseHost auth state.
signInCTASignInCTADetailsSee Sign-in CTA.
tabsPredictorTab[]all tabsWhich tabs to enable. Ignored by embed.
defaultImagePlaceholderUrlstringFallback image shown when the template has no image configured. Shared between the standard hero header and the embed template's image block.
playTabBannersPlayTabBanner[]Custom banners injected into the Play tab. Ignored by embed.
consentsConsentDef[]Consent definitions required before predicting.
matchCardBgImageUrlstringBackground image URL for match prediction cards. Ignored by embed.
betslipPredictorBetslipConfigBetslip integration. See below.

Templates

type PredictorTemplateType = "standard" | "embed";
TemplateUse case
"standard" (default)The full multi-tab interface described throughout this page — Play, Leaderboard, Private Leagues, Rules, Prizes.
"embed"A single upcoming match rendered as a compact card, meant to be dropped inside an article. No tabs, no leaderboard — just branding, a description/image split, one match to predict, and (optionally) a follow-up CTA to the full experience.
<Predictor
  entityId="predictor-template-123"
  sdk={sdk}
  language="en"
  template="embed"
/>

Unlike WidgetTemplate-based components (see Templates), Predictor's template is a plain string union, not a shared enum — it isn't part of the standard/split/overlay family.

Embed template

interface PredictorEmbedConfig {
  imagePosition?: "left" | "right"; // default: "right" — description takes the other side
  matchId?: string; // pin a specific match; otherwise the soonest upcoming match across the whole template is used
  successMessage?: {
    title?: string;
    description?: string;
    cta: OnSuccessCTADetails; // required — label + (onClick | url) + target
  };
}

Layout

  • Gradient background (top to bottom, from the theme's primary color) wrapping the whole card.
  • Branding + title, then description, stacked in a column — the template's own logo/title/description from sdk.loyalty.getTemplateById().
  • Image (the template's own image, falling back to defaultImagePlaceholderUrl), alongside the branding/description column. Controlled by embed.imagePosition ("left" or "right", default "right"); on mobile it stacks below regardless of position. When there's no image, the branding/description column simply takes the full width.
  • Match card (white, inside the gradient): kickoff date, team crests, and centered score steppers (▲ number ▼) instead of the stacked left/right-chevron rows used by the standard Play tab. The full 1X2 odds market is always shown here — before and after a prediction — unlike the standard view, which only reveals the single matching odd once a prediction exists.
  • Submit CTA: a fixed-size button below the card (not full-width), sharing styling with the post-submission CTA described next.
  • Match selection: embed.matchId pins a specific match; otherwise the soonest upcoming match across the whole template (not just the current matchweek) is auto-selected. Requires no groupIdgetTemplateMatches is called for the whole template.

Success message

Optional. When embed.successMessage is configured, submitting a prediction reveals a CTA button (same styling as the submit button, but with successMessage.cta's label/action) followed by a success alert, with a subtle pop-in animation. Omit successMessage to show nothing extra after submission — the match card still switches to its odds display.

successMessage.cta uses the familiar OnSuccessCTADetails shape (see CollectLead), but note the priority differs here: onClickurl. component is not supported — the CTA always renders as EmbedActionButton so it keeps the same fixed styling as the submit button; only the label and click behavior change.

<Predictor
  entityId="predictor-template-123"
  sdk={sdk}
  language="en"
  template="embed"
  embed={{
    imagePosition: "left",
    successMessage: {
      title: "Prediction submitted!",
      description: "Keep playing for a chance to win amazing prizes.",
      cta: {
        defaultLabel: "Play Now",
        url: "https://your-site.com/predictor",
        target: "_blank",
      },
    },
  }}
/>

Tabs

Applies to template="standard" only.

type PredictorTab = "play" | "leaderboard" | "private-leagues" | "rules" | "prizes";
TabDescription
playMain prediction interface — submit match score forecasts.
leaderboardGlobal rankings with pagination and user highlight.
private-leaguesCreate and join private leagues.
rulesSearchable game rules and scoring system.
prizesPrize distribution and rewards.

Pass tabs to limit the visible tabs:

<Predictor {...otherProps} tabs={["play", "leaderboard"]} />

Play tab banners

Inject custom React content into specific slots inside the Play tab.

type PlayTabBannerPosition =
  | "above-group-nav"
  | "below-group-nav"
  | "below-matches"
  | "bottom"
  | "left"
  | "right";

interface PlayTabBanner {
  position: PlayTabBannerPosition;
  render: () => React.ReactNode;
}
PositionLocation
above-group-navAbove the matchweek group navigation.
below-group-navBelow the matchweek group navigation.
below-matchesBelow the list of match cards.
bottomVery bottom of the Play tab.
leftLeft sidebar (desktop only).
rightRight sidebar (desktop only).
<Predictor
  {...otherProps}
  playTabBanners={[
    { position: "above-group-nav", render: () => <SponsorBanner /> },
    { position: "right", render: () => <AdSlot id="predictor-sidebar" /> },
  ]}
/>

Consent gating

interface ConsentDef {
  consentId: string;
  body: string;        // HTML allowed — links, formatting
  required: boolean;
  defaultChecked: boolean;
}
  • Required consents block prediction submission until accepted.
  • Optional consents appear in the same modal but do not block progression.
  • Once accepted, the modal does not reappear for that user.
<Predictor
  {...otherProps}
  consents={[
    {
      consentId: "tos",
      body: 'I accept the <a href="https://example.com/tos" target="_blank">Terms of Service</a>.',
      required: true,
      defaultChecked: false,
    },
  ]}
/>

Betslip integration

When the betslip prop is provided, Predictor renders a Betslip widget internally as a sibling — you do not add a separate <Betslip /> component to the page. See the Betslip component page for the standalone widget reference.

interface PredictorBetslipConfig {
  trigger?: PredictorBetslipTrigger; // "predictions-only" | "odds-only"
  position?: BetslipPosition;
  maxSelections?: number;
  stakePresets?: number[];
  oddsPollingInterval?: number;
  currency?: string;
  ctaUrlTemplate?: string;
  brandingLogoUrl?: string;
  labels?: BetslipLabels;
  themeOptions?: CustomThemeOptions;
}

Trigger modes

ModeWhen selections are sent to Betslip
"predictions-only" (default)Every time the user edits a score (home/away increment or decrement). If the user submits without editing (e.g. default 0-0), the selection is sent at submit time as a fallback. Outcome derivation: "1" (home win), "X" (draw), "2" (away win).
"odds-only"Only when the user clicks an odds button on the post-submit odds display. The odds button becomes a command-bus trigger instead of a direct bookmaker link.

Selection format sent by Predictor

"{matchId}:FT_1X2:{outcomeKey}"
// Example: "fb:m:451634:FT_1X2:1"  (home win)

Automatic removal

When the user deletes a submitted prediction, the matching betslip selection is removed via betslipApi.removeSelection(). When a score is edited in "predictions-only" mode after submission, the new outcome upserts the old one (no explicit removal needed).

Theme inheritance

If betslip.themeOptions is not provided, the Betslip widget inherits the Predictor's own themeOptions. A single theme on the Predictor is enough to style both consistently.

Examples

Basic

<Predictor
  entityId="predictor-template-123"
  sdk={sdk}
  language="en"
/>

Authenticated with selected tabs

<Predictor
  entityId="predictor-template-123"
  sdk={sdk}
  language="en"
  userIsLoggedIn
  tabs={["play", "leaderboard", "private-leagues"]}
  matchCardBgImageUrl="https://your-cdn.com/stadium-bg.jpg"
  themeOptions={{ mode: "dark" }}
/>

Full setup with banners and consents

<Predictor
  entityId="predictor-template-123"
  sdk={sdk}
  language="en"
  userIsLoggedIn={false}
  signInCTA={{ defaultLabel: "Sign in to Predict", onClick: handleSignIn }}
  tabs={["play", "leaderboard", "private-leagues", "rules", "prizes"]}
  matchCardBgImageUrl="https://your-cdn.com/matchcard-bg.jpg"
  playTabBanners={[
    { position: "above-group-nav", render: () => <SponsorBanner /> },
    { position: "right", render: () => <AdSlot id="predictor-sidebar" /> },
  ]}
  consents={[
    {
      consentId: "tos",
      body: 'I accept the <a href="https://example.com/tos" target="_blank">Terms of Service</a>.',
      required: true,
      defaultChecked: false,
    },
  ]}
  themeOptions={{ mode: "light" }}
/>

Predictor + Betslip (predictions trigger)

<Predictor
  entityId="predictor-template-123"
  sdk={sdk}
  language="en"
  userIsLoggedIn
  tabs={["play", "leaderboard"]}
  betslip={{
    trigger: "predictions-only",
    position: "side-right",
    currency: "£",
    stakePresets: [5, 10, 25, 50],
    ctaUrlTemplate:
      "https://your-bookmaker.com/bet?ids={selectionIds}&stake={stake}&ref={currentUrl}",
    labels: { disclaimer: "18+ | Please gamble responsibly" },
  }}
  themeOptions={{ mode: "dark" }}
/>

Predictor + Betslip (odds trigger)

<Predictor
  entityId="predictor-template-123"
  sdk={sdk}
  language="en"
  userIsLoggedIn
  betslip={{
    trigger: "odds-only",
    position: "bottom-right",
    currency: "€",
    ctaUrlTemplate:
      "https://your-bookmaker.com/bet?ids={selectionIds}&stake={stake}",
  }}
/>