Skip to content

feat: make the 404 page nicer and implement 404 handling #117

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 3 commits into
base: main
Choose a base branch
from

Conversation

bassgeta
Copy link
Contributor

@bassgeta bassgeta commented Aug 8, 2025

Problem

Our 404 page is rudimentary and we do not have not found handling when we fetch invoices and invoice me links.

Solution

Upgrade the design and handle 404 when the id from parameters yields no results.

Changes

  • Redesigned the 404 page (I hope it's tastefully quirky)
image - Added not found handling for the invoices/:id page - Added not found handling for the i/:id page

Testing

  1. Go to /invalid-route or whatever, and verify the 404 page shows up
image 2. Go to `/i/not-a-valid-id` and verify the 404 page shows up image 3. Go to `/invoices/not-a-valid-id` and verify that the 404 page shows up image 4. Verify that the links on the 404 page work properly

Resolves #108

Summary by CodeRabbit

  • New Features
    • Redesigned 404 page with richer visuals, icons, and direct links to Payouts, Create Invoice, and Subscription Plans.
  • Refactor
    • Invoice and payment pages now use centralized helper logic for fetching invoices, adding controlled handling for missing invoices while preserving existing page behavior.

@bassgeta bassgeta self-assigned this Aug 8, 2025
Copy link
Contributor

coderabbitai bot commented Aug 8, 2025

Walkthrough

Adds two server-side helper functions to centralize invoice fetching with not-found handling, updates two page routes to use those helpers, and replaces the app-wide 404 page with a redesigned NotFound component.

Changes

Cohort / File(s) Change Summary
InvoiceMe helper & page
src/app/i/[id]/helpers.ts, src/app/i/[id]/page.tsx
Adds getInvoiceMeLink(id) helper (wraps api.invoiceMe.getById.query with isNotFoundError handling) and updates the InvoiceMe page to call this helper instead of the API directly.
Invoice helper & page
src/app/invoices/[ID]/helpers.ts, src/app/invoices/[ID]/page.tsx
Adds getInvoice(id) helper (wraps api.invoice.getById.query with isNotFoundError handling) and updates the Payment page to use this helper.
404 page redesign
src/app/not-found.tsx
Replaces the previous NotFound component with a redesigned, content-rich 404 page including icons, descriptive text, and navigation cards.

Sequence Diagram(s)

sequenceDiagram
    participant Page as Page Component
    participant Helper as Server Helper
    participant API as TRPC API
    participant Util as isNotFoundError

    Page->>Helper: getInvoice(id) / getInvoiceMeLink(id)
    Helper->>API: api.invoice.getById.query(id) / api.invoiceMe.getById.query(id)
    API-->>Helper: data or error
    alt success
        Helper-->>Page: return data
    else error
        Helper->>Util: isNotFoundError(error)?
        alt not found
            Helper-->>Page: return null
        else other error
            Helper-->>Page: throw error
        end
    end
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~15 minutes

Assessment against linked issues

Objective Addressed Explanation
Catch 404 Not Found errors and display a 404 page on Pay Invoice Page (#108)
Catch 404 Not Found errors and display a 404 page on Create Invoice from Invoice Me Link Page (#108)

Assessment against linked issues: Out-of-scope changes

No out-of-scope changes detected.

Possibly related PRs

Suggested reviewers

  • rodrigopavezi
  • aimensahnoun

📜 Recent review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between c381fcd and cc17861.

📒 Files selected for processing (1)
  • src/app/i/[id]/page.tsx (2 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/app/i/[id]/page.tsx
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: Build
✨ Finishing Touches
  • 📝 Generate Docstrings
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch feat/404-page

🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.

Support

Need help? Create a ticket on our support page for assistance with any issues or questions.

CodeRabbit Commands (Invoked using PR/Issue comments)

Type @coderabbitai help to get the list of available commands.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Status, Documentation and Community

  • Visit our Status Page to check the current availability of CodeRabbit.
  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Nitpick comments (5)
src/app/i/[id]/helpers.ts (1)

4-13: Add explicit return type and type the catch variable

Small typing improvements increase clarity and safety.

-export async function getInvoiceMeLink(id: string) {
+export async function getInvoiceMeLink(
+  id: string,
+): Promise<Awaited<ReturnType<typeof api.invoiceMe.getById.query>> | null> {
   try {
     return await api.invoiceMe.getById.query(id);
-  } catch (error) {
+  } catch (error: unknown) {
     if (isNotFoundError(error)) {
       return null;
     }
     throw error;
   }
 }
src/app/invoices/[ID]/helpers.ts (1)

4-13: Mirror the small typing improvements from the other helper

Add explicit return type and type the catch variable for consistency.

-export async function getInvoice(id: string) {
+export async function getInvoice(
+  id: string,
+): Promise<Awaited<ReturnType<typeof api.invoice.getById.query>> | null> {
   try {
     return await api.invoice.getById.query(id);
-  } catch (error) {
+  } catch (error: unknown) {
     if (isNotFoundError(error)) {
       return null;
     }
     throw error;
   }
 }
src/app/invoices/[ID]/page.tsx (1)

29-29: Nit: consider normalizing the param name to id (lowercase) across routes

Here we use params.ID while other routes use params.id. Not a blocker, but standardizing casing reduces cognitive overhead. Fine to handle in a separate PR.

src/app/not-found.tsx (2)

17-21: Wording: make the heading generic to all 404s

“Transaction Not Found” may confuse users who hit unrelated routes. Suggest a more general label.

-            <span className="text-lg font-medium text-gray-700">
-              Transaction Not Found
-            </span>
+            <span className="text-lg font-medium text-gray-700">
+              Page Not Found
+            </span>

69-75: Verify destination: consider linking to / instead of /dashboard

Public 404s (e.g., shared invoice links) may be viewed by unauthenticated users. Sending them to /dashboard could prompt a login or 403. If that’s intentional, ignore; otherwise, default to Home.

-          href="/dashboard"
+          href="/"
@@
-          Back to Dashboard
+          Go Home
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 069b8aa and d260840.

📒 Files selected for processing (5)
  • src/app/i/[id]/helpers.ts (1 hunks)
  • src/app/i/[id]/page.tsx (2 hunks)
  • src/app/invoices/[ID]/helpers.ts (1 hunks)
  • src/app/invoices/[ID]/page.tsx (2 hunks)
  • src/app/not-found.tsx (1 hunks)
🧰 Additional context used
🧠 Learnings (6)
📓 Common learnings
Learnt from: rodrigopavezi
PR: RequestNetwork/easy-invoice#45
File: src/components/invoice-form.tsx:316-319
Timestamp: 2025-05-19T13:00:48.790Z
Learning: The handleFormSubmit function in src/components/invoice-form.tsx correctly uses data.clientEmail from the form submission data to find matching payers, which is the proper implementation.
Learnt from: aimensahnoun
PR: RequestNetwork/easy-invoice#8
File: src/app/invoice-me/page.tsx:21-21
Timestamp: 2025-02-14T12:48:42.125Z
Learning: In the easy-invoice codebase, error handling for TRPC queries is not required at the component level as confirmed by the maintainer.
Learnt from: bassgeta
PR: RequestNetwork/easy-invoice#83
File: src/components/create-recurring-payment/blocks/create-recurring-payment-form.tsx:127-138
Timestamp: 2025-06-23T09:14:42.979Z
Learning: In the RequestNetwork/easy-invoice codebase, when suggesting improvements like error handling for transaction approvals, the user bassgeta prefers consistency over isolated improvements. They prefer not to implement error handling in one place unless it's done consistently across all similar transaction flows in the codebase.
Learnt from: MantisClone
PR: RequestNetwork/easy-invoice#59
File: src/app/api/webhook/route.ts:77-95
Timestamp: 2025-05-22T18:19:12.366Z
Learning: For webhook handlers in the Easy Invoice project, unknown or unexpected subStatus values in payment processing should be treated as errors (using console.error) rather than warnings, and should return a 422 Unprocessable Entity status code.
📚 Learning: 2025-02-12T12:40:14.742Z
Learnt from: aimensahnoun
PR: RequestNetwork/easy-invoice#2
File: src/server/routers/invoice.ts:88-109
Timestamp: 2025-02-12T12:40:14.742Z
Learning: The payRequest endpoint in src/server/routers/invoice.ts is intentionally kept public (using publicProcedure) to allow invoice sharing and payment by anyone with the payment reference, similar to how payment links work in other payment systems.

Applied to files:

  • src/app/i/[id]/page.tsx
  • src/app/invoices/[ID]/page.tsx
  • src/app/invoices/[ID]/helpers.ts
  • src/app/i/[id]/helpers.ts
📚 Learning: 2025-05-19T13:00:48.790Z
Learnt from: rodrigopavezi
PR: RequestNetwork/easy-invoice#45
File: src/components/invoice-form.tsx:316-319
Timestamp: 2025-05-19T13:00:48.790Z
Learning: The handleFormSubmit function in src/components/invoice-form.tsx correctly uses data.clientEmail from the form submission data to find matching payers, which is the proper implementation.

Applied to files:

  • src/app/i/[id]/page.tsx
  • src/app/invoices/[ID]/page.tsx
📚 Learning: 2025-02-14T12:48:42.125Z
Learnt from: aimensahnoun
PR: RequestNetwork/easy-invoice#8
File: src/app/invoice-me/page.tsx:21-21
Timestamp: 2025-02-14T12:48:42.125Z
Learning: In the easy-invoice codebase, error handling for TRPC queries is not required at the component level as confirmed by the maintainer.

Applied to files:

  • src/app/i/[id]/page.tsx
  • src/app/invoices/[ID]/page.tsx
  • src/app/invoices/[ID]/helpers.ts
  • src/app/i/[id]/helpers.ts
📚 Learning: 2025-02-13T21:57:39.869Z
Learnt from: aimensahnoun
PR: RequestNetwork/easy-invoice#8
File: src/app/i/[id]/page.tsx:18-22
Timestamp: 2025-02-13T21:57:39.869Z
Learning: The invoice me links in the easy-invoice project do not have an expiration mechanism. The invoiceMeTable schema only includes id, label, userId, and createdAt fields.

Applied to files:

  • src/app/i/[id]/page.tsx
  • src/app/i/[id]/helpers.ts
📚 Learning: 2025-02-12T13:04:46.135Z
Learnt from: aimensahnoun
PR: RequestNetwork/easy-invoice#2
File: src/server/routers/invoice.ts:0-0
Timestamp: 2025-02-12T13:04:46.135Z
Learning: The `payRequest` function in the invoice router retrieves payment transactions calldata from the Request API and does not process the payment itself. The actual payment status update happens through the webhook handler.

Applied to files:

  • src/app/invoices/[ID]/page.tsx
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: Build
🔇 Additional comments (5)
src/app/i/[id]/helpers.ts (1)

4-13: Good centralization of 404 handling

Returning null for not-found and rethrowing others keeps page components clean and consistent with prior decisions.

src/app/invoices/[ID]/helpers.ts (1)

4-13: Helper extraction looks good

Matches the invoice-me helper and keeps not-found concerns out of the page. Consistent and readable.

src/app/invoices/[ID]/page.tsx (1)

12-12: Refactor to helper is clean and aligns with codebase conventions

Using getInvoice + notFound() keeps the page lean and matches the established pattern.

Also applies to: 29-33

src/app/i/[id]/page.tsx (1)

10-10: LGTM: helper usage + notFound()

This follows the maintainers’ preference to keep component-level TRPC error handling minimal while centralizing 404 handling in helpers.

Also applies to: 22-26

src/app/not-found.tsx (1)

6-12: Nice UX uplift

Thoughtful layout, spacing, and iconography. The page reads well and provides clear next steps.

</span>
</div>
<p className="text-gray-600 mb-2">
Looks like this page got lost in the blockchain. Don't
Copy link
Member

Choose a reason for hiding this comment

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

Similarly these hardcoded comments don't make a lot of sense in the context of EasyInvoice, maybe we can find other comments?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Reworded it and just pushed an update. Still made it a bit quirky, just for the sake of it :D

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

♻️ Duplicate comments (1)
src/app/not-found.tsx (1)

22-27: Generalize the 404 copy; avoid domain-specific assumptions.

The wallet-specific message and the “invoices are safe” reassurance can confuse users on unrelated 404s. Prior feedback already called out hardcoded error text here.

-          <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">
-            Don't worry, your invoices are safe and sound!{" "}
-          </p>
+          <p className="text-gray-600 mb-2">That page doesn’t exist.</p>
+          <p className="text-gray-600">
+            It may have been moved, or the link is invalid.
+          </p>

Note: Next.js not-found.tsx can’t easily receive contextual error details; a neutral message keeps it correct across /invoices/:id, /i/:id, and arbitrary routes.

🧹 Nitpick comments (3)
src/app/not-found.tsx (3)

9-11: Hide decorative brand mark from screen readers (a11y).

The “EI” badge is decorative; hide it from assistive tech.

-          <div className="w-16 h-16 rounded-xl bg-black flex items-center justify-center mx-auto mb-6">
+          <div
+            className="w-16 h-16 rounded-xl bg-black flex items-center justify-center mx-auto mb-6"
+            aria-hidden="true"
+          >
             <span className="text-white font-bold text-xl">EI</span>
           </div>

15-15: Make the “404” heading responsive to avoid overflow on small screens.

-          <h1 className="text-8xl font-bold text-gray-900 mb-4">404</h1>
+          <h1 className="text-6xl sm:text-8xl font-bold text-gray-900 mb-4">404</h1>

30-69: Optional: reduce duplication by mapping a small config to cards.

The three cards share nearly identical structure. Consider an array + map or a tiny Card component to DRY this block and simplify tweaks later (copy, icon, color).

Happy to provide a follow-up patch if you want this refactor in this PR.

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between d260840 and c381fcd.

📒 Files selected for processing (1)
  • src/app/not-found.tsx (1 hunks)
🧰 Additional context used
🧠 Learnings (7)
📓 Common learnings
Learnt from: bassgeta
PR: RequestNetwork/easy-invoice#117
File: src/app/not-found.tsx:29-67
Timestamp: 2025-08-08T09:52:43.308Z
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.
Learnt from: bassgeta
PR: RequestNetwork/easy-invoice#83
File: src/components/create-recurring-payment/blocks/create-recurring-payment-form.tsx:127-138
Timestamp: 2025-06-23T09:14:42.979Z
Learning: In the RequestNetwork/easy-invoice codebase, when suggesting improvements like error handling for transaction approvals, the user bassgeta prefers consistency over isolated improvements. They prefer not to implement error handling in one place unless it's done consistently across all similar transaction flows in the codebase.
Learnt from: rodrigopavezi
PR: RequestNetwork/easy-invoice#45
File: src/components/invoice-form.tsx:316-319
Timestamp: 2025-05-19T13:00:48.790Z
Learning: The handleFormSubmit function in src/components/invoice-form.tsx correctly uses data.clientEmail from the form submission data to find matching payers, which is the proper implementation.
Learnt from: aimensahnoun
PR: RequestNetwork/easy-invoice#8
File: src/app/invoice-me/page.tsx:21-21
Timestamp: 2025-02-14T12:48:42.125Z
Learning: In the easy-invoice codebase, error handling for TRPC queries is not required at the component level as confirmed by the maintainer.
Learnt from: MantisClone
PR: RequestNetwork/easy-invoice#59
File: src/app/api/webhook/route.ts:77-95
Timestamp: 2025-05-22T18:19:12.366Z
Learning: For webhook handlers in the Easy Invoice project, unknown or unexpected subStatus values in payment processing should be treated as errors (using console.error) rather than warnings, and should return a 422 Unprocessable Entity status code.
📚 Learning: 2025-08-08T09:52:43.308Z
Learnt from: bassgeta
PR: RequestNetwork/easy-invoice#117
File: src/app/not-found.tsx:29-67
Timestamp: 2025-08-08T09:52:43.308Z
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.

Applied to files:

  • src/app/not-found.tsx
📚 Learning: 2025-02-12T12:40:14.742Z
Learnt from: aimensahnoun
PR: RequestNetwork/easy-invoice#2
File: src/server/routers/invoice.ts:88-109
Timestamp: 2025-02-12T12:40:14.742Z
Learning: The payRequest endpoint in src/server/routers/invoice.ts is intentionally kept public (using publicProcedure) to allow invoice sharing and payment by anyone with the payment reference, similar to how payment links work in other payment systems.

Applied to files:

  • src/app/not-found.tsx
📚 Learning: 2025-02-20T10:27:02.993Z
Learnt from: aimensahnoun
PR: RequestNetwork/easy-invoice#21
File: src/app/invoices/[ID]/page.tsx:113-148
Timestamp: 2025-02-20T10:27:02.993Z
Learning: The easy-invoice project prefers simpler, direct implementations over abstract utilities. For example, using `.toFixed(2)` directly instead of creating separate number formatting utilities.

Applied to files:

  • src/app/not-found.tsx
📚 Learning: 2025-07-14T14:17:05.340Z
Learnt from: bassgeta
PR: RequestNetwork/easy-invoice#91
File: drizzle/0007_messy_silver_fox.sql:1-12
Timestamp: 2025-07-14T14:17:05.340Z
Learning: In the RequestNetwork/easy-invoice codebase, monetary amounts are stored as text in the database schema rather than numeric types. This is a deliberate architectural decision to maintain consistency across all amount fields in the system.

Applied to files:

  • src/app/not-found.tsx
📚 Learning: 2025-06-23T09:14:42.979Z
Learnt from: bassgeta
PR: RequestNetwork/easy-invoice#83
File: src/components/create-recurring-payment/blocks/create-recurring-payment-form.tsx:127-138
Timestamp: 2025-06-23T09:14:42.979Z
Learning: In the RequestNetwork/easy-invoice codebase, when suggesting improvements like error handling for transaction approvals, the user bassgeta prefers consistency over isolated improvements. They prefer not to implement error handling in one place unless it's done consistently across all similar transaction flows in the codebase.

Applied to files:

  • src/app/not-found.tsx
📚 Learning: 2025-06-04T12:02:39.411Z
Learnt from: aimensahnoun
PR: RequestNetwork/easy-invoice#67
File: src/server/routers/payment.ts:47-49
Timestamp: 2025-06-04T12:02:39.411Z
Learning: In `src/server/routers/payment.ts`, the batchPay input validation already handles empty arrays correctly. The `batchPaymentFormSchema.shape.payouts.optional()` inherits the `.min(1, "At least one payment is required")` validation from the original schema, so empty payouts arrays are automatically rejected even when the field is made optional.

Applied to files:

  • src/app/not-found.tsx
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: Build
🔇 Additional comments (1)
src/app/not-found.tsx (1)

44-53: Keeping gated links visible on 404 is OK per product decision.

Acknowledging the prior decision to keep “Create Invoice” and “Subscription Plans” visible to avoid an empty 404. No lock icons/conditional rendering needed for now.

Also applies to: 55-68

<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

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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

EasyInvoice - Implement pretty 404 Not Found page
3 participants