diff --git a/public/static/img/bronze_medal_badge.png b/public/static/img/bronze_medal_badge.png new file mode 100644 index 0000000..897ef77 Binary files /dev/null and b/public/static/img/bronze_medal_badge.png differ diff --git a/public/static/img/developer_badge.png b/public/static/img/developer_badge.png new file mode 100644 index 0000000..46add30 Binary files /dev/null and b/public/static/img/developer_badge.png differ diff --git a/public/static/img/dunce_cap_badge.png b/public/static/img/dunce_cap_badge.png new file mode 100644 index 0000000..42e13a0 Binary files /dev/null and b/public/static/img/dunce_cap_badge.png differ diff --git a/public/static/img/experienced_player_badge.png b/public/static/img/experienced_player_badge.png new file mode 100644 index 0000000..c688470 Binary files /dev/null and b/public/static/img/experienced_player_badge.png differ diff --git a/public/static/img/getting_started_badge.png b/public/static/img/getting_started_badge.png new file mode 100644 index 0000000..0387828 Binary files /dev/null and b/public/static/img/getting_started_badge.png differ diff --git a/public/static/img/helper_badge.png b/public/static/img/helper_badge.png new file mode 100644 index 0000000..af4d1ba Binary files /dev/null and b/public/static/img/helper_badge.png differ diff --git a/public/static/img/hoster_badge.png b/public/static/img/hoster_badge.png new file mode 100644 index 0000000..3cf84a3 Binary files /dev/null and b/public/static/img/hoster_badge.png differ diff --git a/public/static/img/logo.png b/public/static/img/logo.png deleted file mode 100644 index 6c7e80d..0000000 Binary files a/public/static/img/logo.png and /dev/null differ diff --git a/public/static/img/new_player_badge.png b/public/static/img/new_player_badge.png new file mode 100644 index 0000000..aeefdec Binary files /dev/null and b/public/static/img/new_player_badge.png differ diff --git a/public/static/img/out_of_towner_badge.png b/public/static/img/out_of_towner_badge.png new file mode 100644 index 0000000..26823ca Binary files /dev/null and b/public/static/img/out_of_towner_badge.png differ diff --git a/public/static/img/pilot_program_badge.png b/public/static/img/pilot_program_badge.png new file mode 100644 index 0000000..fca003c Binary files /dev/null and b/public/static/img/pilot_program_badge.png differ diff --git a/public/static/img/regional_winner_badge.png b/public/static/img/regional_winner_badge.png new file mode 100644 index 0000000..3b30a89 Binary files /dev/null and b/public/static/img/regional_winner_badge.png differ diff --git a/public/static/img/regular_player_badge.png b/public/static/img/regular_player_badge.png new file mode 100644 index 0000000..d8e6ad0 Binary files /dev/null and b/public/static/img/regular_player_badge.png differ diff --git a/public/static/img/reigning_champion_badge.png b/public/static/img/reigning_champion_badge.png new file mode 100644 index 0000000..3c53b9c Binary files /dev/null and b/public/static/img/reigning_champion_badge.png differ diff --git a/public/static/img/runners_club_badge.png b/public/static/img/runners_club_badge.png new file mode 100644 index 0000000..b1fd4c9 Binary files /dev/null and b/public/static/img/runners_club_badge.png differ diff --git a/public/static/img/silver_medal_badge.png b/public/static/img/silver_medal_badge.png new file mode 100644 index 0000000..981f47c Binary files /dev/null and b/public/static/img/silver_medal_badge.png differ diff --git a/public/static/img/veteran_1_badge.png b/public/static/img/veteran_1_badge.png new file mode 100644 index 0000000..70cb399 Binary files /dev/null and b/public/static/img/veteran_1_badge.png differ diff --git a/public/static/img/veteran_2_badge.png b/public/static/img/veteran_2_badge.png new file mode 100644 index 0000000..f462242 Binary files /dev/null and b/public/static/img/veteran_2_badge.png differ diff --git a/public/static/img/veteran_3_badge.png b/public/static/img/veteran_3_badge.png new file mode 100644 index 0000000..2a27e6a Binary files /dev/null and b/public/static/img/veteran_3_badge.png differ diff --git a/public/static/img/veteran_4_badge.png b/public/static/img/veteran_4_badge.png new file mode 100644 index 0000000..ddf6360 Binary files /dev/null and b/public/static/img/veteran_4_badge.png differ diff --git a/public/static/img/veteran_5_badge.png b/public/static/img/veteran_5_badge.png new file mode 100644 index 0000000..175b22e Binary files /dev/null and b/public/static/img/veteran_5_badge.png differ diff --git a/public/static/img/veteran_6_badge.png b/public/static/img/veteran_6_badge.png new file mode 100644 index 0000000..cdfdd7c Binary files /dev/null and b/public/static/img/veteran_6_badge.png differ diff --git a/public/static/img/veteran_7_badge.png b/public/static/img/veteran_7_badge.png new file mode 100644 index 0000000..d32d2a4 Binary files /dev/null and b/public/static/img/veteran_7_badge.png differ diff --git a/public/static/img/veteran_8_badge.png b/public/static/img/veteran_8_badge.png new file mode 100644 index 0000000..bcd20a1 Binary files /dev/null and b/public/static/img/veteran_8_badge.png differ diff --git a/public/static/img/winner_badge.png b/public/static/img/winner_badge.png new file mode 100644 index 0000000..00240d6 Binary files /dev/null and b/public/static/img/winner_badge.png differ diff --git a/src/features/badges/components/badge-showcase.tsx b/src/features/badges/components/badge-showcase.tsx index 5744c8b..b4f18b3 100644 --- a/src/features/badges/components/badge-showcase.tsx +++ b/src/features/badges/components/badge-showcase.tsx @@ -1,8 +1,8 @@ -import { Box, Text, Popover, Progress, Title } from "@mantine/core"; +import { Box, Text, Popover, Progress, Title, Image } from "@mantine/core"; import { usePlayerBadges, useAllBadges } from "../queries"; import { useAuth } from "@/contexts/auth-context"; import { Badge, BadgeProgress } from "../types"; -import { useMemo } from "react"; +import { useMemo, useState } from "react"; import { MedalIcon, LockKeyIcon } from "@phosphor-icons/react"; interface BadgeShowcaseProps { @@ -16,6 +16,47 @@ interface BadgeDisplay { progressText: string; } +interface BadgeIconProps { + badge: Badge; + earned: boolean; +} + +const BadgeIcon = ({ badge, earned }: BadgeIconProps) => { + const [imageError, setImageError] = useState(false); + const imagePath = `/static/img/${badge.key}.png`; + + if (imageError) { + // Fallback to icon if image fails to load + return earned ? ( + + ) : ( + + ); + } + + return ( + {badge.name} setImageError(true)} + style={{ + objectFit: 'contain', + opacity: earned ? 1 : 0.4, + }} + /> + ); +}; + const BadgeShowcase = ({ playerId }: BadgeShowcaseProps) => { const { user } = useAuth(); const { data: badgeProgress } = usePlayerBadges(playerId); @@ -35,6 +76,15 @@ const BadgeShowcase = ({ playerId }: BadgeShowcaseProps) => { continue; } + const isVeteranBadge = /^veteran_\d+_badge$/.test(badge.key); + if (isVeteranBadge && !earned) { + continue; + } + + if (badge.key === 'new_player_badge') { + continue; + } + let progressText = ""; if (progress) { const target = getTargetProgress(badge); @@ -129,15 +179,14 @@ const BadgeShowcase = ({ playerId }: BadgeShowcaseProps) => { { { justifyContent: 'center', flexDirection: 'column', gap: '4px', - padding: 'var(--mantine-spacing-xs)', position: 'relative', boxShadow: display.earned ? '0 0 0 1px color-mix(in srgb, var(--mantine-primary-color-6) 20%, transparent)' @@ -168,19 +217,7 @@ const BadgeShowcase = ({ playerId }: BadgeShowcaseProps) => { zIndex: 1, }} > - {display.earned ? ( - - ) : ( - - )} + {showStack && ( { )} - {display.badge.name} - + diff --git a/src/lib/pocketbase/services/badges.ts b/src/lib/pocketbase/services/badges.ts index 3fc7dec..29b7507 100644 --- a/src/lib/pocketbase/services/badges.ts +++ b/src/lib/pocketbase/services/badges.ts @@ -170,6 +170,38 @@ export function createBadgesService(pb: PocketBase) { return tournamentsAttended; } + if (criteria.won_tournament !== undefined) { + const tournamentId = criteria.won_tournament; + + try { + const tournamentMatches = await pb.collection("matches").getFullList({ + filter: `tournament = "${tournamentId}" && status = "ended"`, + expand: 'home,away,home.players,away.players', + }); + + const winnersMatches = tournamentMatches.filter(m => !m.is_losers_bracket); + const finalsMatch = winnersMatches.reduce((highest: any, current: any) => + (!highest || current.lid > highest.lid) ? current : highest, null); + + if (finalsMatch && finalsMatch.status === 'ended') { + const finalsWinnerId = (finalsMatch.home_cups > finalsMatch.away_cups) ? finalsMatch.home : finalsMatch.away; + + const winningTeam = finalsMatch.expand?.[finalsWinnerId === finalsMatch.home ? 'home' : 'away']; + const winningPlayers = winningTeam?.expand?.players || winningTeam?.players || []; + + const playerWon = winningPlayers.some((p: any) => + (typeof p === 'string' ? p : p.id) === playerId + ); + + return playerWon ? 1 : 0; + } + + return 0; + } catch (error) { + return 0; + } + } + if (criteria.tournament_wins !== undefined) { if (tournamentIds.size === 0) return 0; @@ -342,26 +374,28 @@ export function createBadgesService(pb: PocketBase) { const criteria = badge.criteria; - return ( - criteria.matches_played || - criteria.tournament_wins || - criteria.tournaments_attended || - criteria.overtime_matches || - criteria.overtime_wins || - criteria.consecutive_wins || - 1 - ); + // Use explicit checks to handle 0 values correctly + if (criteria.matches_played !== undefined) return criteria.matches_played; + if (criteria.tournament_wins !== undefined) return criteria.tournament_wins; + if (criteria.tournaments_attended !== undefined) return criteria.tournaments_attended; + if (criteria.overtime_matches !== undefined) return criteria.overtime_matches; + if (criteria.overtime_wins !== undefined) return criteria.overtime_wins; + if (criteria.consecutive_wins !== undefined) return criteria.consecutive_wins; + if (criteria.won_tournament !== undefined) return 1; + if (criteria.placement !== undefined) return 1; + if (criteria.margin_of_victory !== undefined) return 1; + if (criteria.tournament_record !== undefined) return 1; + + return 1; }, async awardManualBadge(playerId: string, badgeId: string): Promise { - // Get or create badge progress record const existingProgress = await pb.collection("badge_progress").getFirstListItem( `player = "${playerId}" && badge = "${badgeId}"`, { expand: 'badge' } ).catch(() => null); if (existingProgress) { - // Update existing progress to mark as earned const updated = await pb.collection("badge_progress").update(existingProgress.id, { progress: 1, earned: true, @@ -369,7 +403,6 @@ export function createBadgesService(pb: PocketBase) { return transformBadgeProgress(updated); } - // Create new progress record const created = await pb.collection("badge_progress").create({ badge: badgeId, player: playerId, @@ -401,7 +434,11 @@ export function createBadgesService(pb: PocketBase) { try { const progress = await this.calculateBadgeProgress(playerId, badge); const target = this.getTargetProgress(badge); - const earned = progress >= target; + + const isPlacementBadge = badge.criteria.placement !== undefined; + const earned = badge.progressive || isPlacementBadge + ? progress >= target + : progress === target; if (progress > 0 || earned) { await this.createBadgeProgress({