Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
105 changes: 79 additions & 26 deletions frontend/app/not-found.tsx
Original file line number Diff line number Diff line change
@@ -1,60 +1,113 @@
"use client";

/**
* not-found.tsx — 404 page (issue #356)
*
* Improvements over the previous static version:
* • All text is translated via next-intl (4 locales: en, yo, ig, ha).
* • Authenticated users see additional quick-links: "Sell an asset" and
* "View your wallet". Guest users see the sign-in / create-account links
* instead — giving every visitor an actionable next step.
* • Styled consistently with the rest of the app (Tailwind + dark mode).
*/

import { useTranslations } from "next-intl";
import Link from "next/link";
import { useAuth } from "./hooks/useAuth";

export default function NotFound() {
const t = useTranslations("NotFound");
const { user, isLoading } = useAuth();

const isAuthenticated = !isLoading && user !== null;

/**
* Quick-link rows shown beneath the primary CTAs.
* Guests see the sign-in / create-account links; authenticated users see
* the marketplace, sell, and wallet pages instead.
*/
const quickLinks = isAuthenticated
? [
{ href: "/", label: t("marketplace") },
{ href: "/sell", label: t("sellAirtime") },
{ href: "/wallet", label: t("viewWallet") },
]
: [
{ href: "/", label: t("marketplace") },
{ href: "/sell", label: t("sellAirtime") },
{ href: "/auth/signup", label: t("createAccount") },
{ href: "/auth/verify", label: t("signIn") },
];

return (
<div className="flex flex-1 flex-col bg-gray-50 dark:bg-gray-900">
<main className="flex flex-1 items-center justify-center px-4 py-16 sm:px-6">
<div className="w-full max-w-lg text-center">
{/* Decorative large "404" */}
<p
aria-hidden="true"
className="text-[8rem] font-extrabold leading-none tracking-tighter text-gray-100 select-none dark:text-gray-800"
>
404
</p>

<span aria-hidden="true" className="mt-2 block text-5xl">🔍</span>
<span aria-hidden="true" className="mt-2 block text-5xl">
🔍
</span>

<h1 className="mt-4 text-2xl font-extrabold tracking-tight text-gray-900 dark:text-gray-100">
Page not found
{t("heading")}
</h1>

<p className="mt-3 text-sm text-gray-500 leading-relaxed max-w-sm mx-auto dark:text-gray-400">
The page you&apos;re looking for doesn&apos;t exist or has been moved.
Check the URL or head back to the marketplace.
{t("body")}
</p>

{/* Primary CTAs — always visible */}
<div className="mt-8 flex flex-col gap-3 sm:flex-row sm:justify-center">
<a
<Link
href="/"
className="inline-flex items-center justify-center rounded-xl bg-violet-600 px-6 py-3 text-sm font-semibold text-white transition-colors hover:bg-violet-700 focus:outline-none focus-visible:ring-2 focus-visible:ring-violet-500 focus-visible:ring-offset-2"
className="inline-flex items-center justify-center rounded-xl bg-violet-600 px-6 py-3 text-sm font-semibold text-white transition-colors hover:bg-violet-700 focus:outline-none focus-visible:ring-2 focus-visible:ring-violet-500 focus-visible:ring-offset-2 dark:focus-visible:ring-offset-gray-900"
>
Browse marketplace
</a>
{t("browseListings")}
</Link>

<a
href="/sell"
className="inline-flex items-center justify-center rounded-xl border border-gray-200 bg-white px-6 py-3 text-sm font-semibold text-gray-700 transition-colors hover:bg-gray-50 focus:outline-none focus-visible:ring-2 focus-visible:ring-violet-500 focus-visible:ring-offset-2 dark:border-gray-700 dark:bg-gray-800 dark:text-gray-300 dark:hover:bg-gray-700"
>
Sell airtime / data
</a>
{/* Authenticated: sell + wallet. Guest: sell only (wallet requires auth) */}
{!isLoading && (
<>
<Link
href="/sell"
className="inline-flex items-center justify-center rounded-xl border border-gray-200 bg-white px-6 py-3 text-sm font-semibold text-gray-700 transition-colors hover:bg-gray-50 focus:outline-none focus-visible:ring-2 focus-visible:ring-violet-500 focus-visible:ring-offset-2 dark:border-gray-700 dark:bg-gray-800 dark:text-gray-300 dark:hover:bg-gray-700 dark:focus-visible:ring-offset-gray-900"
>
{t("sellAsset")}
</Link>

{isAuthenticated && (
<Link
href="/wallet"
className="inline-flex items-center justify-center rounded-xl border border-gray-200 bg-white px-6 py-3 text-sm font-semibold text-gray-700 transition-colors hover:bg-gray-50 focus:outline-none focus-visible:ring-2 focus-visible:ring-violet-500 focus-visible:ring-offset-2 dark:border-gray-700 dark:bg-gray-800 dark:text-gray-300 dark:hover:bg-gray-700 dark:focus-visible:ring-offset-gray-900"
>
{t("viewWallet")}
</Link>
)}
</>
)}
</div>

{/* Secondary quick-links */}
<div className="mt-10 border-t border-gray-100 pt-8 dark:border-gray-700">
<p className="mb-4 text-xs font-semibold uppercase tracking-widest text-gray-400 dark:text-gray-500">
You might be looking for
{t("youMightBeLookingFor")}
</p>
<ul className="flex flex-wrap justify-center gap-x-6 gap-y-2">
{[
{ href: "/", label: "Marketplace" },
{ href: "/sell", label: "Sell airtime" },
{ href: "/auth/signup", label: "Create account" },
{ href: "/auth/verify", label: "Sign in" },
].map(({ href, label }) => (
{quickLinks.map(({ href, label }) => (
<li key={href}>
<a
<Link
href={href}
className="text-sm text-violet-600 hover:text-violet-800 hover:underline focus:outline-none focus-visible:ring-1 focus-visible:ring-violet-500 rounded transition-colors dark:text-violet-400 dark:hover:text-violet-300"
>
{label}
</a>
</Link>
</li>
))}
</ul>
Expand All @@ -65,16 +118,16 @@ export default function NotFound() {
<footer className="border-t border-gray-100 bg-white dark:border-gray-700 dark:bg-gray-800">
<div className="mx-auto max-w-7xl px-4 py-6 sm:px-6 lg:px-8 flex flex-col sm:flex-row items-center justify-between gap-4">
<p className="text-sm text-gray-400 dark:text-gray-500">
&copy; {new Date().getFullYear()} AirFlex. Open source under the MIT License.
{t("copyright", { year: new Date().getFullYear() })}
</p>
<div className="flex gap-5 text-sm text-gray-400 dark:text-gray-500">
<a
href="https://github.com/dark-sarge/Airflex"
href="https://github.com/arflexx/Airflex"
target="_blank"
rel="noreferrer"
className="hover:text-gray-600 transition-colors dark:hover:text-gray-300"
>
GitHub
{t("github")}
</a>
</div>
</div>
Expand Down
14 changes: 14 additions & 0 deletions frontend/locales/en/common.json
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,20 @@
"networkError": "Network error. Please try again.",
"resolveFailed": "Failed to resolve account name"
},
"NotFound": {
"heading": "Page not found",
"body": "The page you're looking for doesn't exist or has been moved. Check the URL or head back to the marketplace.",
"browseListings": "Browse Listings",
"sellAsset": "Sell an asset",
"viewWallet": "View your wallet",
"youMightBeLookingFor": "You might be looking for",
"marketplace": "Marketplace",
"sellAirtime": "Sell airtime",
"createAccount": "Create account",
"signIn": "Sign in",
"copyright": "© {year} AirFlex. Open source under the MIT License.",
"github": "GitHub"
},
"Trade": {
"loading": "Loading…",
"expired": "Expired",
Expand Down
14 changes: 14 additions & 0 deletions frontend/locales/ha/common.json
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,20 @@
"networkError": "Kuskuren hanyar sadarwa. Don Allah sake gwadawa.",
"resolveFailed": "An kasa neman sunan asusu"
},
"NotFound": {
"heading": "Ba a sami shafin ba",
"body": "Shafin da kake nema baya nan ko an canza shi. Duba URL ko koma kasuwa.",
"browseListings": "Duba Lissafi",
"sellAsset": "Sayar da abu",
"viewWallet": "Duba jakar hannu ɗinka",
"youMightBeLookingFor": "Wataƙila kana neman",
"marketplace": "Kasuwa",
"sellAirtime": "Sayar da airtime",
"createAccount": "Ƙirƙiri asusu",
"signIn": "Shiga",
"copyright": "© {year} AirFlex. Bude tushe a ƙarƙashin MIT License.",
"github": "GitHub"
},
"Trade": {
"loading": "Ana ɗauka…",
"expired": "Ya ƙare",
Expand Down
14 changes: 14 additions & 0 deletions frontend/locales/ig/common.json
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,20 @@
"networkError": "Mperi netwọk. Biko nwaa ọzọ.",
"resolveFailed": "Enweghị ike ịchọta aha akaụntụ"
},
"NotFound": {
"heading": "Ahụbeghị peeji",
"body": "Peeji ị na-achọ adịghị ma ọ bụ e bugharịala ya. Nyochaa URL ma ọ bụ laghachi na ahịa.",
"browseListings": "Lee Ndepụta",
"sellAsset": "Ree ihe",
"viewWallet": "Lee obere akpa gị",
"youMightBeLookingFor": "Enwere ike na ị na-achọ",
"marketplace": "Ahịa",
"sellAirtime": "Ree airtime",
"createAccount": "Mepụta akaụntụ",
"signIn": "Banye",
"copyright": "© {year} AirFlex. Mepee isi n'okpuru MIT License.",
"github": "GitHub"
},
"Trade": {
"loading": "Na-ebu…",
"expired": "Agwụla",
Expand Down
14 changes: 14 additions & 0 deletions frontend/locales/yo/common.json
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,20 @@
"networkError": "Àṣìṣe nẹ́tíwọ́ọ̀kì. Jọ̀wọ́ gbìyànjú lẹ́ẹ̀kan si.",
"resolveFailed": "Àkúná láti wá orúkọ àkọọ́lẹ̀"
},
"NotFound": {
"heading": "Àwárí ojú-ìwé",
"body": "Ojú-ìwé tí ó wá kò sí tàbí a ti gbé e. Ṣàyẹ̀wò URL tàbí padà sí ọjà.",
"browseListings": "Wo Àwọn Àtòjọ",
"sellAsset": "Ta nkan kan",
"viewWallet": "Wo wọ́lẹ́ẹ̀tì rẹ",
"youMightBeLookingFor": "Ó ṣeéṣe kí o wá fún",
"marketplace": "Ọjà",
"sellAirtime": "Ta airtime",
"createAccount": "Ṣẹ̀dá àkọọ́lẹ̀",
"signIn": "Wọlé",
"copyright": "© {year} AirFlex. Ìmọ̀ sílẹ̀ lábẹ́ MIT License.",
"github": "GitHub"
},
"Trade": {
"loading": "Ń kó…",
"expired": "Ó ti parí",
Expand Down