/** * @license * SPDX-License-Identifier: Apache-2.0 */ import { useEffect, useState, useMemo, lazy, Suspense } from 'react'; import ReactDOM from 'react-dom/client'; import { translations } from './data/translations'; import { Header } from './components/Header'; import { Footer } from './components/Footer'; import { MobileCTABar } from './components/MobileCTABar'; import { Breadcrumbs } from './components/Breadcrumbs'; import { FloatingCTA } from './components/FloatingCTA'; // Lazy load pages for code splitting const HomePage = lazy(() => import('./pages/HomePage').then(m => ({ default: m.HomePage }))); const AboutPage = lazy(() => import('./pages/AboutPage').then(m => ({ default: m.AboutPage }))); const ContactPage = lazy(() => import('./pages/ContactPage').then(m => ({ default: m.ContactPage }))); const SolutionsPage = lazy(() => import('./pages/SolutionsPage').then(m => ({ default: m.SolutionsPage }))); const ServicesPage = lazy(() => import('./pages/ServicesPage').then(m => ({ default: m.ServicesPage }))); const ProductsPage = lazy(() => import('./pages/ProductsPage').then(m => ({ default: m.ProductsPage }))); const PartnersPage = lazy(() => import('./pages/PartnersPage').then(m => ({ default: m.PartnersPage }))); const NewsPage = lazy(() => import('./pages/NewsPage').then(m => ({ default: m.NewsPage }))); const CaseStudiesPage = lazy(() => import('./pages/CaseStudiesPage').then(m => ({ default: m.CaseStudiesPage }))); const ResourcesPage = lazy(() => import('./pages/ResourcesPage').then(m => ({ default: m.ResourcesPage }))); const NotFoundPage = lazy(() => import('./pages/NotFoundPage').then(m => ({ default: m.NotFoundPage }))); // Custom hook to handle hash-based navigation const useHashNavigation = () => { const getHashParts = () => { // removes # and any leading/trailing slashes, then splits const parts = window.location.hash.replace(/^#\/?|\/$/g, '').split('/'); const path = `/${parts[0] || ''}`; const slug = parts.length > 1 ? parts.slice(1).join('/') : null; return { path, slug }; }; const [location, setLocation] = useState(getHashParts()); useEffect(() => { const handleHashChange = () => { setLocation(getHashParts()); window.scrollTo(0, 0); // Scroll to top on page change }; window.addEventListener('hashchange', handleHashChange); // Initial scroll to top window.scrollTo(0, 0); return () => window.removeEventListener('hashchange', handleHashChange); }, []); return location; }; // Loading component for Suspense fallback const LoadingSpinner = () => (
); // Main App Component function App() { const [lang, setLang] = useState('vi'); const [theme, setTheme] = useState('light'); const { path, slug } = useHashNavigation(); // Robust navigation handler to fix environment-specific routing issues useEffect(() => { const handleNavClick = (event: MouseEvent) => { const link = (event.target as HTMLElement).closest('a'); if (!link) { return; } // Get the raw href attribute to avoid browser interpretation issues const href = link.getAttribute('href'); // Check if it's an internal hash-based link if (href && href.startsWith('#/')) { // Prevent the browser's default navigation and stop the event from bubbling up event.preventDefault(); event.stopPropagation(); // Manually update the hash to trigger our hashchange listener if (window.location.hash !== href) { window.location.hash = href; } else { // If clicking the same link, just scroll to top window.scrollTo(0, 0); } } }; // Use the "capture" phase to ensure our handler runs first document.addEventListener('click', handleNavClick, true); return () => { document.removeEventListener('click', handleNavClick, true); }; }, []); useEffect(() => { // Set theme based on system preference initially const prefersDark = window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches; setTheme(prefersDark ? 'dark' : 'light'); // Listener for system theme changes const mediaQuery = window.matchMedia('(prefers-color-scheme: dark)'); const handleChange = (e) => setTheme(e.matches ? 'dark' : 'light'); mediaQuery.addEventListener('change', handleChange); return () => mediaQuery.removeEventListener('change', handleChange); }, []); useEffect(() => { document.body.setAttribute('data-theme', theme); }, [theme]); useEffect(() => { document.documentElement.lang = lang; }, [lang]); const t = useMemo(() => translations[lang], [lang]); const renderPage = () => { switch (path) { case '/': return ; case '/about-us': return ; case '/solutions': return ; case '/services': return ; case '/products': return ; case '/partners': return ; case '/case-studies': return ; case '/news': return ; case '/resources': return ; case '/contact': return ; default: return ; } }; return ( <>
}> {renderPage()}