diff --git a/README.md b/README.md index aaabbb1..fe2d555 100644 --- a/README.md +++ b/README.md @@ -8,7 +8,7 @@ Sito web moderno e responsivo sviluppato con **Svelte + Vite** per presentare se - 📱 **Design completamente responsivo** - ⬆️ **Navbar collapsibile** con hide/show al scroll - 🎨 **Sistema di componenti riusabili** -- 🚀 **Routing client-side** con pagine di errore dedicate +- 🚀 **Routing client-side** con pagine dedicate e fallback 404 - ♿ **Accessibilità** e buone pratiche web ## 🛠️ Requisiti @@ -58,16 +58,19 @@ src/ ## 🗺️ Routing -Il routing è gestito in `src/App.svelte` tramite `window.location.pathname`. +Il routing è gestito in `src/App.svelte` leggendo `window.location.pathname` e reagendo ai cambi di History API. | Route | Componente | Descrizione | |-------|-----------|-------------| | `/` | `Hero` + `Services` | Home page | | `/contatti`, `/contacts` | `Contacts` | Pagina contatti | | `/403` | `Forbidden` | Accesso negato | +| `/404` | `NotFound` | Pagina non trovata | | `/500` | `ServerError` | Errore server | | `/errore` | `ErrorGeneric` | Errore generico | -| `/*` | `NotFound` | Pagina non trovata (404) | +| qualsiasi altra route | `NotFound` | Fallback 404 per pagine non ancora create | + +Le voci della navbar puntano a route reali come `/servizi`, `/metodo`, `/progetti` e `/chi-siamo`: se una pagina non esiste ancora, il contenuto mostrato è il 404 ma l'URL resta quello richiesto. ## 🎨 Tema e Stili @@ -141,19 +144,24 @@ Componente button/link riusabile con tema integrato. - `href` - se presente, renderizza come `` tag - `borderWidth` - spessore bordo interno (default: `'0px'`) +Se `href` punta a una route interna, il click usa la navigazione client-side e aggiorna il contenuto senza ricaricare la pagina. + ### Header -Navbar sticky che si nascondi al scroll verso il basso e riappare al scroll verso l'alto. +Navbar sticky che si nasconde al scroll verso il basso e riappare al scroll verso l'alto. + +Le voci della navbar usano route interne. Le pagine non ancora create devono restituire 404. Modifica il contenuto in `src/components/Header/Header.svelte`. ### ThemeToggle -Pulsante per switchare tra tema light e dark. Il tema viene salvato in `localStorage`. +Pulsante per switchare tra tema light e dark. Il tema viene salvato in `localStorage` e come backup in cookie, così resta disponibile anche dopo operazioni che puliscono parte dello storage del browser. ## 📱 Modificare i contenuti - **Header/Navigazione:** `src/components/Header/Header.svelte` +- **Routing e redirect:** `src/App.svelte`, `src/lib/navigation.js`, `src/lib/errorRouting.js` - **Sezione hero:** `src/components/Hero/Hero.svelte` - **Servizi:** `src/components/Services/Services.svelte` - **Footer:** `src/components/Footer/Footer.svelte` @@ -181,6 +189,8 @@ const getRoute = (path) => { ``` +Se non aggiungi la route in `getRoute`, il path continuerà a mostrare il fallback 404. + ## 🚀 Build e Deploy ### Build di produzione diff --git a/src/App.svelte b/src/App.svelte index de303fb..eca84de 100644 --- a/src/App.svelte +++ b/src/App.svelte @@ -13,8 +13,9 @@ import { initializeStoredConsent } from "./lib/cookieConsent"; import { applyTheme, getSavedTheme } from "./lib/theme"; import { getErrorPath, resolvePathname } from "./lib/errorRouting"; + import { navigateTo } from "./lib/navigation"; - let pathname = typeof window !== 'undefined' ? window.location.pathname : '/' + let pathname = $state(typeof window !== 'undefined' ? window.location.pathname : '/') const getRoute = (path) => { const normalized = resolvePathname(path) @@ -25,28 +26,12 @@ if (normalized === '/404') return '404' if (normalized === '/500') return '500' if (normalized === '/errore') return 'error' - return 'error' + return '404' } let currentRoute = $derived(getRoute(pathname)) let isCenteredMain = $derived(['403', '404', '500', 'error'].includes(currentRoute)) - const navigateTo = (targetPath, replace = false) => { - const resolved = resolvePathname(targetPath) - const current = resolvePathname(window.location.pathname) - if (resolved === current) { - pathname = resolved - return - } - - if (replace) { - window.history.replaceState({}, '', resolved) - } else { - window.history.pushState({}, '', resolved) - } - pathname = resolved - } - const syncRouteFromLocation = () => { const resolved = resolvePathname(window.location.pathname) if (resolved !== window.location.pathname) { diff --git a/src/components/Button/Button.svelte b/src/components/Button/Button.svelte index e77551b..ca9728c 100644 --- a/src/components/Button/Button.svelte +++ b/src/components/Button/Button.svelte @@ -1,5 +1,6 @@ {#if href} @@ -18,6 +27,7 @@ class="button" style={`--button-color: ${color}; --button-text-color: ${textColor}; --button-radius: ${round}; --button-padding: ${padding}; --button-border-width: ${borderWidth};`} {href} + on:click={handleClick} {...restProps} > {text} diff --git a/src/components/Header/Header.svelte b/src/components/Header/Header.svelte index 786552a..2586e74 100644 --- a/src/components/Header/Header.svelte +++ b/src/components/Header/Header.svelte @@ -2,16 +2,21 @@ import { onMount } from 'svelte' import './Header.css' import Button from '../Button/Button.svelte' + import { navigateTo, isInternalPath } from '../../lib/navigation' - let activeNav = 'home' - let isHidden = false + let activeNav = $state(typeof window !== 'undefined' ? window.location.pathname : '/') + let isHidden = $state(false) let lastScrollY = 0 const SCROLL_DELTA = 8 const TOP_OFFSET = 24 - function navigate(section) { - activeNav = section + function handleNavClick(event, path) { + if (!isInternalPath(path)) return + if (event.metaKey || event.ctrlKey || event.shiftKey || event.altKey || event.button !== 0) return + + event.preventDefault() + navigateTo(path) } function handleScroll() { @@ -33,8 +38,14 @@ lastScrollY = window.scrollY || 0 window.addEventListener('scroll', handleScroll, { passive: true }) + const updateActiveNav = () => { + activeNav = window.location.pathname + } + window.addEventListener('popstate', updateActiveNav) + return () => { window.removeEventListener('scroll', handleScroll) + window.removeEventListener('popstate', updateActiveNav) } }) @@ -42,46 +53,46 @@
-
\ No newline at end of file diff --git a/src/lib/errorRouting.js b/src/lib/errorRouting.js index 646b1d9..2fd3bfb 100644 --- a/src/lib/errorRouting.js +++ b/src/lib/errorRouting.js @@ -25,8 +25,9 @@ export const resolvePathname = (path) => { const normalized = normalizePathname(path) const alias = PATH_ALIASES[normalized] if (alias) return alias - if (KNOWN_PATHS.has(normalized)) return normalized - return '/404' + // Non forziamo un "/404" per preservare l'URL inserito dall'utente. + // Sarà getRoute() in App.svelte a mappare le route non conosciute sul componente 404 + return normalized } export const getErrorPath = (statusOrCode) => { diff --git a/src/lib/navigation.js b/src/lib/navigation.js new file mode 100644 index 0000000..080eddb --- /dev/null +++ b/src/lib/navigation.js @@ -0,0 +1,24 @@ +export const isInternalPath = (path) => { + if (!path) return false; + // Percorsi relativi o assoluti interni (es. /, /contatti) ma non ancore interne (#) per le quali basta l'HTML normale + if (path.startsWith('/') && !path.startsWith('//')) return true; + + try { + const url = new URL(path, window.location.origin); + return url.origin === window.location.origin; + } catch { + return false; + } +}; + +export const navigateTo = (path, replace = false) => { + if (typeof window === 'undefined') return; + + if (replace) { + window.history.replaceState({}, '', path); + } else { + window.history.pushState({}, '', path); + } + // Avvisa l'applicazione (App.svelte) che l'URL è cambiato + window.dispatchEvent(new Event('popstate')); +};