feat: enhance routing and theme management, add error handling and improve cookie consent styles
This commit is contained in:
+45
-18
@@ -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>
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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%;
|
||||
}
|
||||
@@ -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'
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
@@ -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">
|
||||
|
||||
@@ -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
@@ -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()
|
||||
}
|
||||
]
|
||||
}
|
||||
})
|
||||
}))
|
||||
|
||||
Reference in New Issue
Block a user