From 11c299310c224c34aea2017c50a9d93822402d3c Mon Sep 17 00:00:00 2001 From: stefanomanca Date: Wed, 6 May 2026 12:59:15 +0200 Subject: [PATCH] feat: enhance routing and theme management, add error handling and improve cookie consent styles --- src/App.svelte | 63 +++++++++++++------ src/components/CookiePopUp/CookiePopUp.css | 24 ++++--- src/components/ThemeToggle/ThemeToggle.svelte | 20 +++--- src/components/Toggle/Toggle.css | 15 +---- src/lib/errorRouting.js | 38 +++++++++++ src/lib/theme.js | 40 ++++++++++++ src/pages/403.svelte | 2 +- src/styles/global.css | 4 +- vite.config.js | 13 +++- 9 files changed, 164 insertions(+), 55 deletions(-) create mode 100644 src/lib/errorRouting.js create mode 100644 src/lib/theme.js diff --git a/src/App.svelte b/src/App.svelte index f74e6db..de303fb 100644 --- a/src/App.svelte +++ b/src/App.svelte @@ -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) + } }) diff --git a/src/components/CookiePopUp/CookiePopUp.css b/src/components/CookiePopUp/CookiePopUp.css index bf08661..622cc3c 100644 --- a/src/components/CookiePopUp/CookiePopUp.css +++ b/src/components/CookiePopUp/CookiePopUp.css @@ -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; } diff --git a/src/components/ThemeToggle/ThemeToggle.svelte b/src/components/ThemeToggle/ThemeToggle.svelte index a6b25ca..964bc6d 100644 --- a/src/components/ThemeToggle/ThemeToggle.svelte +++ b/src/components/ThemeToggle/ThemeToggle.svelte @@ -1,37 +1,33 @@ diff --git a/src/components/Toggle/Toggle.css b/src/components/Toggle/Toggle.css index 5a7ee92..aba240a 100644 --- a/src/components/Toggle/Toggle.css +++ b/src/components/Toggle/Toggle.css @@ -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 { @@ -83,16 +84,4 @@ font-size: 0.85rem; 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%; } \ No newline at end of file diff --git a/src/lib/errorRouting.js b/src/lib/errorRouting.js new file mode 100644 index 0000000..646b1d9 --- /dev/null +++ b/src/lib/errorRouting.js @@ -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' +} diff --git a/src/lib/theme.js b/src/lib/theme.js new file mode 100644 index 0000000..73b4139 --- /dev/null +++ b/src/lib/theme.js @@ -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) +} diff --git a/src/pages/403.svelte b/src/pages/403.svelte index a23395f..efc1124 100644 --- a/src/pages/403.svelte +++ b/src/pages/403.svelte @@ -16,7 +16,7 @@
diff --git a/src/styles/global.css b/src/styles/global.css index c993c09..c2c6d0f 100644 --- a/src/styles/global.css +++ b/src/styles/global.css @@ -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 ) */ @@ -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); } * { diff --git a/vite.config.js b/vite.config.js index eb1df58..ddcce96 100644 --- a/vite.config.js +++ b/vite.config.js @@ -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() + } + ] } -}) +}))