From fb7f49155e4d3e9b150f0c50f62aad4f5d2ab210 Mon Sep 17 00:00:00 2001 From: Yonie <164077864+Isaiahriveraa@users.noreply.github.com> Date: Wed, 24 Dec 2025 01:19:38 -0800 Subject: [PATCH 1/7] Fixed critical race condition in Mongoose model registration Changed: - Converted model registration function to async/await to ensure all schemas are loaded before database connection returns - Updated connection logic to await registration in both new and cached connection paths Files: - src/server/models/index.js - src/server/db.js Why: In serverless environments (Vercel/Netlify), the previous synchronous implementation could return the database connection before models were registered, leading to 'Schema hasn't been registered' errors when executing queries. Moving to async registration ensures reliable model availability across all execution environments. --- src/server/db.js | 6 +++--- src/server/models/index.js | 10 +++++----- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/src/server/db.js b/src/server/db.js index 11779257..dbaf3bfc 100644 --- a/src/server/db.js +++ b/src/server/db.js @@ -27,7 +27,7 @@ async function connectDB() { // IMPORTANT: Even with cached connection, ensure models are registered // This handles cases where the serverless function instance is reused // but models aren't in memory - registerModels(); + await registerModels(); return cached.conn; } @@ -38,11 +38,11 @@ async function connectDB() { }; console.log('🔌 Connecting to MongoDB...'); - cached.promise = mongoose.connect(MONGODB_URI, opts).then((mongoose) => { + cached.promise = mongoose.connect(MONGODB_URI, opts).then(async (mongoose) => { console.log('✅ MongoDB connected successfully'); // Register all models immediately after connection - registerModels(); + await registerModels(); return mongoose; }); diff --git a/src/server/models/index.js b/src/server/models/index.js index 2a6aec88..4e072056 100644 --- a/src/server/models/index.js +++ b/src/server/models/index.js @@ -17,22 +17,22 @@ let modelsRegistered = false; * Register all Mongoose models * This function should be called after MongoDB connection is established */ -export function registerModels() { +export async function registerModels() { // Only register once per serverless function instance if (modelsRegistered) { return; } try { - // Import models in dependency order + // Import models in dependency order using dynamic imports // User has no dependencies - require('./User'); + await import('./User'); // Game has no dependencies - require('./Game'); + await import('./Game'); // Bet depends on User and Game (via references) - require('./Bet'); + await import('./Bet'); modelsRegistered = true; console.log('✅ All Mongoose models registered successfully'); From b5cb7120c0356d63e1a84698cac16202cc80986c Mon Sep 17 00:00:00 2001 From: Yonie <164077864+Isaiahriveraa@users.noreply.github.com> Date: Wed, 24 Dec 2025 01:33:07 -0800 Subject: [PATCH 2/7] Configure CI pipeline and exclude local tooling Changed: - Added GitHub Actions workflow (ci.yml) for automated linting and testing - Updated .gitignore to exclude local development artifacts and temporary files Files: - .github/workflows/ci.yml - .gitignore Why: Establishes a baseline CI process to enforce code quality on every push. Excluding local environment artifacts ensures the shared repository remains clean and environment-agnostic. --- .github/workflows/ci.yml | 3 +-- .gitignore | 7 +++++++ 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 91a204ac..959208c7 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -25,8 +25,7 @@ jobs: run: npm ci - name: Run linter - run: npm run lint || true - # TODO: Remove "|| true" after cleanup branch fixes all lint errors + run: npm run lint - name: Run tests run: npm test diff --git a/.gitignore b/.gitignore index 94402855..be5ecf89 100644 --- a/.gitignore +++ b/.gitignore @@ -81,3 +81,10 @@ next-env.d.ts claude.md # Local Netlify folder .netlify + +.gemini/ +gha-creds-*.json + +# Gemini Agent Files +.github/commands/ +.github/workflows/gemini-*.yml From f3f2709fc7a211d84c6d06bcdefe6d52a0f8874f Mon Sep 17 00:00:00 2001 From: Yonie <164077864+Isaiahriveraa@users.noreply.github.com> Date: Wed, 24 Dec 2025 01:34:04 -0800 Subject: [PATCH 3/7] Remove legacy components and unused data artifacts Changed: - Deleted redundant UI components (BettingModal, UWRecord, etc.) replaced by new design system - Removed unused historical data files and legacy HTML/CSS pages - Cleaned up deprecated API endpoints and service methods Files: - src/app/change-password/page.jsx (deleted) - src/app/change-username/page.jsx (deleted) - src/app/contexts/DarkModeContext.jsx (deleted) - src/app/daily-tasks/page.html (deleted) - src/app/daily-tasks/style.css (deleted) - src/app/data/games.js (deleted) - src/app/data/users.js (deleted) - src/components/BettingModal.jsx (deleted) - src/components/BiscuitIcon.jsx (deleted) - src/components/CardStyleC.jsx (deleted) - src/components/DarkModeToggle.jsx (deleted) - src/components/ErrorState.jsx (deleted) - src/components/FireIcon.jsx (deleted) - src/components/GameCalendar.jsx (deleted) - src/components/GameDetailsModal.jsx (deleted) - src/components/UWRecord.jsx (deleted) - src/components/animation/AnimatedCard.jsx (deleted) - src/components/animation/FadeInView.jsx (deleted) - src/components/animation/variants.js (deleted) - src/components/betting/BetConfirmation.jsx (deleted) - src/components/charts/BettingDistributionChart.jsx (deleted) - src/components/charts/BettingTrendChart.jsx (deleted) - src/components/charts/SportActivityChart.jsx (deleted) - src/components/charts/index.js (deleted) - src/components/dashboard/BettingChart.jsx (deleted) - src/components/dashboard/StatsGrid.jsx (deleted) - src/components/game/index.js (deleted) - src/components/leaderboard/Podium.jsx (deleted) - src/components/shared/index.js (deleted) - src/pages/api/bets/settle-all.js (deleted) - src/pages/api/bets/settle-game.js (deleted) - src/hooks/useKeyboardNav.js (deleted) Why: Reduces technical debt and codebase size by removing obsolete features and experimental UI components. This streamlines the project structure and ensures only the current design language and active features remain, making maintenance and onboarding easier. --- src/app/change-password/page.jsx | 18 - src/app/change-username/page.jsx | 10 - src/app/contexts/DarkModeContext.jsx | 57 ---- src/app/daily-tasks/page.html | 74 ----- src/app/daily-tasks/style.css | 28 -- src/app/data/games.js | 50 --- src/app/data/users.js | 72 ---- src/components/BettingModal.jsx | 297 ----------------- src/components/BiscuitIcon.jsx | 84 ----- src/components/CardStyleC.jsx | 266 --------------- src/components/DarkModeToggle.jsx | 29 -- src/components/ErrorState.jsx | 108 ------ src/components/FireIcon.jsx | 125 ------- src/components/GameCalendar.jsx | 221 ------------- src/components/GameDetailsModal.jsx | 309 ------------------ src/components/UWRecord.jsx | 143 -------- src/components/animation/AnimatedCard.jsx | 18 - src/components/animation/FadeInView.jsx | 21 -- src/components/animation/variants.js | 94 ------ src/components/betting/BetConfirmation.jsx | 86 ----- .../charts/BettingDistributionChart.jsx | 87 ----- src/components/charts/BettingTrendChart.jsx | 132 -------- src/components/charts/SportActivityChart.jsx | 130 -------- src/components/charts/index.js | 8 - src/components/dashboard/BettingChart.jsx | 61 ---- src/components/dashboard/StatsGrid.jsx | 100 ------ src/components/game/index.js | 9 - src/components/leaderboard/Podium.jsx | 103 ------ src/components/shared/index.js | 4 - src/hooks/useKeyboardNav.js | 37 --- src/pages/api/bets/settle-all.js | 155 --------- src/pages/api/bets/settle-game.js | 147 --------- 32 files changed, 3083 deletions(-) delete mode 100644 src/app/change-password/page.jsx delete mode 100644 src/app/change-username/page.jsx delete mode 100644 src/app/contexts/DarkModeContext.jsx delete mode 100644 src/app/daily-tasks/page.html delete mode 100644 src/app/daily-tasks/style.css delete mode 100644 src/app/data/games.js delete mode 100644 src/app/data/users.js delete mode 100644 src/components/BettingModal.jsx delete mode 100644 src/components/BiscuitIcon.jsx delete mode 100644 src/components/CardStyleC.jsx delete mode 100644 src/components/DarkModeToggle.jsx delete mode 100644 src/components/ErrorState.jsx delete mode 100644 src/components/FireIcon.jsx delete mode 100644 src/components/GameCalendar.jsx delete mode 100644 src/components/GameDetailsModal.jsx delete mode 100644 src/components/UWRecord.jsx delete mode 100644 src/components/animation/AnimatedCard.jsx delete mode 100644 src/components/animation/FadeInView.jsx delete mode 100644 src/components/animation/variants.js delete mode 100644 src/components/betting/BetConfirmation.jsx delete mode 100644 src/components/charts/BettingDistributionChart.jsx delete mode 100644 src/components/charts/BettingTrendChart.jsx delete mode 100644 src/components/charts/SportActivityChart.jsx delete mode 100644 src/components/charts/index.js delete mode 100644 src/components/dashboard/BettingChart.jsx delete mode 100644 src/components/dashboard/StatsGrid.jsx delete mode 100644 src/components/game/index.js delete mode 100644 src/components/leaderboard/Podium.jsx delete mode 100644 src/components/shared/index.js delete mode 100644 src/hooks/useKeyboardNav.js delete mode 100644 src/pages/api/bets/settle-all.js delete mode 100644 src/pages/api/bets/settle-game.js diff --git a/src/app/change-password/page.jsx b/src/app/change-password/page.jsx deleted file mode 100644 index 25da7328..00000000 --- a/src/app/change-password/page.jsx +++ /dev/null @@ -1,18 +0,0 @@ -"use client"; - -import { UserProfile, SignedIn, SignedOut, RedirectToSignIn } from "@clerk/nextjs"; - -export default function AccountPage() { - return ( -
- -

Account Settings

- -
- - - - -
- ); -} \ No newline at end of file diff --git a/src/app/change-username/page.jsx b/src/app/change-username/page.jsx deleted file mode 100644 index 7a46836b..00000000 --- a/src/app/change-username/page.jsx +++ /dev/null @@ -1,10 +0,0 @@ -"use client"; - -export default function ProfilePage() { - return ( -
-

Your Profile

- {/* Add your profile display components here */} -
- ); -} \ No newline at end of file diff --git a/src/app/contexts/DarkModeContext.jsx b/src/app/contexts/DarkModeContext.jsx deleted file mode 100644 index 3a793319..00000000 --- a/src/app/contexts/DarkModeContext.jsx +++ /dev/null @@ -1,57 +0,0 @@ -'use client'; - -import React, { createContext, useContext, useEffect, useState, useCallback } from 'react'; - -const DarkModeContext = createContext(); - -export const DarkModeProvider = ({ children }) => { - // Initialize state to match what the inline script set (check the DOM) - // This prevents hydration mismatch by syncing with the pre-rendered state - const [isDark, setIsDark] = useState(() => { - // During SSR, return false (server doesn't have localStorage) - if (typeof window === 'undefined') return false; - // On client, check if dark class was already added by inline script - return document.documentElement.classList.contains('dark'); - }); - const [isLoaded, setIsLoaded] = useState(false); - - // Sync state with DOM on mount (handles edge cases) - useEffect(() => { - const hasDarkClass = document.documentElement.classList.contains('dark'); - if (hasDarkClass !== isDark) { - setIsDark(hasDarkClass); - } - setIsLoaded(true); - }, []); - - // Handle dark mode changes after initial load - useEffect(() => { - if (!isLoaded) return; - - if (isDark) { - document.documentElement.classList.add('dark'); - localStorage.setItem('darkMode', 'true'); - } else { - document.documentElement.classList.remove('dark'); - localStorage.setItem('darkMode', 'false'); - } - }, [isDark, isLoaded]); - - const toggleDarkMode = useCallback(() => { - setIsDark(prev => !prev); - }, []); - - return ( - - {children} - - ); -}; - -export const useDarkMode = () => { - const context = useContext(DarkModeContext); - if (!context) { - throw new Error('useDarkMode must be used within DarkModeProvider'); - } - return context; -}; diff --git a/src/app/daily-tasks/page.html b/src/app/daily-tasks/page.html deleted file mode 100644 index 0f40ff5c..00000000 --- a/src/app/daily-tasks/page.html +++ /dev/null @@ -1,74 +0,0 @@ - - - - Daily Tasks - - - - - -
-

Daily Tasks

-
- -
- -

Total Biscuits: XXX

-
-
-
-
- - - - - - - - - - - - - - - - - - - - - - - - - -
Log in100 biscuits
Share with a friend100 biscuits
Make a bid100 biscuits
-
- - - - - -
\ No newline at end of file diff --git a/src/app/daily-tasks/style.css b/src/app/daily-tasks/style.css deleted file mode 100644 index 77dc0895..00000000 --- a/src/app/daily-tasks/style.css +++ /dev/null @@ -1,28 +0,0 @@ -body{ - background-color: #4B2E83; -} -h1{ - color: black; -} -header{ - background-color: #FCCE5A; font-family:'Times New Roman', Times, serif; - border-radius: 20px; -} -#box1{ - /* width: 200px; */ - /* margin-top: 0px; */ - height: 400px; - background-color: #FFDE8A; - border-radius: 20px; -} -#biscuitlogo{ - height: 100px; - float:left; - margin-right:4px; -} -#biscuitNumber{ - font-size: 20px; -} -#taskList{ - -} diff --git a/src/app/data/games.js b/src/app/data/games.js deleted file mode 100644 index e1d4e36e..00000000 --- a/src/app/data/games.js +++ /dev/null @@ -1,50 +0,0 @@ -export const SAMPLE_GAMES = [ - { - id: 1, - opponent: 'Michigan State', - date: '2024-08-31', - time: '3:30 PM', - location: 'Husky Stadium', - isHome: true, - week: 1 - }, - { - id: 2, - opponent: 'Oregon', - date: '2024-09-14', - time: '7:00 PM', - location: 'Husky Stadium', - isHome: true, - week: 3 - }, - { - id: 3, - opponent: 'USC', - date: '2024-09-28', - time: '4:00 PM', - location: 'Los Angeles Memorial Coliseum', - isHome: false, - week: 5 - }, -]; - -export const HISTORICAL_DATA = { - 'Michigan State': { - wins: 3, - losses: 2, - winPercentage: 0.60, - odds: 1.8 // Higher odds mean better payout for correct prediction - }, - 'Oregon': { - wins: 4, - losses: 6, - winPercentage: 0.40, - odds: 2.2 - }, - 'USC': { - wins: 5, - losses: 5, - winPercentage: 0.50, - odds: 2.0 - } -}; \ No newline at end of file diff --git a/src/app/data/users.js b/src/app/data/users.js deleted file mode 100644 index 6fa1f988..00000000 --- a/src/app/data/users.js +++ /dev/null @@ -1,72 +0,0 @@ -export const SAMPLE_USERS = [ - { - id: 1, - username: 'Nadia', - biscuits: 5200, - winRate: 0.75, - totalBids: 20 - }, - { - id: 2, - username: 'Sophie', - biscuits: 4800, - winRate: 0.70, - totalBids: 15 - }, - { - id: 3, - username: 'Yonie', - biscuits: 4500, - winRate: 0.65, - totalBids: 18 - }, - { - id: 4, - username: 'Vanya', - biscuits: 3800, - winRate: 0.60, - totalBids: 12 - }, - { - id: 5, - username: 'Elizabeth', - biscuits: 3500, - winRate: 0.58, - totalBids: 14 - }, - { - id: 6, - username: 'Zeke', - biscuits: 3200, - winRate: 0.55, - totalBids: 10 - }, - { - id: 7, - username: 'Abel', - biscuits: 3000, - winRate: 0.52, - totalBids: 8 - }, - { - id: 8, - username: 'Angel', - biscuits: 2800, - winRate: 0.50, - totalBids: 6 - }, - { - id: 9, - username: 'Jake', - biscuits: 2500, - winRate: 0.48, - totalBids: 5 - }, - { - id: 10, - username: 'Molly', - biscuits: 2200, - winRate: 0.45, - totalBids: 4 - } -]; \ No newline at end of file diff --git a/src/components/BettingModal.jsx b/src/components/BettingModal.jsx deleted file mode 100644 index 53276699..00000000 --- a/src/components/BettingModal.jsx +++ /dev/null @@ -1,297 +0,0 @@ -'use client'; - -import React, { useState, useEffect } from 'react'; -import { useUserContext } from '@/app/contexts/UserContext'; -import { Modal, Button, Input, Badge, Alert } from './ui'; -import BiscuitIcon from './BiscuitIcon'; -import { TrendingUp, AlertCircle, CheckCircle } from 'lucide-react'; -import { getTeamPositions } from '@shared/utils/game-utils'; -import { BETTING_LIMITS, QUICK_BET_AMOUNTS, BETTING_ERRORS } from '@shared/constants/betting'; - -export default function BettingModal({ game, isOpen, onClose, onBetPlaced }) { - const { user, refreshUser } = useUserContext(); - const userBiscuits = user?.biscuits || 0; - const [betAmount, setBetAmount] = useState(''); - const [selectedTeam, setSelectedTeam] = useState(null); // 'home' or 'away' - const [loading, setLoading] = useState(false); - const [error, setError] = useState(null); - const [success, setSuccess] = useState(false); - - // Calculate potential winnings - const calculatePotentialWin = () => { - if (!betAmount || !selectedTeam) return 0; - const odds = selectedTeam === 'home' ? game.homeOdds : game.awayOdds; - return Math.round(parseFloat(betAmount) * odds); - }; - - const calculateProfit = () => { - const potentialWin = calculatePotentialWin(); - return potentialWin - (parseFloat(betAmount) || 0); - }; - - // Handle bet placement - const handlePlaceBet = async () => { - try { - setLoading(true); - setError(null); - - // Validate - const amount = parseInt(betAmount); - if (!amount || amount <= 0) { - throw new Error(BETTING_ERRORS.INVALID_AMOUNT); - } - - if (!selectedTeam) { - throw new Error(BETTING_ERRORS.NO_TEAM_SELECTED); - } - - if (amount > userBiscuits) { - throw new Error(BETTING_ERRORS.INSUFFICIENT_FUNDS); - } - - if (amount < BETTING_LIMITS.MIN_BET) { - throw new Error(BETTING_ERRORS.MIN_BET_NOT_MET(BETTING_LIMITS.MIN_BET)); - } - - if (amount > BETTING_LIMITS.MAX_BET) { - throw new Error(BETTING_ERRORS.MAX_BET_EXCEEDED(BETTING_LIMITS.MAX_BET)); - } - - // Place bet via API - const response = await fetch('/api/bets/place', { - method: 'POST', - headers: { - 'Content-Type': 'application/json', - }, - body: JSON.stringify({ - gameId: game._id, - betAmount: amount, - predictedWinner: selectedTeam, - }), - }); - - const data = await response.json(); - - if (!response.ok) { - throw new Error(data.error || 'Failed to place bet'); - } - - // Success! - setSuccess(true); - - // Refresh user data to update biscuits - await refreshUser(); - - // Call callback to refresh data - if (onBetPlaced) { - onBetPlaced(data); - } - - // Close modal after 2 seconds - setTimeout(() => { - handleClose(); - }, 2000); - } catch (err) { - console.error('Bet placement error:', err); - setError(err.message); - } finally { - setLoading(false); - } - }; - - const handleClose = () => { - setBetAmount(''); - setSelectedTeam(null); - setError(null); - setSuccess(false); - onClose(); - }; - - if (!game) return null; - - return ( - - {success ? ( -
- -

Bet Placed!

-

Good luck, Husky!

-
- ) : ( - <> - {/* Game Info */} -
-
- - {game.homeTeam} - - VS - - {game.awayTeam} - -
-
- {new Date(game.gameDate).toLocaleDateString('en-US', { - weekday: 'long', - month: 'long', - day: 'numeric', - hour: 'numeric', - minute: '2-digit', - })} -
-
- - {/* Team Selection */} -
- -
- {/* Home Team */} - - - {/* Away Team */} - -
-
- - {/* Bet Amount Input */} -
- - setBetAmount(e.target.value)} - placeholder={`Enter amount (min: ${BETTING_LIMITS.MIN_BET})`} - icon={} - min={BETTING_LIMITS.MIN_BET} - max={userBiscuits} - /> -
- Min: {BETTING_LIMITS.MIN_BET} - Available: {userBiscuits.toLocaleString()} -
- - {/* Quick Bet Buttons */} -
- {QUICK_BET_AMOUNTS.map((amount) => ( - - ))} - -
-
- - {/* Potential Winnings */} - {betAmount && selectedTeam && ( -
-
- Potential Payout: - - - {calculatePotentialWin().toLocaleString()} - -
-
- Profit: - 0 ? 'text-green-600' : 'text-gray-600' - }`}> - {calculateProfit() > 0 ? : null} - - +{calculateProfit().toLocaleString()} - -
-
- )} - - {/* Error Message */} - {error && ( - setError(null)}> - {error} - - )} - - {/* Action Buttons */} -
- - -
- - {/* Help Text */} -
- - Bets cannot be cancelled once placed. Biscuits will be deducted immediately. -
- - )} -
- ); -} diff --git a/src/components/BiscuitIcon.jsx b/src/components/BiscuitIcon.jsx deleted file mode 100644 index b63fe288..00000000 --- a/src/components/BiscuitIcon.jsx +++ /dev/null @@ -1,84 +0,0 @@ -/** - * BiscuitIcon Component - * Custom icon for the HuskyBids currency - * Supports multiple sizes and animation variants - */ - -'use client'; - -import { useState, memo } from 'react'; -import Image from 'next/image'; - -const BiscuitIcon = memo(function BiscuitIcon({ - size = 24, - animate = false, - className = '' -}) { - const [imageError, setImageError] = useState(false); - const animationClass = animate ? 'animate-bounce-slow' : ''; - - return ( -
- {!imageError ? ( - Biscuit setImageError(true)} - /> - ) : ( - - Pts - - )} -
- ); -}); - -// Biscuit Balance Display Component -BiscuitIcon.Balance = memo(function BiscuitBalance({ amount, size = 24, showLabel = true, className = '' }) { - return ( -
- - - {amount?.toLocaleString() || 0} - - {showLabel && ( - pts - )} -
- ); -}); - -// Animated Biscuit Rain (for big wins) -BiscuitIcon.Rain = memo(function BiscuitRain({ count = 10 }) { - return ( -
- {[...Array(count)].map((_, i) => ( -
- -
- ))} -
- ); -}); - -export default BiscuitIcon; \ No newline at end of file diff --git a/src/components/CardStyleC.jsx b/src/components/CardStyleC.jsx deleted file mode 100644 index 0bbfd11d..00000000 --- a/src/components/CardStyleC.jsx +++ /dev/null @@ -1,266 +0,0 @@ -'use client'; - -import React from 'react'; -import Image from 'next/image'; -import { motion } from 'framer-motion'; -import { Trophy, Flame, Award } from 'lucide-react'; - -/** - * Style C: Bold & Visual-Heavy - * Handles both upcoming and completed games with bold colors and visual impact - */ -export default function CardStyleC({ game, onClick, className = '' }) { - // Check if UW is home team (handle variations: "Washington", "Washington Huskies", etc.) - const isUWHome = game.homeTeam?.toLowerCase().includes('washington') && - !game.homeTeam?.toLowerCase().includes('state'); - - const uwScore = isUWHome ? game.homeScore : game.awayScore; - const opponentScore = isUWHome ? game.awayScore : game.homeScore; - - // Fix: Check if UW won based on home/away status and winner field - const isUWWinner = (isUWHome && game.winner === 'home') || (!isUWHome && game.winner === 'away'); - - const isCompleted = game.status === 'completed'; - const isUpcoming = game.status === 'scheduled'; - - // Determine header background - const getHeaderBackground = () => { - if (isCompleted) { - return isUWWinner - ? 'linear-gradient(to bottom right, #4B2E83, #3D2569)' // UW Purple - : 'linear-gradient(to bottom right, #6B7280, #4B5563)'; // Gray - } - return 'linear-gradient(to bottom right, #4B2E83, #3D2569)'; // Default UW Purple for upcoming - }; - - return ( - -
- {/* Bold Header */} -
-
- {/* Logo and Status */} - {isCompleted ? ( - <> - {/* Show winning team logo for completed games */} - {isUWWinner && game.uwLogo && ( -
- UW Logo -
- )} - {!isUWWinner && game.opponentLogo && ( -
- Opponent Logo -
- )} - - {isUWWinner ? 'HUSKIES VICTORY' : 'TOUGH LOSS'} - - - ) : ( - <> - {/* Show UW logo for upcoming games */} - {game.uwLogo && ( -
- UW Logo -
- )} - - {game.formattedDate || 'UPCOMING GAME'} - - - )} -
-
- {game.sport} -
-
- - {/* Main Content - White Background */} -
- {/* Team Matchup - Vertical Stack */} -
- {/* Washington Team */} -
-
- {game.uwLogo && ( -
- UW Logo -
- )} -
-
WASHINGTON
-
Huskies
- {!isCompleted && game.homeOdds && ( -
- Odds: {isUWHome ? game.homeOdds : game.awayOdds}x -
- )} -
-
- {isCompleted ? ( - - {uwScore} - - ) : ( -
- {game.canBet && ( -
Open
- )} -
- )} -
- - {/* VS Divider */} -
VS
- - {/* Opponent Team */} -
-
- {game.opponentLogo && ( -
- Opponent Logo -
- )} -
-
{game.opponent.toUpperCase()}
-
Opponent
- {!isCompleted && game.awayOdds && ( -
- Odds: {isUWHome ? game.awayOdds : game.homeOdds}x -
- )} -
-
- {isCompleted ? ( -
- {opponentScore} -
- ) : ( -
- {game.formattedTime && ( -
{game.formattedTime}
- )} -
- )} -
-
- - {/* Top Performers - Compact (Only for completed games) */} - {isCompleted && ( -
-
- - Top Performers -
- - {(game.uwTopPlayer || game.opponentTopPlayer) ? ( -
- {/* UW Top Player */} - {game.uwTopPlayer ? ( -
-
{game.uwTopPlayer.name}
-
{game.uwTopPlayer.position}
-
{game.uwTopPlayer.stats}
-
- ) : ( -
-
No Data
-
-
-
-
-
- )} - - {/* Opponent Top Player */} - {game.opponentTopPlayer ? ( -
-
{game.opponentTopPlayer.name}
-
{game.opponentTopPlayer.position}
-
{game.opponentTopPlayer.stats}
-
- ) : ( -
-
No Data
-
-
-
-
-
- )} -
- ) : ( -
- No player stats available -
- )} -
- )} - - {/* Compact Stats Footer */} -
-
-
{game.totalBetsPlaced || 0}
-
Bets
-
-
-
{(game.totalBiscuitsWagered || 0).toLocaleString()}
-
Wagered
-
-
-
{game.formattedDate}
-
{game.location?.split(',')[0]}
-
-
-
-
-
- ); -} diff --git a/src/components/DarkModeToggle.jsx b/src/components/DarkModeToggle.jsx deleted file mode 100644 index c91392ed..00000000 --- a/src/components/DarkModeToggle.jsx +++ /dev/null @@ -1,29 +0,0 @@ -'use client'; - -import { Sun, Moon } from 'lucide-react'; -import { useDarkMode } from '../contexts/DarkModeContext'; - -export default function DarkModeToggle() { - const { isDark, toggleDarkMode, isLoaded } = useDarkMode(); - - // Render a stable placeholder during SSR/hydration to prevent mismatch - // The button structure stays the same, only icon changes after hydration - return ( - - ); -} diff --git a/src/components/ErrorState.jsx b/src/components/ErrorState.jsx deleted file mode 100644 index f193c8d6..00000000 --- a/src/components/ErrorState.jsx +++ /dev/null @@ -1,108 +0,0 @@ -/** - * ErrorState Component - * Standardized error display component with retry functionality - * Replaces inconsistent error handling patterns across the app - */ - -'use client'; - -import React from 'react'; -import { AlertTriangle, RefreshCw } from 'lucide-react'; -import { Button } from './ui'; - -export default function ErrorState({ - error, - onRetry, - title = 'Something went wrong', - showReload = false, - className = '', -}) { - // Normalize error message from various sources - const getErrorMessage = (err) => { - if (typeof err === 'string') return err; - if (err?.message) return err.message; - if (err?.error) return err.error; - if (err?.info?.error) return err.info.error; - return 'An unexpected error occurred. Please try again.'; - }; - - const errorMessage = getErrorMessage(error); - - return ( -
-
- {/* Icon */} -
-
- -
-
- - {/* Content */} -
-

- {title} -

-

- {errorMessage} -

- - {/* Actions */} -
- {onRetry && ( - - )} - {showReload && ( - - )} -
-
-
-
- ); -} - -/** - * ErrorStateInline - Compact inline error for small spaces - */ -export function ErrorStateInline({ error, onRetry, className = '' }) { - const getErrorMessage = (err) => { - if (typeof err === 'string') return err; - if (err?.message) return err.message; - if (err?.error) return err.error; - return 'An error occurred'; - }; - - return ( -
-
- -

{getErrorMessage(error)}

-
- {onRetry && ( - - )} -
- ); -} diff --git a/src/components/FireIcon.jsx b/src/components/FireIcon.jsx deleted file mode 100644 index c34f16dd..00000000 --- a/src/components/FireIcon.jsx +++ /dev/null @@ -1,125 +0,0 @@ -/** - * FireIcon Component - * Modern, clean flame icon - */ - -'use client'; - -import { memo } from 'react'; - -const FireIcon = memo(function FireIcon({ - size = 16, - className = '', - variant = 'default' -}) { - const sizeValue = typeof size === 'number' ? size : 16; - - const variants = { - default: { - id: 'flame-default', - colors: ['#fcd34d', '#f97316', '#dc2626'], - inner: '#fef3c7', - }, - subtle: { - id: 'flame-subtle', - colors: ['#fdba74', '#ea580c', '#b91c1c'], - inner: '#fed7aa', - }, - intense: { - id: 'flame-intense', - colors: ['#fef08a', '#fbbf24', '#ea580c'], - inner: '#fffbeb', - }, - mono: { - id: 'flame-mono', - colors: ['#d4d4d8', '#71717a', '#3f3f46'], - inner: '#f4f4f5', - } - }; - - const colors = variants[variant] || variants.default; - - return ( - - {/* Red (Base & Embers) */} - - - {/* Orange (Flame Body) */} - - - {/* Yellow (Core Heat) */} - - - ); -}); - -// Animated version -FireIcon.Animated = memo(function AnimatedFireIcon({ - size = 16, - className = '', - variant = 'intense' -}) { - return ( -
- -
- -
-
- ); -}); - -// Streak counter -FireIcon.Streak = memo(function FireStreak({ - count = 0, - size = 16, - showCount = true, - className = '' -}) { - const getVariant = (n) => { - if (n >= 7) return 'intense'; - if (n >= 3) return 'default'; - return 'subtle'; - }; - - return ( -
- - {showCount && ( - - {count} - - )} -
- ); -}); - -export default FireIcon; diff --git a/src/components/GameCalendar.jsx b/src/components/GameCalendar.jsx deleted file mode 100644 index ce027548..00000000 --- a/src/components/GameCalendar.jsx +++ /dev/null @@ -1,221 +0,0 @@ -'use client'; - -import React, { useState, useEffect, memo } from 'react'; -import { GameCardSkeleton } from './ui/LoadingSkeleton'; -import { formatDate, formatTime } from '@shared/utils/date-utils'; - -/** - * GameCalendar Component - * Displays upcoming UW Huskies games from ESPN API via MongoDB - */ - -const GameCalendar = memo(({ sport = 'football' }) => { - const [games, setGames] = useState([]); - const [loading, setLoading] = useState(true); - const [error, setError] = useState(null); - const [currentWeek, setCurrentWeek] = useState(1); - const [selectedGame, setSelectedGame] = useState(null); - - // Fetch games from API - useEffect(() => { - async function fetchGames() { - try { - setLoading(true); - setError(null); - - const response = await fetch(`/api/games/upcoming?sport=${sport}`); - - if (!response.ok) { - throw new Error(`Failed to fetch games: ${response.statusText}`); - } - - const data = await response.json(); - - if (data.success && data.games) { - setGames(data.games); - - // Set current week to the first game's week if available - if (data.games.length > 0 && data.games[0].week) { - setCurrentWeek(data.games[0].week); - } - } else { - throw new Error(data.error || 'Failed to load games'); - } - } catch (err) { - console.error('Failed to fetch games:', err); - setError(err.message); - } finally { - setLoading(false); - } - } - - fetchGames(); - }, [sport]); - - // Get games for current week - const getGamesForWeek = (week) => { - return games.filter(game => game.week === week); - }; - - // Function to handle week navigation - const changeWeek = (increment) => { - const newWeek = currentWeek + increment; - // Check if there are games in the new week - const gamesInWeek = games.filter(g => g.week === newWeek); - if (gamesInWeek.length > 0) { - setCurrentWeek(newWeek); - } - }; - - // Get min and max weeks - const weeks = games.map(g => g.week).filter(w => w); - const minWeek = weeks.length > 0 ? Math.min(...weeks) : 1; - const maxWeek = weeks.length > 0 ? Math.max(...weeks) : 12; - - const currentGames = getGamesForWeek(currentWeek); - - // Loading state - if (loading) { - return ( -
-
-
-
-
-
-
- {[1, 2, 3].map((i) => ( - - ))} -
-
- ); - } - - // Error state - if (error) { - return ( -
-
-
⚠️ {error}
- -
-
- ); - } - - // No games state - if (games.length === 0) { - return ( -
-
-

No upcoming games found.

-

Games will appear here once they are scheduled.

-
-
- ); - } - - return ( -
-
- -

- Week {currentWeek} -

- -
- -
- {currentGames.length > 0 ? ( - currentGames.map(game => ( -
setSelectedGame(game)} - className="flex items-center justify-between p-3 bg-purple-50 rounded-lg cursor-pointer hover:bg-purple-100 transition-colors" - > -
-
-
- {game.homeTeam === 'Washington Huskies' ? game.awayTeam : game.homeTeam} -
-
- {formatDate(game.gameDate, { includeWeekday: true, includeYear: true, format: 'long' })} at {formatTime(game.gameDate)} -
-
-
-
-
- {game.homeTeam === 'Washington Huskies' ? 'Home' : 'Away'} -
-
- {game.status === 'scheduled' ? 'Upcoming' : game.status} -
-
-
- )) - ) : ( -
- No games scheduled for Week {currentWeek} -
- )} -
- - {/* Game Details Modal */} - {selectedGame && ( -
-
-

Game Details

-

- Teams: {selectedGame.homeTeam} vs {selectedGame.awayTeam} -

-

- Date: {formatDate(selectedGame.gameDate, { includeWeekday: true, includeYear: true, format: 'long' })} -

-

- Time: {formatTime(selectedGame.gameDate)} -

-

- Location: {selectedGame.location} -

-

- Status: {selectedGame.status} -

- {selectedGame.status === 'completed' && ( -

- Score: {selectedGame.homeTeam} {selectedGame.homeScore} - {selectedGame.awayScore} {selectedGame.awayTeam} -

- )} - -
-
- )} -
- ); -}); - -GameCalendar.displayName = 'GameCalendar'; - -export default GameCalendar; diff --git a/src/components/GameDetailsModal.jsx b/src/components/GameDetailsModal.jsx deleted file mode 100644 index cfa9d137..00000000 --- a/src/components/GameDetailsModal.jsx +++ /dev/null @@ -1,309 +0,0 @@ -/** - * Game Details Modal Component - * Shows detailed odds, player stats, and game information - */ - -'use client'; - -import React from 'react'; -import Image from 'next/image'; -import Modal from './ui/Modal'; -import { Badge, Button } from './ui'; -import { Trophy, TrendingUp, User, Info, DollarSign, BarChart3 } from 'lucide-react'; - -export default function GameDetailsModal({ game, isOpen, onClose }) { - if (!game) return null; - - // Determine if UW is home or away - const isUWHome = game.homeTeam === 'Washington Huskies' || game.homeTeam?.includes('Washington'); - - return ( - -
- {/* Teams Header */} -
- {/* UW Team */} -
-
- {game.uwLogo && ( -
- UW Logo -
- )} -

Washington Huskies

- - {isUWHome ? 'Home' : 'Away'} - -
-
- - {/* Opponent Team */} -
-
- {game.opponentLogo && ( -
- Opponent Logo -
- )} -

{game.opponent}

- - {isUWHome ? 'Away' : 'Home'} - -
-
-
- - {/* Betting Odds Section */} - {(game.homeOdds || game.awayOdds) && ( -
-
- -

Current Betting Odds

-
-
-
-
Washington Huskies
-
- {isUWHome ? game.homeOdds?.toFixed(2) : game.awayOdds?.toFixed(2)}x -
-
- ${(10 * (isUWHome ? game.homeOdds : game.awayOdds)).toFixed(0)} payout per $10 bet -
-
-
-
{game.opponent}
-
- {isUWHome ? game.awayOdds?.toFixed(2) : game.homeOdds?.toFixed(2)}x -
-
- ${(10 * (isUWHome ? game.awayOdds : game.homeOdds)).toFixed(0)} payout per $10 bet -
-
-
-
- )} - - {/* ESPN Odds (if available) */} - {game.espnOdds && ( -
-
- -

ESPN Odds Data

- {game.espnOdds.provider && ( - {game.espnOdds.provider} - )} -
-
- {game.espnOdds.spread && ( -
-
Spread
-
{game.espnOdds.spread}
-
- )} - {game.espnOdds.overUnder && ( -
-
Over/Under
-
{game.espnOdds.overUnder}
-
- )} - {game.espnOdds.details && ( -
-
Details
-
{game.espnOdds.details}
-
- )} -
-
- )} - - {/* Top Performing Players */} -
-
- -

Top Performing Players

-
- -
- {/* UW Top Player */} - {game.uwTopPlayer && ( -
-
-
- -
-
-
- {game.uwTopPlayer.name} - {game.uwTopPlayer.position} -
-
Washington Huskies
- {game.uwTopPlayer.stats && ( -
-
- {game.uwTopPlayer.stats} -
-
- )} -
-
-
- )} - - {/* Opponent Top Player */} - {game.opponentTopPlayer && ( -
-
-
- -
-
-
- {game.opponentTopPlayer.name} - {game.opponentTopPlayer.position} -
-
{game.opponent}
- {game.opponentTopPlayer.stats && ( -
-
- {game.opponentTopPlayer.stats} -
-
- )} -
-
-
- )} - - {!game.uwTopPlayer && !game.opponentTopPlayer && ( -
- -

Player statistics not available for this game

-
- )} -
-
- - {/* Betting Statistics */} - {game.totalBetsPlaced > 0 && ( -
-
- -

Betting Activity

-
-
-
-
Total Bets Placed
-
{game.totalBetsPlaced}
-
-
-
Total Wagered
-
- {game.totalBiscuitsWagered?.toLocaleString() || 0} 🥯 -
-
- {game.homeBets > 0 && game.awayBets > 0 && ( - <> -
-
Bets on {isUWHome ? 'UW' : game.opponent}
-
- {isUWHome ? game.homeBets : game.awayBets} -
-
-
-
Bets on {isUWHome ? game.opponent : 'UW'}
-
- {isUWHome ? game.awayBets : game.homeBets} -
-
- - )} -
-
- )} - - {/* Additional Game Info */} -
-

Game Information

-
-
- Sport: - {game.sport} -
- {game.week && ( -
- Week: - Week {game.week} -
- )} -
- Status: - - {game.status.charAt(0).toUpperCase() + game.status.slice(1)} - -
-
- Date: - - {new Date(game.gameDate).toLocaleDateString('en-US', { - weekday: 'long', - month: 'long', - day: 'numeric', - year: 'numeric', - })} - -
-
- Time: - - {new Date(game.gameDate).toLocaleTimeString('en-US', { - hour: 'numeric', - minute: '2-digit', - hour12: true, - })} - -
-
- Location: - {game.location} -
- {game.tvNetwork && ( -
- TV: - {game.tvNetwork} -
- )} -
-
-
-
- ); -} - -// Helper function to get badge variant based on status -function getStatusVariant(status) { - const variants = { - scheduled: 'info', - live: 'success', - completed: 'secondary', - cancelled: 'danger', - postponed: 'warning', - }; - return variants[status] || 'secondary'; -} diff --git a/src/components/UWRecord.jsx b/src/components/UWRecord.jsx deleted file mode 100644 index bcfbd29c..00000000 --- a/src/components/UWRecord.jsx +++ /dev/null @@ -1,143 +0,0 @@ -'use client'; - -import React from 'react'; -import { Trophy } from 'lucide-react'; -import { motion } from 'framer-motion'; - -/** - * UW Record Display Component - * Shows Washington's win-loss record in W-L format - * Optionally breaks down by sport if multiple sports are present - */ -export default function UWRecord({ games, sport }) { - // Calculate overall record for current season only - const calculateRecord = (gamesArray, sportFilter = null) => { - // Determine current season - const currentDate = new Date(); - const currentMonth = currentDate.getMonth(); // 0-11 - const currentYear = currentDate.getFullYear(); - - // For football: Season year is when it starts (Aug 2024 = 2024 season, Aug 2025 = 2025 season) - // Since we're in November 2025, we're in the 2025 season - // BUT completed games from Sept-Dec 2024 are from 2024 season - const currentSeason = currentMonth >= 7 ? currentYear : currentYear - 1; - - console.log('🏈 UWRecord - Current Season:', currentSeason); - console.log('📅 UWRecord - Total games received:', gamesArray.length); - - // Filter by sport if specified - let filteredGames = sportFilter && sportFilter !== 'all' - ? gamesArray.filter(g => g.sport === sportFilter) - : gamesArray; - - console.log('🏀 UWRecord - After sport filter:', filteredGames.length, 'sport:', sportFilter); - - // Filter by current season only - filteredGames = filteredGames.filter(g => { - // Use season field if available - if (g.season) { - const gameSeason = parseInt(g.season); - const matches = gameSeason === currentSeason; - if (!matches) { - console.log(`Game season ${gameSeason} != current ${currentSeason}, filtering out game from ${g.gameDate}`); - } - return matches; - } - // Fallback: calculate season from game date - const gameDate = new Date(g.gameDate); - const gameYear = gameDate.getFullYear(); - const gameMonth = gameDate.getMonth(); - const gameSeason = gameMonth >= 7 ? gameYear : gameYear - 1; - const matches = gameSeason === currentSeason; - if (!matches) { - console.log(`Calculated season ${gameSeason} != current ${currentSeason}, filtering out game from ${g.gameDate}`); - } - return matches; - }); - - console.log('✅ UWRecord - After season filter:', filteredGames.length); - - // Fix: Check if UW won based on home/away status and winner field - const wins = filteredGames.filter(g => { - // Check if UW is home team (handle variations: "Washington", "Washington Huskies", etc.) - const isUWHome = g.homeTeam?.toLowerCase().includes('washington') && - !g.homeTeam?.toLowerCase().includes('state'); - return (isUWHome && g.winner === 'home') || (!isUWHome && g.winner === 'away'); - }).length; - - const losses = filteredGames.length - wins; - - return { wins, losses, total: filteredGames.length }; - }; - - const overallRecord = calculateRecord(games); - - // If no games, don't show anything - if (games.length === 0 || overallRecord.total === 0) { - return null; - } - - // Calculate win percentage for styling - const winPercentage = overallRecord.total > 0 - ? (overallRecord.wins / overallRecord.total) * 100 - : 0; - - // Determine color based on win percentage - const getRecordColor = () => { - if (winPercentage >= 70) return 'text-green-700'; - if (winPercentage >= 50) return 'text-uw-purple-700'; - return 'text-gray-700'; - }; - - // Get breakdown by sport if "all sports" is selected and multiple sports exist - const footballGames = games.filter(g => g.sport === 'football'); - const basketballGames = games.filter(g => g.sport === 'basketball'); - const hasMultipleSports = footballGames.length > 0 && basketballGames.length > 0; - const showBreakdown = sport === 'all' && hasMultipleSports; - - const footballRecord = showBreakdown ? calculateRecord(games, 'football') : null; - const basketballRecord = showBreakdown ? calculateRecord(games, 'basketball') : null; - - return ( - -
- -
-

- UW HUSKIES RECORD -

-
- {overallRecord.wins}-{overallRecord.losses} -
- - {/* Sport Breakdown */} - {showBreakdown && ( -
- {footballRecord && footballRecord.total > 0 && ( -
- Football: - - {footballRecord.wins}-{footballRecord.losses} - -
- )} - {basketballRecord && basketballRecord.total > 0 && ( -
- Basketball: - - {basketballRecord.wins}-{basketballRecord.losses} - -
- )} -
- )} -
-
-
- ); -} diff --git a/src/components/animation/AnimatedCard.jsx b/src/components/animation/AnimatedCard.jsx deleted file mode 100644 index 290716cc..00000000 --- a/src/components/animation/AnimatedCard.jsx +++ /dev/null @@ -1,18 +0,0 @@ -'use client'; -import { motion } from 'framer-motion'; -import { cardHover } from '@components/animation/variants'; - -export default function AnimatedCard({ children, className = '', onClick }) { - return ( - - {children} - - ); -} diff --git a/src/components/animation/FadeInView.jsx b/src/components/animation/FadeInView.jsx deleted file mode 100644 index 77009a58..00000000 --- a/src/components/animation/FadeInView.jsx +++ /dev/null @@ -1,21 +0,0 @@ -'use client'; -import { motion, useInView } from 'framer-motion'; -import { useRef } from 'react'; -import { fadeInUp } from '@components/animation/variants'; - -export default function FadeInView({ children, className = '' }) { - const ref = useRef(null); - const isInView = useInView(ref, { once: true, margin: "-100px" }); - - return ( - - {children} - - ); -} diff --git a/src/components/animation/variants.js b/src/components/animation/variants.js deleted file mode 100644 index e2490793..00000000 --- a/src/components/animation/variants.js +++ /dev/null @@ -1,94 +0,0 @@ -/** - * Framer Motion variants for HuskyBids - * Consistent animation patterns across the app - */ - -export const fadeIn = { - hidden: { opacity: 0 }, - visible: { - opacity: 1, - transition: { duration: 0.5, ease: 'easeOut' } - }, -}; - -export const fadeInUp = { - hidden: { opacity: 0, y: 20 }, - visible: { - opacity: 1, - y: 0, - transition: { duration: 0.5, ease: [0.22, 1, 0.36, 1] } - }, -}; - -export const scaleIn = { - hidden: { opacity: 0, scale: 0.9 }, - visible: { - opacity: 1, - scale: 1, - transition: { duration: 0.4, ease: [0.22, 1, 0.36, 1] } - }, -}; - -export const slideInFromLeft = { - hidden: { opacity: 0, x: -50 }, - visible: { - opacity: 1, - x: 0, - transition: { duration: 0.5, ease: [0.22, 1, 0.36, 1] } - }, -}; - -export const slideInFromRight = { - hidden: { opacity: 0, x: 50 }, - visible: { - opacity: 1, - x: 0, - transition: { duration: 0.5, ease: [0.22, 1, 0.36, 1] } - }, -}; - -export const staggerContainer = { - hidden: {}, - visible: { - transition: { - staggerChildren: 0.1, - delayChildren: 0.2, - } - }, -}; - -export const cardHover = { - rest: { scale: 1, y: 0 }, - hover: { - scale: 1.02, - y: -8, - transition: { duration: 0.3, ease: [0.22, 1, 0.36, 1] } - }, -}; - -export const buttonTap = { - rest: { scale: 1 }, - tap: { scale: 0.95 }, -}; - -// Confetti burst for winning bets -export const confetti = { - hidden: { opacity: 0, scale: 0, rotate: 0 }, - visible: { - opacity: [0, 1, 1, 0], - scale: [0, 1, 1, 0], - rotate: [0, 180, 360, 540], - y: [0, -100, -200, -300], - transition: { duration: 1.5, ease: 'easeOut' } - }, -}; - -// Number counter animation -export const counterAnimation = { - hidden: { opacity: 0, scale: 0.8 }, - visible: { - opacity: 1, - scale: 1, - transition: { duration: 0.5, ease: [0.34, 1.56, 0.64, 1] } // Spring effect - }, -}; diff --git a/src/components/betting/BetConfirmation.jsx b/src/components/betting/BetConfirmation.jsx deleted file mode 100644 index d7972769..00000000 --- a/src/components/betting/BetConfirmation.jsx +++ /dev/null @@ -1,86 +0,0 @@ -'use client'; -import { motion, AnimatePresence } from 'framer-motion'; -import { useEffect, useState } from 'react'; -import confetti from 'canvas-confetti'; - -export default function BetConfirmation({ isVisible, betDetails, onClose }) { - const [showConfetti, setShowConfetti] = useState(false); - - useEffect(() => { - if (isVisible) { - setShowConfetti(true); - - // Trigger confetti - const duration = 2000; - const end = Date.now() + duration; - - const frame = () => { - confetti({ - particleCount: 2, - angle: 60, - spread: 55, - origin: { x: 0 }, - colors: ['#4B2E83', '#E8D21D', '#FFFFFF'], - }); - confetti({ - particleCount: 2, - angle: 120, - spread: 55, - origin: { x: 1 }, - colors: ['#4B2E83', '#E8D21D', '#FFFFFF'], - }); - - if (Date.now() < end) { - requestAnimationFrame(frame); - } - }; - - frame(); - - setTimeout(() => { - setShowConfetti(false); - onClose(); - }, 3000); - } - }, [isVisible, onClose]); - - return ( - - {isVisible && ( - -
- -
- - ✓ - -
- -

Bet Placed!

-

{betDetails?.amount} biscuits

-

on {betDetails?.team}

-

- Potential Win: {betDetails?.potentialWin} -

-
-
-
- )} -
- ); -} diff --git a/src/components/charts/BettingDistributionChart.jsx b/src/components/charts/BettingDistributionChart.jsx deleted file mode 100644 index dc2808e7..00000000 --- a/src/components/charts/BettingDistributionChart.jsx +++ /dev/null @@ -1,87 +0,0 @@ -'use client'; - -import { PieChart, Pie, Cell, ResponsiveContainer, Legend, Tooltip } from 'recharts'; -import { Card } from '../ui'; - -const COLORS = { - won: '#10b981', // green - lost: '#ef4444', // red - pending: '#f59e0b', // amber - cancelled: '#6b7280', // gray -}; - -export default function BettingDistributionChart({ stats }) { - if (!stats) return null; - - const data = [ - { name: 'Won', value: stats.won, color: COLORS.won }, - { name: 'Lost', value: stats.lost, color: COLORS.lost }, - { name: 'Pending', value: stats.pending, color: COLORS.pending }, - { name: 'Cancelled', value: stats.cancelled || 0, color: COLORS.cancelled }, - ].filter(item => item.value > 0); - - // If no data, show empty state - if (data.length === 0 || stats.total === 0) { - return ( - - -

No betting data available yet

-
-
- ); - } - - const CustomTooltip = ({ active, payload }) => { - if (active && payload && payload.length) { - const data = payload[0]; - const percentage = ((data.value / stats.total) * 100).toFixed(1); - return ( -
-

{data.name}

-

- {data.value} bets ({percentage}%) -

-
- ); - } - return null; - }; - - const renderLabel = (entry) => { - const percentage = ((entry.value / stats.total) * 100).toFixed(0); - return `${percentage}%`; - }; - - return ( - - -

Bet Distribution

- - - - {data.map((entry, index) => ( - - ))} - - } /> - `${value}: ${entry.payload.value}`} - /> - - -
-
- ); -} diff --git a/src/components/charts/BettingTrendChart.jsx b/src/components/charts/BettingTrendChart.jsx deleted file mode 100644 index 1a6180fd..00000000 --- a/src/components/charts/BettingTrendChart.jsx +++ /dev/null @@ -1,132 +0,0 @@ -'use client'; - -import { - LineChart, - Line, - AreaChart, - Area, - XAxis, - YAxis, - CartesianGrid, - Tooltip, - ResponsiveContainer, - Legend, -} from 'recharts'; -import { Card } from '../ui'; -import BiscuitIcon from '../BiscuitIcon'; - -export default function BettingTrendChart({ bets }) { - if (!bets || bets.length === 0) { - return ( - - -

No betting data available yet

-
-
- ); - } - - // Sort bets by date (oldest first) - const sortedBets = [...bets] - .filter(bet => bet.gameId && bet.placedAt) - .sort((a, b) => new Date(a.placedAt) - new Date(b.placedAt)); - - // Calculate cumulative profit over time - let cumulativeProfit = 0; - const trendData = sortedBets.map((bet, index) => { - let profitLoss = 0; - - if (bet.status === 'won') { - profitLoss = bet.actualWin - bet.betAmount; - } else if (bet.status === 'lost') { - profitLoss = -bet.betAmount; - } - // pending and cancelled bets don't affect profit yet - - cumulativeProfit += profitLoss; - - return { - date: new Date(bet.placedAt).toLocaleDateString('en-US', { - month: 'short', - day: 'numeric' - }), - profit: cumulativeProfit, - betNumber: index + 1, - betAmount: bet.betAmount, - status: bet.status, - game: bet.gameId ? `${bet.gameId.homeTeam} vs ${bet.gameId.awayTeam}` : 'Unknown', - }; - }); - - const CustomTooltip = ({ active, payload }) => { - if (active && payload && payload.length) { - const data = payload[0].payload; - return ( -
-

{data.date}

-

Bet #{data.betNumber}

-

{data.game}

-
- Cumulative Profit: - - = 0 ? 'text-green-600' : 'text-red-600'}`}> - {data.profit >= 0 ? '+' : ''}{data.profit.toLocaleString()} - -
-
- ); - } - return null; - }; - - // Determine if overall trend is positive - const isPositive = cumulativeProfit >= 0; - - return ( - - -
-

