Skip to content

Commit 9156731

Browse files
committed
Add friendly 404 page
1 parent 8778aa1 commit 9156731

File tree

5 files changed

+167
-20
lines changed

5 files changed

+167
-20
lines changed

.env.example

Lines changed: 18 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,10 @@
11
# Copyright (C) 2025 Dimitrios S. Sfyris
22
# SPDX-License-Identifier: GPL-3.0-or-later
3-
# ─────────────────────────────────────────────────────────────
4-
# Core app settings (Create React App exposes only REACT_APP_* vars to the client)
5-
# ─────────────────────────────────────────────────────────────
63

74
# Brand / UI
8-
REACT_APP_APP_NAME="YourAppNameHere"
9-
REACT_APP_DEFAULT_LOCALE="en-US"
10-
REACT_APP_CURRENCY="USD"
5+
REACT_APP_APP_NAME="AspectReact Store"
6+
REACT_APP_DEFAULT_LOCALE=en-US
7+
REACT_APP_CURRENCY=USD
118

129
# Feature flags
1310
REACT_APP_ENABLE_TOASTS=true
@@ -17,33 +14,39 @@ REACT_APP_ENABLE_TOASTS=true
1714
# ─────────────────────────────────────────────────────────────
1815
# Options:
1916
# local = Demo-only, credentials stored in browser (no backend)
20-
# api = Custom backend (Next.js, Express, etc.) via API_BASE_URL
17+
# api = Custom backend via REACT_APP_API_BASE_URL
2118
# auth0 = Hosted Auth0 Universal Login
2219
#
23-
# Change this to switch modes easily.
24-
REACT_APP_AUTH_MODE="local"
20+
# NOTE: Your code expects one of: local | api | auth0
21+
REACT_APP_AUTH_MODE=local
2522

2623
# API backend (used only in "api" mode)
27-
REACT_APP_API_BASE_URL="http://localhost:4000"
24+
REACT_APP_API_BASE_URL=http://localhost:4000
2825

2926
# ─────────────────────────────────────────────────────────────
3027
# Auth0 (used only in "auth0" mode)
3128
# ─────────────────────────────────────────────────────────────
3229
# Create a SPA app in Auth0 and copy the Domain and Client ID.
3330
# Allowed Callback URLs: http://localhost:3000
3431
# Allowed Logout URLs: http://localhost:3000
35-
REACT_APP_AUTH0_DOMAIN="your-tenant.us.auth0.com"
36-
REACT_APP_AUTH0_CLIENT_ID="your-auth0-client-id"
32+
REACT_APP_AUTH0_DOMAIN=your-tenant.us.auth0.com
33+
REACT_APP_AUTH0_CLIENT_ID=your-auth0-client-id
34+
35+
# ─────────────────────────────────────────────────────────────
36+
# Support / Contact
37+
# ─────────────────────────────────────────────────────────────
38+
# Used on the 404 page and any “report a problem” links
39+
REACT_APP_SUPPORT_EMAIL=support@yourdomain.com
3740

3841
# ─────────────────────────────────────────────────────────────
39-
# Optional integrations (placeholders for future real checkout, analytics, etc.)
42+
# Optional integrations (placeholders for future real checkout, analytics, etc.)
4043
# ─────────────────────────────────────────────────────────────
4144

4245
# Stripe (future)
43-
REACT_APP_STRIPE_PUBLISHABLE_KEY="pk_test_yourPublishableKeyHere"
46+
REACT_APP_STRIPE_PUBLISHABLE_KEY=pk_test_yourPublishableKeyHere
4447

4548
# Analytics (future)
46-
REACT_APP_ANALYTICS_WRITE_KEY="your-analytics-key"
49+
REACT_APP_ANALYTICS_WRITE_KEY=your-analytics-key
4750
REACT_APP_ENABLE_ANALYTICS=false
4851

4952
# ─────────────────────────────────────────────────────────────

.github/workflows/ci.yml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@ jobs:
4646
- name: Tests
4747
env:
4848
CI: true
49-
run: npm test -- --watchAll=false
49+
run: npm run test:ci
5050

