From 7d378cb2d5e647d8efab6f88fc876baca858d445 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Veikko=20Lintuj=C3=A4rvi?=
Date: Fri, 7 Aug 2026 19:59:24 +0300
Subject: [PATCH] Improving route handling
---
package-lock.json | 58 +++++++++++++++++++++++++++++++++++++
package.json | 1 +
src/App.jsx | 20 +++++++++++--
src/components/Hero.jsx | 4 +--
src/components/Navbar.jsx | 21 ++++++--------
src/components/Projects.jsx | 4 +--
src/main.jsx | 5 +++-
vite.config.js | 8 +++++
8 files changed, 102 insertions(+), 19 deletions(-)
diff --git a/package-lock.json b/package-lock.json
index 0567ace..2556e96 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -11,6 +11,7 @@
"jotai": "^2.20.0",
"react": "^19.2.5",
"react-dom": "^19.2.5",
+ "react-router-dom": "^7.17.0",
"sass": "^1.99.0"
},
"devDependencies": {
@@ -1247,6 +1248,19 @@
"devOptional": true,
"license": "MIT"
},
+ "node_modules/cookie": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/cookie/-/cookie-1.1.1.tgz",
+ "integrity": "sha512-ei8Aos7ja0weRpFzJnEA9UHJ/7XQmqglbRwnf2ATjcB9Wq874VKH9kfjjirM6UhU2/E5fFYadylyhFldcqSidQ==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=18"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/express"
+ }
+ },
"node_modules/cross-spawn": {
"version": "7.0.6",
"dev": true,
@@ -2209,6 +2223,44 @@
"react": "^19.2.6"
}
},
+ "node_modules/react-router": {
+ "version": "7.17.0",
+ "resolved": "https://registry.npmjs.org/react-router/-/react-router-7.17.0.tgz",
+ "integrity": "sha512-FDELK7rTMlCHO5+reyXsPlmfr7N1F91lPHsWYfMEGQm/KQ+F4JFM8jGoeQDmDvdTs93Fw9aSilH+uKRb4/jXvQ==",
+ "license": "MIT",
+ "dependencies": {
+ "cookie": "^1.0.1",
+ "set-cookie-parser": "^2.6.0"
+ },
+ "engines": {
+ "node": ">=20.0.0"
+ },
+ "peerDependencies": {
+ "react": ">=18",
+ "react-dom": ">=18"
+ },
+ "peerDependenciesMeta": {
+ "react-dom": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/react-router-dom": {
+ "version": "7.17.0",
+ "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-7.17.0.tgz",
+ "integrity": "sha512-fyU2yjGups/hE6Xz0I5ZYbVL8Gx29eCjgpHaRaTaVU+OOAdfRX05KsvyRm0GO8YQwOkhpU3MurW1jyMUJn+zSw==",
+ "license": "MIT",
+ "dependencies": {
+ "react-router": "7.17.0"
+ },
+ "engines": {
+ "node": ">=20.0.0"
+ },
+ "peerDependencies": {
+ "react": ">=18",
+ "react-dom": ">=18"
+ }
+ },
"node_modules/readdirp": {
"version": "4.1.2",
"license": "MIT",
@@ -2287,6 +2339,12 @@
"semver": "bin/semver.js"
}
},
+ "node_modules/set-cookie-parser": {
+ "version": "2.7.2",
+ "resolved": "https://registry.npmjs.org/set-cookie-parser/-/set-cookie-parser-2.7.2.tgz",
+ "integrity": "sha512-oeM1lpU/UvhTxw+g3cIfxXHyJRc/uidd3yK1P242gzHds0udQBYzs3y8j4gCCW+ZJ7ad0yctld8RYO+bdurlvw==",
+ "license": "MIT"
+ },
"node_modules/shebang-command": {
"version": "2.0.0",
"dev": true,
diff --git a/package.json b/package.json
index 11ff51f..35ac35d 100644
--- a/package.json
+++ b/package.json
@@ -13,6 +13,7 @@
"jotai": "^2.20.0",
"react": "^19.2.5",
"react-dom": "^19.2.5",
+ "react-router-dom": "^7.17.0",
"sass": "^1.99.0"
},
"devDependencies": {
diff --git a/src/App.jsx b/src/App.jsx
index ebb2c77..0569b06 100644
--- a/src/App.jsx
+++ b/src/App.jsx
@@ -1,5 +1,6 @@
import { useState, useEffect, useRef } from 'react';
-import { useAtomValue } from 'jotai';
+import { useLocation, useNavigate } from 'react-router-dom';
+import { useAtomValue, useAtom } from 'jotai';
import { activeSectionState, themeState } from './state/atoms';
import Navbar from './components/Navbar';
import Hero from './components/Hero';
@@ -19,11 +20,26 @@ const SECTIONS = {
const EXIT_DURATION = 200;
export default function App() {
- const activeSection = useAtomValue(activeSectionState);
+ const location = useLocation();
+ const navigate = useNavigate();
+ const [activeSection, setActiveSection] = useAtom(activeSectionState);
const theme = useAtomValue(themeState);
const [displayedSection, setDisplayedSection] = useState(activeSection);
const timerRef = useRef(null);
+ // Sync URL path with activeSection
+ useEffect(() => {
+ const path = location.pathname.substring(1) || 'home';
+ if (Object.keys(SECTIONS).includes(path)) {
+ if (path !== activeSection) {
+ setActiveSection(path);
+ }
+ } else {
+ // Invalid path, redirect to home
+ navigate('/');
+ }
+ }, [location.pathname, activeSection, setActiveSection, navigate]);
+
useEffect(() => {
if (theme === 'light') {
document.documentElement.dataset.theme = 'light';
diff --git a/src/components/Hero.jsx b/src/components/Hero.jsx
index 98e7617..8f5dc96 100644
--- a/src/components/Hero.jsx
+++ b/src/components/Hero.jsx
@@ -20,10 +20,10 @@ export default function Hero() {
diff --git a/src/components/Navbar.jsx b/src/components/Navbar.jsx
index 97f98f3..418e4c6 100644
--- a/src/components/Navbar.jsx
+++ b/src/components/Navbar.jsx
@@ -1,4 +1,5 @@
-import { useAtom } from 'jotai';
+import { Link } from 'react-router-dom';
+import { useAtom, useAtomValue } from 'jotai';
import { navOpenState, activeSectionState } from '../state/atoms';
import ThemeToggle from './ThemeToggle';
import styles from '../styles/components/Navbar.module.scss';
@@ -12,31 +13,27 @@ const NAV_LINKS = [
export default function Navbar() {
const [isOpen, setIsOpen] = useAtom(navOpenState);
- const [activeSection, setActiveSection] = useAtom(activeSectionState);
-
- function handleNav(section) {
- setActiveSection(section);
- setIsOpen(false);
- }
+ const activeSection = useAtomValue(activeSectionState);
return (