Portal - Sportsbook Client View
URL: portal.altsportsleagues.ai
Purpose: External read-only interface for sportsbook operators to view published odds and league data.
Overview
Portal is the client-facing platform where sportsbook operators consume AltSportsData's published odds, browse events, and integrate data into their systems. Unlike PRTL, Portal is read-only - clients can view but not modify odds.
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β Portal Architecture β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ€
β β
β βββββββββββββββ βββββββββββββββββββββββ β
β β League β β β β
β β Sidebar β β Event Detail β β
β β β β (Tabs: Overview, β β
β β βΌ MMA β β Fights, Odds) β β
β β UFC β β β β β
β β BKB β β β β
β β βΌ F1 β β β β
β β Monaco β β β β β
β βββββββββββββββ βββββββββββββββββββββββ β
β β
β β = Live Event β
β β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββKey Differences from PRTL
| Feature | PRTL (Internal) | Portal (External) |
|---|---|---|
| Access | Internal staff only | Sportsbook operators |
| Odds Editing | Full edit capabilities | Read-only view |
| Market Controls | Suspend/resume/settle | View status only |
| Data Source | All markets (draft β settled) | Published markets only |
| Authentication | SSO + 2FA | API Key + OAuth |
Key Features
1. League Sidebar Navigation
Collapsible sidebar with league hierarchy and live event indicators:
<LeagueSidebar className="h-full" />Features:
- Sport grouping (MMA, Motorsports, etc.)
- League expansion/collapse
- Live event count badges
- Active event highlighting
- Collapsible to icon-only view
Visual structure:
βΌ MMA
β UFC (3 live)
β BKB Extreme Fighting (2)
βΌ Motorsports
β Formula 1 (2 live)
βΌ Other
β Jai Alai Miami (5)
β World Drone Racing (2)2. Event Detail Page
Tabbed interface for event information:
// Tab structure
<Tabs defaultValue="overview">
<TabsList>
<TabsTrigger value="overview">Overview</TabsTrigger>
<TabsTrigger value="fights">Fights (8)</TabsTrigger>
<TabsTrigger value="odds">Odds</TabsTrigger>
</TabsList>
<TabsContent value="overview">
<EventOverview event={event} />
</TabsContent>
<TabsContent value="fights">
<FightsList fights={event.fights} />
</TabsContent>
<TabsContent value="odds">
<OddsDisplay markets={event.markets} />
</TabsContent>
</Tabs>3. Status Badges (Read-Only)
Display-only status indicators:
<StatusBadge status="live" /> // Green pulsing dot
<StatusBadge status="upcoming" /> // Blue static dot
<StatusBadge status="completed" /> // Gray dot
<StatusBadge status="suspended" /> // Yellow warningStatus types supported:
live- Event/market currently activeupcoming- Scheduled future eventscheduled- Same as upcomingcompleted- Event finishedcancelled- Event cancelledsuspended- Market temporarily unavailableopen- Market accepting betsclosed- Market no longer accepting bets
4. Fight Cards (View-Only)
Read-only fight card display:
<FightCard fight={fight} />
// Compact version for lists
<FightCardCompact fight={fight} onClick={() => navigate(`/fights/${fight.id}`)} />Elements displayed:
- Fight number and weight class
- Blue corner fighter (name, nickname, record)
- Red corner fighter (name, nickname, record)
- Scheduled rounds
- Status badge
- No edit controls (read-only)
5. Odds Display Components
Multiple components for odds visualization:
OddsMarketCard
Full market card with selections:
<OddsMarketCard market={market} />Displays:
- Market name and type
- Open/Suspended/Closed status
- All selections with odds
- Movement indicators (β β)
- Last updated timestamp
OddsDisplay
Grid layout for multiple markets:
<OddsDisplay markets={event.markets} fightId={selectedFightId} />OddsRow
Compact blue vs red odds for tables:
<OddsRow
blueCornerName="Max Holloway"
redCornerName="Alexander Volkanovski"
blueOdds={-150}
redOdds={130}
blueMovement="up"
redMovement="down"
/>OddsSelectionCard
Individual selection display:
<OddsSelectionCard
selection={selection}
isLocked={market.status === 'suspended'}
/>Component Reference
Layout Components
| Component | Location | Purpose |
|---|---|---|
LeagueSidebar | components/layout/league-sidebar.tsx | Collapsible league navigation |
UI Components
| Component | Location | Purpose |
|---|---|---|
StatusBadge | components/ui/status-badge.tsx | Read-only status indicators |
FightCard | components/ui/fight-card.tsx | Full fight card (view-only) |
FightCardCompact | components/ui/fight-card.tsx | Compact fight row |
OddsMarketCard | components/ui/odds-card.tsx | Full market display |
OddsDisplay | components/ui/odds-card.tsx | Multi-market grid |
OddsRow | components/ui/odds-card.tsx | Compact odds row |
OddsSelectionCard | components/ui/odds-card.tsx | Single selection |
Types
// src/types/event.ts
type Sport = 'mma' | 'jai_alai' | 'f1' | 'esports' | 'drone_racing' | 'arm_wrestling' | 'combat';
type EventStatus = 'live' | 'upcoming' | 'scheduled' | 'completed' | 'cancelled';
interface Fighter {
id: string;
name: string;
nickname?: string;
record?: string;
country?: string;
}
interface Fight {
id: string;
event_id: string;
fight_number: number;
weight_class: WeightClass;
scheduled_rounds: number;
status: FightStatus;
blue_corner?: Fighter;
red_corner?: Fighter;
result?: FightResult;
}
interface OddsMarket {
id: string;
type: string; // 'moneyline', 'method_of_victory', etc.
name: string; // "Winner", "Method of Victory"
status: 'open' | 'suspended' | 'closed';
selections: OddsSelection[];
last_updated: string; // ISO timestamp
}
interface OddsSelection {
id: string;
name: string; // "Max Holloway", "KO/TKO", "Over 2.5"
odds: number; // American odds: -150, +200
implied_probability: number;
movement?: 'up' | 'down' | 'none';
}
interface EventDetail {
id: string;
league_id: string;
league_name: string;
sport: Sport;
name: string;
venue?: string;
location?: string;
date: string;
start_time?: string;
status: EventStatus;
broadcast?: string;
fights: Fight[];
markets?: OddsMarket[];
}Design Patterns
Read-Only Visual Cues
Portal components are visually distinct from PRTL:
- No edit buttons - Fight cards don't show edit icons
- No toggle switches - Markets show status but can't be changed
- No input fields - Odds displayed as text, not inputs
- Locked indicators - Suspended markets show lock icon
// Locked market display
{market.status === 'suspended' && (
<Lock className="h-4 w-4 text-muted-foreground" />
)}Movement Indicators
Show odds changes without allowing edits:
function MovementIndicator({ movement }: { movement?: 'up' | 'down' | 'none' }) {
if (!movement || movement === 'none') return null;
if (movement === 'up') {
return <TrendingUp className="h-3.5 w-3.5 text-green-500" />;
}
return <TrendingDown className="h-3.5 w-3.5 text-red-500" />;
}Odds Formatting
American odds display utility:
function formatOdds(odds: number): string {
if (odds >= 0) return `+${odds}`;
return odds.toString();
}
// Colors
<span className={odds >= 0 ? 'text-green-600' : 'text-foreground'}>
{formatOdds(odds)}
</span>Status Color Scheme
| Status | Background | Text | Dot |
|---|---|---|---|
live | bg-green-500/10 | text-green-600 | bg-green-500 animate-pulse |
upcoming | bg-blue-500/10 | text-blue-600 | bg-blue-500 |
completed | bg-gray-500/10 | text-gray-500 | bg-gray-400 |
suspended | bg-yellow-500/10 | text-yellow-600 | bg-yellow-500 |
cancelled | bg-red-500/10 | text-red-500 | bg-red-400 |
API Integration
Portal connects to read-only API endpoints:
// Read-only endpoints
GET /v1/portal/odds // List published odds
GET /v1/portal/odds/{marketId} // Get market odds
GET /v1/portal/events // List events
GET /v1/portal/sports // List sports
GET /v1/portal/leagues // List leagues
GET /v1/portal/export/odds // Export data (JSON/CSV)
// Webhook management (write)
POST /v1/portal/webhooks // Create subscription
PUT /v1/portal/webhooks/{id} // Update subscriptionAuthentication
External clients authenticate via:
- API Keys - For programmatic access
- OAuth 2.0 - For third-party integrations
- Rate limiting - Per client tier
- IP whitelisting - Optional per-client
Real-Time Updates
Portal receives real-time updates via:
- WebSocket feed - Subscribe to topics
- Webhook delivery - Push notifications
// WebSocket subscription
ws.send(JSON.stringify({
action: 'subscribe',
topics: ['event:123', 'sport:MMA']
}));
// Receive updates
ws.onmessage = (event) => {
const { type, data } = JSON.parse(event.data);
if (type === 'odds_update') {
updateOddsDisplay(data);
}
};Related Documentation
- PRTL (Internal) - Internal trading interface
- API Reference - Portal API endpoints
- Webhooks Guide - Webhook integration
- Requirements Spec - Full requirements document