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>
This commit is contained in:
Ronny
2026-04-12 16:31:24 +02:00
parent 2414fc04d9
commit 1be1cdba2f
52 changed files with 80 additions and 10 deletions
Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 118 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 292 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 154 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 252 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 231 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 854 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 604 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 940 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 151 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 989 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 254 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 245 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 625 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 789 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 658 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 932 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 928 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 153 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 323 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 307 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 138 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 681 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 947 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 566 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 982 B

+4 -2
View File
@@ -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>
+5 -4
View File
@@ -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>
+5 -4
View File
@@ -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>
)}
+66
View File
@@ -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 || '';
}