Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions src/app/__tests__/auth-pages.test.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@ jest.mock('@clerk/nextjs', () => ({
mockSignIn(props);
return <div data-testid="clerk-signin">SignIn Component</div>;
},
ClerkLoading: ({ children }) => <div data-testid="clerk-loading">{children}</div>,
ClerkLoaded: ({ children }) => <div data-testid="clerk-loaded">{children}</div>,
}));

// Mock next/navigation
Expand Down
16 changes: 7 additions & 9 deletions src/app/contexts/LoadingContext.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -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(() => {
Expand Down
24 changes: 5 additions & 19 deletions src/app/hooks/__tests__/useBetValidation.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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
});

Expand All @@ -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,
});

Expand All @@ -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);
Expand Down Expand Up @@ -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(),
}),
});

Expand Down
15 changes: 6 additions & 9 deletions src/app/hooks/useBetValidation.js
Original file line number Diff line number Diff line change
Expand Up @@ -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) => {
Expand All @@ -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,
Expand All @@ -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');
Expand Down
136 changes: 55 additions & 81 deletions src/app/login/[[...sign-in]]/page.js
Original file line number Diff line number Diff line change
@@ -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 (
<div className="w-full max-w-md space-y-8">
{/* Minimal header */}
<header className="text-center mb-8">
<h1 className="text-sm tracking-[0.3em] uppercase text-zinc-400 mb-2">
HuskyBids
</h1>
<p className="text-xs text-zinc-600">
Log in to your account
</p>
</header>
return (
<div className="w-full max-w-md space-y-8">
<ClerkLoading>
<HuskyBidsLoader
centered
subtitle="Loading your account..."
/>
</ClerkLoading>

{/* Clerk SignIn Component with minimal styling */}
<div>
<SignIn
appearance={{
variables: {
colorPrimary: '#71717a',
colorText: '#d4d4d8',
colorTextSecondary: '#71717a',
colorBackground: '#18181b',
colorInputBackground: '#27272a',
colorInputText: '#d4d4d8',
fontFamily: 'ui-monospace, monospace',
fontSize: '14px',
borderRadius: '0px',
},
elements: {
rootBox: 'mx-auto',
card: 'bg-zinc-900 border border-dotted border-zinc-800 shadow-none',
headerTitle: 'text-zinc-300 text-sm font-mono',
headerSubtitle: 'text-zinc-600 text-xs font-mono',
formFieldInput: 'bg-zinc-800 border border-dotted border-zinc-700 text-zinc-300 font-mono text-sm focus:border-zinc-500 rounded-none',
formFieldLabel: 'text-zinc-500 text-xs font-mono uppercase tracking-wider',
formButtonPrimary: 'bg-zinc-800 hover:bg-zinc-700 text-zinc-300 border border-dotted border-zinc-700 font-mono text-sm shadow-none rounded-none',
footerActionLink: 'text-zinc-500 hover:text-zinc-400 font-mono text-xs',
socialButtonsBlockButton: 'border border-dotted border-zinc-700 bg-zinc-800 hover:bg-zinc-700 text-zinc-400 font-mono text-sm rounded-none shadow-none',
dividerLine: 'bg-zinc-800',
dividerText: 'text-zinc-600 font-mono text-xs',
},
layout: {
socialButtonsPlacement: 'bottom',
socialButtonsVariant: 'iconButton',
}
}}
path="/login"
afterSignInUrl={redirectUrl}
signUpUrl="/sign-up"
/>
</div>
<ClerkLoaded>
{/* Header */}
<header className="text-center mb-8">
<h1 className="text-sm tracking-[0.3em] uppercase text-zinc-400 mb-2">
HuskyBids
</h1>
<p className="text-xs text-zinc-600">
Log in to your account
</p>
</header>

{/* Clerk SignIn Component */}
<div>
<SignIn
appearance={minimalClerkAppearance}
path="/login"
afterSignInUrl={redirectUrl}
signUpUrl="/sign-up"
/>
</div>

{/* Footer link */}
<p className="text-center text-xs text-zinc-600 font-mono mt-6">
No account?{' '}
<Link href="/sign-up" className="text-zinc-500 hover:text-zinc-400 underline">
Sign up
</Link>
</p>
</div>
);
}
{/* Footer link */}
<p className="text-center text-xs text-zinc-600 font-mono mt-6">
No account?{' '}
<Link href="/sign-up" className="text-zinc-500 hover:text-zinc-400 underline">
Sign up
</Link>
</p>

/**
* Login Page
* Uses Clerk's pre-built SignIn component for authentication
* Clerk handles redirects natively via afterSignInUrl
*/
export default function LoginPage() {
return (
<div className="min-h-screen bg-zinc-950 flex items-center justify-center px-4 py-12 font-mono">
<Suspense fallback={<div className="text-zinc-500 text-sm">Loading...</div>}>
<LoginContent />
</Suspense>
</div>
);
}
</ClerkLoaded>
</div>
);
}
Comment on lines +10 to +55

