forked from CSGOWTF/csgowtf
Phase 4 establishes the core application structure with SvelteKit routing, data loading, error handling, and state management. ## Routing & Data Loading - Created root layout load function (+layout.ts) with app version and feature flags - Implemented comprehensive error boundary (+error.svelte) with status-based messages - Added page loaders for homepage, matches, players, and about routes - Homepage loader fetches featured matches via API with error fallback - Matches loader supports URL query parameters (map, player_id, limit) ## State Management (Svelte Stores) - preferences.ts: User settings with localStorage persistence * Theme selection (cs2dark, cs2light, auto) * Favorite players tracking * Advanced stats toggle, date format preferences - search.ts: Search state with recent searches (localStorage) - toast.ts: Toast notification system with auto-dismiss * Success, error, warning, info types * Configurable duration and dismissibility ## UI Components - Toast.svelte: Individual notification with Lucide icons - ToastContainer.svelte: Fixed top-right toast display - Integrated ToastContainer into root layout ## Fixes - Fixed Svelte 5 deprecation warnings (removed <svelte:component> in runes mode) - Updated homepage to use PageData from loader - Added proper type safety across all load functions ## Testing - Type check: 0 errors, 0 warnings - Production build: Successful - All Phase 4 core objectives completed 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
119 lines
2.5 KiB
TypeScript
119 lines
2.5 KiB
TypeScript
import { writable, derived } from 'svelte/store';
|
|
import { browser } from '$app/environment';
|
|
|
|
/**
|
|
* Search state store
|
|
* Manages search queries and recent searches
|
|
*/
|
|
|
|
export interface SearchState {
|
|
query: string;
|
|
recentSearches: string[];
|
|
filters: {
|
|
map?: string;
|
|
playerId?: number;
|
|
dateFrom?: string;
|
|
dateTo?: string;
|
|
};
|
|
}
|
|
|
|
const defaultState: SearchState = {
|
|
query: '',
|
|
recentSearches: [],
|
|
filters: {}
|
|
};
|
|
|
|
// Load recent searches from localStorage
|
|
const loadRecentSearches = (): string[] => {
|
|
if (!browser) return [];
|
|
|
|
try {
|
|
const stored = localStorage.getItem('cs2wtf-recent-searches');
|
|
if (stored) {
|
|
return JSON.parse(stored);
|
|
}
|
|
} catch (error) {
|
|
console.error('Failed to load recent searches:', error);
|
|
}
|
|
|
|
return [];
|
|
};
|
|
|
|
// Create the store
|
|
const createSearchStore = () => {
|
|
const { subscribe, set, update } = writable<SearchState>({
|
|
...defaultState,
|
|
recentSearches: loadRecentSearches()
|
|
});
|
|
|
|
return {
|
|
subscribe,
|
|
set,
|
|
update,
|
|
|
|
// Set search query
|
|
setQuery: (query: string) => {
|
|
update((state) => ({ ...state, query }));
|
|
},
|
|
|
|
// Clear search query
|
|
clearQuery: () => {
|
|
update((state) => ({ ...state, query: '' }));
|
|
},
|
|
|
|
// Add to recent searches (max 10)
|
|
addRecentSearch: (query: string) => {
|
|
if (!query.trim()) return;
|
|
|
|
update((state) => {
|
|
const recent = [query, ...state.recentSearches.filter((q) => q !== query)].slice(0, 10);
|
|
|
|
if (browser) {
|
|
localStorage.setItem('cs2wtf-recent-searches', JSON.stringify(recent));
|
|
}
|
|
|
|
return { ...state, recentSearches: recent };
|
|
});
|
|
},
|
|
|
|
// Clear recent searches
|
|
clearRecentSearches: () => {
|
|
if (browser) {
|
|
localStorage.removeItem('cs2wtf-recent-searches');
|
|
}
|
|
update((state) => ({ ...state, recentSearches: [] }));
|
|
},
|
|
|
|
// Set filters
|
|
setFilters: (filters: SearchState['filters']) => {
|
|
update((state) => ({ ...state, filters }));
|
|
},
|
|
|
|
// Update single filter
|
|
setFilter: (key: keyof SearchState['filters'], value: unknown) => {
|
|
update((state) => ({
|
|
...state,
|
|
filters: { ...state.filters, [key]: value }
|
|
}));
|
|
},
|
|
|
|
// Clear filters
|
|
clearFilters: () => {
|
|
update((state) => ({ ...state, filters: {} }));
|
|
},
|
|
|
|
// Reset entire search state
|
|
reset: () => {
|
|
set({ ...defaultState, recentSearches: loadRecentSearches() });
|
|
}
|
|
};
|
|
};
|
|
|
|
export const search = createSearchStore();
|
|
|
|
// Derived store: is search active?
|
|
export const isSearchActive = derived(
|
|
search,
|
|
($search) => $search.query.length > 0 || Object.keys($search.filters).length > 0
|
|
);
|