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
| Prop | Type | Description |
|---|---|---|
entityId | string | Predictor template identifier. |
sdk | FansUnitedSDKModel | SDK instance. |
language | LanguageType | Display language. |
Optional props
| Prop | Type | Default | Description |
|---|---|---|---|
template | PredictorTemplateType ("standard" | "embed") | "standard" | Which presentation to render. See Templates below. |
embed | PredictorEmbedConfig | — | Only read when template === "embed". See Embed template. |
themeOptions | CustomThemeOptions | — | See Theming. |
userIsLoggedIn | boolean | false | Host auth state. |
signInCTA | SignInCTADetails | — | See Sign-in CTA. |
tabs | PredictorTab[] | all tabs | Which tabs to enable. Ignored by embed. |
defaultImagePlaceholderUrl | string | — | Fallback image shown when the template has no image configured. Shared between the standard hero header and the embed template's image block. |
playTabBanners | PlayTabBanner[] | — | Custom banners injected into the Play tab. Ignored by embed. |
consents | ConsentDef[] | — | Consent definitions required before predicting. |
matchCardBgImageUrl | string | — | Background image URL for match prediction cards. Ignored by embed. |
betslip | PredictorBetslipConfig | — | Betslip integration. See below. |
Templates
type PredictorTemplateType = "standard" | "embed";| Template | Use 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 todefaultImagePlaceholderUrl), alongside the branding/description column. Controlled byembed.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.matchIdpins a specific match; otherwise the soonest upcoming match across the whole template (not just the current matchweek) is auto-selected. Requires nogroupId—getTemplateMatchesis 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: onClick → url. 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";| Tab | Description |
|---|---|
play | Main prediction interface — submit match score forecasts. |
leaderboard | Global rankings with pagination and user highlight. |
private-leagues | Create and join private leagues. |
rules | Searchable game rules and scoring system. |
prizes | Prize 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;
}| Position | Location |
|---|---|
above-group-nav | Above the matchweek group navigation. |
below-group-nav | Below the matchweek group navigation. |
below-matches | Below the list of match cards. |
bottom | Very bottom of the Play tab. |
left | Left sidebar (desktop only). |
right | Right 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
| Mode | When 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}",
}}
/>