Copilot AI Dec 25, 2025

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Inconsistent indentation detected. The LoginContent function has an extra indentation level (2 spaces) compared to the rest of the file. The function declaration should start at column 0 like the export default function LoginPage() below it. This appears to be a formatting error introduced during refactoring.

Copilot uses AI. Check for mistakes.

export default function LoginPage() {
return (
<div className="min-h-screen bg-zinc-950 flex items-center justify-center px-4 py-12 font-mono">
<Suspense fallback={<div className="text-zinc-500 text-sm">Loading...</div>}>
<LoginContent />
</Suspense>
</div>
);
}
80 changes: 32 additions & 48 deletions src/app/sign-up/[[...sign-up]]/page.jsx
Original file line number Diff line number Diff line change
@@ -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
Expand All @@ -14,54 +16,36 @@ function SignUpContent() {

return (
<div className="w-full max-w-md space-y-8">
{/* Minimal header */}
<header className="text-center mb-8">
<h1 className="text-sm tracking-[0.3em] uppercase text-zinc-400 mb-2">
HuskyBids
</h1>
<p className="text-xs text-zinc-600">
Create your account
</p>
</header>

{/* Clerk SignUp Component with minimal styling */}
<div>
<SignUp
appearance={{
variables: {
colorPrimary: '#71717a',
colorText: '#d4d4d8',
colorTextSecondary: '#71717a',
colorBackground: '#18181b',
colorInputBackground: '#27272a',
colorInputText: '#d4d4d8',
fontFamily: 'ui-monospace, monospace',
fontSize: '14px',
borderRadius: '0px',
},
elements: {
rootBox: 'mx-auto',
card: 'bg-zinc-900 border border-dotted border-zinc-800 shadow-none',
headerTitle: 'text-zinc-300 text-sm font-mono',
headerSubtitle: 'text-zinc-600 text-xs font-mono',
formFieldInput: 'bg-zinc-800 border border-dotted border-zinc-700 text-zinc-300 font-mono text-sm focus:border-zinc-500 rounded-none',
formFieldLabel: 'text-zinc-500 text-xs font-mono uppercase tracking-wider',
formButtonPrimary: 'bg-zinc-800 hover:bg-zinc-700 text-zinc-300 border border-dotted border-zinc-700 font-mono text-sm shadow-none rounded-none',
footerActionLink: 'text-zinc-500 hover:text-zinc-400 font-mono text-xs',
socialButtonsBlockButton: 'border border-dotted border-zinc-700 bg-zinc-800 hover:bg-zinc-700 text-zinc-400 font-mono text-sm rounded-none shadow-none',
dividerLine: 'bg-zinc-800',
dividerText: 'text-zinc-600 font-mono text-xs',
},
layout: {
socialButtonsPlacement: 'bottom',
socialButtonsVariant: 'iconButton',
}
}}
path="/sign-up"
signInUrl="/login"
afterSignUpUrl={redirectUrl}
{/* Loading state while Clerk initializes */}
<ClerkLoading>
<HuskyBidsLoader
centered
subtitle="Creating your account..."
/>
</div>
</ClerkLoading>

{/* Content shown when Clerk is ready */}
<ClerkLoaded>
{/* Minimal header */}
<header className="text-center mb-8">
<h1 className="text-sm tracking-[0.3em] uppercase text-zinc-400 mb-2">
HuskyBids
</h1>
<p className="text-xs text-zinc-600">
Create your account
</p>
</header>

{/* Clerk SignUp Component with minimal styling */}
<div>
<SignUp
appearance={minimalClerkAppearance}
path="/sign-up"
signInUrl="/login"
afterSignUpUrl={redirectUrl}
/>
</div>
</ClerkLoaded>
</div>
);
}
Expand Down
2 changes: 2 additions & 0 deletions src/app/sign-up/__tests__/redirect.test.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ jest.mock('@clerk/nextjs', () => ({
SignUp: (props) => {
return <div data-testid="clerk-signup" data-props={JSON.stringify(props)}>SignUp Component</div>;
},
ClerkLoading: ({ children }) => <div data-testid="clerk-loading">{children}</div>,
ClerkLoaded: ({ children }) => <div data-testid="clerk-loaded">{children}</div>,
}));

// Mock useSearchParams
Expand Down
Loading