feat[ frontend / ui-core ]: implementate pagine di errore e miglioramenti UX ai componenti
Error Handling: Introdotte nuove pagine di errore dedicate (403, 404, 500 e generica) e aggiornata la logica di routing in App.svelte per la gestione degli stati di errore. Button Component: Estese le funzionalità del componente per supportare link diretti e personalizzazione della larghezza del bordo. Header Component: Ottimizzata la UX implementando la scomparsa dell'header durante lo scroll. Refactoring & Style: Aggiornati gli stili globali per coerenza visiva e rimossi i file obsoleti relativi alle vecchie pagine di errore. Co-authored-by: Copilot <copilot@github.com>
This commit is contained in:
Generated
+24
@@ -7,6 +7,9 @@
|
||||
"": {
|
||||
"name": "cimaprogetti",
|
||||
"version": "1.0.0",
|
||||
"dependencies": {
|
||||
"svelte-spa-router": "^4.0.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@sveltejs/vite-plugin-svelte": "^7.0.0",
|
||||
"svelte": "^5.55.5",
|
||||
@@ -970,6 +973,15 @@
|
||||
"node": "^10 || ^12 || >=14"
|
||||
}
|
||||
},
|
||||
"node_modules/regexparam": {
|
||||
"version": "2.0.2",
|
||||
"resolved": "https://registry.npmjs.org/regexparam/-/regexparam-2.0.2.tgz",
|
||||
"integrity": "sha512-A1PeDEYMrkLrfyOwv2jwihXbo9qxdGD3atBYQA9JJgreAx8/7rC6IUkWOw2NQlOxLp2wL0ifQbh1HuidDfYA6w==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/rolldown": {
|
||||
"version": "1.0.0-rc.17",
|
||||
"resolved": "https://registry.npmjs.org/rolldown/-/rolldown-1.0.0-rc.17.tgz",
|
||||
@@ -1042,6 +1054,18 @@
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/svelte-spa-router": {
|
||||
"version": "4.0.2",
|
||||
"resolved": "https://registry.npmjs.org/svelte-spa-router/-/svelte-spa-router-4.0.2.tgz",
|
||||
"integrity": "sha512-T1WYYk+ymwCr5m5U+n91k4dRAT6cw5HgmoPaI/TpKgAmuugymFoSBlfzkcKIK83QH4H8gUMn4tdQ0B9enFBM6g==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"regexparam": "2.0.2"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/ItalyPaleAle"
|
||||
}
|
||||
},
|
||||
"node_modules/tinyglobby": {
|
||||
"version": "0.2.16",
|
||||
"resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.16.tgz",
|
||||
|
||||
+58
-8
@@ -3,30 +3,80 @@
|
||||
import Hero from "./components/Hero/Hero.svelte";
|
||||
import Services from "./components/Services/Services.svelte";
|
||||
import Footer from "./components/Footer/Footer.svelte";
|
||||
import "./styles/App.css";
|
||||
import Header from "./components/Header/Header.svelte";
|
||||
import Contacts from "./pages/Contacts.svelte";
|
||||
import Forbidden from "./pages/403.svelte";
|
||||
import ServerError from "./pages/500.svelte";
|
||||
import ErrorGeneric from "./pages/errore.svelte";
|
||||
import NotFound from "./pages/404.svelte";
|
||||
|
||||
let pathname = '/'
|
||||
|
||||
const normalizePath = (path) => {
|
||||
if (!path) return '/'
|
||||
const trimmed = path.trim()
|
||||
return trimmed.endsWith('/') && trimmed.length > 1
|
||||
? trimmed.slice(0, -1)
|
||||
: trimmed
|
||||
}
|
||||
|
||||
const getRoute = (path) => {
|
||||
const normalized = normalizePath(path)
|
||||
|
||||
if (normalized === '/') return 'home'
|
||||
if (normalized === '/contatti' || normalized === '/contacts') return 'contacts'
|
||||
if (normalized === '/403') return '403'
|
||||
if (normalized === '/500') return '500'
|
||||
if (normalized === '/errore') return 'error'
|
||||
return '404'
|
||||
}
|
||||
|
||||
$: currentRoute = getRoute(pathname)
|
||||
$: isCenteredMain = ['403', '404', '500', 'error'].includes(currentRoute)
|
||||
|
||||
onMount(() => {
|
||||
try {
|
||||
const saved = localStorage.getItem('theme')
|
||||
if (saved) {
|
||||
document.documentElement.setAttribute('data-theme', saved)
|
||||
return
|
||||
} 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)
|
||||
}
|
||||
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)
|
||||
} catch (e) {
|
||||
document.documentElement.setAttribute('data-theme', 'light')
|
||||
}
|
||||
|
||||
pathname = window.location.pathname
|
||||
|
||||
const handlePopstate = () => {
|
||||
pathname = window.location.pathname
|
||||
}
|
||||
|
||||
window.addEventListener('popstate', handlePopstate)
|
||||
return () => window.removeEventListener('popstate', handlePopstate)
|
||||
})
|
||||
</script>
|
||||
|
||||
<div class="app">
|
||||
<Header />
|
||||
<main>
|
||||
<!-- <Hero /> -->
|
||||
<main class:center-content={isCenteredMain}>
|
||||
{#if currentRoute === 'home'}
|
||||
<Hero />
|
||||
<Services />
|
||||
{:else if currentRoute === 'contacts'}
|
||||
<Contacts />
|
||||
{:else if currentRoute === '403'}
|
||||
<Forbidden />
|
||||
{:else if currentRoute === '500'}
|
||||
<ServerError />
|
||||
{:else if currentRoute === 'error'}
|
||||
<ErrorGeneric />
|
||||
{:else}
|
||||
<NotFound />
|
||||
{/if}
|
||||
</main>
|
||||
<Footer />
|
||||
</div>
|
||||
@@ -2,7 +2,6 @@
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
border: none;
|
||||
background-color: var(--button-color);
|
||||
color: var(--button-text-color);
|
||||
padding: var(--button-padding);
|
||||
@@ -10,7 +9,10 @@
|
||||
font: inherit;
|
||||
cursor: pointer;
|
||||
font-weight: bold;
|
||||
transition: transform 0.2s ease, opacity 0.2s ease, background-color 0.2s ease;
|
||||
transition: transform 0.2s, opacity 0.2s, background-color 0.2s, box-shadow 0.2s;
|
||||
text-decoration: none;
|
||||
border: none;
|
||||
box-shadow: inset 0 0 0 var(--button-border-width) var(--primary-color);
|
||||
}
|
||||
|
||||
.button:hover {
|
||||
@@ -23,6 +25,6 @@
|
||||
}
|
||||
|
||||
.button:focus-visible {
|
||||
outline: 2px solid rgba(255, 255, 255, 0.85);
|
||||
outline: 2px solid var(--primary-color);
|
||||
outline-offset: 3px;
|
||||
}
|
||||
@@ -1,16 +1,28 @@
|
||||
<script>
|
||||
import './Button.css'
|
||||
|
||||
export let color = '#1343F0'
|
||||
export let color = 'var(--primary-color)'
|
||||
export let text = 'Button'
|
||||
export let textColor = '#ffffff'
|
||||
export let textColor = '#fff'
|
||||
export let round = '8px'
|
||||
export let padding = '8px 24px'
|
||||
export let href = null
|
||||
export let borderWidth = '0px'
|
||||
</script>
|
||||
|
||||
<button
|
||||
class="button"
|
||||
style={`--button-color: ${color}; --button-text-color: ${textColor}; --button-radius: ${round}; --button-padding: ${padding};`}
|
||||
>
|
||||
{text}
|
||||
</button>
|
||||
{#if href}
|
||||
<a
|
||||
class="button"
|
||||
style={`--button-color: ${color}; --button-text-color: ${textColor}; --button-radius: ${round}; --button-padding: ${padding}; --button-border-width: ${borderWidth};`}
|
||||
{href}
|
||||
>
|
||||
{text}
|
||||
</a>
|
||||
{:else}
|
||||
<button
|
||||
class="button"
|
||||
style={`--button-color: ${color}; --button-text-color: ${textColor}; --button-radius: ${round}; --button-padding: ${padding}; --button-border-width: ${borderWidth};`}
|
||||
>
|
||||
{text}
|
||||
</button>
|
||||
{/if}
|
||||
@@ -1,7 +1,6 @@
|
||||
p, a, h4 {
|
||||
.footer p, .footer a, .footer h4 {
|
||||
font-size: 0.7rem;
|
||||
margin: 0;
|
||||
transition: color 0.2s;
|
||||
}
|
||||
|
||||
.footer {
|
||||
|
||||
@@ -1,7 +1,15 @@
|
||||
.header {
|
||||
position: sticky;
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
z-index: 100;
|
||||
transform: translateY(0);
|
||||
transition: transform 0.28s ease;
|
||||
}
|
||||
|
||||
.header.hidden {
|
||||
transform: translateY(calc(-100% - 8px));
|
||||
}
|
||||
|
||||
.header-container {
|
||||
@@ -10,7 +18,7 @@
|
||||
padding: 0 20px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
height: 140px;
|
||||
height: var(--navbar-height);
|
||||
}
|
||||
|
||||
.logo-img{
|
||||
|
||||
@@ -1,15 +1,45 @@
|
||||
<script>
|
||||
import { onMount } from 'svelte'
|
||||
import './Header.css'
|
||||
import Button from '../Button/Button.svelte'
|
||||
|
||||
let activeNav = 'home'
|
||||
let isHidden = false
|
||||
let lastScrollY = 0
|
||||
|
||||
const SCROLL_DELTA = 8
|
||||
const TOP_OFFSET = 24
|
||||
|
||||
function navigate(section) {
|
||||
activeNav = section
|
||||
}
|
||||
|
||||
function handleScroll() {
|
||||
const currentScrollY = window.scrollY || 0
|
||||
const diff = currentScrollY - lastScrollY
|
||||
|
||||
if (currentScrollY <= TOP_OFFSET) {
|
||||
isHidden = false
|
||||
} else if (diff > SCROLL_DELTA) {
|
||||
isHidden = true
|
||||
} else if (diff < -SCROLL_DELTA) {
|
||||
isHidden = false
|
||||
}
|
||||
|
||||
lastScrollY = currentScrollY
|
||||
}
|
||||
|
||||
onMount(() => {
|
||||
lastScrollY = window.scrollY || 0
|
||||
window.addEventListener('scroll', handleScroll, { passive: true })
|
||||
|
||||
return () => {
|
||||
window.removeEventListener('scroll', handleScroll)
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
<header class="header" id="home">
|
||||
<header class="header" class:hidden={isHidden} id="home">
|
||||
<div class="header-container">
|
||||
<div class="logo">
|
||||
<a href="/" aria-label="CIMA PROGETTI home">
|
||||
|
||||
@@ -0,0 +1,26 @@
|
||||
<script>
|
||||
import "../styles/error-pages.css";
|
||||
import Button from "../components/Button/Button.svelte";
|
||||
</script>
|
||||
|
||||
<div class="error-page">
|
||||
<div class="error-content">
|
||||
<div class="title">
|
||||
<h1 class="title-top">ci</h1>
|
||||
<h1 class="title-bottom">dispiace</h1>
|
||||
<span class="divider"></span>
|
||||
</div>
|
||||
<div class="error-message">
|
||||
<p class="error-description">Sembra che tu non abbia il permesso per arrivare fin qui...</p>
|
||||
<p>...se invece si tratta di un nostro sbaglio non esitare a contattarci!</p>
|
||||
</div>
|
||||
<div class="button-container">
|
||||
<Button text="HOME" href="/" />
|
||||
<Button text="ASSISTENZA" href="/assistenza" borderWidth="3px" color="transparent" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="error-background">
|
||||
<h1 class="error-code">403</h1>
|
||||
<h1 class="error-text">ERROR</h1>
|
||||
</div>
|
||||
</div>
|
||||
@@ -0,0 +1,23 @@
|
||||
<script>
|
||||
import "../styles/error-pages.css";
|
||||
import Button from "../components/Button/Button.svelte";
|
||||
</script>
|
||||
|
||||
<div class="error-page">
|
||||
<div class="error-content">
|
||||
<div class="title">
|
||||
<h1 class="title-top">ci</h1>
|
||||
<h1 class="title-bottom">dispiace</h1>
|
||||
<span class="divider"></span>
|
||||
</div>
|
||||
<div class="error-message">
|
||||
<p class="error-description">Stai cercando una pagina che non esiste...</p>
|
||||
<p>...se invece si tratta di un nostro sbaglio non esitare a contattarci!</p>
|
||||
</div>
|
||||
<Button text="HOME" href="/" />
|
||||
</div>
|
||||
<div class="error-background">
|
||||
<h1 class="error-code">404</h1>
|
||||
<h1 class="error-text">ERROR</h1>
|
||||
</div>
|
||||
</div>
|
||||
@@ -0,0 +1,23 @@
|
||||
<script>
|
||||
import "../styles/error-pages.css";
|
||||
import Button from "../components/Button/Button.svelte";
|
||||
</script>
|
||||
|
||||
<div class="error-page">
|
||||
<div class="error-content">
|
||||
<div class="title">
|
||||
<h1 class="title-top">ci</h1>
|
||||
<h1 class="title-bottom">dispiace</h1>
|
||||
<span class="divider"></span>
|
||||
</div>
|
||||
<div class="error-message">
|
||||
<p class="error-description">Anche i sistemi migliori ogni tanto crollano...</p>
|
||||
<p>...l'importante è intervenire tempestivamente e noi siamo gia al lavoro!</p>
|
||||
</div>
|
||||
<Button text="HOME" href="/" />
|
||||
</div>
|
||||
<div class="error-background">
|
||||
<h1 class="error-code">500</h1>
|
||||
<h1 class="error-text">ERROR</h1>
|
||||
</div>
|
||||
</div>
|
||||
@@ -1,28 +0,0 @@
|
||||
<script>
|
||||
</script>
|
||||
|
||||
<div class="error-page">
|
||||
<h1>403 - Accesso Negato</h1>
|
||||
<p>Non hai i permessi per accedere a questa risorsa.</p>
|
||||
</div>
|
||||
|
||||
<style>
|
||||
.error-page {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
min-height: 60vh;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
h1 {
|
||||
font-size: 3rem;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
p {
|
||||
font-size: 1.2rem;
|
||||
color: #666;
|
||||
}
|
||||
</style>
|
||||
@@ -1,28 +0,0 @@
|
||||
<script>
|
||||
</script>
|
||||
|
||||
<div class="error-page">
|
||||
<h1>404 - Pagina Non Trovata</h1>
|
||||
<p>La pagina che stai cercando non esiste.</p>
|
||||
</div>
|
||||
|
||||
<style>
|
||||
.error-page {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
min-height: 60vh;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
h1 {
|
||||
font-size: 3rem;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
p {
|
||||
font-size: 1.2rem;
|
||||
color: #666;
|
||||
}
|
||||
</style>
|
||||
@@ -1,28 +0,0 @@
|
||||
<script>
|
||||
</script>
|
||||
|
||||
<div class="error-page">
|
||||
<h1>500 - Errore del Server</h1>
|
||||
<p>Si è verificato un errore interno del server. Riprova più tardi.</p>
|
||||
</div>
|
||||
|
||||
<style>
|
||||
.error-page {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
min-height: 60vh;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
h1 {
|
||||
font-size: 3rem;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
p {
|
||||
font-size: 1.2rem;
|
||||
color: #666;
|
||||
}
|
||||
</style>
|
||||
@@ -1,28 +0,0 @@
|
||||
<script>
|
||||
</script>
|
||||
|
||||
<div class="error-page">
|
||||
<h1>Errore</h1>
|
||||
<p>Si è verificato un errore. Per favore, riprova più tardi.</p>
|
||||
</div>
|
||||
|
||||
<style>
|
||||
.error-page {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
min-height: 60vh;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
h1 {
|
||||
font-size: 3rem;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
p {
|
||||
font-size: 1.2rem;
|
||||
color: #666;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,22 @@
|
||||
<script>
|
||||
import "../styles/error-pages.css";
|
||||
import Button from "../components/Button/Button.svelte";
|
||||
</script>
|
||||
|
||||
<div class="error-page">
|
||||
<div class="error-content">
|
||||
<div class="title">
|
||||
<h1 class="title-top">ci</h1>
|
||||
<h1 class="title-bottom">dispiace</h1>
|
||||
<span class="divider"></span>
|
||||
</div>
|
||||
<div class="error-message">
|
||||
<p class="error-description">Sembra ci sia un errore...</p>
|
||||
<p>...ma possiamo aiutarti anche in questo caso!</p>
|
||||
</div>
|
||||
<Button text="HOME" href="/" />
|
||||
</div>
|
||||
<div class="error-background">
|
||||
<h1 class="error-text">ERROR</h1>
|
||||
</div>
|
||||
</div>
|
||||
@@ -1,9 +0,0 @@
|
||||
.app {
|
||||
min-height: 100vh;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
main {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,90 @@
|
||||
.error-page {
|
||||
min-height: 100vh;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.error-page h1 {
|
||||
margin: 0;
|
||||
font-size: 3rem;
|
||||
}
|
||||
|
||||
.error-content {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
gap: 20px;
|
||||
}
|
||||
|
||||
.error-description {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.error-content p {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.button-container {
|
||||
display: flex;
|
||||
gap: 20px;
|
||||
}
|
||||
|
||||
.error-content button {
|
||||
width: min-content;
|
||||
}
|
||||
|
||||
.title {
|
||||
position: relative;
|
||||
display: inline-flex;
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
line-height: 0.9;
|
||||
}
|
||||
|
||||
.error-page .title-top {
|
||||
margin-left: -2rem;
|
||||
color: var(--primary-color);
|
||||
}
|
||||
|
||||
.title-bottom {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.divider {
|
||||
display: block;
|
||||
position: relative;
|
||||
width: 50%;
|
||||
height: 5px;
|
||||
background-color: var(--primary-color);
|
||||
margin-top: 8px;
|
||||
}
|
||||
|
||||
.error-message,
|
||||
.error-solution {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.error-background {
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
right: 0;
|
||||
z-index: -1;
|
||||
}
|
||||
|
||||
.error-background h1 {
|
||||
text-align: right;
|
||||
font-family: Helvetica, 'Segoe UI', sans-serif;
|
||||
}
|
||||
|
||||
.error-code {
|
||||
font-size: 4rem;
|
||||
line-height: 0.4;
|
||||
}
|
||||
|
||||
.error-text {
|
||||
font-size: 15rem !important;
|
||||
opacity: 0.2;
|
||||
line-height: 0.8;
|
||||
}
|
||||
@@ -1,5 +1,6 @@
|
||||
:root {
|
||||
--primary-color: #0066cc;
|
||||
--navbar-height: 140px;
|
||||
--primary-color: #1343F0;
|
||||
--text-color: #111827;
|
||||
--muted-color: #6b7280;
|
||||
--surface: #ffffff;
|
||||
@@ -17,6 +18,7 @@
|
||||
|
||||
* {
|
||||
box-sizing: border-box;
|
||||
transition: color 0.2s;
|
||||
}
|
||||
|
||||
body {
|
||||
@@ -85,4 +87,9 @@ h1 {
|
||||
|
||||
h2 {
|
||||
font-family: 'IBM Plex Mono', monospace;
|
||||
}
|
||||
|
||||
p, a {
|
||||
|
||||
font-family: Helvetica, 'Segoe UI', sans-serif;
|
||||
}
|
||||
Reference in New Issue
Block a user