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
7 changes: 6 additions & 1 deletion next.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,12 @@ import type { NextConfig } from 'next';
import path from 'path';

const nextConfig: NextConfig = {
/* config options here */
modularizeImports: {
lodash: {
transform: 'lodash/{{member}}',
},
},

eslint: {
// Many legacy files do not match Prettier; keep type checking without blocking production builds.
ignoreDuringBuilds: true,
Expand Down
35 changes: 35 additions & 0 deletions src/app/courses/loading.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
export default function CoursesLoading() {
return (
<div className="min-h-screen bg-gray-50 dark:bg-gray-900 p-6">
<div className="max-w-7xl mx-auto">
<div className="animate-pulse mb-8">
<div className="h-8 w-40 bg-gray-200 dark:bg-gray-700 rounded mb-2" />
<div className="h-4 w-64 bg-gray-200 dark:bg-gray-700 rounded" />
</div>

<div className="flex gap-4 mb-6">
{[...Array(4)].map((_, i) => (
<div key={i} className="h-10 w-24 bg-gray-200 dark:bg-gray-700 rounded-lg" />
))}
</div>

<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
{[...Array(9)].map((_, i) => (
<div key={i} className="bg-white dark:bg-gray-800 rounded-xl overflow-hidden shadow-sm">
<div className="h-48 bg-gray-200 dark:bg-gray-700" />
<div className="p-5">
<div className="h-5 w-3/4 bg-gray-200 dark:bg-gray-700 rounded mb-2" />
<div className="h-4 w-full bg-gray-200 dark:bg-gray-700 rounded mb-2" />
<div className="h-4 w-2/3 bg-gray-200 dark:bg-gray-700 rounded mb-4" />
<div className="flex justify-between items-center">
<div className="h-6 w-16 bg-gray-200 dark:bg-gray-700 rounded" />
<div className="h-8 w-24 bg-gray-200 dark:bg-gray-700 rounded-lg" />
</div>
</div>
</div>
))}
</div>
</div>
</div>
);
}
34 changes: 34 additions & 0 deletions src/app/dashboard/loading.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
export default function DashboardLoading() {
return (
<div className="min-h-screen bg-gray-50 dark:bg-gray-900 p-6">
<div className="max-w-7xl mx-auto">
<div className="animate-pulse mb-8">
<div className="h-8 w-48 bg-gray-200 dark:bg-gray-700 rounded mb-2" />
<div className="h-4 w-32 bg-gray-200 dark:bg-gray-700 rounded" />
</div>

<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-6 mb-8">
{[...Array(4)].map((_, i) => (
<div key={i} className="bg-white dark:bg-gray-800 rounded-xl p-6 shadow-sm">
<div className="h-4 w-20 bg-gray-200 dark:bg-gray-700 rounded mb-3" />
<div className="h-8 w-16 bg-gray-200 dark:bg-gray-700 rounded" />
</div>
))}
</div>

<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
{[...Array(6)].map((_, i) => (
<div key={i} className="bg-white dark:bg-gray-800 rounded-xl overflow-hidden shadow-sm">
<div className="h-40 bg-gray-200 dark:bg-gray-700" />
<div className="p-4">
<div className="h-5 w-3/4 bg-gray-200 dark:bg-gray-700 rounded mb-2" />
<div className="h-4 w-1/2 bg-gray-200 dark:bg-gray-700 rounded mb-3" />
<div className="h-2 w-full bg-gray-200 dark:bg-gray-700 rounded" />
</div>
</div>
))}
</div>
</div>
</div>
);
}
4 changes: 4 additions & 0 deletions src/app/layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -25,11 +25,15 @@ const RTL_LOCALES = new Set(['ar', 'he']);
const geistSans = Geist({
variable: '--font-geist-sans',
subsets: ['latin'],
display: 'swap',
preload: false,
});

const geistMono = Geist_Mono({
variable: '--font-geist-mono',
subsets: ['latin'],
display: 'swap',
preload: false,
});

export const metadata: Metadata = {
Expand Down
10 changes: 10 additions & 0 deletions src/app/loading.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
export default function GlobalLoading() {
return (
<div className="min-h-screen flex items-center justify-center bg-white dark:bg-gray-950">
<div className="text-center">
<div className="w-12 h-12 border-4 border-blue-500 border-t-transparent rounded-full animate-spin mx-auto mb-4" />
<p className="text-gray-500 dark:text-gray-400">Loading...</p>
</div>
</div>
);
}
40 changes: 40 additions & 0 deletions src/app/profile/loading.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
export default function ProfileLoading() {
return (
<div className="min-h-screen bg-gray-50 dark:bg-gray-900 p-6">
<div className="max-w-4xl mx-auto">
<div className="bg-white dark:bg-gray-800 rounded-xl p-6 mb-6">
<div className="flex items-center gap-6">
<div className="w-24 h-24 bg-gray-200 dark:bg-gray-700 rounded-full" />
<div className="flex-1">
<div className="h-6 w-48 bg-gray-200 dark:bg-gray-700 rounded mb-2" />
<div className="h-4 w-32 bg-gray-200 dark:bg-gray-700 rounded mb-2" />
<div className="h-4 w-64 bg-gray-200 dark:bg-gray-700 rounded" />
</div>
</div>
</div>

<div className="grid grid-cols-3 gap-4 mb-6">
{[...Array(3)].map((_, i) => (
<div key={i} className="bg-white dark:bg-gray-800 rounded-xl p-4">
<div className="h-4 w-20 bg-gray-200 dark:bg-gray-700 rounded mb-2" />
<div className="h-8 w-12 bg-gray-200 dark:bg-gray-700 rounded" />
</div>
))}
</div>

<div className="bg-white dark:bg-gray-800 rounded-xl p-6">
<div className="flex gap-4 mb-6">
{[...Array(4)].map((_, i) => (
<div key={i} className="h-6 w-20 bg-gray-200 dark:bg-gray-700 rounded" />
))}
</div>
<div className="space-y-4">
{[...Array(5)].map((_, i) => (
<div key={i} className="h-16 bg-gray-100 dark:bg-gray-700 rounded-lg" />
))}
</div>
</div>
</div>
</div>
);
}
34 changes: 34 additions & 0 deletions src/app/search/loading.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
export default function SearchLoading() {
return (
<div className="min-h-screen bg-gray-50 dark:bg-gray-900 p-6">
<div className="max-w-4xl mx-auto">
<div className="animate-pulse mb-6">
<div className="h-12 w-full bg-gray-200 dark:bg-gray-700 rounded-xl" />
</div>

<div className="flex gap-2 mb-6">
{[...Array(5)].map((_, i) => (
<div key={i} className="h-8 w-20 bg-gray-200 dark:bg-gray-700 rounded-full" />
))}
</div>

<div className="h-5 w-32 bg-gray-200 dark:bg-gray-700 rounded mb-4" />

<div className="space-y-4">
{[...Array(8)].map((_, i) => (
<div key={i} className="bg-white dark:bg-gray-800 rounded-xl p-4">
<div className="flex gap-4">
<div className="w-32 h-24 bg-gray-200 dark:bg-gray-700 rounded-lg shrink-0" />
<div className="flex-1">
<div className="h-5 w-3/4 bg-gray-200 dark:bg-gray-700 rounded mb-2" />
<div className="h-4 w-full bg-gray-200 dark:bg-gray-700 rounded mb-2" />
<div className="h-4 w-1/2 bg-gray-200 dark:bg-gray-700 rounded" />
</div>
</div>
</div>
))}
</div>
</div>
</div>
);
}
44 changes: 44 additions & 0 deletions src/components/ui/SuspenseLoader.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
import { Suspense, ReactNode } from 'react';
import { Loader2 } from 'lucide-react';

interface SuspenseLoaderProps {
children: ReactNode;
fallback?: ReactNode;
minimumDelay?: number;
}

export function SuspenseLoader({ children, fallback, minimumDelay = 200 }: SuspenseLoaderProps) {
const DefaultFallback = (
<div className="flex items-center justify-center p-4 min-h-25">
<Loader2 className="w-6 h-6 animate-spin text-blue-500 mr-2" />
<span className="text-sm text-gray-500">Loading...</span>
</div>
);

return <Suspense fallback={fallback || DefaultFallback}>{children}</Suspense>;
}

export function SkeletonLoader({ className }: { className?: string }) {
return <div className={`animate-pulse bg-gray-200 dark:bg-gray-700 rounded ${className}`} />;
}

export function InlineLoader({ size = 'md' }: { size?: 'sm' | 'md' | 'lg' }) {
const sizes = {
sm: 'w-4 h-4',
md: 'w-6 h-6',
lg: 'w-8 h-8',
};

return <Loader2 className={`${sizes[size]} animate-spin text-blue-500`} />;
}

export function PageLoader() {
return (
<div className="min-h-screen flex items-center justify-center">
<div className="text-center">
<Loader2 className="w-10 h-10 animate-spin text-blue-500 mx-auto mb-4" />
<p className="text-gray-500">Loading...</p>
</div>
</div>
);
}
43 changes: 43 additions & 0 deletions src/hooks/useLazyLoad.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import React, { lazy, Suspense, ReactNode, ComponentType } from 'react';
import { Loader2 } from 'lucide-react';

interface LazyLoadOptions {
fallback?: ReactNode;
name?: string;
}

function DefaultFallback() {
return (
<div className="flex items-center justify-center p-8">
<Loader2 className="w-8 h-8 animate-spin text-blue-500" />
<span className="ml-2 text-gray-500">Loading...</span>
</div>
);
}

export function createLazyComponent<T extends ComponentType<any>>(
importFn: () => Promise<any>,
options: LazyLoadOptions = {},
) {
const { fallback } = options;

const LazyComponent = lazy(importFn);

return function LazyWrapper(props: any) {
return (
<Suspense fallback={fallback || <DefaultFallback />}>
<LazyComponent {...props} />
</Suspense>
);
};
}

export function preloadComponent(importFn: () => Promise<unknown>) {
if (typeof window !== 'undefined') {
importFn();
}
}

export function createLazy<T>(importFn: () => Promise<any>): React.LazyExoticComponent<T> {
return lazy(importFn);
}
Loading