Event Card

A single-event card — a competition header over the two competitors and their kickoff time, live score, or final result. Team sports show one score column; tennis shows a per-set breakdown with an expandable scoreboard. Live events auto-refresh on an interval. Sport-agnostic: works for any sport the Sports API covers (football, basketball, tennis, ice hockey, American football, …), adapting its status labels and score layout to each. Football events can additionally let signed-in users place predictions across several markets.

Import

import { EventCard } from "fansunited-frontend-components";
import { EventCardProps } from "fansunited-frontend-core";

Required props

PropTypeDescription
eventIdstringEvent ID to render, e.g. "fb:m:12345".
sdkFansUnitedSDKModelSDK instance.
languageLanguageTypeDisplay language.

Optional props

PropTypeDescription
themeOptionsCustomThemeOptionsSee Theming.
typeEventCardType"header" | "detailed". The kind of card. Defaults to "header". "detailed" is reserved for a future release — see Card type.
templateEventCardTemplateType"compact" | "standard" | "hero". Visual density of the card. Defaults to "standard". See Templates.
showCompetitionbooleanShow the competition header (crest + name + stage/round or country subtitle). Defaults to true.
showOddsbooleanRender betting odds in each template. Defaults to true. Not yet supported by the backend — see Odds.
liveRefreshbooleanPoll the events API while the event is LIVE so score/status stay current. Defaults to true. See Live refresh.
refreshIntervalnumberLive-refresh poll interval in milliseconds. Defaults to 30000 (30s).
userIsLoggedInbooleanWhether the visitor is signed in. Required for predictions — see Predictions. Defaults to false.
predictionsEventCardPredictionsConfigLet signed-in users place single predictions on the event. See Predictions.

Templates

template picks the visual prominence of the same card — it does not change what data is shown.

ValueLayout
"compact"A livescore-style row: competition header over a match row, kickoff time / status on the left, competitors stacked on the right with a single score column (team sports) or a per-set grid (tennis).
"standard" (default)The two competitors flank a central column showing the kickoff time (upcoming) or the score / final result, each crest beside its name.
"hero"A large card — each competitor's crest stacked over its name, flanking a centered score / kickoff time in an inset panel, with a left accent bar on live events.

Card type

type is a higher-level switch than template:

ValueBehavior
"header" (default)The condensed event card described on this page (competition header + competitors + time/score). The only kind implemented today.
"detailed"A richer event view (lineups, stats, timeline, …). Reserved, not implemented yet — the prop and its contract are in place so it's stable before the feature lands. Passing it has no effect today; the header card is rendered regardless.

Predictions

Signed-in users can place Fans United single predictions on the event, across several markets, in
a collapsible drawer at the bottom of the card. All three templates support it, each with a drawer
designed for its own layout — see Prediction drawer per template.

<EventCard
  eventId="fb:m:12345"
  sdk={sdk}
  language="en"
  userIsLoggedIn
  predictions={{
    markets: ["FT_1X2", "CORRECT_SCORE_ADVANCED", "OVER_GOALS_2_5"],
  }}
/>;

EventCardPredictionsConfig

FieldTypeDescription
marketsEventCardPredictionMarket[]Markets to offer, rendered in this order. Omitted or empty means no prediction UI. Values outside the supported set are skipped.
display"drawer" | "inline"Whether the markets sit behind a collapsible footer row ("drawer", the default) or are rendered directly ("inline"). See Display mode.
cutoffMinutesnumberHow many minutes before kickoff predictions close. Defaults to 15, which is also the minimum — the Prediction API rejects predictions made inside the last 15 minutes before kickoff, so smaller values are raised to 15. Use a larger value to close the drawer earlier.
allowDeletebooleanLet users remove a prediction they already made. Defaults to true. Removal stays available while the match is in play — see Behaviour.
defaultExpandedbooleanStart the drawer expanded instead of collapsed. Defaults to false. Only applies when display is "drawer" — it keeps the collapse control, unlike display: "inline".

