diff --git a/src/app/__tests__/auth-pages.test.jsx b/src/app/__tests__/auth-pages.test.jsx
index eb8db863..62235bd9 100644
--- a/src/app/__tests__/auth-pages.test.jsx
+++ b/src/app/__tests__/auth-pages.test.jsx
@@ -23,6 +23,8 @@ jest.mock('@clerk/nextjs', () => ({
mockSignIn(props);
return
SignIn Component
;
},
+ ClerkLoading: ({ children }) => {children}
,
+ ClerkLoaded: ({ children }) => {children}
,
}));
// Mock next/navigation
diff --git a/src/app/contexts/LoadingContext.jsx b/src/app/contexts/LoadingContext.jsx
index 7d8da08b..dbaaac58 100644
--- a/src/app/contexts/LoadingContext.jsx
+++ b/src/app/contexts/LoadingContext.jsx
@@ -66,16 +66,14 @@ export function LoadingProvider({ children }) {
// Clear loading state directly on route changes
useEffect(() => {
// When pathname changes, clear any loading state immediately
- if (isLoading) {
- setIsLoading(false);
- setMessage('');
- loadingStartTimeRef.current = null;
- if (clearTimeoutRef.current) {
- clearTimeout(clearTimeoutRef.current);
- clearTimeoutRef.current = null;
- }
+ setIsLoading(false);
+ setMessage('');
+ loadingStartTimeRef.current = null;
+ if (clearTimeoutRef.current) {
+ clearTimeout(clearTimeoutRef.current);
+ clearTimeoutRef.current = null;
}
- }, [pathname, isLoading]);
+ }, [pathname]);
// Cleanup on unmount
useEffect(() => {
diff --git a/src/app/hooks/__tests__/useBetValidation.test.js b/src/app/hooks/__tests__/useBetValidation.test.js
index 8a8bd7bf..89f9c89a 100644
--- a/src/app/hooks/__tests__/useBetValidation.test.js
+++ b/src/app/hooks/__tests__/useBetValidation.test.js
@@ -7,7 +7,7 @@ describe('useBetValidation', () => {
const createScheduledGame = (overrides = {}) => ({
_id: 'game-123',
status: GAME_STATUSES.SCHEDULED,
- startTime: new Date(Date.now() + 86400000).toISOString(), // Tomorrow
+ gameDate: new Date(Date.now() + 86400000).toISOString(), // Tomorrow
homeTeam: 'Washington Huskies',
awayTeam: 'Oregon Ducks',
homeOdds: 1.8,
@@ -68,19 +68,6 @@ describe('useBetValidation', () => {
it('should return canBet: false when game has already started', () => {
const { result } = renderHook(() => useBetValidation());
const game = createScheduledGame({
- startTime: new Date(Date.now() - 3600000).toISOString(), // 1 hour ago
- });
-
- const check = result.current.canBet(game);
-
- expect(check.canBet).toBe(false);
- expect(check.reason).toBe(BETTING_ERRORS.GAME_ALREADY_STARTED);
- });
-
- it('should use gameDate if startTime is not available', () => {
- const { result } = renderHook(() => useBetValidation());
- const game = createScheduledGame({
- startTime: undefined,
gameDate: new Date(Date.now() - 3600000).toISOString(), // 1 hour ago
});
@@ -90,10 +77,9 @@ describe('useBetValidation', () => {
expect(check.reason).toBe(BETTING_ERRORS.GAME_ALREADY_STARTED);
});
- it('should return canBet: true when startTime and gameDate are both undefined', () => {
+ it('should return canBet: true when gameDate is undefined', () => {
const { result } = renderHook(() => useBetValidation());
const game = createScheduledGame({
- startTime: undefined,
gameDate: undefined,
});
@@ -104,10 +90,10 @@ describe('useBetValidation', () => {
expect(check.reason).toBeNull();
});
- it('should return canBet: true when startTime is invalid date string', () => {
+ it('should return canBet: true when gameDate is invalid date string', () => {
const { result } = renderHook(() => useBetValidation());
const game = createScheduledGame({
- startTime: 'invalid-date',
+ gameDate: 'invalid-date',
});
const check = result.current.canBet(game);
@@ -263,7 +249,7 @@ describe('useBetValidation', () => {
const validation = result.current.validate({
...defaultParams(),
game: createScheduledGame({
- startTime: new Date(Date.now() - 3600000).toISOString(),
+ gameDate: new Date(Date.now() - 3600000).toISOString(),
}),
});
diff --git a/src/app/hooks/useBetValidation.js b/src/app/hooks/useBetValidation.js
index ab01ddc4..84d32556 100644
--- a/src/app/hooks/useBetValidation.js
+++ b/src/app/hooks/useBetValidation.js
@@ -28,8 +28,8 @@ export function useBetValidation() {
/**
* Check if a game is available for betting.
* Use this for proactive UI disabling (disable bet button before user attempts).
- *
- * @param {Object} game - Game object with status and startTime
+ *
+ * @param {Object} game - Game object with status and gameDate
* @returns {{ canBet: boolean, reason: string | null }}
*/
const canBet = useCallback((game) => {
@@ -47,20 +47,17 @@ export function useBetValidation() {
return { canBet: false, reason: BETTING_ERRORS.GAME_NOT_SCHEDULED };
}
- // Check if game has already started (startTime or gameDate in the past)
+ // Check if game has already started (gameDate in the past)
// Only perform check if we have a valid time value
- const gameTimeValue = game.startTime || game.gameDate;
- if (gameTimeValue) {
- const gameTime = new Date(gameTimeValue);
+ if (game.gameDate) {
+ const gameTime = new Date(game.gameDate);
const now = new Date();
if (DEBUG) {
const isValidDate = !isNaN(gameTime.getTime());
console.log('[useBetValidation] canBet: Date comparison debug', {
gameId: game._id,
- startTime: game.startTime,
gameDate: game.gameDate,
- usedValue: gameTimeValue,
gameTimeParsed: isValidDate ? gameTime.toISOString() : 'Invalid Date',
now: now.toISOString(),
isValidDate,
@@ -78,7 +75,7 @@ export function useBetValidation() {
return { canBet: false, reason: BETTING_ERRORS.GAME_ALREADY_STARTED };
}
} else {
- if (DEBUG) console.log('[useBetValidation] canBet: no startTime or gameDate available, allowing bet');
+ if (DEBUG) console.log('[useBetValidation] canBet: no gameDate available, allowing bet');
}
if (DEBUG) console.log('[useBetValidation] canBet: all checks passed, betting allowed');
diff --git a/src/app/login/[[...sign-in]]/page.js b/src/app/login/[[...sign-in]]/page.js
index 8ebde92a..53e60b53 100644
--- a/src/app/login/[[...sign-in]]/page.js
+++ b/src/app/login/[[...sign-in]]/page.js
@@ -1,91 +1,65 @@
'use client';
-import { SignIn } from '@clerk/nextjs';
+import { SignIn, ClerkLoading, ClerkLoaded } from '@clerk/nextjs';
import { useSearchParams } from 'next/navigation';
import Link from 'next/link';
import { Suspense } from 'react';
+import { HuskyBidsLoader } from '@/components/experimental';
+import { minimalClerkAppearance } from '@/shared/utils/clerk-appearance';
-/**
- * Login Page Content
- * Extracted to allow Suspense wrapping
- */
-function LoginContent() {
- const searchParams = useSearchParams();
- const redirectUrl = searchParams.get('redirect') || '/dashboard';
+ function LoginContent() {
+ const searchParams = useSearchParams();
+ const redirectUrl = searchParams.get('redirect') || '/dashboard';
- return (
-
- {/* Minimal header */}
-
+ return (
+
+
+
+
- {/* Clerk SignIn Component with minimal styling */}
-
-
-
+
+ {/* Header */}
+
+
+ {/* Clerk SignIn Component */}
+
+
+
- {/* Footer link */}
-
- No account?{' '}
-
- Sign up
-
-
-
- );
-}
+ {/* Footer link */}
+
+ No account?{' '}
+
+ Sign up
+
+
-/**
- * Login Page
- * Uses Clerk's pre-built SignIn component for authentication
- * Clerk handles redirects natively via afterSignInUrl
- */
-export default function LoginPage() {
- return (
-
- Loading...
}>
-
-
-
- );
-}
+
+
+ );
+ }
+
+ export default function LoginPage() {
+ return (
+
+ Loading...
}>
+
+
+
+ );
+ }
\ No newline at end of file
diff --git a/src/app/sign-up/[[...sign-up]]/page.jsx b/src/app/sign-up/[[...sign-up]]/page.jsx
index 2283c215..93294bbc 100644
--- a/src/app/sign-up/[[...sign-up]]/page.jsx
+++ b/src/app/sign-up/[[...sign-up]]/page.jsx
@@ -1,8 +1,10 @@
'use client';
-import { SignUp } from '@clerk/nextjs';
+import { SignUp, ClerkLoading, ClerkLoaded } from '@clerk/nextjs';
import { useSearchParams } from 'next/navigation';
import { Suspense } from 'react';
+import { HuskyBidsLoader } from '@/components/experimental';
+import { minimalClerkAppearance } from '@/shared/utils/clerk-appearance';
/**
* Sign Up Page Content
@@ -14,54 +16,36 @@ function SignUpContent() {
return (
- {/* Minimal header */}
-
-
- HuskyBids
-
-
- Create your account
-
-
-
- {/* Clerk SignUp Component with minimal styling */}
-
-
+
-
+
+
+ {/* Content shown when Clerk is ready */}
+
+ {/* Minimal header */}
+
+
+ HuskyBids
+
+
+ Create your account
+
+
+
+ {/* Clerk SignUp Component with minimal styling */}
+
+
+
+
);
}
diff --git a/src/app/sign-up/__tests__/redirect.test.jsx b/src/app/sign-up/__tests__/redirect.test.jsx
index f19afdbb..6bb83e20 100644
--- a/src/app/sign-up/__tests__/redirect.test.jsx
+++ b/src/app/sign-up/__tests__/redirect.test.jsx
@@ -8,6 +8,8 @@ jest.mock('@clerk/nextjs', () => ({
SignUp: (props) => {
return SignUp Component
;
},
+ ClerkLoading: ({ children }) => {children}
,
+ ClerkLoaded: ({ children }) => {children}
,
}));
// Mock useSearchParams
diff --git a/src/app/tasks/page.jsx b/src/app/tasks/page.jsx
index e065a828..057532e8 100644
--- a/src/app/tasks/page.jsx
+++ b/src/app/tasks/page.jsx
@@ -39,6 +39,7 @@ export default function TasksPage() {
revalidateOnFocus: false,
});
+ // Memoize to prevent useEffect from running on every render due to new array reference
const tasks = useMemo(() => data?.tasks || [], [data?.tasks]);
const summary = data?.summary || null;
const streak = data?.streak || null;
diff --git a/src/components/experimental/ui/HuskyBidsLoader.jsx b/src/components/experimental/ui/HuskyBidsLoader.jsx
index 84c36639..a521afd8 100644
--- a/src/components/experimental/ui/HuskyBidsLoader.jsx
+++ b/src/components/experimental/ui/HuskyBidsLoader.jsx
@@ -4,7 +4,7 @@
*
* @example
*
- *
+ *
*/
'use client';
@@ -13,6 +13,7 @@ import { cn } from '@/shared/utils';
export default function HuskyBidsLoader({
size = 'md',
centered = false,
+ subtitle = null,
className
}) {
const text = 'HuskyBids';
@@ -33,19 +34,32 @@ export default function HuskyBidsLoader({
className
)}
>
-
- {letters.map((letter, index) => (
-
- {letter}
-
- ))}
-
+ {/* Vertical flex container to stack HuskyBids + subtitle */}
+
+
+ {/* Animated "HuskyBids" text */}
+
+ {letters.map((letter, index) => (
+
+ {letter}
+
+ ))}
+
+
+ {/* Conditionally render subtitle if provided */}
+ {subtitle && (
+
+ {subtitle}
+
+ )}
+
+
);
}
diff --git a/src/server/services/BettingService.js b/src/server/services/BettingService.js
index 19d9ae27..6335620b 100644
--- a/src/server/services/BettingService.js
+++ b/src/server/services/BettingService.js
@@ -62,11 +62,11 @@ class BettingService {
// Validation: Check if game has started
// Use a small buffer (e.g., 5 seconds) to account for slight server time differences
const now = new Date();
- const gameTime = new Date(game.gameDate || game.startTime); // Handle both field names
+ const gameTime = new Date(game.gameDate);
const BUFFER_MS = 5000;
const cutoffTime = new Date(now.getTime() - BUFFER_MS);
- if (gameTime <= cutoffTime) {
+ if (isNaN(gameTime.getTime()) || gameTime <= cutoffTime) {
throw new Error(`Betting is closed - game has already started (gameDate: ${gameTime.toISOString()}, now: ${now.toISOString()})`);
}
diff --git a/src/server/services/__tests__/BettingService.test.js b/src/server/services/__tests__/BettingService.test.js
index 45cb2e77..4975d9cd 100644
--- a/src/server/services/__tests__/BettingService.test.js
+++ b/src/server/services/__tests__/BettingService.test.js
@@ -102,6 +102,7 @@ describe('BettingService', () => {
_id: 'game123',
status: 'scheduled',
startTime: new Date(Date.now() + 86400000), // Tomorrow
+ gameDate: new Date(Date.now() + 86400000), // Synced with startTime
homeBets: 0,
awayBets: 0,
homeBiscuitsWagered: 0,
diff --git a/src/shared/utils/clerk-appearance.js b/src/shared/utils/clerk-appearance.js
index 296556cc..24d58291 100644
--- a/src/shared/utils/clerk-appearance.js
+++ b/src/shared/utils/clerk-appearance.js
@@ -12,15 +12,15 @@
export const minimalClerkAppearance = {
variables: {
- colorPrimary: '#a1a1aa', // zinc-400
+ colorPrimary: '#71717a', // zinc-500 (matches login/signup inline config)
colorText: '#d4d4d8', // zinc-300
colorTextSecondary: '#71717a', // zinc-500
colorBackground: '#18181b', // zinc-900
colorInputBackground: '#27272a', // zinc-800
colorInputText: '#d4d4d8', // zinc-300
- fontFamily: 'ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace',
- fontSize: '0.875rem', // 14px
- borderRadius: '0', // No rounded corners
+ fontFamily: 'ui-monospace, monospace',
+ fontSize: '14px',
+ borderRadius: '0px',
},
elements: {
rootBox: 'mx-auto',
@@ -99,6 +99,6 @@ export const minimalClerkAppearance = {
},
layout: {
socialButtonsPlacement: 'bottom',
- socialButtonsVariant: 'blockButton',
+ socialButtonsVariant: 'iconButton', // Icon buttons (not block) to match original
}
};