Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Submit feedback
Contribute to GitLab
Sign in
Toggle navigation
U
upgrade-data-crawler-fe
Project
Project
Details
Activity
Releases
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
ThinhNC
upgrade-data-crawler-fe
Commits
9151bfc2
Commit
9151bfc2
authored
Sep 06, 2026
by
ThinhNC
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
feat: implement custom 404 not-found page with localized client component
parent
82f476d0
Changes
4
Show whitespace changes
Inline
Side-by-side
Showing
4 changed files
with
144 additions
and
114 deletions
+144
-114
page.tsx
src/app/404/page.tsx
+8
-2
not-found.tsx
src/app/not-found.tsx
+8
-110
page.tsx
src/app/not-found/page.tsx
+8
-2
not-found-client.tsx
src/components/common/not-found-client.tsx
+120
-0
No files found.
src/app/404/page.tsx
View file @
9151bfc2
import
{
notFound
}
from
"next/navigation"
;
import
type
{
Metadata
}
from
"next"
;
import
{
NotFoundClient
}
from
"@/components/common/not-found-client"
;
export
const
metadata
:
Metadata
=
{
title
:
"Không Tìm Thấy Trang (404)"
,
description
:
"Trang bạn yêu cầu không tồn tại trong hệ thống Data Crawler"
,
};
export
default
function
Page404
()
{
notFound
()
;
return
<
NotFoundClient
/>
;
}
src/app/not-found.tsx
View file @
9151bfc2
"use client"
;
import
type
{
Metadata
}
from
"next"
;
import
{
NotFoundClient
}
from
"@/components/common/not-found-client"
;
import
React
from
"react"
;
import
Link
from
"next/link"
;
import
{
usePathname
}
from
"next/navigation"
;
import
{
Compass
,
Home
,
Bot
}
from
"lucide-react"
;
import
{
useLanguage
}
from
"@/providers/language-provider"
;
import
{
ThemeToggle
}
from
"@/components/common/theme-toggle"
;
import
{
LanguageToggle
}
from
"@/components/common/language-toggle"
;
import
{
STANDALONE_PATHS
}
from
"@/components/common/app-shell"
;
import
{
Button
}
from
"@/components/ui/button"
;
export
const
metadata
:
Metadata
=
{
title
:
"Không Tìm Thấy Trang (404)"
,
description
:
"Trang bạn yêu cầu không tồn tại trong hệ thống Data Crawler"
,
};
export
default
function
NotFound
()
{
const
pathname
=
usePathname
();
const
{
t
}
=
useLanguage
();
// Determine if this 404 page is accessed standalone (outside the dashboard layout / no Navbar)
const
isStandalone
=
!
pathname
||
STANDALONE_PATHS
.
some
((
path
)
=>
pathname
.
startsWith
(
path
))
||
pathname
===
"/"
||
pathname
===
"/not-found"
||
pathname
===
"/404"
;
const
content
=
(
<
div
className=
"flex flex-col items-center justify-center text-center max-w-lg mx-auto"
>
{
/* 404 Glowing Compass Icon */
}
<
div
className=
"relative mb-6"
>
<
div
className=
"absolute -inset-4 rounded-full bg-emerald-500/15 blur-xl"
/>
<
div
className=
"relative flex h-20 w-20 items-center justify-center rounded-3xl border border-emerald-500/30 bg-card shadow-lg shadow-emerald-950/10"
>
<
Compass
className=
"h-10 w-10 text-emerald-600 dark:text-emerald-400 animate-[spin_12s_linear_infinite]"
/>
</
div
>
</
div
>
{
/* 404 Badge */
}
<
div
className=
"inline-flex items-center rounded-full border border-emerald-500/20 bg-emerald-500/10 px-3 py-1 text-xs font-semibold text-emerald-600 dark:text-emerald-400 mb-4"
>
<
span
>
404
</
span
>
</
div
>
{
/* Title with Theme-Aware High Contrast */
}
<
h1
className=
"text-3xl font-extrabold tracking-tight text-foreground sm:text-4xl mb-3"
>
{
t
.
auth
.
notFound
.
title
}
</
h1
>
{
/* Subtitle / Description */
}
<
p
className=
"max-w-md text-sm text-muted-foreground mb-8 leading-relaxed"
>
{
t
.
auth
.
notFound
.
subtitle
}
</
p
>
{
/* Single Action Button: Return Home */
}
<
div
>
<
Link
href=
"/"
>
<
Button
size=
"lg"
variant=
"default"
className=
"rounded-2xl bg-emerald-600 hover:bg-emerald-700 text-white font-medium cursor-pointer shadow-sm shadow-emerald-600/20 px-6"
>
<
Home
className=
"mr-2 h-4 w-4"
/>
{
t
.
auth
.
notFound
.
backHome
}
</
Button
>
</
Link
>
</
div
>
</
div
>
);
// If accessed outside dashboard or in standalone auth mode, wrap with dedicated header & footer controls
if
(
isStandalone
)
{
return
(
<
div
className=
"relative flex min-h-screen flex-col justify-between p-4 sm:p-6 overflow-hidden"
>
{
/* Decorative Biophilic Background Glows */
}
<
div
className=
"pointer-events-none absolute -top-24 left-1/2 -z-10 h-[500px] w-[500px] -translate-x-1/2 rounded-full bg-emerald-500/10 blur-3xl dark:bg-emerald-500/15"
/>
<
div
className=
"pointer-events-none absolute -bottom-24 right-1/4 -z-10 h-96 w-96 rounded-full bg-teal-500/10 blur-3xl dark:bg-teal-500/10"
/>
{
/* Minimal Header with Logo, Language & Theme Controls */
}
<
header
className=
"flex w-full items-center justify-between"
>
<
Link
href=
"/"
className=
"flex items-center gap-2.5 transition-opacity hover:opacity-90"
>
<
div
className=
"flex h-9 w-9 items-center justify-center rounded-2xl bg-gradient-to-br from-emerald-500 to-teal-700 shadow-md shadow-emerald-500/20 text-white"
>
<
Bot
className=
"h-5 w-5"
/>
</
div
>
<
span
className=
"font-bold text-base tracking-tight text-foreground"
>
DataCrawler
</
span
>
</
Link
>
{
/* Theme & Language Controls */
}
<
div
className=
"flex items-center gap-2"
>
<
LanguageToggle
/>
<
ThemeToggle
/>
</
div
>
</
header
>
{
/* Centered Main 404 Hero */
}
<
div
className=
"my-auto flex items-center justify-center py-8"
>
{
content
}
</
div
>
{
/* Clean Minimal Footer */
}
<
footer
className=
"text-center text-[11px] text-muted-foreground/80 py-2"
>
Data Crawler ©
{
new
Date
().
getFullYear
()
}
- Code by @hnihTyoB
</
footer
>
</
div
>
);
}
// If accessed inside the main dashboard (where Navbar and default footer are already rendered by AppShell)
return
(
<
div
className=
"flex min-h-[calc(100vh-14rem)] flex-col items-center justify-center text-center px-4 py-8"
>
{
content
}
</
div
>
);
return
<
NotFoundClient
/>;
}
src/app/not-found/page.tsx
View file @
9151bfc2
import
{
notFound
}
from
"next/navigation"
;
import
type
{
Metadata
}
from
"next"
;
import
{
NotFoundClient
}
from
"@/components/common/not-found-client"
;
export
const
metadata
:
Metadata
=
{
title
:
"Không Tìm Thấy Trang (404)"
,
description
:
"Trang bạn yêu cầu không tồn tại trong hệ thống Data Crawler"
,
};
export
default
function
NotFoundPage
()
{
notFound
()
;
return
<
NotFoundClient
/>
;
}
src/components/common/not-found-client.tsx
0 → 100644
View file @
9151bfc2
"use client"
;
import
React
,
{
useEffect
}
from
"react"
;
import
Link
from
"next/link"
;
import
{
usePathname
}
from
"next/navigation"
;
import
{
Compass
,
Home
,
Bot
}
from
"lucide-react"
;
import
{
useLanguage
}
from
"@/providers/language-provider"
;
import
{
ThemeToggle
}
from
"@/components/common/theme-toggle"
;
import
{
LanguageToggle
}
from
"@/components/common/language-toggle"
;
import
{
STANDALONE_PATHS
}
from
"@/components/common/app-shell"
;
import
{
Button
}
from
"@/components/ui/button"
;
export
function
NotFoundClient
()
{
const
pathname
=
usePathname
();
const
{
t
}
=
useLanguage
();
// Dynamically update browser tab title to reflect language switch
useEffect
(()
=>
{
const
title
=
`
${
t
.
auth
.
notFound
.
title
}
(404) | Data Crawler`
;
document
.
title
=
title
;
},
[
t
]);
// Determine if this 404 page is accessed standalone (outside the dashboard layout / no Navbar)
const
isStandalone
=
!
pathname
||
STANDALONE_PATHS
.
some
((
path
)
=>
pathname
.
startsWith
(
path
))
||
pathname
===
"/"
||
pathname
===
"/not-found"
||
pathname
===
"/404"
;
const
content
=
(
<
div
className=
"flex flex-col items-center justify-center text-center max-w-lg mx-auto"
>
{
/* 404 Glowing Compass Icon */
}
<
div
className=
"relative mb-6"
>
<
div
className=
"absolute -inset-4 rounded-full bg-emerald-500/15 blur-xl"
/>
<
div
className=
"relative flex h-20 w-20 items-center justify-center rounded-3xl border border-emerald-500/30 bg-card shadow-lg shadow-emerald-950/10"
>
<
Compass
className=
"h-10 w-10 text-emerald-600 dark:text-emerald-400 animate-[spin_12s_linear_infinite]"
/>
</
div
>
</
div
>
{
/* 404 Badge */
}
<
div
className=
"inline-flex items-center rounded-full border border-emerald-500/20 bg-emerald-500/10 px-3 py-1 text-xs font-semibold text-emerald-600 dark:text-emerald-400 mb-4"
>
<
span
>
404
</
span
>
</
div
>
{
/* Title with Theme-Aware High Contrast */
}
<
h1
className=
"text-3xl font-extrabold tracking-tight text-foreground sm:text-4xl mb-3"
>
{
t
.
auth
.
notFound
.
title
}
</
h1
>
{
/* Subtitle / Description */
}
<
p
className=
"max-w-md text-sm text-muted-foreground mb-8 leading-relaxed"
>
{
t
.
auth
.
notFound
.
subtitle
}
</
p
>
{
/* Single Action Button: Return Home */
}
<
div
>
<
Link
href=
"/"
>
<
Button
size=
"lg"
variant=
"default"
className=
"rounded-2xl bg-emerald-600 hover:bg-emerald-700 text-white font-medium cursor-pointer shadow-sm shadow-emerald-600/20 px-6"
>
<
Home
className=
"mr-2 h-4 w-4"
/>
{
t
.
auth
.
notFound
.
backHome
}
</
Button
>
</
Link
>
</
div
>
</
div
>
);
// If accessed outside dashboard or in standalone auth mode, wrap with dedicated header & footer controls
if
(
isStandalone
)
{
return
(
<
div
className=
"relative flex min-h-screen flex-col justify-between p-4 sm:p-6 overflow-hidden"
>
{
/* Decorative Biophilic Background Glows */
}
<
div
className=
"pointer-events-none absolute -top-24 left-1/2 -z-10 h-[500px] w-[500px] -translate-x-1/2 rounded-full bg-emerald-500/10 blur-3xl dark:bg-emerald-500/15"
/>
<
div
className=
"pointer-events-none absolute -bottom-24 right-1/4 -z-10 h-96 w-96 rounded-full bg-teal-500/10 blur-3xl dark:bg-teal-500/10"
/>
{
/* Minimal Header with Logo, Language & Theme Controls */
}
<
header
className=
"flex w-full items-center justify-between"
>
<
Link
href=
"/"
className=
"flex items-center gap-2.5 transition-opacity hover:opacity-90"
>
<
div
className=
"flex h-9 w-9 items-center justify-center rounded-2xl bg-gradient-to-br from-emerald-500 to-teal-700 shadow-md shadow-emerald-500/20 text-white"
>
<
Bot
className=
"h-5 w-5"
/>
</
div
>
<
span
className=
"font-bold text-base tracking-tight text-foreground"
>
DataCrawler
</
span
>
</
Link
>
{
/* Theme & Language Controls */
}
<
div
className=
"flex items-center gap-2"
>
<
LanguageToggle
/>
<
ThemeToggle
/>
</
div
>
</
header
>
{
/* Centered Main 404 Hero */
}
<
div
className=
"my-auto flex items-center justify-center py-8"
>
{
content
}
</
div
>
{
/* Clean Minimal Footer */
}
<
footer
className=
"text-center text-[11px] text-muted-foreground/80 py-2"
>
Data Crawler ©
{
new
Date
().
getFullYear
()
}
- Code by @hnihTyoB
</
footer
>
</
div
>
);
}
// If accessed inside the main dashboard (where Navbar and default footer are already rendered by AppShell)
return
(
<
div
className=
"flex min-h-[calc(100vh-14rem)] flex-col items-center justify-center text-center px-4 py-8"
>
{
content
}
</
div
>
);
}
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment