feat: enhance routing and theme management, add error handling and improve cookie consent styles

This commit is contained in:
2026-05-06 12:59:15 +02:00
parent 8b86350a2d
commit 11c299310c
9 changed files with 164 additions and 55 deletions
+45 -18
View File
@@ -11,56 +11,83 @@
import ErrorGeneric from "./pages/errore.svelte";
import NotFound from "./pages/404.svelte";
import { initializeStoredConsent } from "./lib/cookieConsent";
import { applyTheme, getSavedTheme } from "./lib/theme";
import { getErrorPath, resolvePathname } from "./lib/errorRouting";
let pathname = '/'
const normalizePath = (path) => {
if (!path) return '/'
const trimmed = path.trim()
return trimmed.endsWith('/') && trimmed.length > 1
? trimmed.slice(0, -1)
: trimmed
}
let pathname = typeof window !== 'undefined' ? window.location.pathname : '/'
const getRoute = (path) => {
const normalized = normalizePath(path)
const normalized = resolvePathname(path)
if (normalized === '/') return 'home'
if (normalized === '/contatti' || normalized === '/contacts') return 'contacts'
if (normalized === '/403') return '403'
if (normalized === '/404') return '404'
if (normalized === '/500') return '500'
if (normalized === '/errore') return 'error'
return '404'
return 'error'
}
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) {
window.history.replaceState({}, '', resolved)
}
pathname = resolved
}
onMount(() => {
initializeStoredConsent()
try {
const saved = localStorage.getItem('theme')
const saved = getSavedTheme()
if (saved) {
document.documentElement.setAttribute('data-theme', saved)
applyTheme(saved)
} else {
const prefersDark = window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches
const theme = prefersDark ? 'dark' : 'light'
document.documentElement.setAttribute('data-theme', theme)
localStorage.setItem('theme', theme)
applyTheme(theme)
}
} catch (e) {
document.documentElement.setAttribute('data-theme', 'light')
}
pathname = window.location.pathname
syncRouteFromLocation()
const handlePopstate = () => {
pathname = window.location.pathname
syncRouteFromLocation()
}
const handleErrorRedirect = (event) => {
const code = event?.detail?.code ?? event?.detail?.status
navigateTo(getErrorPath(code))
}
window.addEventListener('popstate', handlePopstate)
return () => window.removeEventListener('popstate', handlePopstate)
window.addEventListener('app:error-redirect', handleErrorRedirect)
return () => {
window.removeEventListener('popstate', handlePopstate)
window.removeEventListener('app:error-redirect', handleErrorRedirect)
}
})
</script>
+17 -7
View File
@@ -26,14 +26,8 @@
border: 2px solid rgba(127, 127, 127, 0.3);
}
.cookie-popup-container {
display: flex;
flex-direction: column;
gap: 16px;
}
.cookie-popup-content p {
margin: 10px 0 15px;
margin: 15px 0 15px;
}
.cookie-popup-title {
@@ -100,6 +94,22 @@
min-width: fit-content;
}
.cookie-toggle-item {
cursor: pointer;
display: flex;
align-items: center;
padding: 8px 0;
margin: 0;
}
.cookie-toggle-item .toggle {
display: flex;
justify-content: center;
align-items: center;
flex: 1;
width: 100%;
}
.cookie-selector p {
margin: 0 0 5px;
}
+8 -12
View File
@@ -1,37 +1,33 @@
<script>
import { onMount } from "svelte";
import "./ThemeToggle.css";
import { applyTheme as persistTheme, getSavedTheme } from "../../lib/theme";
let theme = "light";
function applyTheme(t) {
function setTheme(t) {
theme = t;
try {
document.documentElement.setAttribute("data-theme", t);
localStorage.setItem("theme", t);
} catch (e) {
// ignore in SSR or restricted environments
}
persistTheme(t);
}
function toggle() {
applyTheme(theme === "dark" ? "light" : "dark");
setTheme(theme === "dark" ? "light" : "dark");
}
onMount(() => {
try {
const saved = localStorage.getItem("theme");
const saved = getSavedTheme();
if (saved) {
applyTheme(saved);
setTheme(saved);
return;
}
const prefersDark =
window.matchMedia &&
window.matchMedia("(prefers-color-scheme: dark)").matches;
applyTheme(prefersDark ? "dark" : "light");
setTheme(prefersDark ? "dark" : "light");
} catch (e) {
// safe fallback
applyTheme("light");
setTheme("light");
}
});
</script>
+2 -13
View File
@@ -14,7 +14,7 @@
padding: 0;
cursor: pointer;
display: inline-flex;
align-items: flex-start;
align-items: center;
gap: 12px;
}
@@ -68,6 +68,7 @@
display: flex;
flex-direction: column;
gap: 4px;
justify-content: center;
}
.toggle-label {
@@ -84,15 +85,3 @@
color: var(--muted-color, rgba(0, 0, 0, 0.6));
line-height: 1.4;
}
.cookie-toggle-item {
cursor: pointer;
display: flex;
padding: 8px 0;
margin: 0;
}
.cookie-toggle-item .toggle {
flex: 1;
width: 100%;
}
+38
View File
@@ -0,0 +1,38 @@
const KNOWN_PATHS = new Set([
'/',
'/contatti',
'/contacts',
'/403',
'/404',
'/500',
'/errore'
])
const PATH_ALIASES = {
'/assistenza': '/contatti',
'/supporto': '/contatti',
'/error': '/errore'
}
export const normalizePathname = (path) => {
if (!path) return '/'
const trimmed = String(path).trim()
if (!trimmed) return '/'
return trimmed.endsWith('/') && trimmed.length > 1 ? trimmed.slice(0, -1) : trimmed
}
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'
}
export const getErrorPath = (statusOrCode) => {
const code = String(statusOrCode ?? '').trim()
if (code === '403') return '/403'
if (code === '404') return '/404'
if (code === '500') return '/500'
return '/errore'
}
+40
View File
@@ -0,0 +1,40 @@
const THEME_KEY = 'theme'
const THEME_COOKIE_MAX_AGE = 60 * 60 * 24 * 365
export const getSavedTheme = () => {
if (typeof window === 'undefined') return null
try {
const stored = localStorage.getItem(THEME_KEY)
if (stored === 'dark' || stored === 'light') return stored
const cookieTheme = document.cookie
.split('; ')
.find((entry) => entry.startsWith(`${THEME_KEY}=`))
?.split('=')[1]
if (cookieTheme === 'dark' || cookieTheme === 'light') return cookieTheme
} catch {
// ignore storage access issues
}
return null
}
export const saveTheme = (theme) => {
if (typeof window === 'undefined') return
try {
localStorage.setItem(THEME_KEY, theme)
document.cookie = `${THEME_KEY}=${theme}; path=/; max-age=${THEME_COOKIE_MAX_AGE}; SameSite=Lax`
} catch {
// ignore storage access issues
}
}
export const applyTheme = (theme) => {
if (typeof document === 'undefined') return
document.documentElement.setAttribute('data-theme', theme)
saveTheme(theme)
}
+1 -1
View File
@@ -16,7 +16,7 @@
</div>
<div class="button-container">
<Button text="HOME" href="/" />
<Button text="ASSISTENZA" href="/assistenza" borderWidth="3px" color="transparent" />
<Button text="ASSISTENZA" href="/contatti" borderWidth="3px" color="transparent" />
</div>
</div>
<div class="error-background">
+2 -2
View File
@@ -5,7 +5,7 @@
--muted-color: #6b7280;
--surface: #ffffff;
--background: rgba(246, 249, 255, 0.5);
--background-opaque: rgba(0, 0, 0, 0.12);
--background-opaque: rgba(0, 0, 0, 0.2);
}
/* Dark theme overrides (toggle by setting attribute on <html>) */
@@ -15,7 +15,7 @@
--muted-color: #9ca3af;
--surface: #000;
--background: rgba(0, 0, 0, 0.8);
--background-opaque: rgba(255, 255, 255, 0.12);
--background-opaque: rgba(255, 255, 255, 0.2);
}
* {
+11 -2
View File
@@ -1,9 +1,18 @@
import { defineConfig } from 'vite'
import { svelte } from '@sveltejs/vite-plugin-svelte'
export default defineConfig({
export default defineConfig(() => ({
plugins: [svelte()],
server: {
port: 5173,
middlewares: [
(req, res, next) => {
if (req.url === '/' || req.url.match(/\.\w+$/) || req.url.includes('/public/')) {
return next()
}
})
req.url = '/'
next()
}
]
}
}))