Performance Trend

-
- Total: - - - {isPositive ? '+' : ''}{cumulativeProfit.toLocaleString()} - -
-
- - - - - - - - - - - `${value >= 0 ? '+' : ''}${value}`} - /> - } /> - - - -
-
- ); -} diff --git a/src/components/charts/SportActivityChart.jsx b/src/components/charts/SportActivityChart.jsx deleted file mode 100644 index 7faa4b0e..00000000 --- a/src/components/charts/SportActivityChart.jsx +++ /dev/null @@ -1,130 +0,0 @@ -'use client'; - -import { - BarChart, - Bar, - XAxis, - YAxis, - CartesianGrid, - Tooltip, - ResponsiveContainer, - Legend, - Cell, -} from 'recharts'; -import { Card } from '../ui'; -import BiscuitIcon from '../BiscuitIcon'; - -export default function SportActivityChart({ bets }) { - if (!bets || bets.length === 0) { - return ( - - -

No betting data available yet

-
-
- ); - } - - // Group bets by sport - const sportStats = {}; - - bets.forEach(bet => { - if (!bet.gameId) return; - - const sport = bet.gameId.sport || 'unknown'; - if (!sportStats[sport]) { - sportStats[sport] = { - sport: sport.charAt(0).toUpperCase() + sport.slice(1), - totalBets: 0, - won: 0, - lost: 0, - pending: 0, - totalWagered: 0, - totalWon: 0, - }; - } - - sportStats[sport].totalBets++; - sportStats[sport].totalWagered += bet.betAmount; - - if (bet.status === 'won') { - sportStats[sport].won++; - sportStats[sport].totalWon += bet.actualWin; - } else if (bet.status === 'lost') { - sportStats[sport].lost++; - } else if (bet.status === 'pending') { - sportStats[sport].pending++; - } - }); - - // Convert to array and calculate win rates - const data = Object.values(sportStats).map(stat => ({ - ...stat, - winRate: stat.won + stat.lost > 0 - ? ((stat.won / (stat.won + stat.lost)) * 100).toFixed(1) - : 0, - netProfit: stat.totalWon - (stat.totalWagered - (stat.pending * 0)), // Approximate, excluding pending - })); - - const SPORT_COLORS = { - Football: '#4c1d95', // UW Purple - Basketball: '#f59e0b', // UW Gold - Soccer: '#10b981', - Baseball: '#3b82f6', - Hockey: '#ef4444', - }; - - const CustomTooltip = ({ active, payload }) => { - if (active && payload && payload.length) { - const data = payload[0].payload; - return ( -
-

{data.sport}

-
-

Total Bets: {data.totalBets}

-

Won: {data.won}

-

Lost: {data.lost}

-

Pending: {data.pending}

-

Win Rate: {data.winRate}%

-
- Wagered: - - {data.totalWagered.toLocaleString()} -
-
-
- ); - } - return null; - }; - - return ( - - -

Activity by Sport

- - - - - - } /> - - - - - - -
-
- ); -} diff --git a/src/components/charts/index.js b/src/components/charts/index.js deleted file mode 100644 index e87b7a93..00000000 --- a/src/components/charts/index.js +++ /dev/null @@ -1,8 +0,0 @@ -/** - * Chart Components Index - * Centralized exports for all chart components - */ - -export { default as BettingDistributionChart } from './BettingDistributionChart'; -export { default as BettingTrendChart } from './BettingTrendChart'; -export { default as SportActivityChart } from './SportActivityChart'; diff --git a/src/components/dashboard/BettingChart.jsx b/src/components/dashboard/BettingChart.jsx deleted file mode 100644 index 431b502b..00000000 --- a/src/components/dashboard/BettingChart.jsx +++ /dev/null @@ -1,61 +0,0 @@ -'use client'; -import { LineChart, Line, AreaChart, Area, XAxis, YAxis, CartesianGrid, Tooltip, ResponsiveContainer } from 'recharts'; -import { useDarkMode } from '@/app/contexts/DarkModeContext'; - -export default function BettingChart({ data, title = 'Betting History' }) { - const { isDark } = useDarkMode(); - - const colors = { - line: isDark ? '#9B8DCE' : '#4B2E83', - gradient: isDark ? ['#9B8DCE', '#7D6BC2'] : ['#4B2E83', '#7D6BC2'], - grid: isDark ? '#334155' : '#E5E7EB', - text: isDark ? '#CBD5E1' : '#6B7280', - }; - - return ( -
-

- {title} -

- - - - - - - - - - - - - - - - - - -
- ); -} diff --git a/src/components/dashboard/StatsGrid.jsx b/src/components/dashboard/StatsGrid.jsx deleted file mode 100644 index 26143a41..00000000 --- a/src/components/dashboard/StatsGrid.jsx +++ /dev/null @@ -1,100 +0,0 @@ -'use client'; -import { motion } from 'framer-motion'; -import { TrendingUp, TrendingDown, Wallet, Trophy, Target, BarChart3 } from 'lucide-react'; -import { staggerContainer, fadeInUp } from '@components/animation/variants'; - -export default function StatsGrid({ stats }) { - const statCards = [ - { - label: 'Biscuit Balance', - value: stats.balance?.toLocaleString() || 0, - icon: Wallet, - gradient: 'from-uw-purple-500 to-uw-purple-700 dark:from-uw-purple-400 dark:to-uw-purple-600', - trend: null, - }, - { - label: 'Total Wagered', - value: stats.totalWagered?.toLocaleString() || 0, - icon: Target, - gradient: 'from-blue-500 to-blue-700 dark:from-blue-400 dark:to-blue-600', - trend: stats.totalWageredChange, - }, - { - label: 'Total Won', - value: stats.totalWon?.toLocaleString() || 0, - icon: Trophy, - gradient: 'from-green-500 to-green-700 dark:from-green-400 dark:to-green-600', - trend: stats.totalWonChange, - }, - { - label: 'Profit/Loss', - value: stats.profitLoss || 0, - icon: stats.profitLoss >= 0 ? TrendingUp : TrendingDown, - gradient: stats.profitLoss >= 0 - ? 'from-green-500 to-green-700 dark:from-green-400 dark:to-green-600' - : 'from-red-500 to-red-700 dark:from-red-400 dark:to-red-600', - trend: stats.profitLossChange, - }, - { - label: 'Win Rate', - value: `${stats.winRate?.toFixed(1) || 0}%`, - icon: BarChart3, - gradient: 'from-uw-gold-500 to-uw-gold-700 dark:from-uw-gold-400 dark:to-uw-gold-600', - trend: stats.winRateChange, - }, - ]; - - return ( - - {statCards.map((card, index) => ( - - ))} - - ); -} - -function StatCard({ label, value, icon: Icon, gradient, trend, delay }) { - return ( - - {/* Glass card background */} -
- {/* Gradient accent bar */} -
- - {/* Icon with gradient background */} -
- -
- - {/* Label */} -

- {label} -

- - {/* Value */} -
-

- {value} -

- - {/* Trend indicator */} - {trend !== null && trend !== undefined && ( - = 0 ? 'text-green-600 dark:text-green-400' : 'text-red-600 dark:text-red-400'}`}> - {trend >= 0 ? '+' : ''}{trend}% - - )} -
-
- - ); -} diff --git a/src/components/game/index.js b/src/components/game/index.js deleted file mode 100644 index 93d0bbfa..00000000 --- a/src/components/game/index.js +++ /dev/null @@ -1,9 +0,0 @@ -// Game components export file -export { default as GameCard } from './GameCard'; -export { default as TeamDisplay } from './TeamDisplay'; -export { default as OddsDisplay } from './OddsDisplay'; -export { default as PlayerStat } from './PlayerStat'; -export { default as PlayerStatCard } from './PlayerStatCard'; -export { default as PlaceholderCard } from './PlaceholderCard'; -export { default as EmptyStateMessage } from './EmptyStateMessage'; -export { default as PremiumGameCard } from './PremiumGameCard'; diff --git a/src/components/leaderboard/Podium.jsx b/src/components/leaderboard/Podium.jsx deleted file mode 100644 index fbbf2af1..00000000 --- a/src/components/leaderboard/Podium.jsx +++ /dev/null @@ -1,103 +0,0 @@ -'use client'; -import { motion } from 'framer-motion'; -import { Trophy, Medal, Award } from 'lucide-react'; -import Image from 'next/image'; - -export default function Podium({ topThree }) { - const [first, second, third] = topThree; - - const positions = [ - { user: second, rank: 2, height: 'h-64', icon: Medal, color: 'from-gray-300 to-gray-500', delay: 0.2 }, - { user: first, rank: 1, height: 'h-80', icon: Trophy, color: 'from-uw-gold-400 to-uw-gold-600', delay: 0 }, - { user: third, rank: 3, height: 'h-56', icon: Award, color: 'from-orange-300 to-orange-500', delay: 0.4 }, - ]; - - return ( -
- {/* Confetti background */} -
- {[...Array(20)].map((_, i) => ( - - ))} -
- - {/* Podium */} -
- {positions.map((pos, idx) => pos.user && ( - - {/* User card */} - - {/* Rank badge */} -
- -
- - {/* Avatar */} -
-
- {pos.user.username} -
-
- - {/* Username */} -

- {pos.user.username} -

- - {/* Stats */} -
-
- Balance: - - {pos.user.biscuits?.toLocaleString()} - -
-
- Win Rate: - - {pos.user.winRate?.toFixed(1)}% - -
-
-
- - {/* Podium stand */} -
- - #{pos.rank} - -
-
- ))} -
-
- ); -} diff --git a/src/components/shared/index.js b/src/components/shared/index.js deleted file mode 100644 index 4fcb4a80..00000000 --- a/src/components/shared/index.js +++ /dev/null @@ -1,4 +0,0 @@ -// Shared components export file -export { default as EmptyState } from './EmptyState'; -export { default as TeamLogo } from './TeamLogo'; -export { default as GameMetadata } from './GameMetadata'; diff --git a/src/hooks/useKeyboardNav.js b/src/hooks/useKeyboardNav.js deleted file mode 100644 index 18987364..00000000 --- a/src/hooks/useKeyboardNav.js +++ /dev/null @@ -1,37 +0,0 @@ -'use client' -import { useEffect } from "react"; -import { useRouter } from "next/navigation"; -import { globalShortcuts, sidebarConfig } from "@/components/experimental"; - -export default function useKeyboardNav() { - const router = useRouter(); - useEffect(() => { - //create lookup map once O(1) access - const navMap = new Map( - sidebarConfig.map(item => [item.key, item]) - ) - - globalShortcuts.forEach(shortcut => { - navMap.set(shortcut.key, shortcut); - }); - - const handleKeyDown = (event) => { - //check if the user is typing in an input field - const targetTag = event.target.tagName; - const isTyping = targetTag === 'INPUT' || - targetTag === 'TEXTAREA' || - event.target.contentEditable === 'true'; - if (isTyping) return; //dont nav if typing - //make the key uppercase - const key = event.key.toUpperCase(); - //constant look up time. - const navItem = navMap.get(key); - if (navItem) { - event.preventDefault(); - router.push(navItem.href); - } - }; - window.addEventListener('keydown', handleKeyDown) - return () => window.removeEventListener('keydown', handleKeyDown); - }, [router]); -} diff --git a/src/pages/api/bets/settle-all.js b/src/pages/api/bets/settle-all.js deleted file mode 100644 index d28ae01c..00000000 --- a/src/pages/api/bets/settle-all.js +++ /dev/null @@ -1,155 +0,0 @@ -/** - * Settle All Completed Games - * Processes all completed games with pending bets - */ - -import connectDB from '@server/db'; -import Game from '@server/models/Game'; -import Bet from '@server/models/Bet'; -import User from '@server/models/User'; -import { getAuth } from '@clerk/nextjs/server'; - -export default async function handler(req, res) { - if (req.method !== 'POST') { - return res.status(405).json({ error: 'Method not allowed' }); - } - - try { - // Only allow authenticated users (you can add admin check here) - const { userId } = getAuth(req); - if (!userId) { - return res.status(401).json({ error: 'Unauthorized' }); - } - - await connectDB(); - - // Find all completed games with a winner set - const completedGames = await Game.find({ - status: 'completed', - winner: { $in: ['home', 'away'] }, // Exclude ties and null - }); - - if (completedGames.length === 0) { - return res.status(200).json({ - success: true, - message: 'No completed games to settle', - gamesProcessed: 0, - }); - } - - console.log(`🎯 Processing ${completedGames.length} completed games`); - - let totalGamesSettled = 0; - let totalBetsSettled = 0; - let totalWon = 0; - let totalLost = 0; - let totalPayout = 0; - const gameResults = []; - - // Process each game - for (const game of completedGames) { - try { - // Get all pending bets for this game - const pendingBets = await Bet.find({ - gameId: game._id, - status: 'pending' - }).populate('userId'); - - if (pendingBets.length === 0) { - console.log(`⏭️ No pending bets for ${game.homeTeam} vs ${game.awayTeam}`); - continue; - } - - console.log(`\n📋 Settling ${pendingBets.length} bets for: ${game.homeTeam} vs ${game.awayTeam}`); - console.log(` Winner: ${game.winner === 'home' ? game.homeTeam : game.awayTeam}`); - - let gameWon = 0; - let gameLost = 0; - let gamePayout = 0; - - // Process each bet - for (const bet of pendingBets) { - try { - const didWin = bet.predictedWinner === game.winner; - - if (didWin) { - // Settle as won - await bet.settleAsWon(); - - // Add winnings to user's account - const user = await User.findById(bet.userId); - if (user) { - user.biscuits += bet.actualWin; - user.winningBets = (user.winningBets || 0) + 1; - user.totalBiscuitsWon = (user.totalBiscuitsWon || 0) + bet.actualWin; - await user.save(); - - gamePayout += bet.actualWin; - gameWon++; - } - } else { - // Settle as lost - await bet.settleAsLost(); - - // Update user stats - const user = await User.findById(bet.userId); - if (user) { - user.losingBets = (user.losingBets || 0) + 1; - user.totalBiscuitsLost = (user.totalBiscuitsLost || 0) + bet.betAmount; - await user.save(); - } - - gameLost++; - } - } catch (betError) { - console.error(` ⚠️ Error settling bet ${bet._id}:`, betError.message); - } - } - - totalGamesSettled++; - totalBetsSettled += pendingBets.length; - totalWon += gameWon; - totalLost += gameLost; - totalPayout += gamePayout; - - gameResults.push({ - gameId: game._id, - homeTeam: game.homeTeam, - awayTeam: game.awayTeam, - winner: game.winner, - betsSettled: pendingBets.length, - won: gameWon, - lost: gameLost, - payout: gamePayout, - }); - - console.log(` ✅ Settled: ${gameWon} won, ${gameLost} lost, ${gamePayout} biscuits paid`); - } catch (gameError) { - console.error(`Error processing game ${game._id}:`, gameError.message); - } - } - - console.log(`\n📊 SETTLEMENT SUMMARY:`); - console.log(` Games processed: ${totalGamesSettled}`); - console.log(` Bets settled: ${totalBetsSettled}`); - console.log(` Won: ${totalWon}, Lost: ${totalLost}`); - console.log(` Total payout: ${totalPayout} biscuits`); - - res.status(200).json({ - success: true, - message: 'All completed games settled', - gamesProcessed: totalGamesSettled, - betsSettled: totalBetsSettled, - won: totalWon, - lost: totalLost, - totalPayout, - games: gameResults, - }); - } catch (error) { - console.error('Error settling all games:', error); - res.status(500).json({ - success: false, - error: error.message, - }); - } -} diff --git a/src/pages/api/bets/settle-game.js b/src/pages/api/bets/settle-game.js deleted file mode 100644 index ad10cfef..00000000 --- a/src/pages/api/bets/settle-game.js +++ /dev/null @@ -1,147 +0,0 @@ -/** - * Settle Bets for a Completed Game - * Processes all pending bets for a game and updates user balances - */ - -import connectDB from '@server/db'; -import Game from '@server/models/Game'; -import Bet from '@server/models/Bet'; -import User from '@server/models/User'; -import { getAuth } from '@clerk/nextjs/server'; - -export default async function handler(req, res) { - if (req.method !== 'POST') { - return res.status(405).json({ error: 'Method not allowed' }); - } - - try { - // Only allow authenticated admins (you can add admin check here) - const { userId } = getAuth(req); - if (!userId) { - return res.status(401).json({ error: 'Unauthorized' }); - } - - await connectDB(); - - const { gameId } = req.body; - - if (!gameId) { - return res.status(400).json({ error: 'Game ID is required' }); - } - - // Find the game - const game = await Game.findById(gameId); - if (!game) { - return res.status(404).json({ error: 'Game not found' }); - } - - // Verify game is completed - if (game.status !== 'completed') { - return res.status(400).json({ - error: 'Game is not completed yet', - gameStatus: game.status - }); - } - - // Verify winner is set - if (!game.winner || game.winner === 'tie') { - return res.status(400).json({ - error: game.winner === 'tie' - ? 'Tie games require manual settlement' - : 'Winner not set for this game' - }); - } - - // Get all pending bets for this game - const pendingBets = await Bet.find({ - gameId: game._id, - status: 'pending' - }).populate('userId'); - - if (pendingBets.length === 0) { - return res.status(200).json({ - success: true, - message: 'No pending bets to settle', - settled: 0, - }); - } - - console.log(`🎯 Settling ${pendingBets.length} bets for game: ${game.homeTeam} vs ${game.awayTeam}`); - console.log(`Winner: ${game.winner === 'home' ? game.homeTeam : game.awayTeam}`); - - let wonCount = 0; - let lostCount = 0; - let totalPayout = 0; - const errors = []; - - // Process each bet - for (const bet of pendingBets) { - try { - const didWin = bet.predictedWinner === game.winner; - - if (didWin) { - // Settle as won - await bet.settleAsWon(); - - // Add winnings to user's account - const user = await User.findById(bet.userId); - if (user) { - user.biscuits += bet.actualWin; - user.winningBets = (user.winningBets || 0) + 1; - user.totalBiscuitsWon = (user.totalBiscuitsWon || 0) + bet.actualWin; - await user.save(); - - totalPayout += bet.actualWin; - wonCount++; - - console.log(`✅ Paid out ${bet.actualWin} biscuits to user ${user.clerkId}`); - } - } else { - // Settle as lost - await bet.settleAsLost(); - - // Update user stats - const user = await User.findById(bet.userId); - if (user) { - user.losingBets = (user.losingBets || 0) + 1; - user.totalBiscuitsLost = (user.totalBiscuitsLost || 0) + bet.betAmount; - await user.save(); - } - - lostCount++; - console.log(`❌ Bet lost: ${bet.betAmount} biscuits`); - } - } catch (error) { - console.error(`Error settling bet ${bet._id}:`, error); - errors.push({ - betId: bet._id, - error: error.message, - }); - } - } - - console.log(`📊 Settlement complete: ${wonCount} won, ${lostCount} lost, ${totalPayout} biscuits paid out`); - - res.status(200).json({ - success: true, - message: 'Bets settled successfully', - game: { - id: game._id, - homeTeam: game.homeTeam, - awayTeam: game.awayTeam, - winner: game.winner, - }, - settled: pendingBets.length, - won: wonCount, - lost: lostCount, - totalPayout, - errors: errors.length > 0 ? errors : undefined, - }); - } catch (error) { - console.error('Error settling bets:', error); - res.status(500).json({ - success: false, - error: error.message, - }); - } -} From e0815d9a3ef8b71f8993260bb9a28525ef155f23 Mon Sep 17 00:00:00 2001 From: Yonie <164077864+Isaiahriveraa@users.noreply.github.com> Date: Wed, 24 Dec 2025 01:40:22 -0800 Subject: [PATCH 4/7] Update core components to fix imports and enforce new design standards MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Changed: - Updated import paths in all active pages to resolve references to deleted legacy components - Refactored remaining components (AppShell, GameCards) to fully adopt the 'MinimalLayout' patterns - Standardized authentication routing in middleware and hooks (removing hash routing) - Centralized icon components into src/components/icons Files: - src/app/betting-history/page.jsx - src/app/dashboard/page.jsx - src/app/games/page.jsx - src/app/layout.jsx - src/app/leaderboard/page.jsx - src/app/tasks/page.jsx - src/app/hooks/useKeyboardNav.js - src/components/experimental/layout/AppShell.jsx - src/components/game/OddsDisplay.jsx - src/components/game/PlaceholderCard.jsx - src/components/game/PlayerStat.jsx - src/components/game/PlayerStatCard.jsx - src/components/icons/BiscuitIcon.jsx - src/components/icons/FireIcon.jsx - src/components/ui/Card.jsx - src/components/ui/LoadingSpinner.jsx - src/middleware.ts - src/pages/api/bets/auto-settle-user.js - src/pages/api/games/sync-from-espn.js - src/server/services/StatisticsService.js Why: To stabilize the application build after the removal of legacy artifacts. This ensures all active code correctly references the new architecture, eliminating dead dependencies and ensuring that the remaining features—like authentication and navigation—function correctly within the unified design system. --- src/app/betting-history/page.jsx | 2 +- src/app/dashboard/page.jsx | 4 +- src/app/games/page.jsx | 2 +- src/app/hooks/useKeyboardNav.js | 37 ++++++ src/app/layout.jsx | 39 ++---- src/app/leaderboard/page.jsx | 7 +- src/app/tasks/page.jsx | 6 +- .../experimental/layout/AppShell.jsx | 2 +- src/components/game/OddsDisplay.jsx | 2 +- src/components/game/PlaceholderCard.jsx | 2 +- src/components/game/PlayerStat.jsx | 2 - src/components/game/PlayerStatCard.jsx | 1 - src/components/icons/BiscuitIcon.jsx | 84 ++++++++++++ src/components/icons/FireIcon.jsx | 125 ++++++++++++++++++ src/components/ui/Card.jsx | 12 +- src/components/ui/LoadingSpinner.jsx | 12 +- src/middleware.ts | 2 - src/pages/api/bets/auto-settle-user.js | 1 - src/pages/api/games/sync-from-espn.js | 1 - src/server/services/StatisticsService.js | 14 -- 20 files changed, 285 insertions(+), 72 deletions(-) create mode 100644 src/app/hooks/useKeyboardNav.js create mode 100644 src/components/icons/BiscuitIcon.jsx create mode 100644 src/components/icons/FireIcon.jsx diff --git a/src/app/betting-history/page.jsx b/src/app/betting-history/page.jsx index f7e0ed18..3c2c7eaf 100644 --- a/src/app/betting-history/page.jsx +++ b/src/app/betting-history/page.jsx @@ -1,6 +1,6 @@ 'use client'; -import { useState, useEffect } from 'react'; +import { useState } from 'react'; import useSWR from 'swr'; import { useUser } from '@clerk/nextjs'; import Link from 'next/link'; diff --git a/src/app/dashboard/page.jsx b/src/app/dashboard/page.jsx index df2f066e..c4a23bd1 100644 --- a/src/app/dashboard/page.jsx +++ b/src/app/dashboard/page.jsx @@ -3,8 +3,8 @@ import { useEffect, useRef } from 'react'; import Link from 'next/link'; import { useRouter } from 'next/navigation'; -import { useUserContext } from '../contexts/UserContext'; -import { useUserStats } from '../hooks/useAPI'; +import { useUserContext } from '@/app/contexts/UserContext'; +import { useUserStats } from '@/app/hooks/useAPI'; import { BalanceDisplay, DottedDivider, diff --git a/src/app/games/page.jsx b/src/app/games/page.jsx index 8111dee1..5e44c034 100644 --- a/src/app/games/page.jsx +++ b/src/app/games/page.jsx @@ -12,7 +12,7 @@ import { } from '@/components/experimental'; import ErrorBoundary from '@components/ErrorBoundary'; import { GameCardSkeleton } from '@/components/ui/LoadingSkeleton'; -import { useUserContext } from '../contexts/UserContext'; +import { useUserContext } from '@/app/contexts/UserContext'; // SWR fetcher function const fetcher = (url) => fetch(url).then(res => { diff --git a/src/app/hooks/useKeyboardNav.js b/src/app/hooks/useKeyboardNav.js new file mode 100644 index 00000000..18987364 --- /dev/null +++ b/src/app/hooks/useKeyboardNav.js @@ -0,0 +1,37 @@ +'use client' +import { useEffect } from "react"; +import { useRouter } from "next/navigation"; +import { globalShortcuts, sidebarConfig } from "@/components/experimental"; + +export default function useKeyboardNav() { + const router = useRouter(); + useEffect(() => { + //create lookup map once O(1) access + const navMap = new Map( + sidebarConfig.map(item => [item.key, item]) + ) + + globalShortcuts.forEach(shortcut => { + navMap.set(shortcut.key, shortcut); + }); + + const handleKeyDown = (event) => { + //check if the user is typing in an input field + const targetTag = event.target.tagName; + const isTyping = targetTag === 'INPUT' || + targetTag === 'TEXTAREA' || + event.target.contentEditable === 'true'; + if (isTyping) return; //dont nav if typing + //make the key uppercase + const key = event.key.toUpperCase(); + //constant look up time. + const navItem = navMap.get(key); + if (navItem) { + event.preventDefault(); + router.push(navItem.href); + } + }; + window.addEventListener('keydown', handleKeyDown) + return () => window.removeEventListener('keydown', handleKeyDown); + }, [router]); +} diff --git a/src/app/layout.jsx b/src/app/layout.jsx index 612ddb7d..21a1200a 100644 --- a/src/app/layout.jsx +++ b/src/app/layout.jsx @@ -4,7 +4,6 @@ import './globals.css'; import { ClerkProvider } from '@clerk/nextjs'; import { UserProvider } from './contexts/UserContext'; import { LoadingProvider } from './contexts/LoadingContext'; -import { DarkModeProvider } from './contexts/DarkModeContext'; import { AccessibilityProvider } from './contexts/AccessibilityContext'; import MinimalLayout from './MinimalLayout'; @@ -12,40 +11,20 @@ export default function RootLayout({ children }) { return ( - {/* Inline script to prevent dark mode flash of unstyled content */} -