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
216 changes: 106 additions & 110 deletions package-lock.json

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@
"react-hook-form": "^7.60.0",
"react-hot-toast": "^2.6.0",
"react-icons": "^5.5.0",
"react-intersection-observer": "^10.0.3",
"recharts": "^2.15.4",
"socket.io-client": "^4.8.3",
"tailwind-merge": "^2.6.0",
Expand Down
4 changes: 4 additions & 0 deletions src/app/layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ import { OfflineModeProvider } from "./context/OfflineModeContext";
import { I18nProvider } from "@/hooks/useInternationalization";
import { InternationalizationEngine } from "@/components/i18n/InternationalizationEngine";
import { CulturalAdaptationManager } from "@/components/i18n/CulturalAdaptationManager";
import PerformanceMonitor from "@/components/performance/PerformanceMonitor";
import PrefetchingEngine from "@/components/performance/PrefetchingEngine";

const geistSans = Geist({
variable: "--font-geist-sans",
Expand Down Expand Up @@ -37,6 +39,8 @@ export default function RootLayout({
<CulturalAdaptationManager>
<ThemeProvider>
<OfflineModeProvider>
<PerformanceMonitor />
<PrefetchingEngine />
{children}
</OfflineModeProvider>
</ThemeProvider>
Expand Down
51 changes: 51 additions & 0 deletions src/components/performance/LazyLoadingManager.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
'use client';

import React, { Suspense, useEffect, useState } from 'react';
import { useInView } from 'react-intersection-observer';

interface LazyLoadingManagerProps {
children: React.ReactNode;
fallback?: React.ReactNode;
threshold?: number; // 0 to 1
rootMargin?: string;
componentName?: string;
}

/**
* Intelligent wrapper for lazy loading components when they enter the viewport.
*/
const LazyLoadingManager: React.FC<LazyLoadingManagerProps> = ({
children,
fallback = <div className="animate-pulse bg-gray-200 h-32 w-full rounded-md" />,
threshold = 0.1,
rootMargin = '200px',
componentName = 'Component'
}) => {
const [hasBeenInView, setHasBeenInView] = useState(false);
const { ref, inView } = useInView({
threshold,
rootMargin,
triggerOnce: true
});

useEffect(() => {
if (inView && !hasBeenInView) {
setHasBeenInView(true);
console.log(`[LazyLoading] Triggering load for ${componentName}`);
}
}, [inView, hasBeenInView, componentName]);

return (
<div ref={ref} className="w-full">
{hasBeenInView ? (
<Suspense fallback={fallback}>
{children}
</Suspense>
) : (
fallback
)}
</div>
);
};

export default LazyLoadingManager;
55 changes: 55 additions & 0 deletions src/components/performance/PerformanceMonitor.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
'use client';

import React, { useEffect, useState } from 'react';
import { measureWebVitals, PerformanceMetric } from '../../utils/performanceUtils';

/**
* Component to monitor and display performance metrics in real-time.
* In a production environment, this could be hidden or restricted to admin users.
*/
const PerformanceMonitor: React.FC = () => {
const [metrics, setMetrics] = useState<Record<string, PerformanceMetric>>({});
const [isVisible, setIsVisible] = useState(false);

useEffect(() => {
measureWebVitals((metric) => {
setMetrics((prev: Record<string, PerformanceMetric>) => ({
...prev,
[metric.name]: metric,
}));

// Logic for alerts based on thresholds
if (metric.name === 'LCP' && metric.value > 2500) {
console.warn(`[Performance Alert] LCP is high: ${metric.value.toFixed(2)}ms`);
}
if (metric.name === 'FID' && metric.value > 100) {
console.warn(`[Performance Alert] FID is high: ${metric.value.toFixed(2)}ms`);
}
});
}, []);

if (process.env.NODE_ENV === 'production' && !isVisible) return null;

return (
<div className={`fixed bottom-4 right-4 z-50 p-4 rounded-lg bg-black/80 text-white text-xs font-mono shadow-xl transition-opacity ${isVisible ? 'opacity-100' : 'opacity-0 hover:opacity-100'}`}>
<div className="flex justify-between items-center mb-2 border-b border-white/20 pb-1">
<span className="font-bold ">🚀 Performance Monitor</span>
<button onClick={() => setIsVisible(!isVisible)} className="ml-2 hover:text-blue-400">
{isVisible ? 'Hide' : 'Show'}
</button>
</div>
<div className="space-y-1">
{Object.values(metrics).map((metric) => (
<div key={metric.name} className="flex justify-between gap-4">
<span>{metric.name}:</span>
<span className={metric.value > 2000 ? 'text-red-400' : 'text-green-400'}>
{metric.value.toFixed(2)}{metric.label || ''}
</span>
</div>
))}
</div>
</div>
);
};

export default PerformanceMonitor;
49 changes: 49 additions & 0 deletions src/components/performance/PrefetchingEngine.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
'use client';

import React, { useCallback, useEffect } from 'react';
import { useRouter } from 'next/navigation';
import { isSlowConnection } from '../../utils/performanceUtils';

interface PrefetchingEngineProps {
strategies?: ('hover' | 'proximity' | 'intent')[];
}

/**
* Engine to predictively prefetch routes based on user behavior and network conditions.
*/
const PrefetchingEngine: React.FC<PrefetchingEngineProps> = ({ strategies = ['hover'] }) => {
const router = useRouter();

const handleIntent = useCallback((href: string) => {
if (isSlowConnection()) {
console.log(`[Prefetching] Slow connection detected. Skipping prefetch for: ${href}`);
return;
}

console.log(`[Prefetching] Predictive prefetch started for: ${href}`);
router.prefetch(href);
}, [router]);

useEffect(() => {
if (!strategies.includes('hover')) return;

const handleMouseOver = (e: MouseEvent) => {
const target = e.target as HTMLElement;
const link = target.closest('a');

if (link && link.href && link.origin === window.location.origin) {
const href = link.pathname;
handleIntent(href);
}
};

document.addEventListener('mouseover', handleMouseOver);
return () => document.removeEventListener('mouseover', handleMouseOver);
}, [strategies, handleIntent]);

// Proximity strategy could be implemented with Intersection Observer on all links

return null; // Background component
};

export default PrefetchingEngine;
40 changes: 40 additions & 0 deletions src/hooks/usePerformanceOptimization.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
'use client';

import { useEffect, useRef } from 'react';

interface UsePerformanceOptions {
componentName: string;
threshold?: number; // threshold in ms for performance alerts
}

/**
* Hook to monitor component render performance and identify potential bottlenecks.
*/
export const usePerformanceOptimization = ({ componentName, threshold = 16 }: UsePerformanceOptions) => {
const startTime = useRef(performance.now());

useEffect(() => {
const endTime = performance.now();
const duration = endTime - startTime.current;

if (duration > threshold) {
console.warn(`[Performance Warning] Component "${componentName}" took ${duration.toFixed(2)}ms to mount. Threshold is ${threshold}ms.`);
}

// Capture render count for optimization analysis
// In a real scenario, this could be sent to a tracking service
}, [componentName, threshold]);

// Utility to track interaction performance within the component
const trackInteraction = (actionName: string, action: () => void) => {
const start = performance.now();
action();
const end = performance.now();
const duration = end - start;
if (duration > threshold) {
console.warn(`[Performance Warning] Interaction "${actionName}" in "${componentName}" took ${duration.toFixed(2)}ms.`);
}
};

return { trackInteraction };
};
66 changes: 66 additions & 0 deletions src/services/bundleOptimizer.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
/**
* Service for bundle analysis and dynamic loading optimization.
*/

export interface BundleChunk {
id: string;
name: string;
size?: number;
priority: 'high' | 'medium' | 'low';
}

class BundleOptimizer {
private chunks: Map<string, BundleChunk> = new Map();

/**
* Registers a chunk for monitoring.
*/
registerChunk(chunk: BundleChunk) {
this.chunks.set(chunk.id, chunk);
}

/**
* Recommends a loading strategy based on chunk priority and network conditions.
*/
getLoadingStrategy(chunkId: string, isSlow: boolean) {
const chunk = this.chunks.get(chunkId);
if (!chunk) return 'default';

if (isSlow) {
return chunk.priority === 'high' ? 'eager' : 'lazy';
}

return chunk.priority === 'low' ? 'lazy' : 'eager';
}

/**
* Identifies large chunks that might need further code splitting.
*/
analyzeChunkSizes(threshold: number = 200) { // threshold in KB
const heavyChunks = Array.from(this.chunks.values()).filter(
(chunk) => chunk.size && chunk.size > threshold
);

if (heavyChunks.length > 0) {
console.warn(`[Bundle Analysis] Found ${heavyChunks.length} heavy chunks (> ${threshold}KB). Consider further code splitting.`);
heavyChunks.forEach((chunk) => {
console.warn(` - Chunk: ${chunk.name} (${chunk.size}KB)`);
});
}

return heavyChunks;
}

/**
* Performs basic bundle size reporting.
*/
reportBundleHealth() {
const totalChunks = this.chunks.size;
const totalSize = Array.from(this.chunks.values()).reduce((acc, c) => acc + (c.size || 0), 0);

console.log(`[Bundle Optimizer] Monitoring ${totalChunks} logical chunks. Total estimated size: ${totalSize}KB.`);
this.analyzeChunkSizes();
}
}

export const bundleOptimizer = new BundleOptimizer();
8 changes: 4 additions & 4 deletions src/utils/i18nUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,9 @@
* i18n Utilities - Helper functions for formatting dates, numbers, currencies, etc.
*/

import type { LanguageCode, CulturalPreferences, LocaleConfig } from '@/locales/types';
import type { LanguageCode, CulturalPreferences } from '@/locales/types';
import { getLocaleConfig } from '@/locales/config';
import { format, formatDistanceToNow, formatRelative, type Locale } from 'date-fns';
import { format, formatDistanceToNow, type Locale } from 'date-fns';
import { enUS, es, fr, de, ar, he, ja, zhCN, ptBR, ru, it, ko } from 'date-fns/locale';

// Date-fns locale mapping
Expand Down Expand Up @@ -181,7 +181,7 @@ export function parseNumber(
const prefs = getCulturalPreferences(language);

// Replace localized separators with standard ones
let normalized = value
const normalized = value
.replace(new RegExp(`\\${prefs.thousandsSeparator}`, 'g'), '')
.replace(new RegExp(`\\${prefs.decimalSeparator}`, 'g'), '.');

Expand Down Expand Up @@ -235,7 +235,7 @@ export function formatDuration(
seconds: number,
language: LanguageCode
): string {
const config = getLocaleConfig(language);
getLocaleConfig(language);

const hours = Math.floor(seconds / 3600);
const minutes = Math.floor((seconds % 3600) / 60);
Expand Down
Loading