Fix Scroll.

This commit is contained in:
2025-02-10 12:53:25 +01:00
parent 4b74640d87
commit d23c381913
10 changed files with 454 additions and 129 deletions
+1 -1
View File
@@ -82,7 +82,7 @@ define(['./workbox-4a2e5f00'], (function (workbox) { 'use strict';
"revision": "3ca0b8505b4bec776b69afdba2768812" "revision": "3ca0b8505b4bec776b69afdba2768812"
}, { }, {
"url": "index.html", "url": "index.html",
"revision": "0.364nagk4too" "revision": "0.p5tdckiicb"
}], {}); }], {});
workbox.cleanupOutdatedCaches(); workbox.cleanupOutdatedCaches();
workbox.registerRoute(new workbox.NavigationRoute(workbox.createHandlerBoundToURL("index.html"), { workbox.registerRoute(new workbox.NavigationRoute(workbox.createHandlerBoundToURL("index.html"), {
+1 -1
View File
File diff suppressed because one or more lines are too long
+17 -12
View File
@@ -7,6 +7,8 @@ import AppRoutes from './routes';
import ErrorBoundary from './components/ErrorBoundary'; import ErrorBoundary from './components/ErrorBoundary';
import CookieBanner from './components/CookieBanner'; import CookieBanner from './components/CookieBanner';
import PageTransition from './components/PageTransition'; import PageTransition from './components/PageTransition';
import { ScrollProvider } from './components/ScrollContext';
import DebugOverlay from './components/DebugOverlay';
import { logPageView } from './utils/analytics'; import { logPageView } from './utils/analytics';
import './styles/scrollbar.css'; import './styles/scrollbar.css';
@@ -20,18 +22,21 @@ function App() {
<HelmetProvider> <HelmetProvider>
<ErrorBoundary> <ErrorBoundary>
<BrowserRouter> <BrowserRouter>
<div className="flex flex-col min-h-screen bg-background text-foreground"> <ScrollProvider>
<Layout> <div className="flex flex-col min-h-screen bg-background text-foreground relative">
<ErrorBoundary> <Layout>
<Suspense fallback={null}> <ErrorBoundary>
<PageTransition> <Suspense fallback={null}>
<AppRoutes /> <PageTransition>
</PageTransition> <AppRoutes />
</Suspense> </PageTransition>
</ErrorBoundary> </Suspense>
</Layout> </ErrorBoundary>
<CookieBanner /> </Layout>
</div> <CookieBanner />
<DebugOverlay />
</div>
</ScrollProvider>
</BrowserRouter> </BrowserRouter>
</ErrorBoundary> </ErrorBoundary>
</HelmetProvider> </HelmetProvider>
+41
View File
@@ -0,0 +1,41 @@
import React, { useState, useEffect } from 'react';
import { useScrollContext } from './ScrollContext';
interface KeyboardEvent {
key: string;
}
const DebugOverlay: React.FC = () => {
const [showDebug, setShowDebug] = useState<boolean>(false);
const { isTransitioning } = useScrollContext();
useEffect(() => {
const handleKeyPress = (e: KeyboardEvent) => {
if (e.key === 'd') {
setShowDebug((prev: boolean) => !prev);
}
};
window.addEventListener('keydown', handleKeyPress);
return () => window.removeEventListener('keydown', handleKeyPress);
}, []);
if (!showDebug) return null;
return (
<div className="fixed bottom-4 right-4 bg-black/80 text-white p-4 rounded-lg z-[100] font-mono text-sm">
<div>Transitioning: {isTransitioning ? 'Yes' : 'No'}</div>
<div>Body Overflow: {document.body.style.overflow}</div>
<div>Scroll Position: {window.scrollY}</div>
<div>View Height: {window.innerHeight}</div>
<button
onClick={() => document.body.style.overflow = ''}
className="bg-blue-500 px-2 py-1 rounded mt-2"
>
Reset Overflow
</button>
</div>
);
};
export default DebugOverlay;
+122 -71
View File
@@ -1,13 +1,24 @@
// components/Layout.tsx // Layout.tsx
import { useEffect, useState, ReactNode, useCallback } from "react"; import { useEffect, useState, ReactNode } from "react";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import { motion, AnimatePresence } from "framer-motion"; import { motion, AnimatePresence } from "framer-motion";
import { Menu, X, Home, User, Briefcase, BookOpen, Phone, LayoutDashboard } from "lucide-react"; import {
Menu,
X,
Home,
User,
Briefcase,
BookOpen,
Phone,
LayoutDashboard,
} from "lucide-react";
import { supabase } from "../utils/supabaseClient"; import { supabase } from "../utils/supabaseClient";
import { ContactInfo } from "./ContactInfo"; import { ContactInfo } from "./ContactInfo";
import { NavLink } from "./NavLink"; import { NavLink } from "./NavLink";
import LanguageSwitcher from "./LanguageSwitcher"; import LanguageSwitcher from "./LanguageSwitcher";
import { Link, useLocation } from "react-router-dom"; import { Link, useLocation } from "react-router-dom";
import Footer from "./Footer";
import { useScrollContext } from "./ScrollContext";
interface LayoutProps { interface LayoutProps {
children: ReactNode; children: ReactNode;
@@ -16,41 +27,27 @@ interface LayoutProps {
const Layout = ({ children }: LayoutProps) => { const Layout = ({ children }: LayoutProps) => {
const { t } = useTranslation(); const { t } = useTranslation();
const [isAdmin, setIsAdmin] = useState(false); const [isAdmin, setIsAdmin] = useState(false);
const [isOpen, setIsOpen] = useState(false);
const [scrolled, setScrolled] = useState(false); const [scrolled, setScrolled] = useState(false);
const [isMounted, setIsMounted] = useState(false);
const location = useLocation(); const location = useLocation();
const { isMenuOpen, setIsMenuOpen } = useScrollContext();
// Memoized toggle function // Initial mount
const toggleMenu = useCallback((force?: boolean) => { useEffect(() => {
setIsOpen(prev => typeof force === 'boolean' ? force : !prev); setIsMounted(true);
}, []); }, []);
// Route change handler // Schließe das Menü nur beim Routenwechsel
useEffect(() => { useEffect(() => {
toggleMenu(false); setIsMenuOpen(false);
}, [location.pathname, toggleMenu]); }, [location.pathname, setIsMenuOpen]);
// Scroll lock effect // Toggle Menu
useEffect(() => { const toggleMenu = () => {
const body = document.body; setIsMenuOpen(!isMenuOpen);
if (isOpen) { };
const scrollY = window.scrollY;
body.style.position = 'fixed';
body.style.top = `-${scrollY}px`;
body.style.width = '100%';
body.style.overflow = 'hidden';
} else {
const scrollY = body.style.top;
body.style.position = '';
body.style.top = '';
body.style.width = '';
body.style.overflow = '';
if (scrollY) {
window.scrollTo(0, parseInt(scrollY || '0') * -1);
}
}
}, [isOpen]);
// Prüfe Admin-Status und registriere den Scroll-Handler
useEffect(() => { useEffect(() => {
const checkAdmin = async () => { const checkAdmin = async () => {
const { data, error } = await supabase.auth.getSession(); const { data, error } = await supabase.auth.getSession();
@@ -82,11 +79,31 @@ const Layout = ({ children }: LayoutProps) => {
}, []); }, []);
const navigationLinks = [ const navigationLinks = [
{ path: "/", label: t("navigation.main.home"), icon: <Home className="h-5 w-5" /> }, {
{ path: "/about", label: t("navigation.main.about"), icon: <User className="h-5 w-5" /> }, path: "/",
{ path: "/portfolio", label: t("navigation.main.portfolio"), icon: <Briefcase className="h-5 w-5" /> }, label: t("navigation.main.home"),
{ path: "/blog", label: t("navigation.main.blog"), icon: <BookOpen className="h-5 w-5" /> }, icon: <Home className="h-5 w-5" />,
{ path: "/contact", label: t("navigation.main.contact"), icon: <Phone className="h-5 w-5" /> }, },
{
path: "/about",
label: t("navigation.main.about"),
icon: <User className="h-5 w-5" />,
},
{
path: "/portfolio",
label: t("navigation.main.portfolio"),
icon: <Briefcase className="h-5 w-5" />,
},
{
path: "/blog",
label: t("navigation.main.blog"),
icon: <BookOpen className="h-5 w-5" />,
},
{
path: "/contact",
label: t("navigation.main.contact"),
icon: <Phone className="h-5 w-5" />,
},
...(isAdmin ...(isAdmin
? [ ? [
{ {
@@ -98,11 +115,17 @@ const Layout = ({ children }: LayoutProps) => {
: []), : []),
]; ];
if (!isMounted) {
return null;
}
return ( return (
<div className="min-h-screen bg-zinc-900 flex flex-col"> // Relativer Container, um z-index-Steuerung zu ermöglichen
<div className="min-h-screen bg-zinc-900 flex flex-col relative">
{/* Navigation oberste Ebene */}
<nav <nav
className={` className={`
fixed w-full z-[80] transition-all duration-300 fixed w-full z-40 transition-all duration-300
${scrolled ${scrolled
? "bg-zinc-900/80 backdrop-blur supports-[backdrop-filter]:bg-zinc-900/80" ? "bg-zinc-900/80 backdrop-blur supports-[backdrop-filter]:bg-zinc-900/80"
: "bg-transparent" : "bg-transparent"
@@ -113,6 +136,7 @@ const Layout = ({ children }: LayoutProps) => {
> >
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8"> <div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
<div className="flex justify-between items-center h-16"> <div className="flex justify-between items-center h-16">
{/* Logo */}
<Link to="/" className="flex items-center space-x-2 group shrink-0"> <Link to="/" className="flex items-center space-x-2 group shrink-0">
<motion.img <motion.img
src="/logo.png" src="/logo.png"
@@ -122,47 +146,66 @@ const Layout = ({ children }: LayoutProps) => {
transition={{ type: "spring", stiffness: 400, damping: 10 }} transition={{ type: "spring", stiffness: 400, damping: 10 }}
/> />
</Link> </Link>
{/* Desktop Navigation */}
<div className="hidden md:flex items-center gap-2"> <div className="hidden md:flex items-center gap-2">
{navigationLinks.map(({ path, label, icon }) => ( {navigationLinks.map(({ path, label, icon }) => (
<NavLink key={path} to={path} icon={icon} label={label} className="text-sm" /> <NavLink
key={path}
to={path}
icon={icon}
label={label}
className="text-sm"
/>
))} ))}
<div className="ml-4 text-zinc-400 pl-2 border-l border-zinc-700"> <div className="ml-4 text-zinc-400 pl-2 border-l border-zinc-700">
<LanguageSwitcher /> <LanguageSwitcher />
</div> </div>
</div> </div>
{/* Mobile Menu Button */}
<motion.button <motion.button
className="md:hidden relative z-[85] p-2 rounded-lg text-zinc-400 hover:text-white hover:bg-zinc-800/50 transition-colors" className="md:hidden relative z-50 p-2 rounded-lg text-zinc-400 hover:text-white hover:bg-zinc-800/50 transition-colors"
onClick={() => toggleMenu()} onClick={toggleMenu}
aria-expanded={isOpen} aria-expanded={isMenuOpen}
aria-controls="mobile-menu" aria-controls="mobile-menu"
aria-label={isOpen ? "Close menu" : "Open menu"} aria-label={isMenuOpen ? "Close menu" : "Open menu"}
whileTap={{ scale: 0.95 }} whileTap={{ scale: 0.95 }}
> >
{isOpen ? <X className="h-6 w-6" /> : <Menu className="h-6 w-6" />} {isMenuOpen ? <X className="h-6 w-6" /> : <Menu className="h-6 w-6" />}
</motion.button> </motion.button>
</div> </div>
</div> </div>
</nav>
<AnimatePresence> {/* Mobile Menu */}
{isOpen && ( <AnimatePresence>
<> {isMenuOpen && (
<motion.div <>
initial={{ opacity: 0 }} {/* Backdrop: Sichtbar, aber mit pointer-events-none blockiert er keine Interaktionen */}
animate={{ opacity: 1 }} <motion.div
exit={{ opacity: 0 }} initial={{ opacity: 0 }}
transition={{ duration: 0.2 }} animate={{ opacity: 1 }}
className="fixed inset-0 bg-black/50 md:hidden z-[75]" exit={{ opacity: 0 }}
onClick={() => toggleMenu(false)} transition={{ duration: 0.2 }}
/> className="fixed inset-0 bg-black/50 md:hidden z-20 pointer-events-none"
<motion.div />
initial={{ x: "100%" }}
animate={{ x: 0 }} {/* Sidebar oberhalb des Hauptinhalts */}
exit={{ x: "100%" }} <motion.div
transition={{ type: "spring", stiffness: 300, damping: 30 }} initial={{ x: "100%" }}
className="fixed right-0 top-0 h-full w-80 bg-zinc-900 shadow-xl md:hidden overflow-y-auto border-l border-zinc-800 z-[75]" animate={{ x: 0 }}
> exit={{ x: "100%" }}
<div className="flex flex-col h-full pt-16"> transition={{ type: "spring", stiffness: 300, damping: 30 }}
<div className="flex-1 overflow-y-auto"> className="fixed right-0 top-0 h-full w-80 bg-zinc-900 shadow-xl md:hidden z-50 border-l border-zinc-800"
>
{/* Header-Spacer für die Navigation */}
<div className="h-16" />
{/* Scrollbarer Sidebar-Inhalt */}
<div className="h-[calc(100vh_-_4rem)] overflow-y-auto">
<div className="flex flex-col h-full">
<div className="flex-1">
<ContactInfo /> <ContactInfo />
<div className="py-4 px-4"> <div className="py-4 px-4">
{navigationLinks.map(({ path, label, icon }) => ( {navigationLinks.map(({ path, label, icon }) => (
@@ -171,7 +214,8 @@ const Layout = ({ children }: LayoutProps) => {
to={path} to={path}
icon={icon} icon={icon}
label={label} label={label}
onClick={() => toggleMenu(false)} // Alternativ: Hier könnte ein Klick auch das Menü schließen
onClick={() => setIsMenuOpen(false)}
className="flex items-center w-full mb-1" className="flex items-center w-full mb-1"
/> />
))} ))}
@@ -179,17 +223,24 @@ const Layout = ({ children }: LayoutProps) => {
</div> </div>
<div className="border-t border-zinc-800 p-6 mt-auto"> <div className="border-t border-zinc-800 p-6 mt-auto">
<div className="flex items-center justify-between"> <div className="flex items-center justify-between">
<span className="text-sm text-zinc-500">Sprache ändern</span> <span className="text-sm text-zinc-500">
Sprache ändern
</span>
<LanguageSwitcher /> <LanguageSwitcher />
</div> </div>
</div> </div>
</div> </div>
</motion.div> </div>
</> </motion.div>
)} </>
</AnimatePresence> )}
</nav> </AnimatePresence>
{children}
{/* Hauptinhalt hier kann normal gescrollt werden */}
<main className="flex-1 pt-16 relative z-30">{children}</main>
{/* Footer */}
<Footer />
</div> </div>
); );
}; };
+6 -6
View File
@@ -1,6 +1,5 @@
// components/PageContainer.tsx // components/PageContainer.tsx
import React from 'react'; import React, { Suspense } from 'react';
import Footer from './Footer';
interface PageContainerProps { interface PageContainerProps {
children: React.ReactNode; children: React.ReactNode;
@@ -9,10 +8,11 @@ interface PageContainerProps {
const PageContainer = ({ children }: PageContainerProps) => { const PageContainer = ({ children }: PageContainerProps) => {
return ( return (
<div className="flex flex-col min-h-screen"> <div className="flex flex-col min-h-screen">
<main className="flex-1 pt-16"> <Suspense fallback={null}>
{children} <main className="flex-1 pt-16">
</main> {children}
<Footer /> </main>
</Suspense>
</div> </div>
); );
}; };
+52 -25
View File
@@ -1,7 +1,8 @@
"use client" "use client"
import React, { useEffect, useState } from "react" import React, { useEffect, useState, useCallback } from "react"
import { motion, AnimatePresence, cubicBezier } from "framer-motion" import { motion, AnimatePresence, cubicBezier } from "framer-motion"
import { useLocation } from "react-router-dom" import { useLocation } from "react-router-dom"
import { useScrollContext } from './ScrollContext'
interface PageTransitionProps { interface PageTransitionProps {
children: React.ReactNode children: React.ReactNode
@@ -34,55 +35,81 @@ const Logo = () => (
const PageTransition = ({ children }: PageTransitionProps) => { const PageTransition = ({ children }: PageTransitionProps) => {
const location = useLocation() const location = useLocation()
const [isAnimating, setIsAnimating] = useState(false) const [showLogo, setShowLogo] = useState(false)
const [showContent, setShowContent] = useState(true) const [showContent, setShowContent] = useState(true)
const { setIsTransitioning, lockScroll, unlockScroll } = useScrollContext()
const resetScroll = useCallback(() => {
requestAnimationFrame(() => {
window.scrollTo({
top: 0,
behavior: 'instant'
});
});
}, []);
const handleTransitionStart = useCallback(() => {
setIsTransitioning(true);
lockScroll();
setShowContent(false);
setShowLogo(true);
}, [setIsTransitioning, lockScroll]);
const handleTransitionEnd = useCallback(() => {
setShowContent(true);
setShowLogo(false);
setIsTransitioning(false);
unlockScroll();
resetScroll();
}, [setIsTransitioning, unlockScroll, resetScroll]);
useEffect(() => { useEffect(() => {
setIsAnimating(true) const logoTimer = setTimeout(() => {
setShowContent(false) setShowLogo(false);
}, 800);
const contentTimer = setTimeout(() => { const transitionTimer = setTimeout(() => {
setShowContent(true) handleTransitionEnd();
}, 800) }, 1000);
const animationTimer = setTimeout(() => { handleTransitionStart();
setIsAnimating(false)
}, 1000)
return () => { return () => {
clearTimeout(contentTimer) clearTimeout(logoTimer);
clearTimeout(animationTimer) clearTimeout(transitionTimer);
} unlockScroll();
}, [location.pathname]) };
}, [location.pathname, handleTransitionStart, handleTransitionEnd, unlockScroll]);
return ( return (
<div className="relative min-h-screen bg-zinc-900"> <div className="relative min-h-screen bg-zinc-900">
{/* Overlay mit Logo */} <AnimatePresence mode="wait">
<AnimatePresence> {showLogo && (
{isAnimating && (
<motion.div <motion.div
key="overlay" className="fixed inset-0 z-[60] bg-zinc-900 pointer-events-none"
className="fixed inset-0 z-[60] bg-zinc-900 flex items-center justify-center"
initial={{ opacity: 0 }} initial={{ opacity: 0 }}
animate={{ opacity: 1 }} animate={{ opacity: 1 }}
exit={{ opacity: 0 }} exit={{ opacity: 0 }}
transition={{ duration: 0.3 }} transition={{ duration: 0.3, ease: customEase }}
> >
<Logo /> <div className="absolute inset-0 flex items-center justify-center">
<Logo />
</div>
</motion.div> </motion.div>
)} )}
</AnimatePresence> </AnimatePresence>
{/* Content */}
<AnimatePresence mode="wait"> <AnimatePresence mode="wait">
{showContent && ( {showContent && (
<motion.div <motion.div
key={location.pathname} className="relative z-0 min-h-screen"
className="relative z-0"
initial={{ opacity: 0 }} initial={{ opacity: 0 }}
animate={{ opacity: 1 }} animate={{ opacity: 1 }}
exit={{ opacity: 0 }} exit={{ opacity: 0 }}
transition={{ duration: 0.3 }} transition={{
duration: 0.3,
ease: customEase,
}}
> >
{children} {children}
</motion.div> </motion.div>
+56
View File
@@ -0,0 +1,56 @@
// ScrollContext.tsx
import React, { createContext, useContext, useState, useCallback } from 'react';
interface ScrollContextType {
isTransitioning: boolean;
setIsTransitioning: (value: boolean) => void;
isMenuOpen: boolean;
setIsMenuOpen: (value: boolean) => void;
lockScroll: () => void;
unlockScroll: () => void;
}
const ScrollContext = createContext<ScrollContextType | undefined>(undefined);
export const ScrollProvider = ({ children }: { children: React.ReactNode }) => {
const [isTransitioning, setIsTransitioning] = useState(false);
const [isMenuOpen, setIsMenuOpen] = useState(false);
const lockScroll = useCallback(() => {
const scrollY = window.scrollY;
document.body.style.position = 'fixed';
document.body.style.top = `-${scrollY}px`;
document.body.style.width = '100%';
}, []);
const unlockScroll = useCallback(() => {
const scrollY = document.body.style.top;
document.body.style.position = '';
document.body.style.top = '';
document.body.style.width = '';
window.scrollTo(0, parseInt(scrollY || '0') * -1);
}, []);
return (
<ScrollContext.Provider
value={{
isTransitioning,
setIsTransitioning,
isMenuOpen,
setIsMenuOpen,
lockScroll,
unlockScroll
}}
>
{children}
</ScrollContext.Provider>
);
};
export const useScrollContext = () => {
const context = useContext(ScrollContext);
if (context === undefined) {
throw new Error('useScrollContext must be used within a ScrollProvider');
}
return context;
};
+34
View File
@@ -0,0 +1,34 @@
// hooks/useScrollLock.ts
import { useEffect } from 'react';
import { useLocation } from 'react-router-dom';
import { useScrollContext } from '../components/ScrollContext';
export const useScrollLock = (lock: boolean) => {
const location = useLocation();
const { isTransitioning, lockScroll, unlockScroll } = useScrollContext();
// Handle lock state changes
useEffect(() => {
if (lock && !isTransitioning) {
lockScroll();
} else {
unlockScroll();
}
}, [lock, isTransitioning, lockScroll, unlockScroll]);
// Handle location changes
useEffect(() => {
const timeout = setTimeout(() => {
unlockScroll();
window.scrollTo(0, 0);
}, 100);
return () => clearTimeout(timeout);
}, [location.pathname, unlockScroll]);
// Cleanup on unmount
useEffect(() => {
return () => {
unlockScroll();
};
}, [unlockScroll]);
};
+123 -12
View File
@@ -1,5 +1,5 @@
// routes.tsx // routes.tsx
import React from 'react'; import React, { Suspense } from 'react';
import { Routes, Route } from 'react-router-dom'; import { Routes, Route } from 'react-router-dom';
import RouteErrorBoundary from './components/RouteErrorBoundary'; import RouteErrorBoundary from './components/RouteErrorBoundary';
import PageContainer from './components/PageContainer'; import PageContainer from './components/PageContainer';
@@ -20,17 +20,128 @@ const TermsPage = React.lazy(() => import('./pages/TermsPage'));
const AppRoutes = () => { const AppRoutes = () => {
return ( return (
<Routes> <Routes>
<Route path="/" element={<PageContainer><HomePage /></PageContainer>} errorElement={<RouteErrorBoundary />} /> <Route
<Route path="/about" element={<PageContainer><AboutPage /></PageContainer>} errorElement={<RouteErrorBoundary />} /> path="/"
<Route path="/portfolio" element={<PageContainer><PortfolioPage /></PageContainer>} errorElement={<RouteErrorBoundary />} /> element={
<Route path="/portfolio/:slug" element={<PageContainer><ProjectPage /></PageContainer>} errorElement={<RouteErrorBoundary />} /> <PageContainer>
<Route path="/blog" element={<PageContainer><BlogPage /></PageContainer>} errorElement={<RouteErrorBoundary />} /> <Suspense fallback={null}>
<Route path="/blog/:slug" element={<PageContainer><BlogPostPage /></PageContainer>} errorElement={<RouteErrorBoundary />} /> <HomePage />
<Route path="/contact" element={<PageContainer><ContactPage /></PageContainer>} errorElement={<RouteErrorBoundary />} /> </Suspense>
<Route path="/login" element={<PageContainer><LoginPage /></PageContainer>} errorElement={<RouteErrorBoundary />} /> </PageContainer>
<Route path="/dashboard" element={<PageContainer><DashboardPage /></PageContainer>} errorElement={<RouteErrorBoundary />} /> }
<Route path="/privacy" element={<PageContainer><PrivacyPolicyPage /></PageContainer>} errorElement={<RouteErrorBoundary />} /> errorElement={<RouteErrorBoundary />}
<Route path="/terms" element={<PageContainer><TermsPage /></PageContainer>} errorElement={<RouteErrorBoundary />} /> />
<Route
path="/about"
element={
<PageContainer>
<Suspense fallback={null}>
<AboutPage />
</Suspense>
</PageContainer>
}
errorElement={<RouteErrorBoundary />}
/>
{/* Rest of the routes follow the same pattern */}
<Route
path="/portfolio"
element={
<PageContainer>
<Suspense fallback={null}>
<PortfolioPage />
</Suspense>
</PageContainer>
}
errorElement={<RouteErrorBoundary />}
/>
<Route
path="/portfolio/:slug"
element={
<PageContainer>
<Suspense fallback={null}>
<ProjectPage />
</Suspense>
</PageContainer>
}
errorElement={<RouteErrorBoundary />}
/>
<Route
path="/blog"
element={
<PageContainer>
<Suspense fallback={null}>
<BlogPage />
</Suspense>
</PageContainer>
}
errorElement={<RouteErrorBoundary />}
/>
<Route
path="/blog/:slug"
element={
<PageContainer>
<Suspense fallback={null}>
<BlogPostPage />
</Suspense>
</PageContainer>
}
errorElement={<RouteErrorBoundary />}
/>
<Route
path="/contact"
element={
<PageContainer>
<Suspense fallback={null}>
<ContactPage />
</Suspense>
</PageContainer>
}
errorElement={<RouteErrorBoundary />}
/>
<Route
path="/login"
element={
<PageContainer>
<Suspense fallback={null}>
<LoginPage />
</Suspense>
</PageContainer>
}
errorElement={<RouteErrorBoundary />}
/>
<Route
path="/dashboard"
element={
<PageContainer>
<Suspense fallback={null}>
<DashboardPage />
</Suspense>
</PageContainer>
}
errorElement={<RouteErrorBoundary />}
/>
<Route
path="/privacy"
element={
<PageContainer>
<Suspense fallback={null}>
<PrivacyPolicyPage />
</Suspense>
</PageContainer>
}
errorElement={<RouteErrorBoundary />}
/>
<Route
path="/terms"
element={
<PageContainer>
<Suspense fallback={null}>
<TermsPage />
</Suspense>
</PageContainer>
}
errorElement={<RouteErrorBoundary />}
/>
</Routes> </Routes>
); );
}; };