feat: Implement error and not found pages with improved user feedback and navigation

This commit is contained in:
tuyenio
2026-06-30 10:01:41 +07:00
parent e081f54bd4
commit 4465d174fe
6 changed files with 139 additions and 14 deletions
+25 -11
View File
@@ -1,29 +1,41 @@
import Link from "next/link";
import { Globe2, Mail, MapPin, Phone } from "lucide-react";
import { HeritageSeal } from "@/app/components/landing/HeritageOrnament";
import { HeritageSeal, SectionDivider } from "@/app/components/landing/HeritageOrnament";
import { contact, navigation } from "@/app/content";
export function SiteFooter() {
return (
<footer id="contact" className="px-4 pb-8 sm:px-6">
<div className="mx-auto grid max-w-7xl gap-8 border-t border-[var(--surface-border)] pt-8 lg:grid-cols-[1.1fr_0.72fr_0.82fr]">
<footer id="contact" className="px-4 pb-10 sm:px-6">
<SectionDivider className="mx-auto max-w-7xl pb-12 pt-2" />
<div className="mx-auto grid max-w-7xl gap-8 lg:grid-cols-[1.15fr_0.7fr_0.85fr]">
<div className="min-w-0">
<p className="public-display text-xl font-bold text-[var(--tour-ink)]">VR360 Như Quỳnh</p>
<p className="mt-3 max-w-[62ch] text-sm leading-6 text-[var(--muted-foreground)]">
Cổng số hóa di tích không gian văn hóa Như Quỳnh, tỉnh Hưng Yên bằng công nghệ VR360.
<div className="flex items-center gap-3">
<span className="grid h-11 w-11 shrink-0 place-items-center rounded-full border border-[rgb(142_95_11_/_0.24)] bg-[linear-gradient(135deg,rgb(255_255_255_/_0.94),rgb(232_219_176_/_0.74))] shadow-[inset_0_1px_0_rgb(255_255_255_/_0.92)]">
<HeritageSeal className="h-6 w-6 text-[var(--primary)]" />
</span>
<p className="public-display text-xl font-bold leading-tight text-[var(--tour-ink)]">VR360 Như Quỳnh</p>
</div>
<p className="mt-4 max-w-[58ch] text-sm leading-7 text-[var(--muted-foreground)]">
Cổng số hóa di tích không gian văn hóa Như Quỳnh, tỉnh Hưng Yên bằng công nghệ tham quan thực tế o VR360.
</p>
</div>
<nav className="grid gap-2 text-sm" aria-label="Liên kết nhanh">
<nav className="grid content-start gap-2.5 text-sm" aria-label="Liên kết nhanh">
<p className="font-bold text-[var(--tour-ink)]">Liên kết nhanh</p>
{navigation.map((item) => (
<Link key={item.href} href={item.href} className="w-fit text-[var(--muted-foreground)] transition-colors hover:text-[var(--tour-ink)]">
<Link
key={item.href}
href={item.href}
className="group inline-flex w-fit items-center gap-2 text-[var(--muted-foreground)] transition-colors hover:text-[var(--primary)]"
>
<span aria-hidden className="h-1 w-1 rotate-45 bg-[var(--primary)]/45 transition-transform group-hover:scale-150" />
{item.label}
</Link>
))}
</nav>
<div className="grid gap-3 text-sm leading-6 text-[var(--muted-foreground)] lg:text-right">
<div className="grid content-start gap-3 text-sm leading-6 text-[var(--muted-foreground)] lg:text-right">
<p className="font-bold text-[var(--tour-ink)]">Thông tin liên hệ</p>
<p className="inline-flex items-start gap-2 lg:justify-end">
<MapPin className="mt-0.5 h-4 w-4 shrink-0 text-[var(--primary)]" strokeWidth={1.8} />
@@ -48,8 +60,10 @@ export function SiteFooter() {
</Link>
</div>
</div>
<div className="mx-auto mt-8 max-w-7xl border-t border-[var(--surface-border)] pt-5 text-center text-sm font-semibold text-[var(--muted-foreground)]">
© 2026 VR360 Di sản Như Quỳnh.
<div className="mx-auto mt-10 flex max-w-7xl flex-col items-center justify-between gap-2 border-t border-[var(--surface-border)] pt-5 text-sm font-semibold text-[var(--muted-foreground)] sm:flex-row">
<p>© 2026 VR360 Di sản Như Quỳnh.</p>
<p className="text-[var(--muted-foreground)]/85">Chuyển đi số di sản · giai đoạn 20262030</p>
</div>
</footer>
);
+43
View File
@@ -0,0 +1,43 @@
"use client";
import Link from "next/link";
import { useEffect } from "react";
import { Home, RotateCcw } from "lucide-react";
import { HeritageSeal } from "@/app/components/landing/HeritageOrnament";
export default function Error({
error,
reset,
}: {
error: Error & { digest?: string };
reset: () => void;
}) {
useEffect(() => {
// Ghi log lỗi để theo dõi (có thể nối với dịch vụ giám sát sau này).
console.error(error);
}, [error]);
return (
<main className="public-page flex min-h-[100dvh] flex-col items-center justify-center bg-[var(--background)] px-4 py-24 text-center text-[var(--foreground)] sm:px-6">
<HeritageSeal className="h-16 w-16 text-[var(--primary)] opacity-85" />
<p className="public-kicker public-kicker--center mt-7">Đã xảy ra sự cố</p>
<h1 className="public-display public-heading-safe mt-3 text-[clamp(1.9rem,5vw,3rem)] font-bold text-[var(--tour-ink)]">
Không thể tải nội dung
</h1>
<p className="mt-4 max-w-[52ch] text-base leading-7 text-[var(--muted-foreground)] sm:text-lg">
Đã lỗi không mong muốn khi hiển thị trang. Vui lòng thử lại; nếu sự cố tiếp diễn, hãy
quay về trang chủ truy cập lại sau ít phút.
</p>
<div className="mt-9 flex flex-wrap items-center justify-center gap-3">
<button type="button" onClick={reset} className="public-cta">
<RotateCcw className="h-4 w-4" strokeWidth={1.8} />
Thử lại
</button>
<Link href="/" className="public-cta-secondary">
<Home className="h-4 w-4" strokeWidth={1.8} />
Về trang chủ
</Link>
</div>
</main>
);
}
+2 -2
View File
@@ -636,8 +636,8 @@ a {
}
.public-page .public-heading-safe {
font-size: clamp(1.85rem, 8.7vw, 2.35rem);
line-height: 1.12;
font-size: clamp(1.6rem, 7.4vw, 2.15rem);
line-height: 1.14;
}
.public-page .public-hero-title {
+10
View File
@@ -0,0 +1,10 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 48 48" width="48" height="48">
<rect width="48" height="48" rx="11" fill="#0e3a2c" />
<g fill="none" stroke="#dcc07f" stroke-width="1.6" stroke-linejoin="round">
<circle cx="24" cy="24" r="16.5" />
<rect x="14" y="14" width="20" height="20" />
<rect x="14" y="14" width="20" height="20" transform="rotate(45 24 24)" />
<circle cx="24" cy="24" r="4" />
</g>
<circle cx="24" cy="24" r="1.7" fill="#dcc07f" />
</svg>

After

Width:  |  Height:  |  Size: 491 B

+46
View File
@@ -0,0 +1,46 @@
import type { Metadata } from "next";
import Link from "next/link";
import { Compass, Home } from "lucide-react";
import { HeritageSeal } from "@/app/components/landing/HeritageOrnament";
import { SiteFooter } from "@/app/components/landing/SiteFooter";
import { SiteHeader } from "@/app/components/landing/SiteHeader";
export const metadata: Metadata = {
title: "Không tìm thấy trang | VR360 Như Quỳnh",
description: "Trang bạn tìm không tồn tại hoặc đã được di chuyển.",
};
export default function NotFound() {
return (
<main className="public-page flex min-h-[100dvh] flex-col bg-[var(--background)] text-[var(--foreground)]">
<SiteHeader />
<section className="mx-auto flex w-full max-w-3xl flex-1 flex-col items-center justify-center px-4 py-24 text-center sm:px-6">
<HeritageSeal className="h-16 w-16 text-[var(--primary)] opacity-85" />
<p className="public-display mt-7 text-[clamp(3.5rem,12vw,6rem)] font-bold leading-none text-[var(--tour-ink)]">
404
</p>
<p className="public-kicker public-kicker--center mt-6">Không tìm thấy trang</p>
<h1 className="public-display public-heading-safe mt-3 text-[clamp(1.7rem,4vw,2.5rem)] font-bold text-[var(--tour-ink)]">
Trang này chưa đưc số hóa
</h1>
<p className="mt-4 max-w-[52ch] text-base leading-7 text-[var(--muted-foreground)] sm:text-lg">
Đưng dẫn bạn truy cập thể đã thay đi hoặc nội dung đang đưc bổ sung. Hãy quay về
trang chủ hoặc khám phá hệ thống di tích của Như Quỳnh.
</p>
<div className="mt-9 flex flex-wrap items-center justify-center gap-3">
<Link href="/" className="public-cta">
<Home className="h-4 w-4" strokeWidth={1.8} />
Về trang chủ
</Link>
<Link href="/di-tich" className="public-cta-secondary">
<Compass className="h-4 w-4" strokeWidth={1.8} />
Khám phá di tích
</Link>
</div>
</section>
<SiteFooter />
</main>
);
}
+13 -1
View File
@@ -269,7 +269,7 @@ export default function Home() {
</div>
<div className="mt-10 grid gap-3 sm:grid-cols-2 lg:grid-cols-3">
{upcomingSites.map((site) => (
{upcomingSites.slice(0, 6).map((site) => (
<Link
key={site.id}
href={site.detailUrl}
@@ -290,6 +290,18 @@ export default function Home() {
</Link>
))}
</div>
{upcomingSites.length > 6 && (
<div className="mt-8 flex justify-center">
<Link
href="/di-tich"
className="inline-flex items-center gap-2 rounded-full border border-[var(--surface-border)] bg-[var(--surface-band)] px-6 py-2.5 text-sm font-bold text-[var(--tour-ink)] transition hover:border-[rgb(200_164_106_/_0.5)] hover:text-[var(--primary)]"
>
Xem thêm {upcomingSites.length - 6} di tích khác
<ArrowRight className="h-4 w-4" strokeWidth={1.8} />
</Link>
</div>
)}
</section>
<section className="border-y border-[var(--surface-border)] bg-[var(--surface-band)]">