Skip to content
Open
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
13 changes: 13 additions & 0 deletions src/app/i/[id]/helpers.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import { isNotFoundError } from "@/lib/utils";
import { api } from "@/trpc/server";

export async function getInvoiceMeLink(id: string) {
try {
return await api.invoiceMe.getById.query(id);
} catch (error) {
if (isNotFoundError(error)) {
return null;
}
throw error;
}
}
6 changes: 2 additions & 4 deletions src/app/i/[id]/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,11 @@ import { Footer } from "@/components/footer";
import { Header } from "@/components/header";
import { InvoiceCreator } from "@/components/invoice-creator";
import { getInvoiceCount } from "@/lib/helpers/invoice";
import { api } from "@/trpc/server";
import { ArrowLeft } from "lucide-react";
import type { Metadata } from "next";
import Link from "next/link";
import { notFound } from "next/navigation";
import { getInvoiceMeLink } from "./helpers";

export const metadata: Metadata = {
title: "Invoice Me | EasyInvoice",
Expand All @@ -19,9 +19,7 @@ export default async function InvoiceMePage({
}: {
params: { id: string };
}) {
// TODO solve unauthenticated access
// TODO solve not found error like the subscription plan page
const invoiceMeLink = await api.invoiceMe.getById.query(params.id);
const invoiceMeLink = await getInvoiceMeLink(params.id);

if (!invoiceMeLink) {
notFound();
Expand Down
13 changes: 13 additions & 0 deletions src/app/invoices/[ID]/helpers.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import { isNotFoundError } from "@/lib/utils";
import { api } from "@/trpc/server";

export async function getInvoice(id: string) {
try {
return await api.invoice.getById.query(id);
} catch (error) {
if (isNotFoundError(error)) {
return null;
}
throw error;
}
}
3 changes: 2 additions & 1 deletion src/app/invoices/[ID]/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import { formatDate } from "@/lib/date-utils";
import { api } from "@/trpc/server";
import type { Metadata } from "next";
import { notFound } from "next/navigation";
import { getInvoice } from "./helpers";
export const metadata: Metadata = {
title: "Invoice Payment | EasyInvoice",
description: "Process payment for your invoice",
Expand All @@ -25,7 +26,7 @@ export default async function PaymentPage({
}: {
params: { ID: string };
}) {
const invoice = await api.invoice.getById.query(params.ID);
const invoice = await getInvoice(params.ID);

if (!invoice) {
notFound();
Expand Down
75 changes: 65 additions & 10 deletions src/app/not-found.tsx
Original file line number Diff line number Diff line change
@@ -1,24 +1,79 @@
import { DollarSign, FileText, Home, Wallet, Zap } from "lucide-react";
import Link from "next/link";

export default function NotFound() {
return (
<div className="min-h-screen flex items-center justify-center bg-gray-50">
<div className="max-w-md w-full text-center">
<div className="min-h-screen flex flex-col items-center justify-center bg-gray-50 px-6">
<div className="max-w-2xl w-full text-center">
<div className="mb-8">
<h1 className="text-6xl font-bold text-gray-900 mb-4">404</h1>
<h2 className="text-2xl font-semibold text-gray-700 mb-2">
Not Found
</h2>
<div className="w-16 h-16 rounded-xl bg-black flex items-center justify-center mx-auto mb-6">
<span className="text-white font-bold text-xl">EI</span>
</div>
</div>

<div className="mb-8">
<h1 className="text-8xl font-bold text-gray-900 mb-4">404</h1>
<div className="flex items-center justify-center gap-2 mb-4">
<Wallet className="w-5 h-5 text-gray-600" />
<span className="text-lg font-medium text-gray-700">
Page Not Found
</span>
</div>
<p className="text-gray-600 mb-2">
This page seems to have been sent to a non-existent wallet address.
</p>
<p className="text-gray-600">
Sorry, we couldn't find the page you're looking for.
Don't worry, your invoices are safe and sound!{" "}
</p>
</div>

<div className="grid grid-cols-1 md:grid-cols-3 gap-4 mb-8">
<Link
href="/payouts"
className="shadow-md group p-6 bg-white rounded-lg border border-gray-200 hover:border-gray-300 transition-colors"
Copy link
Contributor

@coderabbitai coderabbitai bot Aug 8, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Add visible focus states to all links (keyboard a11y).

Links currently have hover only. Add focus-visible rings so keyboard users can see focus.

-            className="shadow-md group p-6 bg-white rounded-lg border border-gray-200 hover:border-gray-300 transition-colors"
+            className="shadow-md group p-6 bg-white rounded-lg border border-gray-200 hover:border-gray-300 transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-black focus-visible:ring-offset-2"
-            className="shadow-md group p-6 bg-white rounded-lg border border-gray-200 hover:border-gray-300 transition-colors"
+            className="shadow-md group p-6 bg-white rounded-lg border border-gray-200 hover:border-gray-300 transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-black focus-visible:ring-offset-2"
-            className="shadow-md group p-6 bg-white rounded-lg border border-gray-200 hover:border-gray-300 transition-colors"
+            className="shadow-md group p-6 bg-white rounded-lg border border-gray-200 hover:border-gray-300 transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-black focus-visible:ring-offset-2"
-          className="inline-flex items-center gap-2 px-6 py-3 bg-black text-white font-medium rounded-lg hover:bg-gray-800 transition-colors duration-200"
+          className="inline-flex items-center gap-2 px-6 py-3 bg-black text-white font-medium rounded-lg hover:bg-gray-800 transition-colors duration-200 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-white focus-visible:ring-offset-2 focus-visible:ring-offset-black"

Also applies to: 46-46, 57-57, 73-73

🤖 Prompt for AI Agents
In src/app/not-found.tsx at lines 33, 46, 57, and 73, the links only have hover
styles but lack visible focus states for keyboard accessibility. Add
focus-visible ring styles to the className of these links to ensure keyboard
users can see when the link is focused. Use appropriate focus-visible utility
classes to create a visible ring or outline on focus without affecting mouse
users.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I agree 👍

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@MantisClone Thanks for confirming! The accessibility improvements will help ensure all users can navigate the 404 page effectively with keyboard navigation.


🧠 Learnings used
Learnt from: bassgeta
PR: RequestNetwork/easy-invoice#117
File: src/app/not-found.tsx:29-67
Timestamp: 2025-08-08T09:52:43.700Z
Learning: Repository: RequestNetwork/easy-invoice
Context: src/app/not-found.tsx (Next.js, TypeScript/React)
Learning: It is acceptable (by current product decision) for the NotFound page to display links to /invoices/create and /subscription-plans even though these routes are auth-gated. Rationale: prevent the 404 from feeling empty; most users are authenticated. No lock icons or conditional rendering required for now; can be revisited later as an enhancement.

>
<div className="w-12 h-12 rounded-lg bg-blue-100 flex items-center justify-center mx-auto mb-4 group-hover:bg-blue-200 transition-colors">
<DollarSign className="w-6 h-6 text-blue-600" />
</div>
<h3 className="font-semibold text-gray-900 mb-2">Payouts</h3>
<p className="text-sm text-gray-600">
Single, batch or recurring payouts
</p>
</Link>

<Link
href="/invoices/create"
className="shadow-md group p-6 bg-white rounded-lg border border-gray-200 hover:border-gray-300 transition-colors"
>
<div className="w-12 h-12 rounded-lg bg-purple-100 flex items-center justify-center mx-auto mb-4 group-hover:bg-purple-200 transition-colors">
<Zap className="w-6 h-6 text-purple-600" />
</div>
<h3 className="font-semibold text-gray-900 mb-2">Create Invoice</h3>
<p className="text-sm text-gray-600">Start a new payment request</p>
</Link>

<Link
href="/subscription-plans"
className="shadow-md group p-6 bg-white rounded-lg border border-gray-200 hover:border-gray-300 transition-colors"
>
<div className="w-12 h-12 rounded-lg bg-green-100 flex items-center justify-center mx-auto mb-4 group-hover:bg-green-200 transition-colors">
<FileText className="w-6 h-6 text-green-600" />
</div>
<h3 className="font-semibold text-gray-900 mb-2">
Subscription Plans
</h3>
<p className="text-sm text-gray-600">
Manage your subscription plans
</p>
</Link>
</div>

<Link
href="/"
className="inline-flex items-center px-6 py-3 bg-primary text-white font-medium rounded-lg hover:bg-blue-700 transition-colors duration-200"
href="/dashboard"
className="inline-flex items-center gap-2 px-6 py-3 bg-black text-white font-medium rounded-lg hover:bg-gray-800 transition-colors duration-200"
>
Go Home
<Home className="w-4 h-4" />
Back to Dashboard
</Link>
</div>
</div>
Expand Down