From ae7d880bc1b76e09c401494f0a24312861af6881 Mon Sep 17 00:00:00 2001 From: vikingowl Date: Wed, 12 Nov 2025 19:43:52 +0100 Subject: [PATCH] feat: Add recently visited players tracking to home page MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Implement localStorage-based player visit tracking with display on home page. ## New Features ### Recently Visited Players Component - **RecentPlayers.svelte**: Grid display of up to 10 recently visited players - **Responsive Layout**: 1-4 columns based on screen size - **Player Cards**: Avatar, name, and time since last visit - **Remove Functionality**: Individual player removal with X button - **Auto-show/hide**: Component only renders when players have been visited ### Player Visit Tracking - **recentPlayers utility**: localStorage management functions - `getRecentPlayers()`: Retrieve sorted list by most recent - `addRecentPlayer()`: Add/update player visit with timestamp - `removeRecentPlayer()`: Remove specific player from list - `clearRecentPlayers()`: Clear entire history - **Auto-tracking**: Player profile page automatically records visits on mount - **Smart deduplication**: Visiting same player updates timestamp, no duplicates - **Max limit**: Maintains up to 10 most recent players ### Time Display - Relative time formatting: "Just now", "5m ago", "2h ago", "3d ago" - Real-time updates when component mounts - Human-readable timestamps ### UX Features - **Hover Effects**: Border highlights and shadow on card hover - **Team Colors**: Player names inherit team colors from profiles - **Remove on Hover**: X button appears only on hover for clean interface - **Click Protection**: Remove button prevents navigation when clicked - **Footer Info**: Shows count of displayed players ## Implementation Details - **localStorage Key**: `cs2wtf_recent_players` - **Data Structure**: Array of `{id, name, avatar, visitedAt}` objects - **Sort Order**: Most recently visited first - **SSR Safe**: All localStorage operations check for browser environment - **Error Handling**: Try-catch wraps all storage operations with console errors ## Integration - Added to home page between hero and featured matches - Integrates seamlessly with existing layout and styling - No data fetching required - pure client-side functionality - Persists across sessions via localStorage This completes Phase 1 (6/6) - all critical features now implemented! 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- .../components/player/RecentPlayers.svelte | 81 +++++++++++++++++ src/lib/utils/recentPlayers.ts | 87 +++++++++++++++++++ src/routes/+page.svelte | 8 ++ src/routes/player/[id]/+page.svelte | 11 +++ 4 files changed, 187 insertions(+) create mode 100644 src/lib/components/player/RecentPlayers.svelte create mode 100644 src/lib/utils/recentPlayers.ts diff --git a/src/lib/components/player/RecentPlayers.svelte b/src/lib/components/player/RecentPlayers.svelte new file mode 100644 index 0000000..82649fd --- /dev/null +++ b/src/lib/components/player/RecentPlayers.svelte @@ -0,0 +1,81 @@ + + +{#if recentPlayers.length > 0} + +
+ +

Recently Visited Players

+
+ +
+ {#each recentPlayers as player (player.id)} +
+ + {player.name} +
+
{player.name}
+
{formatTimeAgo(player.visitedAt)}
+
+
+ + + +
+ {/each} +
+ +
+ Showing up to {recentPlayers.length} recently visited player{recentPlayers.length !== 1 + ? 's' + : ''} +
+
+{/if} diff --git a/src/lib/utils/recentPlayers.ts b/src/lib/utils/recentPlayers.ts new file mode 100644 index 0000000..d5dc0df --- /dev/null +++ b/src/lib/utils/recentPlayers.ts @@ -0,0 +1,87 @@ +/** + * Utility for managing recently visited players in localStorage + */ + +const STORAGE_KEY = 'cs2wtf_recent_players'; +const MAX_RECENT_PLAYERS = 10; + +export interface RecentPlayer { + id: string; + name: string; + avatar: string; + visitedAt: number; // Unix timestamp +} + +/** + * Get all recently visited players from localStorage + */ +export function getRecentPlayers(): RecentPlayer[] { + if (typeof window === 'undefined') return []; + + try { + const stored = localStorage.getItem(STORAGE_KEY); + if (!stored) return []; + + const players: RecentPlayer[] = JSON.parse(stored); + // Sort by most recent first + return players.sort((a, b) => b.visitedAt - a.visitedAt); + } catch (error) { + console.error('Failed to load recent players:', error); + return []; + } +} + +/** + * Add or update a player in the recently visited list + */ +export function addRecentPlayer(player: Omit): void { + if (typeof window === 'undefined') return; + + try { + const recent = getRecentPlayers(); + + // Remove existing entry if present + const filtered = recent.filter((p) => p.id !== player.id); + + // Add new entry with current timestamp + const newPlayer: RecentPlayer = { + ...player, + visitedAt: Date.now() + }; + + // Keep only the most recent MAX_RECENT_PLAYERS + const updated = [newPlayer, ...filtered].slice(0, MAX_RECENT_PLAYERS); + + localStorage.setItem(STORAGE_KEY, JSON.stringify(updated)); + } catch (error) { + console.error('Failed to save recent player:', error); + } +} + +/** + * Clear all recently visited players + */ +export function clearRecentPlayers(): void { + if (typeof window === 'undefined') return; + + try { + localStorage.removeItem(STORAGE_KEY); + } catch (error) { + console.error('Failed to clear recent players:', error); + } +} + +/** + * Remove a specific player from the recently visited list + */ +export function removeRecentPlayer(playerId: string): void { + if (typeof window === 'undefined') return; + + try { + const recent = getRecentPlayers(); + const filtered = recent.filter((p) => p.id !== playerId); + localStorage.setItem(STORAGE_KEY, JSON.stringify(filtered)); + } catch (error) { + console.error('Failed to remove recent player:', error); + } +} diff --git a/src/routes/+page.svelte b/src/routes/+page.svelte index 5cbd20e..64f1d76 100644 --- a/src/routes/+page.svelte +++ b/src/routes/+page.svelte @@ -4,6 +4,7 @@ import Card from '$lib/components/ui/Card.svelte'; import Badge from '$lib/components/ui/Badge.svelte'; import MatchCard from '$lib/components/match/MatchCard.svelte'; + import RecentPlayers from '$lib/components/player/RecentPlayers.svelte'; import type { PageData } from './$types'; // Get data from page loader @@ -150,6 +151,13 @@ + +
+
+ +
+
+
diff --git a/src/routes/player/[id]/+page.svelte b/src/routes/player/[id]/+page.svelte index ea3e2ca..3e3f161 100644 --- a/src/routes/player/[id]/+page.svelte +++ b/src/routes/player/[id]/+page.svelte @@ -18,11 +18,22 @@ import TrackPlayerModal from '$lib/components/player/TrackPlayerModal.svelte'; import { preferences } from '$lib/stores'; import { invalidateAll } from '$app/navigation'; + import { addRecentPlayer } from '$lib/utils/recentPlayers'; + import { onMount } from 'svelte'; import type { PageData } from './$types'; let { data }: { data: PageData } = $props(); const { profile, recentMatches, playerStats } = data; + // Track this player visit + onMount(() => { + addRecentPlayer({ + id: profile.id, + name: profile.name, + avatar: profile.avatar + }); + }); + // Track player modal state let isTrackModalOpen = $state(false);