Requirements

There is no enabled flag — the drawer renders when all of these hold, and is absent otherwise
(the card then looks exactly as it does without predictions configured):

  • the event is a football event (the prediction API is football-only);
  • userIsLoggedIn is true — an anonymous visitor sees no prediction UI and no sign-in prompt;
  • predictions.markets contains at least one supported market;
  • the event resolves to a Football API match — see Event ids vs match ids.

The SDK must also be able to authenticate the user (an authProvider supplying their id token) —
the prediction endpoints all require it.

Event ids vs match ids

The card renders events from the Sports API, but predictions are made against the Football
API
, and those are two different data providers. Their ids are kept aligned, so an event and its
match usually share one id — but that isn't guaranteed: a newly created football match can be 123
in the Football API and 456 in the Sports API.

Because the Prediction API only understands Football API match ids, the widget never sends an event id
to it. Instead, for a football event with predictions configured, it looks the id up via
sdk.football.getMatchById and then verifies the result is the same fixture — both team ids must
match exactly (the two APIs do share competitor ids) and the kickoff times must agree. Only then are
predictions offered, against the id the Football API returned.

If the lookup 404s, fails, or comes back as a different fixture, the prediction drawer simply isn't
rendered
and the card behaves as it does without predictions configured. Predicting on the wrong
match would be worse than not offering predictions at all, so the check errs toward hiding.

This costs one extra getMatchById call, made only for football events that actually have predictions
configured for a signed-in user — never for other sports, anonymous visitors, or cards without a
market list.

Display mode

predictions.display picks how prominent the markets are. Both modes are available in every
template, styled to suit each one, and behave identically otherwise.

ValueBehaviour
"drawer" (default)The markets sit behind a collapsible "Make your prediction" footer row. The card keeps the footprint it has without predictions until the user engages, so adding a market list doesn't make every card taller.
"inline"The markets render directly, with no collapse control and no chevron. For placements where predicting is the point of the card.

The heading row is kept in both modes — as the toggle in "drawer", as a plain label in "inline"
because without it the market rows would sit under the score with no context.

defaultExpanded is a third, distinct option: "drawer" with defaultExpanded: true opens on load
but the user can still collapse it, whereas "inline" has nothing to collapse.

<EventCard
  eventId="fb:m:12345"
  sdk={sdk}
  language="en"
  userIsLoggedIn
  predictions={{
    markets: ["FT_1X2", "CORRECT_SCORE_ADVANCED"],
    display: "inline",
  }}
/>;

Prediction drawer per template

The behaviour is identical across templates; only the presentation differs, so the drawer belongs to
the card it sits in rather than looking bolted on.

TemplateDrawer
"compact"A thin footer strip below the match row. Each market is one line — question on the left, control on the right. Competitors are identified by a small crest (no name — the rows above already spell those out) and the steppers run horizontally, so no row grows taller than the livescore rhythm above it.
"standard"A full-bleed footer whose divider spans the card. Each market is a block: the question above its control, with competitors shown as crest + name.
"hero"The same layout as "standard", in a second inset panel below the score panel so the card reads as two deliberate blocks. The live accent bar stays on the score panel.

Supported markets

Every MarketEnum value is supported.

Answered byMarkets
Home / Draw / AwayFT_1X2, HT_1X2
Two of three outcomes (1X / 12 / X2)DOUBLE_CHANCE
Half-time / full-time doubleHT_FT
Exact scorelineCORRECT_SCORE, CORRECT_SCORE_HT, CORRECT_SCORE_ADVANCED
A corner countCORNERS_MATCH
Yes / NoBOTH_TEAMS_SCORE, OVER_GOALS_0_5OVER_GOALS_6_5, OVER_CORNERS_6_5OVER_CORNERS_13_5, RED_CARD_MATCH, PENALTY_MATCH
A playerPLAYER_SCORE, PLAYER_SCORE_FIRST_GOAL, PLAYER_SCORE_TWICE, PLAYER_SCORE_HATTRICK, PLAYER_YELLOW_CARD, PLAYER_RED_CARD

