import {
Suspense,
lazy,
type ComponentType,
Component,
type ErrorInfo,
} from 'react';
import { LoadingSpinner } from './loading-spinner';
import { logger } from '@/utils/logger';
import { Button } from './button';
import { AlertTriangle, RefreshCw } from 'lucide-react';
// CRITIQUE FIX #16: Composant de fallback amélioré pour les erreurs de chargement lazy
function LazyErrorFallback({
pageName,
error,
onRetry,
}: {
pageName: string;
error?: Error;
onRetry?: () => void;
}) {
return (
Failed to load {pageName}
{(() => {
try {
if (!error) return 'Currently unable to access this component. Please check your connection.';
if (typeof error === 'string') return error;
if (error instanceof Error) return error.message;
return String(error);
} catch (e) {
return 'An unknown error occurred.';
}
})()}
{onRetry && (
)}
);
}
// CRITIQUE FIX #16: ErrorBoundary spécifique pour les composants lazy
class LazyErrorBoundary extends Component<
{
children: React.ReactNode;
pageName: string;
onError?: (error: Error, errorInfo: ErrorInfo) => void;
},
{ hasError: boolean; error?: Error }
> {
constructor(props: {
children: React.ReactNode;
pageName: string;
onError?: (error: Error, errorInfo: ErrorInfo) => void;
}) {
super(props);
this.state = { hasError: false };
}
static getDerivedStateFromError(error: Error) {
return { hasError: true, error };
}
override componentDidCatch(error: Error, errorInfo: ErrorInfo) {
// CRITIQUE FIX #16: Logger l'erreur avec le logger centralisé au lieu de console.error
logger.error('[LazyComponent] Failed to load lazy component', {
pageName: this.props.pageName,
error: error instanceof Error ? error.message : String(error),
stack: error instanceof Error ? error.stack : undefined,
componentStack: errorInfo.componentStack,
});
if (this.props.onError) {
this.props.onError(error, errorInfo);
}
}
handleRetry = () => {
this.setState({ hasError: false, error: undefined });
};
override render() {
if (this.state.hasError) {
return (
);
}
return this.props.children;
}
}
/**
* LazyComponentProps - Propriétés pour les composants lazy créés
*
* @interface LazyComponentProps
*/
interface LazyComponentProps {
/**
* Composant de fallback personnalisé à afficher pendant le chargement
* Si non fourni, utilise LoadingSpinner par défaut
*/
fallback?: React.ReactNode;
}
/**
* createLazyComponent - Factory pour créer des composants lazy avec Suspense
*
* Crée un composant lazy avec gestion automatique du Suspense et du fallback.
* Utile pour le code splitting et le chargement à la demande des composants.
*
* @template T - Type du composant à charger
* @param {() => Promise<{ default: T }>} importFunc - Fonction d'import dynamique
* @param {React.ReactNode} fallback - Composant de fallback (optionnel)
* @returns {ComponentType} Composant wrapper avec Suspense intégré
*
* @example
* ```tsx
* // Créer un composant lazy
* const LazyDashboard = createLazyComponent(
* () => import('@/pages/DashboardPage').then(m => ({ default: m.DashboardPage }))
* );
*
* // Utiliser le composant lazy
* } />
* ```
*
* @function
*/
// CRITIQUE FIX #16: Wrapper pour gérer les erreurs de chargement lazy de manière standardisée
// CRITIQUE FIX #16: Wrapper pour gérer les erreurs de chargement lazy de manière standardisée
function createLazyWithErrorHandling>(
importFunc: () => Promise<{ default: T } | T>, // Allow direct component return if needed
pageName: string,
) {
return importFunc()
.then((module) => {
// Handle both default exports and named exports (if the named export matches the module structure)
// This is a safety check: if 'default' exists, usage is correct.
// If 'default' is missing but we have a module object, we might need to find the component.
// For consistent usage with React.lazy, we usually expect { default: Component }.
// However, if the user does named imports in the lambda:
// () => import('...').then(m => ({ default: m.NamedComponent }))
// That is ALREADY handled by the lambda itself returning the correct shape.
// This catch is mainly for network errors during fetch.
return module as { default: T };
})
.catch((err) => {
// CRITIQUE FIX #16: Logger l'erreur avec le logger centralisé
const errorMessage = err instanceof Error ? err.message : String(err);
logger.error('[LazyComponent] Failed to import lazy component', {
pageName,
error: errorMessage,
stack: err instanceof Error ? err.stack : undefined,
});
// Retourner un composant d'erreur au lieu de laisser l'erreur se propager
return Promise.resolve({
default: () => (
),
}) as unknown as Promise<{ default: T }>;
});
}
export function createLazyComponent>(
importFunc: () => Promise,
fallback?: React.ReactNode,
pageName?: string,
) {
// CRITIQUE FIX #16: Utiliser la fonction avec gestion d'erreur si pageName est fourni
const safeImportFunc = pageName
? () => createLazyWithErrorHandling(importFunc as any, pageName)
: importFunc;
const LazyComponent = lazy(safeImportFunc);
return function WrappedLazyComponent(
props: React.ComponentProps & LazyComponentProps,
) {
// Extraire fallback des props pour ne pas le passer au composant lazy
const { fallback: _fallback, ...componentProps } = props;
// CRITIQUE FIX #16: Wrapper avec ErrorBoundary pour capturer les erreurs runtime
const component = (
}>
{/* LazyComponent props are compatible but TypeScript can't infer it */}
);
// Si pageName est fourni, wrapper avec LazyErrorBoundary
if (pageName) {
return (
{component}
);
}
return component;
};
}
// Composants lazy communs
// CRITIQUE FIX #16: Ajouter pageName pour tous les composants lazy pour une meilleure gestion d'erreur
export const LazyDashboard = createLazyComponent(
() => import('@/features/dashboard/pages/DashboardPage'), // Default export usage
undefined,
'Dashboard',
);
export const LazyChat = createLazyComponent(
() =>
import('@/features/chat/pages/ChatPage').then((m) => ({
default: m.ChatPage,
})),
undefined,
'Chat',
);
export const LazyLibrary = createLazyComponent(
() =>
import('@/features/library/pages/LibraryPage').then((m) => ({
default: m.LibraryPage,
})),
undefined,
'Library',
);
export const LazyProfile = createLazyComponent(
() =>
import('@/features/profile/pages/UserProfilePage').then((m) => ({
default: m.UserProfilePage,
})),
undefined,
'Profile',
);
export const LazySettings = createLazyComponent(
() =>
import('@/features/settings/pages/SettingsPage').then((m) => ({
default: m.SettingsPage,
})),
undefined,
'Settings',
);
export const LazyLogin = createLazyComponent(
() => import('@/features/auth/pages/LoginPage'),
undefined,
'Login',
);
export const LazyRegister = createLazyComponent(
() => import('@/features/auth/pages/RegisterPage'),
undefined,
'Register',
);
export const LazyForgotPassword = createLazyComponent(
() => import('@/features/auth/pages/ForgotPasswordPage'),
undefined,
'Forgot Password',
);
export const LazyVerifyEmail = createLazyComponent(
() => import('@/features/auth/pages/VerifyEmailPage'),
undefined,
'Verify Email',
);
export const LazyResetPassword = createLazyComponent(
() => import('@/features/auth/pages/ResetPasswordPage'),
undefined,
'Reset Password',
);
export const LazySessions = createLazyComponent(
() => import('@/features/auth/pages/SessionsPage'),
undefined,
'Sessions',
);
export const LazyNotFound = createLazyComponent(
() => import('@/features/error/pages/NotFoundPage'),
undefined,
'Not Found',
);
export const LazyServerError = createLazyComponent(
() => import('@/features/error/pages/ServerErrorPage'),
undefined,
'Server Error',
);
export const LazyUserProfile = createLazyComponent(
() =>
import('@/features/profile/pages/UserProfilePage').then((m) => ({
default: m.UserProfilePage,
})),
undefined,
'User Profile',
);
export const LazyRoles = createLazyComponent(
() =>
import('@/features/roles/pages/RolesPage').then((m) => ({
default: m.RolesPage,
})),
undefined,
'Roles',
);
export const LazyTrackDetail = createLazyComponent(
() =>
import('@/features/tracks/pages/TrackDetailPage').then((m) => ({
default: m.TrackDetailPage,
})),
undefined,
'Track Detail',
);
export const LazyPlaylistRoutes = createLazyComponent(
() =>
import('@/features/playlists/routes').then((m) => ({
default: m.PlaylistRoutes,
})),
undefined,
'Playlists',
);
export const LazyAdminDashboard = createLazyComponent(
() =>
import('@/components/admin/AdminDashboardView').then((m) => ({
default: m.AdminDashboardView,
})),
undefined,
'Admin Dashboard',
);
export const LazyAnalytics = createLazyComponent(
() =>
import('@/components/views/AnalyticsView').then((m) => ({
default: m.AnalyticsView,
})),
undefined,
'Analytics',
);
export const LazyWebhooks = createLazyComponent(
() =>
import('@/components/developer/WebhooksView').then((m) => ({
default: m.WebhooksView,
})),
undefined,
'Webhooks',
);
export const LazyDesignSystemDemo = createLazyComponent(
() =>
import('@/components/demo/DesignSystemDemo').then((m) => ({
default: m.DesignSystemDemo,
})),
undefined,
'Design System Demo',
);
export const LazySocial = createLazyComponent(
() =>
import('@/components/views/SocialView').then((m) => ({
default: m.SocialView,
})),
undefined,
'Social',
);
export const LazyGear = createLazyComponent(
() =>
import('@/components/views/GearView').then((m) => ({
default: m.GearView,
})),
undefined,
'Gear',
);
export const LazyLive = createLazyComponent(
() =>
import('@/components/views/LiveView').then((m) => ({
default: m.LiveView,
})),
undefined,
'Live',
);
export const LazyEducation = createLazyComponent(
() =>
import('@/components/views/EducationView').then((m) => ({
default: m.EducationView,
})),
undefined,
'Education',
);
export const LazyQueue = createLazyComponent(
() =>
import('@/components/library/playlists/QueueView').then((m) => ({
default: m.QueueView,
})),
undefined,
'Queue',
);
export const LazyDeveloper = createLazyComponent(
() =>
import('@/components/developer/DeveloperDashboardView').then((m) => ({
default: m.DeveloperDashboardView,
})),
undefined,
'Developer',
);
export const LazyNotifications = createLazyComponent(
() =>
import('@/components/views/NotificationsView').then((m) => ({
default: m.NotificationsView,
})),
undefined,
'Notifications',
);
export const LazyMarketplace = createLazyComponent(
() =>
import('@/pages/marketplace/MarketplaceHome').then((m) => ({
default: m.MarketplaceHome,
})),
undefined,
'Marketplace',
);
export const LazySearch = createLazyComponent(
() =>
import('@/features/search/pages/SearchPage').then((m) => ({
default: m.SearchPage,
})),
undefined,
'Search',
);
// New pages for navigation fix
export const LazySellerDashboard = createLazyComponent(
() =>
import('@/components/seller/SellerDashboardView').then((m) => ({
default: m.SellerDashboardView,
})),
undefined,
'Seller Dashboard',
);
export const LazyWishlist = createLazyComponent(
() =>
import('@/components/commerce/WishlistView').then((m) => ({
default: m.WishlistView,
})),
undefined,
'Wishlist',
);
export const LazyPurchases = createLazyComponent(
() =>
import('@/components/views/PurchasesView').then((m) => ({
default: m.PurchasesView,
})),
undefined,
'Purchases',
);