mirror of
https://github.com/Dvorinka/MyClubServer.git
synced 2026-06-04 02:32:57 +00:00
167 lines
6.1 KiB
TypeScript
167 lines
6.1 KiB
TypeScript
import React from 'react';
|
|
import ReactDOM from 'react-dom/client';
|
|
import './index.css';
|
|
import './styles/global-enhancements.css';
|
|
import './styles/admin-enhancements.css';
|
|
import './styles/home-style-pack.css';
|
|
import './styles/sparta-styles.css';
|
|
// Quill editor styles (MUST be imported globally) - CRITICAL for rich text editor
|
|
import 'quill/dist/quill.snow.css';
|
|
import 'react-image-crop/dist/ReactCrop.css';
|
|
// Custom editor styles AFTER quill base styles to ensure proper override
|
|
import './styles/custom-editor.css';
|
|
import { theme } from './App';
|
|
import AppLazy from './App.lazy';
|
|
import { ColorModeScript } from '@chakra-ui/react';
|
|
import { HelmetProvider } from 'react-helmet-async';
|
|
import reportWebVitals from './reportWebVitals';
|
|
import * as serviceWorkerRegistration from './serviceWorkerRegistration';
|
|
import { promptUserToUpdate } from './serviceWorkerRegistration';
|
|
import { installGlobalErrorHandlers, reportError } from './services/errorReporter';
|
|
// Cookie consent utilities
|
|
type Consent = { analytics?: boolean };
|
|
const getConsent = (): Consent | null => {
|
|
try {
|
|
const raw = localStorage.getItem('cookie_consent');
|
|
return raw ? JSON.parse(raw) : null;
|
|
} catch {
|
|
return null;
|
|
}
|
|
};
|
|
const onConsentChange = (cb: (c: Consent) => void) => {
|
|
window.addEventListener('cookie-consent-change', (e: Event) => {
|
|
const detail = (e as CustomEvent).detail as Consent;
|
|
cb(detail || {});
|
|
});
|
|
};
|
|
// Example analytics initializer (placeholder)
|
|
const initAnalytics = () => {
|
|
// Place your analytics loader here (e.g., GA/Matomo), gated by consent.analytics
|
|
// This function will be called only when analytics consent is granted.
|
|
};
|
|
|
|
// Error Boundary component
|
|
class ErrorBoundary extends React.Component<{children: React.ReactNode}, {hasError: boolean}> {
|
|
constructor(props: {children: React.ReactNode}) {
|
|
super(props);
|
|
this.state = { hasError: false };
|
|
}
|
|
|
|
static getDerivedStateFromError() {
|
|
return { hasError: true };
|
|
}
|
|
|
|
componentDidCatch(error: Error, errorInfo: React.ErrorInfo) {
|
|
reportError({ message: error.message, stack: error.stack, component: 'ErrorBoundary', context: { react: errorInfo.componentStack } });
|
|
console.error('Error caught by ErrorBoundary:', error, errorInfo);
|
|
}
|
|
|
|
render() {
|
|
if (this.state.hasError) {
|
|
return (
|
|
<div style={{ padding: '20px', fontFamily: 'Arial, sans-serif' }}>
|
|
<h2>Something went wrong.</h2>
|
|
<p>Please refresh the page or try again later.</p>
|
|
</div>
|
|
);
|
|
}
|
|
|
|
return this.props.children;
|
|
}
|
|
}
|
|
|
|
installGlobalErrorHandlers();
|
|
window.addEventListener('unhandledrejection', (event) => {
|
|
const reason: any = (event as any).reason;
|
|
const message = typeof reason === 'string' ? reason : (reason?.message || 'Unhandled rejection');
|
|
const stack = typeof reason === 'object' ? (reason?.stack || '') : '';
|
|
reportError({ message, stack });
|
|
});
|
|
|
|
const rootElement = document.getElementById('root');
|
|
|
|
if (!rootElement) {
|
|
// Failed to find the root element
|
|
} else {
|
|
try {
|
|
// Initialize CSS variables immediately to prevent flash of fallback colors
|
|
const r = document.documentElement;
|
|
|
|
// Try to load cached colors from localStorage for instant display
|
|
let cachedColors: any = null;
|
|
try {
|
|
const cached = localStorage.getItem('club_theme_cache');
|
|
if (cached) {
|
|
cachedColors = JSON.parse(cached);
|
|
// Use cache if it's less than 24 hours old
|
|
const age = Date.now() - (cachedColors.timestamp || 0);
|
|
if (age > 24 * 60 * 60 * 1000) {
|
|
cachedColors = null; // Cache expired
|
|
}
|
|
}
|
|
} catch (e) {
|
|
// Ignore localStorage errors
|
|
}
|
|
|
|
// Set CSS variables - use cached values if available, otherwise defaults
|
|
r.style.setProperty('--club-primary', cachedColors?.primary || '#0b5cff');
|
|
r.style.setProperty('--club-secondary', cachedColors?.secondary || '#ffd200');
|
|
r.style.setProperty('--club-accent', cachedColors?.accent || '#141414');
|
|
r.style.setProperty('--club-text-on-primary', cachedColors?.textOnPrimary || '#ffffff');
|
|
if (cachedColors?.textOnSecondary) r.style.setProperty('--club-text-on-secondary', cachedColors.textOnSecondary);
|
|
if (cachedColors?.textOnAccent) r.style.setProperty('--club-text-on-accent', cachedColors.textOnAccent);
|
|
r.style.setProperty('--club-bg-light', cachedColors?.background || '#ffffff');
|
|
r.style.setProperty('--club-text-light', cachedColors?.text || '#000000');
|
|
|
|
// Load fonts
|
|
const link = document.createElement('link');
|
|
link.href = 'https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&family=Montserrat:wght@600;700;800&display=swap';
|
|
link.rel = 'stylesheet';
|
|
document.head.appendChild(link);
|
|
|
|
const root = ReactDOM.createRoot(
|
|
document.getElementById('root') as HTMLElement
|
|
);
|
|
|
|
root.render(
|
|
<React.StrictMode>
|
|
<HelmetProvider>
|
|
<ErrorBoundary>
|
|
{/* Ensure color mode (light/dark) persists and matches Chakra config before UI renders */}
|
|
<ColorModeScript initialColorMode={theme.config.initialColorMode} />
|
|
<AppLazy />
|
|
</ErrorBoundary>
|
|
</HelmetProvider>
|
|
</React.StrictMode>
|
|
);
|
|
// App rendered
|
|
// Initialize analytics if consent allows
|
|
const current = getConsent();
|
|
if (current?.analytics) {
|
|
initAnalytics();
|
|
}
|
|
// React to future consent changes
|
|
onConsentChange((c) => {
|
|
if (c?.analytics) {
|
|
initAnalytics();
|
|
}
|
|
});
|
|
} catch (error) {
|
|
// Optionally report to monitoring service
|
|
// console.error('Error rendering application:', error);
|
|
rootElement.innerHTML = `
|
|
<div style="padding: 20px; font-family: Arial, sans-serif; color: #721c24; background-color: #f8d7da; border: 1px solid #f5c6cb; border-radius: 4px;">
|
|
<h2>Application Error</h2>
|
|
<p>${String(error)}</p>
|
|
<p>Please check the console for more details and refresh the page.</p>
|
|
</div>
|
|
`;
|
|
}
|
|
}
|
|
|
|
// Report web vitals (disabled logging by default). Hook up your analytics here if needed.
|
|
reportWebVitals();
|
|
|
|
// Enable PWA service worker with user prompt on updates
|
|
serviceWorkerRegistration.register({ onUpdate: promptUserToUpdate });
|