Improving route handling
This commit is contained in:
58
package-lock.json
generated
58
package-lock.json
generated
@@ -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,
|
||||
|
||||
@@ -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": {
|
||||
|
||||
20
src/App.jsx
20
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';
|
||||
|
||||
@@ -20,10 +20,10 @@ export default function Hero() {
|
||||
</p>
|
||||
|
||||
<div className={styles.cta}>
|
||||
<a href="#projects" className={styles.btnPrimary}>
|
||||
<a href="/projects" className={styles.btnPrimary}>
|
||||
See my work
|
||||
</a>
|
||||
<a href="#contact" className={styles.btnGhost}>
|
||||
<a href="/contact" className={styles.btnGhost}>
|
||||
Get in touch
|
||||
</a>
|
||||
</div>
|
||||
|
||||
@@ -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 (
|
||||
<header className={styles.header}>
|
||||
<nav className={styles.nav}>
|
||||
<button className={styles.logo} onClick={() => handleNav('home')}>
|
||||
<Link to="/" className={styles.logo} onClick={() => setIsOpen(false)}>
|
||||
<span className={styles.logoBracket}><</span>
|
||||
human id="veikko" class="lintujarvi"
|
||||
<span className={styles.logoBracket}>/></span>
|
||||
</button>
|
||||
</Link>
|
||||
|
||||
<ul className={`${styles.links} ${isOpen ? styles.open : ''}`}>
|
||||
{NAV_LINKS.map(({ label, section }) => (
|
||||
<li key={section}>
|
||||
<button
|
||||
onClick={() => handleNav(section)}
|
||||
<Link
|
||||
to={`/${section}`}
|
||||
onClick={() => setIsOpen(false)}
|
||||
className={activeSection === section ? styles.active : undefined}
|
||||
>
|
||||
{label}
|
||||
</button>
|
||||
</Link>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
|
||||
@@ -16,10 +16,10 @@ const PROJECTS = [
|
||||
star: false,
|
||||
},
|
||||
{
|
||||
name: 'Pro bono project - Runosaari festival website',
|
||||
name: 'Runosaari festival website - Pro bono',
|
||||
desc: "Supporting local culture initiatives have always been close to my heart. I'm proudly helping Runosaari festival with their website and online presence already for the sixth year!",
|
||||
tags: ['React', 'Vite', 'Apache2', 'Ubuntu Server'],
|
||||
href: '#',
|
||||
href: 'https://www.runosaari.fi/',
|
||||
star: false,
|
||||
},
|
||||
];
|
||||
|
||||
@@ -1,10 +1,13 @@
|
||||
import { StrictMode } from 'react';
|
||||
import { createRoot } from 'react-dom/client';
|
||||
import { BrowserRouter as Router } from 'react-router-dom';
|
||||
import './styles/main.scss';
|
||||
import App from './App.jsx';
|
||||
|
||||
createRoot(document.getElementById('root')).render(
|
||||
<StrictMode>
|
||||
<Router>
|
||||
<App />
|
||||
</Router>
|
||||
</StrictMode>,
|
||||
);
|
||||
|
||||
@@ -4,4 +4,12 @@ import react from '@vitejs/plugin-react'
|
||||
// https://vite.dev/config/
|
||||
export default defineConfig({
|
||||
plugins: [react()],
|
||||
build: {
|
||||
rollupOptions: {
|
||||
output: {
|
||||
assetFileNames: 'assets/[name]-[hash][extname]',
|
||||
entryFileNames: 'assets/[name]-[hash].js',
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user