5151
- name: Build (CRA)
5252
env:
@@ -59,6 +59,7 @@ jobs:
5959
REACT_APP_API_BASE_URL: ${{ vars.REACT_APP_API_BASE_URL || '' }}
6060
REACT_APP_AUTH0_DOMAIN: ${{ secrets.REACT_APP_AUTH0_DOMAIN }}
6161
REACT_APP_AUTH0_CLIENT_ID: ${{ secrets.REACT_APP_AUTH0_CLIENT_ID }}
62+
REACT_APP_SUPPORT_EMAIL: ${{ vars.REACT_APP_SUPPORT_EMAIL || 'support@yourdomain.com' }}
6263
run: npm run build
6364

6465
- name: Upload production build

src/App.js

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ import Contact from "./component/Contact";
2323
import Exit from "./component/Exit";
2424
import AuthSection from "./component/AuthSection";
2525
import ProtectedPage from "./component/ProtectedPage";
26+
import NotFound from "./component/NotFound";
2627

2728
import Login from "./component/Login";
2829
import Signup from "./component/Signup";
@@ -240,6 +241,9 @@ const AppWrapper = () => {
240241

241242
{/* Demo/testing */}
242243
<Route path="/authSection" element={<AuthSection />} />
244+
245+
{/* 404 catch-all (last) */}
246+
<Route path="*" element={<NotFound />} />
243247
</Routes>
244248
</main>
245249

src/component/NotFound.jsx

Lines changed: 133 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,133 @@
1+
/*
2+
* Copyright (C) 2025 Dimitrios S. Sfyris
3+
* SPDX-License-Identifier: GPL-3.0-or-later
4+
*/
5+
import React, { useEffect } from "react";
6+
import { Link, useNavigate } from "react-router-dom";
7+
import { APP_NAME, SUPPORT_EMAIL } from "../lib/config";
8+
9+
function setMeta(name, content) {
10+
let tag = document.querySelector(`meta[name="${name}"]`);
11+
if (!tag) {
12+
tag = document.createElement("meta");
13+
tag.setAttribute("name", name);
14+
document.head.appendChild(tag);
15+
}
16+
tag.setAttribute("content", content);
17+
}
18+
19+
export default function NotFound() {
20+
const navigate = useNavigate();
21+
22+
useEffect(() => {
23+
const prevTitle = document.title;
24+
document.title = `404 — Page Not Found | ${APP_NAME}`;
25+
setMeta("robots", "noindex,follow");
26+
setMeta(
27+
"description",
28+
"The page you’re looking for was not found. Explore products, learn about us, or contact support."
29+
);
30+
return () => {
31+
document.title = prevTitle;
32+
};
33+
}, []);
34+
35+
const path =
36+
typeof window !== "undefined" ? window.location.pathname : "/nonexistent";
37+
const subject = `Broken link: ${path}`;
38+
const mailto = `mailto:${encodeURIComponent(
39+
SUPPORT_EMAIL
40+
)}?subject=${encodeURIComponent(subject)}`;
41+
42+
return (
43+
<div className="mx-auto max-w-4xl p-6">
44+
<div className="rounded-2xl border border-neutral-200 bg-white p-8 shadow-card">
45+
<p className="text-sm tracking-wide text-neutral-500">Error 404</p>
46+
<h1 className="mt-1 font-display text-3xl font-semibold text-ink">
47+
We couldn’t find that page
48+
</h1>
49+
<p className="mt-2 text-neutral-600">
50+
The requested address{" "}
51+
<code className="rounded bg-neutral-100 px-1 py-0.5">{path}</code>{" "}
52+
doesn’t exist. It might have been moved or the link could be
53+
incorrect.
54+
</p>
55+
56+
<div className="mt-6 grid gap-3 sm:flex">
57+
<button
58+
onClick={() => navigate(-1)}
59+
className="inline-flex items-center justify-center rounded-xl border border-neutral-300 px-4 py-2 text-ink hover:bg-neutral-50"
60+
>
61+
Go back
62+
</button>
63+
<Link
64+
to="/"
65+
className="inline-flex items-center justify-center rounded-xl bg-ink px-4 py-2 text-white hover:bg-neutral-900"
66+
>
67+
Go to homepage
68+
</Link>
69+
<Link
70+
to="/product"
71+
className="inline-flex items-center justify-center rounded-xl bg-neutral-900/80 px-4 py-2 text-white hover:bg-neutral-900"
72+
>
73+
Browse products
74+
</Link>
75+
<Link
76+
to="/contact"
77+
className="inline-flex items-center justify-center rounded-xl border border-neutral-300 px-4 py-2 text-ink hover:bg-neutral-50"
78+
>
79+
Contact us
80+
</Link>
81+
</div>
82+
83+
<div className="mt-8">
84+
<h2 className="text-sm font-semibold uppercase tracking-wide text-neutral-500">
85+
Popular destinations
86+
</h2>
87+
<ul className="mt-3 grid gap-2 sm:grid-cols-2">
88+
<li>
89+
<Link
90+
className="block rounded-lg border border-neutral-200 p-3 hover:bg-neutral-50"
91+
to="/product"
92+
>
93+
Shop / Products
94+
</Link>
95+
</li>
96+
<li>
97+
<Link
98+
className="block rounded-lg border border-neutral-200 p-3 hover:bg-neutral-50"
99+
to="/about"
100+
>
101+
About
102+
</Link>
103+
</li>
104+
<li>
105+
<Link
106+
className="block rounded-lg border border-neutral-200 p-3 hover:bg-neutral-50"
107+
to="/cart"
108+
>
109+
Your cart
110+
</Link>
111+
</li>
112+
<li>
113+
<Link
114+
className="block rounded-lg border border-neutral-200 p-3 hover:bg-neutral-50"
115+
to="/login"
116+
>
117+
Sign in
118+
</Link>
119+
</li>
120+
</ul>
121+
</div>
122+
123+
<p className="mt-6 text-xs text-neutral-500">
124+
If you believe this is a broken link, please{" "}
125+
<a className="underline hover:no-underline" href={mailto}>
126+
report it to {SUPPORT_EMAIL}
127+
</a>
128+
.
129+
</p>
130+
</div>
131+
</div>
132+
);
133+
}

src/lib/config.js

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
*/
55
// Small helper to coerce env strings to boolean
66
const toBool = (val, fallback = false) => {
7-
if (val === undefined) return fallback;
7+
if (val === undefined || val === null) return fallback;
88
return ["1", "true", "yes", "on"].includes(String(val).toLowerCase());
99
};
1010

@@ -18,8 +18,9 @@ export const CURRENCY = process.env.REACT_APP_CURRENCY || "USD";
1818
// ────────────────────────────────────────────
1919
// Auth mode
2020
// Options: "auth0" | "local" | "api"
21+
// (Default aligned with .env.example -> "local")
2122
// ────────────────────────────────────────────
22-
export const AUTH_MODE = (process.env.REACT_APP_AUTH_MODE || "auth0").toLowerCase();
23+
export const AUTH_MODE = (process.env.REACT_APP_AUTH_MODE || "local").toLowerCase();
2324
export const IS_AUTH0 = AUTH_MODE === "auth0";
2425
export const IS_LOCAL = AUTH_MODE === "local";
2526
export const IS_API = AUTH_MODE === "api";
@@ -30,16 +31,21 @@ export const IS_API = AUTH_MODE === "api";
3031
export const ENABLE_TOASTS = toBool(process.env.REACT_APP_ENABLE_TOASTS, true);
3132

3233
// ────────────────────────────────────────────
33-
// API (used in "api" auth mode and future data calls)
34+
/** API (used in "api" auth mode and future data calls) */
3435
// ────────────────────────────────────────────
3536
export const API_BASE_URL = process.env.REACT_APP_API_BASE_URL || "";
3637

3738
// ────────────────────────────────────────────
38-
// Auth0 (used only when AUTH_MODE === "auth0")
39+
/** Auth0 (used only when AUTH_MODE === "auth0") */
3940
// ────────────────────────────────────────────
4041
export const AUTH0_DOMAIN = process.env.REACT_APP_AUTH0_DOMAIN || "";
4142
export const AUTH0_CLIENT_ID = process.env.REACT_APP_AUTH0_CLIENT_ID || "";
4243

44+
// ────────────────────────────────────────────
45+
/** Support / Contact (used e.g. on NotFound page) */
46+
// ────────────────────────────────────────────
47+
export const SUPPORT_EMAIL = process.env.REACT_APP_SUPPORT_EMAIL || "support@yourdomain.com";
48+
4349
// ────────────────────────────────────────────
4450
// Optional integrations (kept here for future use)
4551
// ────────────────────────────────────────────

0 commit comments

Comments
 (0)