feat: local country flags replacing team crests
48 country flags downloaded from flagcdn.com (320px PNG, ~55KB total)
stored in frontend/public/flags/{iso-code}.png.
New utility getFlagUrl() maps team names to local flag files.
Applied to MatchCard, DashboardPage, and TipModal.
Falls back to original crest URL if no mapping exists (e.g. TBD).
No external API calls at runtime — all flags served statically.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
|
After Width: | Height: | Size: 1.5 KiB |
|
After Width: | Height: | Size: 118 B |
|
After Width: | Height: | Size: 1.6 KiB |
|
After Width: | Height: | Size: 1.2 KiB |
|
After Width: | Height: | Size: 292 B |
|
After Width: | Height: | Size: 2.6 KiB |
|
After Width: | Height: | Size: 1.3 KiB |
|
After Width: | Height: | Size: 1.5 KiB |
|
After Width: | Height: | Size: 154 B |
|
After Width: | Height: | Size: 252 B |
|
After Width: | Height: | Size: 231 B |
|
After Width: | Height: | Size: 854 B |
|
After Width: | Height: | Size: 604 B |
|
After Width: | Height: | Size: 940 B |
|
After Width: | Height: | Size: 151 B |
|
After Width: | Height: | Size: 989 B |
|
After Width: | Height: | Size: 4.9 KiB |
|
After Width: | Height: | Size: 1.2 KiB |
|
After Width: | Height: | Size: 3.2 KiB |
|
After Width: | Height: | Size: 254 B |
|
After Width: | Height: | Size: 245 B |
|
After Width: | Height: | Size: 1.2 KiB |
|
After Width: | Height: | Size: 625 B |
|
After Width: | Height: | Size: 1.8 KiB |
|
After Width: | Height: | Size: 4.4 KiB |
|
After Width: | Height: | Size: 789 B |
|
After Width: | Height: | Size: 2.0 KiB |
|
After Width: | Height: | Size: 658 B |
|
After Width: | Height: | Size: 932 B |
|
After Width: | Height: | Size: 2.4 KiB |
|
After Width: | Height: | Size: 928 B |
|
After Width: | Height: | Size: 3.3 KiB |
|
After Width: | Height: | Size: 153 B |
|
After Width: | Height: | Size: 323 B |
|
After Width: | Height: | Size: 1.5 KiB |
|
After Width: | Height: | Size: 1.1 KiB |
|
After Width: | Height: | Size: 4.7 KiB |
|
After Width: | Height: | Size: 1.2 KiB |
|
After Width: | Height: | Size: 307 B |
|
After Width: | Height: | Size: 2.9 KiB |
|
After Width: | Height: | Size: 138 B |
|
After Width: | Height: | Size: 681 B |
|
After Width: | Height: | Size: 1.1 KiB |
|
After Width: | Height: | Size: 947 B |
|
After Width: | Height: | Size: 1.2 KiB |
|
After Width: | Height: | Size: 2.1 KiB |
|
After Width: | Height: | Size: 566 B |
|
After Width: | Height: | Size: 982 B |
@@ -1,6 +1,7 @@
|
||||
import { Check, TrendingUp, X } from 'lucide-react';
|
||||
import { useEffect, useState } from 'react';
|
||||
import { Match } from '../api/client';
|
||||
import { getFlagUrl } from '../utils/flagUrl';
|
||||
import styles from './MatchCard.module.css';
|
||||
|
||||
interface Props {
|
||||
@@ -41,10 +42,11 @@ function formatKickoff(utcDate: string): string {
|
||||
}
|
||||
|
||||
function FlagBox({ crest, name }: { crest: string | null; name: string }) {
|
||||
const src = getFlagUrl(name, crest);
|
||||
return (
|
||||
<div className={styles.flagBox}>
|
||||
{crest
|
||||
? <img className={styles.crest} src={crest} alt={name} />
|
||||
{src
|
||||
? <img className={styles.crest} src={src} alt={name} />
|
||||
: <span style={{ fontSize: 18 }}>🏳️</span>
|
||||
}
|
||||
</div>
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { useState } from 'react';
|
||||
import { Match, api } from '../api/client';
|
||||
import { getFlagUrl } from '../utils/flagUrl';
|
||||
import styles from './TipModal.module.css';
|
||||
|
||||
interface Props {
|
||||
@@ -63,8 +64,8 @@ export default function TipModal({ match, onClose, onSaved }: Props) {
|
||||
<div className={styles.teamsRow}>
|
||||
<div className={styles.teamBlock}>
|
||||
<div className={styles.flagLarge}>
|
||||
{match.homeTeam.crest
|
||||
? <img src={match.homeTeam.crest} alt={match.homeTeam.name} className={styles.flagImg} />
|
||||
{match.homeTeam.name
|
||||
? <img src={getFlagUrl(match.homeTeam.name, match.homeTeam.crest)} alt={match.homeTeam.name} className={styles.flagImg} />
|
||||
: <span className={styles.flagEmoji}>🏳️</span>
|
||||
}
|
||||
</div>
|
||||
@@ -75,8 +76,8 @@ export default function TipModal({ match, onClose, onSaved }: Props) {
|
||||
|
||||
<div className={styles.teamBlock}>
|
||||
<div className={styles.flagLarge}>
|
||||
{match.awayTeam.crest
|
||||
? <img src={match.awayTeam.crest} alt={match.awayTeam.name} className={styles.flagImg} />
|
||||
{match.awayTeam.name
|
||||
? <img src={getFlagUrl(match.awayTeam.name, match.awayTeam.crest)} alt={match.awayTeam.name} className={styles.flagImg} />
|
||||
: <span className={styles.flagEmoji}>🏳️</span>
|
||||
}
|
||||
</div>
|
||||
|
||||
@@ -2,6 +2,7 @@ import { useEffect, useState } from 'react';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
import { TrendingUp, TrendingDown } from 'lucide-react';
|
||||
import { api, DashboardData, Match } from '../api/client';
|
||||
import { getFlagUrl } from '../utils/flagUrl';
|
||||
import TipModal from '../components/TipModal';
|
||||
import styles from './DashboardPage.module.css';
|
||||
|
||||
@@ -88,8 +89,8 @@ export default function DashboardPage(_props: Props) {
|
||||
<div className={styles.heroTeams}>
|
||||
<div className={styles.heroTeam}>
|
||||
<div className={styles.heroCrestBox}>
|
||||
{hero.match.homeTeam.crest ? (
|
||||
<img src={hero.match.homeTeam.crest} alt={hero.match.homeTeam.name} className={styles.heroCrest} />
|
||||
{hero.match.homeTeam.name ? (
|
||||
<img src={getFlagUrl(hero.match.homeTeam.name, hero.match.homeTeam.crest)} alt={hero.match.homeTeam.name} className={styles.heroCrest} />
|
||||
) : (
|
||||
<span className={styles.heroCrestFallback}>🏳️</span>
|
||||
)}
|
||||
@@ -108,8 +109,8 @@ export default function DashboardPage(_props: Props) {
|
||||
|
||||
<div className={styles.heroTeam}>
|
||||
<div className={styles.heroCrestBox}>
|
||||
{hero.match.awayTeam.crest ? (
|
||||
<img src={hero.match.awayTeam.crest} alt={hero.match.awayTeam.name} className={styles.heroCrest} />
|
||||
{hero.match.awayTeam.name ? (
|
||||
<img src={getFlagUrl(hero.match.awayTeam.name, hero.match.awayTeam.crest)} alt={hero.match.awayTeam.name} className={styles.heroCrest} />
|
||||
) : (
|
||||
<span className={styles.heroCrestFallback}>🏳️</span>
|
||||
)}
|
||||
|
||||
@@ -0,0 +1,66 @@
|
||||
/**
|
||||
* Maps team names to local flag image paths.
|
||||
* Flags are stored as static PNGs in /flags/{iso-code}.png
|
||||
* Source: flagcdn.com (320px width, downloaded once)
|
||||
*/
|
||||
|
||||
const TEAM_TO_FLAG: Record<string, string> = {
|
||||
'Algeria': 'dz',
|
||||
'Argentina': 'ar',
|
||||
'Australia': 'au',
|
||||
'Austria': 'at',
|
||||
'Belgium': 'be',
|
||||
'Bosnia-Herzegovina': 'ba',
|
||||
'Brazil': 'br',
|
||||
'Canada': 'ca',
|
||||
'Cape Verde Islands': 'cv',
|
||||
'Colombia': 'co',
|
||||
'Congo DR': 'cd',
|
||||
'Croatia': 'hr',
|
||||
'Curaçao': 'cw',
|
||||
'Czechia': 'cz',
|
||||
'Ecuador': 'ec',
|
||||
'Egypt': 'eg',
|
||||
'England': 'gb-eng',
|
||||
'France': 'fr',
|
||||
'Germany': 'de',
|
||||
'Ghana': 'gh',
|
||||
'Haiti': 'ht',
|
||||
'Iran': 'ir',
|
||||
'Iraq': 'iq',
|
||||
'Ivory Coast': 'ci',
|
||||
'Japan': 'jp',
|
||||
'Jordan': 'jo',
|
||||
'Mexico': 'mx',
|
||||
'Morocco': 'ma',
|
||||
'Netherlands': 'nl',
|
||||
'New Zealand': 'nz',
|
||||
'Norway': 'no',
|
||||
'Panama': 'pa',
|
||||
'Paraguay': 'py',
|
||||
'Portugal': 'pt',
|
||||
'Qatar': 'qa',
|
||||
'Saudi Arabia': 'sa',
|
||||
'Scotland': 'gb-sct',
|
||||
'Senegal': 'sn',
|
||||
'South Africa': 'za',
|
||||
'South Korea': 'kr',
|
||||
'Spain': 'es',
|
||||
'Sweden': 'se',
|
||||
'Switzerland': 'ch',
|
||||
'Tunisia': 'tn',
|
||||
'Turkey': 'tr',
|
||||
'United States': 'us',
|
||||
'Uruguay': 'uy',
|
||||
'Uzbekistan': 'uz',
|
||||
};
|
||||
|
||||
/**
|
||||
* Returns the local flag URL for a team name.
|
||||
* Falls back to the original crest URL if no mapping exists.
|
||||
*/
|
||||
export function getFlagUrl(teamName: string, fallbackCrest: string | null): string {
|
||||
const code = TEAM_TO_FLAG[teamName];
|
||||
if (code) return `/flags/${code}.png`;
|
||||
return fallbackCrest || '';
|
||||
}
|
||||