Real-world examples showing how to integrate the Squad Golf API into your applications. From simple widgets to complex real-time dashboards.
import React from 'react';
import { SquadGolfProvider, useLeaderboard } from '@squad-golf/api-client/react';
// Initialize client outside component to avoid recreating
const client = new SquadGolfClient({
apiKey: process.env.NEXT_PUBLIC_SQUAD_GOLF_API_KEY,
enableWebSocket: true
});
function App() {
return (
<SquadGolfProvider client={client}>
<LiveLeaderboard tournamentId="t_masters2024" />
</SquadGolfProvider>
);
}
function LiveLeaderboard({ tournamentId }) {
const {
leaderboard,
isLive,
lastUpdated,
error,
loading
} = useLeaderboard(tournamentId);
if (loading) return <LoadingSpinner />;
if (error) return <ErrorMessage error={error} />;
if (!leaderboard) return <div>No leaderboard data available</div>;
return (
<div className="leaderboard-container">
<div className="leaderboard-header">
<h1>Live Leaderboard</h1>
<div className="status-indicators">
{isLive && (
<span className="live-indicator">
š“ LIVE
</span>
)}
<span className="last-updated">
Updated: {lastUpdated?.toLocaleTimeString()}
</span>
</div>
</div>
<div className="leaderboard-table">
<div className="table-header">
<span>Pos</span>
<span>Player</span>
<span>Score</span>
<span>Total</span>
<span>Thru</span>
</div>
{leaderboard.players.map((player, index) => (
<div
key={player.player._id}
className={`player-row ${index < 3 ? 'top-three' : ''}`}
>
<span className="position">
{player.position === 1 ? 'š' : player.position}
</span>
<div className="player-info">
<span className="player-name">{player.player.fullName}</span>
<span className="player-country">{player.player.country}</span>
</div>
<span className={`score ${player.score < 0 ? 'under-par' : player.score > 0 ? 'over-par' : 'even'}`}>
{player.score === 0 ? 'E' :
player.score > 0 ? `+${player.score}` :
player.score}
</span>
<span className="total">{player.total}</span>
<span className="thru">{player.thru || 'F'}</span>
</div>
))}
</div>
</div>
);
}
function LoadingSpinner() {
return (
<div className="loading-container">
<div className="spinner"></div>
<p>Loading leaderboard...</p>
</div>
);
}
function ErrorMessage({ error }) {
return (
<div className="error-container">
<h3>Unable to load leaderboard</h3>
<p>{error.message}</p>
<button onClick={() => window.location.reload()}>
Try Again
</button>
</div>
);
}
import React, { useState, useEffect, useCallback } from 'react';
import { SquadGolfClient } from '@squad-golf/api-client';
const client = new SquadGolfClient({
apiKey: process.env.NEXT_PUBLIC_SQUAD_GOLF_API_KEY
});
function PlayerSearch() {
const [query, setQuery] = useState('');
const [players, setPlayers] = useState([]);
const [loading, setLoading] = useState(false);
const [selectedPlayer, setSelectedPlayer] = useState(null);
// Debounced search function
const searchPlayers = useCallback(
debounce(async (searchQuery) => {
if (!searchQuery.trim()) {
setPlayers([]);
return;
}
setLoading(true);
try {
const results = await client.players.searchPlayers({
name: searchQuery,
limit: 10
});
setPlayers(results);
} catch (error) {
console.error('Search failed:', error);
setPlayers([]);
} finally {
setLoading(false);
}
}, 300),
[]
);
useEffect(() => {
searchPlayers(query);
}, [query, searchPlayers]);
const handlePlayerSelect = async (player) => {
setSelectedPlayer(null);
setLoading(true);
try {
// Get detailed player information
const detailedPlayer = await client.players.getPlayer(player._id);
setSelectedPlayer(detailedPlayer);
} catch (error) {
console.error('Failed to load player details:', error);
} finally {
setLoading(false);
}
};
return (
<div className="player-search-container">
<div className="search-section">
<h2>Player Search</h2>
<div className="search-input-container">
<input
type="text"
placeholder="Search for players..."
value={query}
onChange={(e) => setQuery(e.target.value)}
className="search-input"
/>
{loading && <div className="search-spinner">š</div>}
</div>
{query && players.length > 0 && (
<div className="search-results">
{players.map((player) => (
<div
key={player._id}
className="search-result-item"
onClick={() => handlePlayerSelect(player)}
>
<div className="player-basic-info">
<span className="player-name">{player.fullName}</span>
<span className="player-country">
{player.countryFlag} {player.country}
</span>
</div>
{player.currentRankings?.owgr && (
<div className="player-ranking">
<span className="owgr-rank">
OWGR #{player.currentRankings.owgr.rank}
</span>
</div>
)}
</div>
))}
</div>
)}
{query && players.length === 0 && !loading && (
<div className="no-results">
No players found for "{query}"
</div>
)}
</div>
{selectedPlayer && (
<PlayerProfile player={selectedPlayer} />
)}
</div>
);
}
function PlayerProfile({ player }) {
return (
<div className="player-profile">
<div className="profile-header">
<h3>{player.fullName}</h3>
<span className="country">
{player.countryFlag} {player.country}
</span>
</div>
<div className="profile-stats">
<div className="stat-grid">
{player.currentRankings?.owgr && (
<div className="stat-item">
<span className="stat-label">OWGR Rank</span>
<span className="stat-value">#{player.currentRankings.owgr.rank}</span>
</div>
)}
{player.currentRankings?.fedexCup && (
<div className="stat-item">
<span className="stat-label">FedEx Cup Rank</span>
<span className="stat-value">#{player.currentRankings.fedexCup.rank}</span>
</div>
)}
{player.careerStats?.pgatourWins && (
<div className="stat-item">
<span className="stat-label">PGA Tour Wins</span>
<span className="stat-value">{player.careerStats.pgatourWins}</span>
</div>
)}
{player.careerStats?.majorWins && (
<div className="stat-item">
<span className="stat-label">Major Wins</span>
<span className="stat-value">{player.careerStats.majorWins}</span>
</div>
)}
</div>
</div>
</div>
);
}
// Utility function for debouncing
function debounce(func, wait) {
let timeout;
return function executedFunction(...args) {
const later = () => {
clearTimeout(timeout);
func(...args);
};
clearTimeout(timeout);
timeout = setTimeout(later, wait);
};
}
export default PlayerSearch;
class SquadGolfWidget {
constructor(options) {
this.apiKey = options.apiKey;
this.container = options.container;
this.tournamentId = options.tournamentId;
this.refreshInterval = options.refreshInterval || 60000; // 1 minute
this.baseURL = 'https://api.squad.golf';
this.init();
}
async init() {
this.render();
await this.loadTournament();
this.startAutoRefresh();
}
render() {
this.container.innerHTML = `
<div class="squad-golf-widget">
<div class="widget-header">
<h3 class="tournament-name">Loading...</h3>
<div class="widget-status">
<span class="status-indicator"></span>
<span class="last-updated">Last updated: --</span>
</div>
</div>
<div class="widget-content">
<div class="loading-message">Loading tournament data...</div>
</div>
<div class="widget-footer">
<span class="powered-by">Powered by Squad Golf API</span>
</div>
</div>
`;
this.addStyles();
}
async loadTournament() {
try {
const response = await fetch(`${this.baseURL}/api/v1/tournaments/${this.tournamentId}`, {
headers: {
'Authorization': `Bearer ${this.apiKey}`,
'Content-Type': 'application/json'
}
});
if (!response.ok) {
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
}
const data = await response.json();
this.updateWidget(data.data);
} catch (error) {
console.error('Failed to load tournament:', error);
this.showError(error.message);
}
}
updateWidget(tournament) {
const nameEl = this.container.querySelector('.tournament-name');
const statusEl = this.container.querySelector('.status-indicator');
const updatedEl = this.container.querySelector('.last-updated');
const contentEl = this.container.querySelector('.widget-content');
// Update header
nameEl.textContent = tournament.name;
statusEl.className = `status-indicator ${tournament.status}`;
statusEl.textContent = this.formatStatus(tournament.status);
updatedEl.textContent = `Last updated: ${new Date().toLocaleTimeString()}`;
// Update content
if (tournament.leaderboard && tournament.leaderboard.length > 0) {
contentEl.innerHTML = this.renderLeaderboard(tournament.leaderboard.slice(0, 5));
} else {
contentEl.innerHTML = this.renderTournamentInfo(tournament);
}
}
renderLeaderboard(players) {
return `
<div class="leaderboard-mini">
<div class="leaderboard-header">
<span>Pos</span>
<span>Player</span>
<span>Score</span>
</div>
${players.map(player => `
<div class="leaderboard-row">
<span class="position">${player.position}</span>
<span class="player">${player.player.fullName}</span>
<span class="score ${player.score < 0 ? 'under-par' : player.score > 0 ? 'over-par' : 'even'}">
${player.score === 0 ? 'E' :
player.score > 0 ? `+${player.score}` :
player.score}
</span>
</div>
`).join('')}
</div>
`;
}
renderTournamentInfo(tournament) {
const startDate = new Date(tournament.startDate).toLocaleDateString();
const endDate = new Date(tournament.endDate).toLocaleDateString();
return `
<div class="tournament-info">
<div class="info-item">
<span class="label">Dates:</span>
<span class="value">${startDate} - ${endDate}</span>
</div>
${tournament.location ? `
<div class="info-item">
<span class="label">Location:</span>
<span class="value">${tournament.location.name}</span>
</div>
` : ''}
${tournament.fieldSize ? `
<div class="info-item">
<span class="label">Field Size:</span>
<span class="value">${tournament.fieldSize} players</span>
</div>
` : ''}
</div>
`;
}
formatStatus(status) {
const statusMap = {
'upcoming': 'Upcoming',
'active': 'In Progress',
'completed': 'Completed',
'cancelled': 'Cancelled'
};
return statusMap[status] || status;
}
showError(message) {
const contentEl = this.container.querySelector('.widget-content');
contentEl.innerHTML = `
<div class="error-message">
<span class="error-icon">ā ļø</span>
<span class="error-text">Failed to load tournament data</span>
<small>${message}</small>
</div>
`;
}
startAutoRefresh() {
setInterval(() => {
this.loadTournament();
}, this.refreshInterval);
}
addStyles() {
if (document.getElementById('squad-golf-widget-styles')) return;
const styles = document.createElement('style');
styles.id = 'squad-golf-widget-styles';
styles.textContent = `
.squad-golf-widget {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
background: white;
border: 1px solid #E8EDE3;
border-radius: 8px;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
overflow: hidden;
max-width: 400px;
}
.widget-header {
background: linear-gradient(135deg, #2563EB 0%, #1D4ED8 100%);
color: white;
padding: 15px;
}
.tournament-name {
margin: 0 0 8px 0;
font-size: 16px;
font-weight: 600;
}
.widget-status {
display: flex;
align-items: center;
gap: 8px;
font-size: 12px;
opacity: 0.9;
}
.status-indicator {
padding: 2px 6px;
border-radius: 10px;
background: rgba(255, 255, 255, 0.2);
font-size: 10px;
text-transform: uppercase;
font-weight: 500;
}
.status-indicator.active {
background: #16A34A;
}
.widget-content {
padding: 15px;
min-height: 100px;
}
.loading-message, .error-message {
text-align: center;
color: #5C5F56;
font-size: 14px;
}
.error-message {
display: flex;
flex-direction: column;
gap: 8px;
align-items: center;
}
.error-message small {
color: #999;
font-size: 12px;
}
.leaderboard-mini {
font-size: 14px;
}
.leaderboard-header {
display: grid;
grid-template-columns: 40px 1fr 60px;
gap: 8px;
padding: 8px 0;
border-bottom: 1px solid #E8EDE3;
font-weight: 600;
color: #5C5F56;
font-size: 12px;
text-transform: uppercase;
}
.leaderboard-row {
display: grid;
grid-template-columns: 40px 1fr 60px;
gap: 8px;
padding: 8px 0;
border-bottom: 1px solid #F5F5F3;
}
.leaderboard-row:last-child {
border-bottom: none;
}
.position {
font-weight: 600;
text-align: center;
}
.score {
text-align: center;
font-weight: 600;
}
.score.under-par { color: #16A34A; }
.score.over-par { color: #DC2626; }
.score.even { color: #2D3027; }
.tournament-info {
display: flex;
flex-direction: column;
gap: 8px;
}
.info-item {
display: flex;
justify-content: space-between;
font-size: 14px;
}
.info-item .label {
color: #5C5F56;
font-weight: 500;
}
.info-item .value {
color: #2D3027;
}
.widget-footer {
background: #F5F5F3;
padding: 8px 15px;
text-align: center;
}
.powered-by {
font-size: 11px;
color: #5C5F56;
}
`;
document.head.appendChild(styles);
}
destroy() {
if (this.refreshTimer) {
clearInterval(this.refreshTimer);
}
this.container.innerHTML = '';
}
}
// Usage example:
// const widget = new SquadGolfWidget({
// apiKey: 'your-api-key',
// container: document.getElementById('tournament-widget'),
// tournamentId: 't_masters2024',
// refreshInterval: 30000 // 30 seconds
// });