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
11 changes: 11 additions & 0 deletions TODO.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
# TODO: Add Custom 404 Not Found Page

## Steps to Complete
- [x] Install react-router-dom in client
- [x] Create NotFound.jsx in client/src/pages/ with custom design
- [x] Update App.jsx to add BrowserRouter, Routes, and catch-all route
- [x] Update server.ts to add 404 fallback handler for API routes
- [x] Install dependencies
- [ ] Test 404 page for invalid frontend routes
- [ ] Test 404 response for invalid backend API routes
- [x] Ensure design matches app's theme
19 changes: 17 additions & 2 deletions api/server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -152,7 +152,7 @@ app.get("/api/framed-avatar/:username", async (req: Request, res: Response) => {
const textPosition = ((req.query.textPosition as string) || "bottom").toLowerCase();

// Emoji overlay parameters
const emojis = req.query.emojis as string | undefined;
const emojis = req.query.emojis ? decodeURIComponent(req.query.emojis as string) : undefined;
const emojiSizeStr = (req.query.emojiSize as string) || "40";
const emojiPosition = ((req.query.emojiPosition as string) || "top").toLowerCase();

Expand Down Expand Up @@ -586,6 +586,21 @@ app.get("/api/health", (req: Request, res: Response) => {
res.json({ status: "ok", timestamp: new Date().toISOString() });
});

// Catch-all for invalid API routes
app.use("/api/*", (req: Request, res: Response) => {
res.status(404).json({ error: "Not Found", message: "API endpoint not found." });
});

// Catch-all for SPA: serve index.html for non-API routes
app.get("*", (req: Request, res: Response) => {
const indexPath = path.join(ASSET_BASE_PATH, "public", "index.html");
if (fs.existsSync(indexPath)) {
res.sendFile(indexPath);
} else {
res.status(404).json({ error: "Not Found", message: "Page not found." });
}
});

// Start server
app.listen(PORT, () => {
console.log(`🚀 Server running at http://localhost:${PORT}`);
Expand All @@ -595,4 +610,4 @@ app.listen(PORT, () => {
console.log(` GET /api/framed-avatar/:username - Generate framed avatar`);
console.log(` GET /api/smart-frame/:username - AI-powered smart frame suggestions`);
console.log(` GET /api/health - Health check`);
});
});
3 changes: 2 additions & 1 deletion client/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,8 @@
"lucide-react": "^0.544.0",
"pnpm": "^10.18.1",
"react": "^19.1.1",
"react-dom": "^19.1.1"
"react-dom": "^19.1.1",
"react-router-dom": "^7.1.1"
},
"devDependencies": {
"@eslint/js": "^9.36.0",
Expand Down
45 changes: 45 additions & 0 deletions client/pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

35 changes: 22 additions & 13 deletions client/src/App.jsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import React, { useState, useEffect, useMemo } from "react";
import { BrowserRouter, Routes, Route } from "react-router-dom";
import AOS from 'aos';
import 'aos/dist/aos.css';
import NotFound from "./pages/NotFound.jsx";
import {
Frame,
Download,
Expand Down Expand Up @@ -486,18 +488,21 @@ function App() {
const finalRadiusForDisplay = shape === "circle" ? maxRadius : radius;

return (
<div
style={{
minHeight: "100vh",
background: colors.bgBody,
padding: "24px 16px",
color: colors.textPrimary,
}}>
<div
style={{
maxWidth: "1200px",
margin: "0 auto",
}}>
<BrowserRouter>
<Routes>
<Route path="/" element={
<div
style={{
minHeight: "100vh",
background: colors.bgBody,
padding: "24px 16px",
color: colors.textPrimary,
}}>
<div
style={{
maxWidth: "1200px",
margin: "0 auto",
}}>
{/* --- 1. Top Bar: Title + Community Button --- */}
<div
style={{
Expand Down Expand Up @@ -1831,7 +1836,11 @@ function App() {
}
`}</style>
</div>
);
} />
<Route path="*" element={<NotFound />} />
</Routes>
</BrowserRouter>
);
}

export default App;
146 changes: 146 additions & 0 deletions client/src/pages/NotFound.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,146 @@
import React, { useEffect, useState } from "react";
import { useNavigate } from "react-router-dom";
import { Home, AlertCircle, Sparkles } from "lucide-react";

function NotFound() {
const navigate = useNavigate();
const [isDark, setIsDark] = useState(false);

useEffect(() => {
const checkDark = window.matchMedia("(prefers-color-scheme: dark)").matches;
setIsDark(checkDark);

const mediaQuery = window.matchMedia("(prefers-color-scheme: dark)");
const handler = (e) => setIsDark(e.matches);
mediaQuery.addEventListener("change", handler);
return () => mediaQuery.removeEventListener("change", handler);
}, []);

const colors = {
textPrimary: isDark ? "#e5e7eb" : "#111827",
textSecondary: isDark ? "#9ca3af" : "#6b7280",
bgBody: isDark
? "#0F172A"
: "linear-gradient(135deg, #e0e7ff 0%, #f3e8ff 50%, #fce7f3 100%)",
bgCard: isDark ? "#1E293B" : "white",
border: isDark ? "#374151" : "#e5e7eb",
accentPrimary: "#7c3aed",
accentSecondary: "#a855f7",
};

return (
<div
style={{
minHeight: "100vh",
background: colors.bgBody,
display: "flex",
alignItems: "center",
justifyContent: "center",
padding: "24px",
}}
>
<div
style={{
background: colors.bgCard,
borderRadius: "16px",
padding: "48px",
maxWidth: "500px",
width: "100%",
textAlign: "center",
boxShadow: "0 25px 50px -12px rgba(0, 0, 0, 0.5)",
border: `1px solid ${colors.border}`,
}}
>
<div style={{ marginBottom: "24px" }}>
<div style={{ position: "relative", display: "inline-block" }}>
<AlertCircle
size={80}
color={colors.accentPrimary}
strokeWidth={1.5}
/>
<Sparkles
size={24}
color={colors.accentSecondary}
style={{
position: "absolute",
top: "-8px",
right: "-8px",
}}
/>
</div>
</div>

<h1
style={{
fontSize: "48px",
fontWeight: "900",
fontFamily: "Georgia, Times New Roman, Times, serif",
fontStyle: "italic",
background: "linear-gradient(to right, #7c3aed, #a855f7, #ec4899)",
WebkitBackgroundClip: "text",
WebkitTextFillColor: "transparent",
backgroundClip: "text",
margin: "0 0 16px 0",
}}
>
404
</h1>

<h2
style={{
fontSize: "24px",
fontWeight: "bold",
color: colors.textPrimary,
margin: "0 0 16px 0",
}}
>
Page Not Found
</h2>

<p
style={{
color: colors.textSecondary,
fontSize: "16px",
lineHeight: "1.6",
margin: "0 0 32px 0",
}}
>
Oops! It looks like you've wandered into uncharted territory. The page
you're looking for doesn't exist or may have been moved.
</p>

<button
onClick={() => navigate("/")}
style={{
background: "linear-gradient(to right, #7c3aed, #a855f7)",
color: "white",
padding: "14px 28px",
borderRadius: "8px",
border: "none",
fontWeight: "600",
fontSize: "16px",
cursor: "pointer",
display: "inline-flex",
alignItems: "center",
gap: "8px",
transition: "all 0.2s",
boxShadow: "0 4px 6px -1px rgba(0, 0, 0, 0.1)",
}}
onMouseEnter={(e) => {
e.currentTarget.style.transform = "translateY(-2px)";
e.currentTarget.style.boxShadow = "0 6px 12px -2px rgba(0, 0, 0, 0.2)";
}}
onMouseLeave={(e) => {
e.currentTarget.style.transform = "translateY(0)";
e.currentTarget.style.boxShadow = "0 4px 6px -1px rgba(0, 0, 0, 0.1)";
}}
>
<Home size={20} />
Go Home
</button>
</div>
</div>
);
}

export default NotFound;
2 changes: 1 addition & 1 deletion public/frames/starry/metadata.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,5 +2,5 @@
"name": "Starry Theme",
"author": "vishal",
"description": "Black and Red Theme frame",
  "size": 256
"size": 256
}
Binary file added test.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.