Predictor
Season-long football score predictor with a multi-tab interface: Play, Leaderboard, Private Leagues, Rules, and Prizes. Includes consent gating, custom Play-tab banners, and first-class Betslip integration.
Import
import { Predictor } from "fansunited-frontend-components";
import {
PredictorProps,
PredictorTab,
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. |
Predictor does not take a
templateprop — its layout is a fixed multi-tab interface.
Optional props
| Prop | Type | Default | Description |
|---|---|---|---|
themeOptions | CustomThemeOptions | — | See Theming. |
userIsLoggedIn | boolean | false | Host auth state. |
signInCTA | SignInCTADetails | — | See Sign-in CTA. |
tabs | PredictorTab[] | all tabs | Which tabs to enable. |
defaultImagePlaceholderUrl | string | — | Fallback image URL for the hero header. |
playTabBanners | PlayTabBanner[] | — | Custom banners injected into the Play tab. |
consents | ConsentDef[] | — | Consent definitions required before predicting. |
matchCardBgImageUrl | string | — | Background image URL for match prediction cards. |
betslip | PredictorBetslipConfig | — | Betslip integration. See below. |
Tabs
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}",
}}
/>