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 (
+ 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({