A market outside this set is skipped and a console.warn names it, rather than the row silently
rendering nothing.

Player markets pick from both squads, grouped by team, with shirt numbers and headshots.
Configuring one costs two extra getTeamById calls (made only when a player market is present) —
squads rather than lineups, because lineups are confirmed about an hour before kickoff, by which
point predictions have already closed. PLAYER_SCORE_FIRST_GOAL additionally offers Own goal and
No goal will be scored.

Layout by option count. Markets with a handful of options render as pills; HT_FT renders as a
labelled 3×3 grid in standard / hero; and anything with a long list — HT_FT in compact, and
the player markets everywhere — uses a one-line select, so a compact row never grows taller than the
livescore rows above it.

Behaviour

  • Before the cutoff each market is a row with its own selector. Picking values and hitting submit
    sends one prediction per market; only markets the user actually touched are sent, so a card with a
    correct-score market doesn't silently save 0:0 for someone who only picked a winner. A market
    that fails to save keeps its value inline for a retry — the ones that succeeded stay saved.
  • A saved market shows the value read-only with edit and remove affordances. Editing re-opens the
    selector and re-submitting overwrites; removing deletes that market's prediction only (each market
    is its own prediction record).
  • After the cutoff, while the match is in play, the drawer becomes a read-only summary of the
    markets the user predicted. Editing is no longer possible, but they can still remove a
    prediction (unless allowDelete is false). Removing the last one hides the drawer entirely.
  • Once the match is finished or cancelled the summary is fully read-only — the result is graded,
    so removal stops too — and each market shows its outcome and the points earned. With nothing
    predicted, the drawer never appears.

Live refresh

When liveRefresh is true (the default) and the event is LIVE, the card re-fetches on refreshInterval so the score and status stay current. Only live events poll — upcoming and finished events are fetched once. Set liveRefresh={false} to disable polling entirely.

Sports & statuses

  • Team sports render a single aggregate score (goals / points).
  • Tennis renders a per-set breakdown — a compact per-set grid (compact template) or a result-first summary with an expandable full scoreboard (standard / hero), including tiebreak superscripts.
  • Status labels are localized and sport-aware. An event resolves to one of four states — upcoming, live, finished, cancelled. The finished label adapts to the sport: clock-based sports (football, basketball, hockey) show FT; tennis, which has no game clock, shows Finished.
  • If the event can't be loaded, the card renders a contained "load failed" / "not available" message rather than breaking the host layout.

Examples

Basic (standard template)

import { EventCard } from "fansunited-frontend-components";

<EventCard eventId="fb:m:12345" sdk={sdk} language="en" />;

Compact livescore row

<EventCard
  eventId="fb:m:12345"
  sdk={sdk}
  language="en"
  template="compact"
/>;

Hero card without the competition header

<EventCard
  eventId="fb:m:12345"
  sdk={sdk}
  language="en"
  template="hero"
  showCompetition={false}
/>;

Finished event, no live polling

<EventCard
  eventId="fb:m:12345"
  sdk={sdk}
  language="en"
  liveRefresh={false}
/>;

Slower live refresh (60s)

<EventCard
  eventId="fb:m:12345"
  sdk={sdk}
  language="en"
  refreshInterval={60000}
/>;

Predictions closing an hour early, expanded, no delete

<EventCard
  eventId="fb:m:12345"
  sdk={sdk}
  language="en"
  userIsLoggedIn=USERISSIGNEDIN
  predictions={{
    markets: ["FT_1X2", "BOTH_TEAMS_SCORE", "CORNERS_MATCH"],
    cutoffMinutes: 60,
    allowDelete: false,
    defaultExpanded: true,
  }}
/>;

Did this page help you?