fix issues
This commit is contained in:
@@ -19,7 +19,7 @@ const DEFAULT_CONFIG_PATH = 'apps/api/src/config/features.config.ts';
|
||||
* Output: { 'sponsors.portal': 'enabled' }
|
||||
*/
|
||||
function flattenFeatures(
|
||||
config: Record<string, any>,
|
||||
config: Record<string, unknown>,
|
||||
prefix: string = ''
|
||||
): FlattenedFeatures {
|
||||
const flattened: FlattenedFeatures = {};
|
||||
@@ -29,7 +29,7 @@ function flattenFeatures(
|
||||
|
||||
if (typeof value === 'object' && value !== null && !Array.isArray(value)) {
|
||||
// Recursively flatten nested objects
|
||||
Object.assign(flattened, flattenFeatures(value, fullKey));
|
||||
Object.assign(flattened, flattenFeatures(value as Record<string, unknown>, fullKey));
|
||||
} else if (isFeatureState(value)) {
|
||||
// Assign feature state
|
||||
flattened[fullKey] = value;
|
||||
|
||||
@@ -6,6 +6,7 @@ import { AuthProvider } from '@/lib/auth/AuthContext';
|
||||
import { FeatureFlagService } from '@/lib/feature/FeatureFlagService';
|
||||
import { FeatureFlagProvider } from '@/lib/feature/FeatureFlagProvider';
|
||||
import { ContainerProvider } from '@/lib/di/providers/ContainerProvider';
|
||||
import { QueryClientProvider } from '@/lib/providers/QueryClientProvider';
|
||||
import { initializeGlobalErrorHandling } from '@/lib/infrastructure/GlobalErrorHandler';
|
||||
import { initializeApiLogger } from '@/lib/infrastructure/ApiRequestLogger';
|
||||
import { Metadata, Viewport } from 'next';
|
||||
@@ -81,39 +82,41 @@ export default async function RootLayout({
|
||||
</head>
|
||||
<body className="antialiased overflow-x-hidden">
|
||||
<ContainerProvider>
|
||||
<AuthProvider>
|
||||
<FeatureFlagProvider flags={enabledFlags}>
|
||||
<NotificationProvider>
|
||||
<NotificationIntegration />
|
||||
<EnhancedErrorBoundary enableDevOverlay={process.env.NODE_ENV === 'development'}>
|
||||
<header className="fixed top-0 left-0 right-0 z-50 bg-deep-graphite/80 backdrop-blur-sm border-b border-white/5">
|
||||
<div className="max-w-7xl mx-auto px-6 py-4">
|
||||
<div className="flex items-center justify-between">
|
||||
<div className="flex items-center space-x-3">
|
||||
<Link href="/" className="inline-flex items-center">
|
||||
<Image
|
||||
src="/images/logos/wordmark-rectangle-dark.svg"
|
||||
alt="GridPilot"
|
||||
width={160}
|
||||
height={30}
|
||||
className="h-6 w-auto md:h-8"
|
||||
priority
|
||||
/>
|
||||
</Link>
|
||||
<p className="hidden sm:block text-sm text-gray-400 font-light">
|
||||
Making league racing less chaotic
|
||||
</p>
|
||||
<QueryClientProvider>
|
||||
<AuthProvider>
|
||||
<FeatureFlagProvider flags={enabledFlags}>
|
||||
<NotificationProvider>
|
||||
<NotificationIntegration />
|
||||
<EnhancedErrorBoundary enableDevOverlay={process.env.NODE_ENV === 'development'}>
|
||||
<header className="fixed top-0 left-0 right-0 z-50 bg-deep-graphite/80 backdrop-blur-sm border-b border-white/5">
|
||||
<div className="max-w-7xl mx-auto px-6 py-4">
|
||||
<div className="flex items-center justify-between">
|
||||
<div className="flex items-center space-x-3">
|
||||
<Link href="/" className="inline-flex items-center">
|
||||
<Image
|
||||
src="/images/logos/wordmark-rectangle-dark.svg"
|
||||
alt="GridPilot"
|
||||
width={160}
|
||||
height={30}
|
||||
className="h-6 w-auto md:h-8"
|
||||
priority
|
||||
/>
|
||||
</Link>
|
||||
<p className="hidden sm:block text-sm text-gray-400 font-light">
|
||||
Making league racing less chaotic
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
<div className="pt-16">{children}</div>
|
||||
{/* Development Tools */}
|
||||
{process.env.NODE_ENV === 'development' && <DevToolbar />}
|
||||
</EnhancedErrorBoundary>
|
||||
</NotificationProvider>
|
||||
</FeatureFlagProvider>
|
||||
</AuthProvider>
|
||||
</header>
|
||||
<div className="pt-16">{children}</div>
|
||||
{/* Development Tools */}
|
||||
{process.env.NODE_ENV === 'development' && <DevToolbar />}
|
||||
</EnhancedErrorBoundary>
|
||||
</NotificationProvider>
|
||||
</FeatureFlagProvider>
|
||||
</AuthProvider>
|
||||
</QueryClientProvider>
|
||||
</ContainerProvider>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
49
apps/website/app/leaderboards/LeaderboardsPageWrapper.tsx
Normal file
49
apps/website/app/leaderboards/LeaderboardsPageWrapper.tsx
Normal file
@@ -0,0 +1,49 @@
|
||||
'use client';
|
||||
|
||||
import { useRouter } from 'next/navigation';
|
||||
import LeaderboardsTemplate from '@/templates/LeaderboardsTemplate';
|
||||
import type { DriverLeaderboardViewModel } from '@/lib/view-models/DriverLeaderboardViewModel';
|
||||
import type { TeamSummaryViewModel } from '@/lib/view-models/TeamSummaryViewModel';
|
||||
|
||||
interface LeaderboardsPageData {
|
||||
drivers: DriverLeaderboardViewModel | null;
|
||||
teams: TeamSummaryViewModel[] | null;
|
||||
}
|
||||
|
||||
export function LeaderboardsPageWrapper({ data }: { data: LeaderboardsPageData | null }) {
|
||||
const router = useRouter();
|
||||
|
||||
if (!data || (!data.drivers && !data.teams)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const drivers = data.drivers?.drivers || [];
|
||||
const teams = data.teams || [];
|
||||
|
||||
const handleDriverClick = (driverId: string) => {
|
||||
router.push(`/drivers/${driverId}`);
|
||||
};
|
||||
|
||||
const handleTeamClick = (teamId: string) => {
|
||||
router.push(`/teams/${teamId}`);
|
||||
};
|
||||
|
||||
const handleNavigateToDrivers = () => {
|
||||
router.push('/leaderboards/drivers');
|
||||
};
|
||||
|
||||
const handleNavigateToTeams = () => {
|
||||
router.push('/teams/leaderboard');
|
||||
};
|
||||
|
||||
return (
|
||||
<LeaderboardsTemplate
|
||||
drivers={drivers}
|
||||
teams={teams}
|
||||
onDriverClick={handleDriverClick}
|
||||
onTeamClick={handleTeamClick}
|
||||
onNavigateToDrivers={handleNavigateToDrivers}
|
||||
onNavigateToTeams={handleNavigateToTeams}
|
||||
/>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
'use client';
|
||||
|
||||
import { useRouter } from 'next/navigation';
|
||||
import DriverRankingsTemplate from '@/templates/DriverRankingsTemplate';
|
||||
import { useState } from 'react';
|
||||
import type { DriverLeaderboardViewModel } from '@/lib/view-models/DriverLeaderboardViewModel';
|
||||
|
||||
type SkillLevel = 'pro' | 'advanced' | 'intermediate' | 'beginner';
|
||||
type SortBy = 'rank' | 'rating' | 'wins' | 'podiums' | 'winRate';
|
||||
|
||||
export function DriverRankingsPageWrapper({ data }: { data: DriverLeaderboardViewModel | null }) {
|
||||
const router = useRouter();
|
||||
|
||||
// Client-side state for filtering and sorting
|
||||
const [searchQuery, setSearchQuery] = useState('');
|
||||
const [selectedSkill, setSelectedSkill] = useState<'all' | SkillLevel>('all');
|
||||
const [sortBy, setSortBy] = useState<SortBy>('rank');
|
||||
const [showFilters, setShowFilters] = useState(false);
|
||||
|
||||
if (!data || !data.drivers) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const handleDriverClick = (driverId: string) => {
|
||||
if (driverId.startsWith('demo-')) return;
|
||||
router.push(`/drivers/${driverId}`);
|
||||
};
|
||||
|
||||
const handleBackToLeaderboards = () => {
|
||||
router.push('/leaderboards');
|
||||
};
|
||||
|
||||
return (
|
||||
<DriverRankingsTemplate
|
||||
drivers={data.drivers}
|
||||
searchQuery={searchQuery}
|
||||
selectedSkill={selectedSkill}
|
||||
sortBy={sortBy}
|
||||
showFilters={showFilters}
|
||||
onSearchChange={setSearchQuery}
|
||||
onSkillChange={setSelectedSkill}
|
||||
onSortChange={setSortBy}
|
||||
onToggleFilters={() => setShowFilters(!showFilters)}
|
||||
onDriverClick={handleDriverClick}
|
||||
onBackToLeaderboards={handleBackToLeaderboards}
|
||||
/>
|
||||
);
|
||||
}
|
||||
@@ -1,62 +1,11 @@
|
||||
import { PageWrapper } from '@/components/shared/state/PageWrapper';
|
||||
import DriverRankingsTemplate from '@/templates/DriverRankingsTemplate';
|
||||
import { PageDataFetcher } from '@/lib/page/PageDataFetcher';
|
||||
import { DRIVER_SERVICE_TOKEN } from '@/lib/di/tokens';
|
||||
import { DriverService } from '@/lib/services/drivers/DriverService';
|
||||
import { Users } from 'lucide-react';
|
||||
import { redirect , useRouter } from 'next/navigation';
|
||||
import { redirect } from 'next/navigation';
|
||||
import type { DriverLeaderboardViewModel } from '@/lib/view-models/DriverLeaderboardViewModel';
|
||||
import { useState } from 'react';
|
||||
|
||||
// ============================================================================
|
||||
// TYPES
|
||||
// ============================================================================
|
||||
|
||||
type SkillLevel = 'pro' | 'advanced' | 'intermediate' | 'beginner';
|
||||
type SortBy = 'rank' | 'rating' | 'wins' | 'podiums' | 'winRate';
|
||||
|
||||
// ============================================================================
|
||||
// WRAPPER COMPONENT (Client-side state management)
|
||||
// ============================================================================
|
||||
|
||||
function DriverRankingsPageWrapper({ data }: { data: DriverLeaderboardViewModel | null }) {
|
||||
const router = useRouter();
|
||||
|
||||
// Client-side state for filtering and sorting
|
||||
const [searchQuery, setSearchQuery] = useState('');
|
||||
const [selectedSkill, setSelectedSkill] = useState<'all' | SkillLevel>('all');
|
||||
const [sortBy, setSortBy] = useState<SortBy>('rank');
|
||||
const [showFilters, setShowFilters] = useState(false);
|
||||
|
||||
if (!data || !data.drivers) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const handleDriverClick = (driverId: string) => {
|
||||
if (driverId.startsWith('demo-')) return;
|
||||
router.push(`/drivers/${driverId}`);
|
||||
};
|
||||
|
||||
const handleBackToLeaderboards = () => {
|
||||
router.push('/leaderboards');
|
||||
};
|
||||
|
||||
return (
|
||||
<DriverRankingsTemplate
|
||||
drivers={data.drivers}
|
||||
searchQuery={searchQuery}
|
||||
selectedSkill={selectedSkill}
|
||||
sortBy={sortBy}
|
||||
showFilters={showFilters}
|
||||
onSearchChange={setSearchQuery}
|
||||
onSkillChange={setSelectedSkill}
|
||||
onSortChange={setSortBy}
|
||||
onToggleFilters={() => setShowFilters(!showFilters)}
|
||||
onDriverClick={handleDriverClick}
|
||||
onBackToLeaderboards={handleBackToLeaderboards}
|
||||
/>
|
||||
);
|
||||
}
|
||||
import { DriverRankingsPageWrapper } from './DriverRankingsPageWrapper';
|
||||
|
||||
// ============================================================================
|
||||
// MAIN PAGE COMPONENT
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
import { PageWrapper } from '@/components/shared/state/PageWrapper';
|
||||
import LeaderboardsTemplate from '@/templates/LeaderboardsTemplate';
|
||||
import { PageDataFetcher } from '@/lib/page/PageDataFetcher';
|
||||
import { DRIVER_SERVICE_TOKEN, TEAM_SERVICE_TOKEN } from '@/lib/di/tokens';
|
||||
import { DriverService } from '@/lib/services/drivers/DriverService';
|
||||
import { TeamService } from '@/lib/services/teams/TeamService';
|
||||
import { Trophy } from 'lucide-react';
|
||||
import { redirect , useRouter } from 'next/navigation';
|
||||
import { redirect } from 'next/navigation';
|
||||
import type { DriverLeaderboardViewModel } from '@/lib/view-models/DriverLeaderboardViewModel';
|
||||
import type { TeamSummaryViewModel } from '@/lib/view-models/TeamSummaryViewModel';
|
||||
import { LeaderboardsPageWrapper } from './LeaderboardsPageWrapper';
|
||||
|
||||
// ============================================================================
|
||||
// TYPES
|
||||
@@ -18,48 +18,6 @@ interface LeaderboardsPageData {
|
||||
teams: TeamSummaryViewModel[] | null;
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// WRAPPER COMPONENT
|
||||
// ============================================================================
|
||||
|
||||
function LeaderboardsPageWrapper({ data }: { data: LeaderboardsPageData | null }) {
|
||||
const router = useRouter();
|
||||
|
||||
if (!data || (!data.drivers && !data.teams)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const drivers = data.drivers?.drivers || [];
|
||||
const teams = data.teams || [];
|
||||
|
||||
const handleDriverClick = (driverId: string) => {
|
||||
router.push(`/drivers/${driverId}`);
|
||||
};
|
||||
|
||||
const handleTeamClick = (teamId: string) => {
|
||||
router.push(`/teams/${teamId}`);
|
||||
};
|
||||
|
||||
const handleNavigateToDrivers = () => {
|
||||
router.push('/leaderboards/drivers');
|
||||
};
|
||||
|
||||
const handleNavigateToTeams = () => {
|
||||
router.push('/teams/leaderboard');
|
||||
};
|
||||
|
||||
return (
|
||||
<LeaderboardsTemplate
|
||||
drivers={drivers}
|
||||
teams={teams}
|
||||
onDriverClick={handleDriverClick}
|
||||
onTeamClick={handleTeamClick}
|
||||
onNavigateToDrivers={handleNavigateToDrivers}
|
||||
onNavigateToTeams={handleNavigateToTeams}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// MAIN PAGE COMPONENT
|
||||
// ============================================================================
|
||||
|
||||
@@ -0,0 +1,44 @@
|
||||
'use client';
|
||||
|
||||
import { useRouter } from 'next/navigation';
|
||||
import TeamLeaderboardTemplate from '@/templates/TeamLeaderboardTemplate';
|
||||
import { useState } from 'react';
|
||||
import type { TeamSummaryViewModel } from '@/lib/view-models/TeamSummaryViewModel';
|
||||
|
||||
type SkillLevel = 'pro' | 'advanced' | 'intermediate' | 'beginner';
|
||||
type SortBy = 'rating' | 'wins' | 'winRate' | 'races';
|
||||
|
||||
export function TeamLeaderboardPageWrapper({ data }: { data: TeamSummaryViewModel[] | null }) {
|
||||
const router = useRouter();
|
||||
|
||||
// Client-side state for filtering and sorting
|
||||
const [searchQuery, setSearchQuery] = useState('');
|
||||
const [filterLevel, setFilterLevel] = useState<SkillLevel | 'all'>('all');
|
||||
const [sortBy, setSortBy] = useState<SortBy>('rating');
|
||||
|
||||
if (!data || data.length === 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const handleTeamClick = (teamId: string) => {
|
||||
router.push(`/teams/${teamId}`);
|
||||
};
|
||||
|
||||
const handleBackToTeams = () => {
|
||||
router.push('/teams');
|
||||
};
|
||||
|
||||
return (
|
||||
<TeamLeaderboardTemplate
|
||||
teams={data}
|
||||
searchQuery={searchQuery}
|
||||
filterLevel={filterLevel}
|
||||
sortBy={sortBy}
|
||||
onSearchChange={setSearchQuery}
|
||||
onFilterLevelChange={setFilterLevel}
|
||||
onSortChange={setSortBy}
|
||||
onTeamClick={handleTeamClick}
|
||||
onBackToTeams={handleBackToTeams}
|
||||
/>
|
||||
);
|
||||
}
|
||||
@@ -1,58 +1,11 @@
|
||||
import { PageWrapper } from '@/components/shared/state/PageWrapper';
|
||||
import TeamLeaderboardTemplate from '@/templates/TeamLeaderboardTemplate';
|
||||
import { PageDataFetcher } from '@/lib/page/PageDataFetcher';
|
||||
import { TEAM_SERVICE_TOKEN } from '@/lib/di/tokens';
|
||||
import { TeamService } from '@/lib/services/teams/TeamService';
|
||||
import { Trophy } from 'lucide-react';
|
||||
import { redirect , useRouter } from 'next/navigation';
|
||||
import { redirect } from 'next/navigation';
|
||||
import type { TeamSummaryViewModel } from '@/lib/view-models/TeamSummaryViewModel';
|
||||
import { useState } from 'react';
|
||||
|
||||
// ============================================================================
|
||||
// TYPES
|
||||
// ============================================================================
|
||||
|
||||
type SkillLevel = 'pro' | 'advanced' | 'intermediate' | 'beginner';
|
||||
type SortBy = 'rating' | 'wins' | 'winRate' | 'races';
|
||||
|
||||
// ============================================================================
|
||||
// WRAPPER COMPONENT (Client-side state management)
|
||||
// ============================================================================
|
||||
|
||||
function TeamLeaderboardPageWrapper({ data }: { data: TeamSummaryViewModel[] | null }) {
|
||||
const router = useRouter();
|
||||
|
||||
// Client-side state for filtering and sorting
|
||||
const [searchQuery, setSearchQuery] = useState('');
|
||||
const [filterLevel, setFilterLevel] = useState<SkillLevel | 'all'>('all');
|
||||
const [sortBy, setSortBy] = useState<SortBy>('rating');
|
||||
|
||||
if (!data || data.length === 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const handleTeamClick = (teamId: string) => {
|
||||
router.push(`/teams/${teamId}`);
|
||||
};
|
||||
|
||||
const handleBackToTeams = () => {
|
||||
router.push('/teams');
|
||||
};
|
||||
|
||||
return (
|
||||
<TeamLeaderboardTemplate
|
||||
teams={data}
|
||||
searchQuery={searchQuery}
|
||||
filterLevel={filterLevel}
|
||||
sortBy={sortBy}
|
||||
onSearchChange={setSearchQuery}
|
||||
onFilterLevelChange={setFilterLevel}
|
||||
onSortChange={setSortBy}
|
||||
onTeamClick={handleTeamClick}
|
||||
onBackToTeams={handleBackToTeams}
|
||||
/>
|
||||
);
|
||||
}
|
||||
import { TeamLeaderboardPageWrapper } from './TeamLeaderboardPageWrapper';
|
||||
|
||||
// ============================================================================
|
||||
// MAIN PAGE COMPONENT
|
||||
|
||||
@@ -2,6 +2,7 @@ import { Container } from 'inversify';
|
||||
|
||||
// Module imports
|
||||
import { ApiModule } from './modules/api.module';
|
||||
import { AuthModule } from './modules/auth.module';
|
||||
import { CoreModule } from './modules/core.module';
|
||||
import { DashboardModule } from './modules/dashboard.module';
|
||||
import { DriverModule } from './modules/driver.module';
|
||||
@@ -23,6 +24,7 @@ export function createContainer(): Container {
|
||||
container.load(
|
||||
CoreModule,
|
||||
ApiModule,
|
||||
AuthModule,
|
||||
LeagueModule,
|
||||
DriverModule,
|
||||
TeamModule,
|
||||
|
||||
@@ -13,6 +13,7 @@ export * from './providers/ContainerProvider';
|
||||
// Modules
|
||||
export * from './modules/analytics.module';
|
||||
export * from './modules/api.module';
|
||||
export * from './modules/auth.module';
|
||||
export * from './modules/core.module';
|
||||
export * from './modules/dashboard.module';
|
||||
export * from './modules/driver.module';
|
||||
|
||||
30
apps/website/lib/di/modules/auth.module.ts
Normal file
30
apps/website/lib/di/modules/auth.module.ts
Normal file
@@ -0,0 +1,30 @@
|
||||
import { ContainerModule } from 'inversify';
|
||||
import { AuthService } from '../../services/auth/AuthService';
|
||||
import { SessionService } from '../../services/auth/SessionService';
|
||||
import { AuthApiClient } from '../../api/auth/AuthApiClient';
|
||||
|
||||
import {
|
||||
AUTH_SERVICE_TOKEN,
|
||||
SESSION_SERVICE_TOKEN,
|
||||
AUTH_API_CLIENT_TOKEN
|
||||
} from '../tokens';
|
||||
|
||||
export const AuthModule = new ContainerModule((options) => {
|
||||
const bind = options.bind;
|
||||
|
||||
// Session Service
|
||||
bind<SessionService>(SESSION_SERVICE_TOKEN)
|
||||
.toDynamicValue((ctx) => {
|
||||
const authApiClient = ctx.get<AuthApiClient>(AUTH_API_CLIENT_TOKEN);
|
||||
return new SessionService(authApiClient);
|
||||
})
|
||||
.inSingletonScope();
|
||||
|
||||
// Auth Service
|
||||
bind<AuthService>(AUTH_SERVICE_TOKEN)
|
||||
.toDynamicValue((ctx) => {
|
||||
const authApiClient = ctx.get<AuthApiClient>(AUTH_API_CLIENT_TOKEN);
|
||||
return new AuthService(authApiClient);
|
||||
})
|
||||
.inSingletonScope();
|
||||
});
|
||||
@@ -91,6 +91,9 @@ export const LeagueModule = new ContainerModule((options) => {
|
||||
|
||||
// League Membership Service
|
||||
bind<LeagueMembershipService>(LEAGUE_MEMBERSHIP_SERVICE_TOKEN)
|
||||
.to(LeagueMembershipService)
|
||||
.toDynamicValue((ctx) => {
|
||||
const leagueApiClient = ctx.get<LeaguesApiClient>(LEAGUE_API_CLIENT_TOKEN);
|
||||
return new LeagueMembershipService(leagueApiClient);
|
||||
})
|
||||
.inSingletonScope();
|
||||
});
|
||||
|
||||
@@ -35,14 +35,29 @@ export class ConsoleLogger implements Logger {
|
||||
const emoji = this.EMOJIS[level];
|
||||
const prefix = this.PREFIXES[level];
|
||||
|
||||
console.groupCollapsed(`%c${emoji} [${source.toUpperCase()}] ${prefix}: ${message}`, `color: ${color}; font-weight: bold;`);
|
||||
// Edge runtime doesn't support console.groupCollapsed/groupEnd
|
||||
// Fallback to simple logging for compatibility
|
||||
const supportsGrouping = typeof console.groupCollapsed === 'function' && typeof console.groupEnd === 'function';
|
||||
|
||||
if (supportsGrouping) {
|
||||
// Safe to call - we've verified both functions exist
|
||||
(console as any).groupCollapsed(`%c${emoji} [${source.toUpperCase()}] ${prefix}: ${message}`, `color: ${color}; font-weight: bold;`);
|
||||
} else {
|
||||
// Simple format for edge runtime
|
||||
console.log(`${emoji} [${source.toUpperCase()}] ${prefix}: ${message}`);
|
||||
}
|
||||
|
||||
console.log(`%cTimestamp:`, 'color: #666; font-weight: bold;', new Date().toISOString());
|
||||
console.log(`%cSource:`, 'color: #666; font-weight: bold;', source);
|
||||
|
||||
if (context) {
|
||||
console.log(`%cContext:`, 'color: #666; font-weight: bold;');
|
||||
console.dir(context, { depth: 3, colors: true });
|
||||
// console.dir may not be available in edge runtime
|
||||
if (typeof console.dir === 'function') {
|
||||
console.dir(context, { depth: 3, colors: true });
|
||||
} else {
|
||||
console.log(JSON.stringify(context, null, 2));
|
||||
}
|
||||
}
|
||||
|
||||
if (error) {
|
||||
@@ -56,7 +71,10 @@ export class ConsoleLogger implements Logger {
|
||||
}
|
||||
}
|
||||
|
||||
console.groupEnd();
|
||||
if (supportsGrouping) {
|
||||
// Safe to call - we've verified the function exists
|
||||
(console as any).groupEnd();
|
||||
}
|
||||
}
|
||||
|
||||
debug(message: string, context?: unknown): void {
|
||||
|
||||
42
apps/website/lib/providers/QueryClientProvider.tsx
Normal file
42
apps/website/lib/providers/QueryClientProvider.tsx
Normal file
@@ -0,0 +1,42 @@
|
||||
'use client';
|
||||
|
||||
import { QueryClient, QueryClientProvider as TanstackQueryClientProvider } from '@tanstack/react-query';
|
||||
import { ReactNode, useState } from 'react';
|
||||
|
||||
interface QueryClientProviderProps {
|
||||
children: ReactNode;
|
||||
}
|
||||
|
||||
/**
|
||||
* Provides React Query client to the application
|
||||
*
|
||||
* Must wrap any components that use React Query hooks (useQuery, useMutation, etc.)
|
||||
* Creates a new QueryClient instance per component tree to avoid state sharing
|
||||
*/
|
||||
export function QueryClientProvider({ children }: QueryClientProviderProps) {
|
||||
// Create a new QueryClient instance for each component tree
|
||||
// This prevents state sharing between different renders
|
||||
const [queryClient] = useState(
|
||||
() =>
|
||||
new QueryClient({
|
||||
defaultOptions: {
|
||||
queries: {
|
||||
// Disable automatic refetching in production for better performance
|
||||
refetchOnWindowFocus: process.env.NODE_ENV === 'development',
|
||||
refetchOnReconnect: true,
|
||||
retry: 1,
|
||||
staleTime: 5 * 60 * 1000, // 5 minutes
|
||||
},
|
||||
mutations: {
|
||||
retry: 0,
|
||||
},
|
||||
},
|
||||
})
|
||||
);
|
||||
|
||||
return (
|
||||
<TanstackQueryClientProvider client={queryClient}>
|
||||
{children}
|
||||
</TanstackQueryClientProvider>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user