Skip to content

Conversation

@bassgeta
Copy link
Contributor

@bassgeta bassgeta commented Oct 17, 2025

Problem

EasyInvoice will be moving towards a dashboard solution and its existing layout isn't applicable anymore.

Solution

Rework the layout to be more in line with the mock design.

Changes

  • two new route groups auth and dashboard, each with its own layout - Next recommended
  • moved all components specific to that page to that page's _components folder so we untangle our components top level folder
  • new AuthContext for user data
  • new Sidebar
  • new TopBar
  • reworked the current routes so they fit the designs and add titles for all of them
  • removed BackgroundWrapper
  • new PageTitle and PageDescription components

Resolves #161

Summary by CodeRabbit

  • New Features

    • Sign-in flow and auth provider; new dashboard layout, sidebar, topbar, PageTitle/PageDescription components
    • New dashboard pages and flows: Home, Subscriptions (plans/payments/subscribers), Invoices (create, list, sent/received, invoice-me), Payments & Payouts (direct, batch, recurring), Ecommerce (manage, sales), Receipts, Crypto-to-fiat UI and subscription preview
  • Style

    • Added highlight color tokens; footer spacing tweak
  • Refactor

    • Simplified navigation defaults and tabs; consolidated/import path reorganizations and component moves

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Oct 17, 2025

Walkthrough

Reorganizes the App Router with an auth route-group and a dashboard route-group, adds AuthProvider and requireAuth guard, introduces new dashboard layouts/components (Sidebar, Topbar, PageTitle/PageDescription), normalizes import paths, adds many auth-protected pages under (dashboard), removes legacy top-level layouts/pages, and adds highlight CSS/tailwind color tokens.

Changes

Cohort / File(s) Summary
Auth group & provider
src/app/(auth)/layout.tsx, src/app/(auth)/signin/page.tsx, src/context/auth/index.tsx, src/lib/auth.ts, src/app/layout.tsx
New auth layout and signin page; new AuthProvider + useAuthContext; added requireAuth helper; RootLayout made async and provides initial session to AuthProvider.
Dashboard layouts & navigation
src/app/(dashboard)/layout.tsx, src/app/(dashboard)/dashboard/layout.tsx, src/app/(dashboard)/payouts/recurring/layout.tsx, src/components/navigation/sidebar.tsx, src/components/navigation/topbar.tsx, src/components/page-elements.tsx, src/components/footer.tsx
New DashboardLayout(s), RecurringLayout, Sidebar, Topbar, PageTitle/PageDescription; minor Footer class tweaks.
Pages moved/added under (dashboard)
src/app/(dashboard)/**/page.tsx (home, invoices, subscriptions, payouts, ecommerce, receipts, crypto-to-fiat, ecommerce/sales, etc.), src/app/(dashboard)/payouts/*, src/app/(dashboard)/subscriptions/*, src/app/(dashboard)/invoices/*, src/app/(dashboard)/s/[id]/*
Many pages re-created or added under (dashboard) with requireAuth guards and TRPC data fetches; new pages for direct/batch/recurring payouts, subscriptions, invoices, ecommerce management, receipts, home, crypto-to-fiat.
Removed legacy top-level pages/layouts/components
src/app/dashboard/*, src/app/ecommerce/layout.tsx, src/app/crypto-to-fiat/page.tsx, src/app/invoices/*, src/app/i/[id]/page.tsx, src/app/invoice-me/page.tsx, src/app/s/[id]/page.tsx, src/components/background-wrapper.tsx, src/components/subscription-plans/*, src/components/ecommerce/ecommerce-navigation.tsx, src/components/payout-navigation.tsx
Deleted legacy dashboard/ecommerce pages, top-level layouts and several legacy navigation and subscription-related components.
Invoices UI & tabs
src/app/(dashboard)/invoices/_components/invoices-tabs.tsx, src/app/(dashboard)/invoices/_components/invoices-received.tsx, src/app/(dashboard)/invoices/_components/invoices-sent.tsx
New InvoicesTabs client component and refactored invoice list components; minor render simplifications and import updates.
Subscription / recurring changes
src/app/(dashboard)/s/[id]/_components/subscription-plan-preview.tsx, src/components/recurring-payment-status-badge.tsx, src/app/(dashboard)/payouts/recurring/**, src/components/subscription-plan-preview.tsx (removed)
New dashboard SubscriptionPlanPreview; renamed StatusBadge → RecurringPaymentStatusBadge and updated usages; added recurring payouts pages/layouts; removed legacy subscription preview component.
Import path normalizations
src/app/(dashboard)/**/_components/*, src/components/invoice/*, many components under src/app/(dashboard)
Replaced many relative imports with absolute @/... aliases or reorganized to local relative paths; multiple import path adjustments.
Root & routing adjustments
src/app/page.tsx, src/app/login/google/callback/route.ts, src/app/layout.tsx
Root page renamed/redirect updated (redirect targets changed), Google login new-user redirect changed to /home, RootLayout now supplies initial session to AuthProvider.
Styling & config
src/app/globals.css, tailwind.config.ts
Added CSS variables --highlight/--highlight-foreground and tailwind highlight color token.
Small component/path edits
multiple _components/* files in crypto-to-fiat, receipts, subscriptions, payouts, ecommerce, invoices
Numerous small import-path tweaks (relative → absolute), minor UI adjustments, and a few control-flow tweaks (e.g., default active tabs, redirect targets).

Sequence Diagram(s)

sequenceDiagram
    participant User
    participant AppRouter as App Router
    participant RootLayout
    participant AuthProvider
    participant DashboardGroup as (dashboard) Layout
    participant Page

    User->>AppRouter: Request protected /dashboard route
    AppRouter->>RootLayout: Load async RootLayout
    RootLayout->>RootLayout: await getCurrentSession()
    RootLayout->>AuthProvider: render with initialSession
    AppRouter->>DashboardGroup: Enter (dashboard) group
    DashboardGroup->>Page: Load page component
    Page->>Page: await requireAuth()
    alt session exists
        Page->>TRPC: fetch data
        Page-->>User: Render page
    else
        Page-->>User: redirect('/signin')
    end
Loading
sequenceDiagram
    participant Client
    participant useAuth as useAuthContext
    participant TRPC as api.auth.getSessionInfo
    participant Server

    Client->>useAuth: mount/use context
    useAuth->>TRPC: query (initialData from RootLayout)
    TRPC->>Server: fetch session info
    Server-->>TRPC: session result
    TRPC-->>useAuth: return session
    useAuth-->>Client: provide auth state
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Focus areas:

  • Auth boundary and session propagation: src/context/auth/index.tsx, src/lib/auth.ts, src/app/layout.tsx.
  • Route reorganizations and many deleted/added pages: ensure no routing gaps or stale imports.
  • Import path normalization across many modules (absolute vs relative) — watch for broken resolutions.
  • High-complexity pages/components with data fetching and UI (invoices [ID] page, subscription preview, many newly added dashboard pages).
  • Deleted components that may still be referenced (BackgroundWrapper, legacy layouts, subscription-plan files).

Possibly related PRs

Suggested reviewers

  • rodrigopavezi
  • MantisClone
  • aimensahnoun

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title Check ✅ Passed The PR title "feat: rework the layout to be more in line with dashboard designs" is clear, concise, and directly reflects the primary change in the changeset. The changeset demonstrates extensive layout restructuring including new route groups, Sidebar and Topbar components, new DashboardLayout structure, and migration to a dashboard-centric architecture. The title accurately summarizes this main objective without vagueness or noise, making it immediately understandable to someone scanning the commit history.
Linked Issues Check ✅ Passed The code changes comprehensively address the coding objectives from issue #161. The PR implements the core requirements: a new left-side Sidebar navigation component (src/components/navigation/sidebar.tsx) with module sections and nested links replaces the top navigation; new route groups (auth) and (dashboard) establish proper dashboard UX structure; a new DashboardLayout component with Sidebar, Topbar, and Footer composition aligns the layout with the referenced design; and new AuthContext infrastructure (src/context/auth/index.tsx) and pages are properly authenticated using the new requireAuth utility. Additionally, PageTitle and PageDescription components provide consistent page headers, and pages have been refactored to fit the new design as stated in the PR objectives. All these changes directly correspond to the stated requirements for implementing a dashboard-like navigation structure and layout alignment.
Out of Scope Changes Check ✅ Passed The vast majority of changes are directly related to implementing the dashboard layout navigation refactoring from issue #161. Core in-scope changes include the new Sidebar and Topbar navigation components, route group structures, DashboardLayout composition, and page reorganization. Some changes that might appear tangential—such as adding CSS highlight variables (--highlight, --highlight-foreground) in globals.css and tailwind.config.ts, and renaming StatusBadge to RecurringPaymentStatusBadge—are relatively minor and supportive in nature. The authentication infrastructure additions (AuthProvider, useAuthContext, requireAuth utility) and route path adjustments are explicitly listed in the PR objectives as part of the layout rework to support dashboard UX. These do not represent significant scope creep, and no major out-of-scope features or functionality changes were introduced beyond the intended navigation and layout restructuring.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch feat/161-nav-rework

Comment @coderabbitai help to get the list of available commands and usage tips.

@bassgeta bassgeta changed the title feat: new sidebar and new homepage feat: rework the layout to be more in line with dashboard designs Oct 21, 2025
@bassgeta bassgeta self-assigned this Oct 21, 2025
@bassgeta bassgeta marked this pull request as ready for review October 21, 2025 06:07
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: 18

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
src/app/(dashboard)/invoices/_components/invoices-received.tsx (1)

156-173: Ensure isPayingInvoices is reset on success and failure.

Currently only error/status paths reset it; success path can leave it stuck.

         onSuccess: () => {
           toast.success("Batch payment successful", {
             description: `Successfully processed ${selectedInvoices.length} invoices`,
           });
           setSelectedInvoices([]);
           setLastSelectedNetwork(null);
+          setIsPayingInvoices(false);
         },
         onError: () => {
           setIsPayingInvoices(false);
         },
         onStatusChange: (status) => {
           setIsPayingInvoices(status === "processing");
         },
       });
 
       if (!result.success) {
         console.error("Batch payment failed:", result.error);
+        setIsPayingInvoices(false);
       }

Also applies to: 174-180

src/components/invoice/invoice-creator.tsx (1)

119-137: Do not cast undefined to User; make prop optional and pass undefined.

Casting undefined as unknown as User can cause runtime crashes if InvoiceForm assumes a non-null user.

-            currentUser={
-              currentUser
-                ? ({
-                    id: currentUser.id,
-                    name: currentUser.name,
-                    email: currentUser.email,
-                  } as User)
-                : (undefined as unknown as User)
-            }
+            currentUser={currentUser
+              ? { id: currentUser.id, name: currentUser.name, email: currentUser.email } as User
+              : undefined}

Follow-up: update InvoiceForm prop to currentUser?: User (optional) and guard usages accordingly.

🧹 Nitpick comments (31)
src/app/(dashboard)/dashboard/receipts/page.tsx (1)

4-4: Note: Import path strategy differs from other files in this PR.

This file moves from an absolute to a relative import, while other files in this PR (e.g., receipts.tsx, direct-payout.tsx, subscribers-table.tsx) move from relative to absolute. This reflects the PR's goal of moving page-specific components to _components folders, but the mixed approach may impact maintainability.

Consider documenting the import path strategy:

  • Absolute aliases (@/...) for shared components
  • Relative paths (./...) for page-specific components in _components folders
src/components/navigation/sidebar.tsx (1)

37-47: Consider extracting pathname-to-section mapping.

The pathname matching logic for auto-expanding sections could be simplified using a mapping object for better maintainability.

useEffect(() => {
  const pathToSection: Record<string, Section> = {
    '/invoices': 'billing',
    '/subscriptions': 'subscriptions',
    '/payments': 'payouts',
    '/ecommerce': 'ecommerce',
  };
  
  const matchedSection = Object.entries(pathToSection).find(([path]) => 
    pathname.startsWith(path)
  )?.[1];
  
  if (matchedSection) {
    setExpandedSections(prev => ({ ...prev, [matchedSection]: true }));
  }
}, [pathname]);
src/app/(dashboard)/payments/recurring/page.tsx (1)

6-9: Type the route metadata for safety.

Add an explicit Metadata type to catch typos at build time.

+import type { Metadata } from "next";
 
-export const metadata = {
+export const metadata = {
   title: "Recurring Payments | Easy Invoice",
   description: "Manage your recurring payments using Easy Invoice",
-};
+} satisfies Metadata;
src/app/(dashboard)/invoices/_components/invoices-received.tsx (2)

96-121: Use functional state updates to avoid stale closures when selecting invoices.

Prevents race conditions under rapid toggling.

-      setSelectedInvoices([...selectedInvoices, invoice]);
-      setLastSelectedNetwork(invoiceNetwork);
+      setSelectedInvoices((prev) => [...prev, invoice]);
+      setLastSelectedNetwork((prev) => prev ?? invoiceNetwork);
     } else {
-      setSelectedInvoices(selectedInvoices.filter((i) => i.id !== invoice.id));
-      if (selectedInvoices.length === 1) {
-        setLastSelectedNetwork(null);
-      }
+      setSelectedInvoices((prev) => {
+        const next = prev.filter((i) => i.id !== invoice.id);
+        if (next.length === 0) setLastSelectedNetwork(null);
+        return next;
+      });
     }

35-36: Prefer absolute alias imports to avoid brittle deep relatives.

Helps prevent breakage when moving files.

-import { MultiCurrencyStatCard } from "../../../../components/multi-currency-stat-card";
+import { MultiCurrencyStatCard } from "@/components/multi-currency-stat-card";
src/app/(dashboard)/invoices/[ID]/_components/payment-section.tsx (4)

349-354: Verify ethers API (v5 vs v6) and standardize signer creation.

The code uses v5 constructs (providers.Web3Provider). If on v6, use new BrowserProvider and updated APIs. Centralize provider creation in a small util to keep consistency with InvoicesReceived.

Two options:

  • v5:
    const provider = new ethers.providers.Web3Provider(walletProvider as ExternalProvider);
    const signer = provider.getSigner();
  • v6:
    const provider = new ethers.BrowserProvider(walletProvider as any);
    const signer = await provider.getSigner();
    Run the repo check script from my earlier comment to confirm.

231-236: Tighten types for paymentData and signer; avoid any.

Define minimal interfaces for the two flows to reduce runtime errors.

-const handleCrosschainPayments = async (paymentData: any, signer: any) => {
+type CrosschainPaymentData = {
+  paymentIntent: string;
+  paymentIntentId: string;
+  metadata: { supportsEIP2612: boolean };
+  approvalPermitPayload?: string;
+  approvalCalldata?: { to: string; data: string; value?: string };
+};
+const handleCrosschainPayments = async (paymentData: CrosschainPaymentData, signer: ethers.Signer) => {
@@
-const handleDirectPayments = async (paymentData: any, signer: any) => {
+type DirectPaymentData = {
+  metadata: { needsApproval: boolean; paymentTransactionIndex: number };
+  transactions: Array<{ to: string; data: string; value?: string }>;
+};
+const handleDirectPayments = async (paymentData: DirectPaymentData, signer: ethers.Signer) => {

Adjust signer type to match your ethers major.

Also applies to: 252-273, 289-296, 317-327


335-347: Normalize chainId types before comparison.

Avoid mismatches between string/number.

-    if (targetChain !== chainId) {
+    if (Number(targetChain) !== Number(chainId)) {

194-201: Avoid artificial init delays; derive readiness from AppKit if possible.

Hard-coded 2s delay can block UX unnecessarily. Prefer AppKit readiness callbacks or feature detection (e.g., walletProvider present).

Also applies to: 219-229

src/components/invoice/invoice-creator.tsx (1)

15-17: Import path change is fine; consider keeping aliases for stability.

Relative paths work, but @/components/... avoids deep relatives if files move.

src/app/(dashboard)/ecommerce/sales/page.tsx (1)

1-5: Optional: add typed metadata for consistency.

If you want titles/descriptions per page, add export const metadata satisfies Metadata.

Also applies to: 16-22

src/components/navigation/topbar.tsx (2)

10-16: Avoid layout shift on auth loading; unify markup

Render a single container and toggle just the UserMenu. This prevents a brief flicker when AuthProvider reports isLoading/unauth then flips to authed.

 export function Topbar() {
   const auth = useAuthContext();

-  if (!auth.isAuthenticated) {
-    return (
-      <div className="flex flex-row h-16 w-full items-center justify-end px-4 border-b bg-background">
-        <ModeToggle />
-      </div>
-    );
-  }
-
-  return (
-    <div className="flex flex-row h-16 w-full items-center justify-between px-4 border-b bg-background">
-      <div className="flex items-center gap-2 ml-auto">
-        <UserMenu user={auth.user} />
-        <ModeToggle />
-      </div>
-    </div>
-  );
+  return (
+    <div className="flex flex-row h-16 w-full items-center justify-end px-4 border-b bg-background">
+      <div className="flex items-center gap-2">
+        {auth.isAuthenticated && <UserMenu user={auth.user} />}
+        <ModeToggle />
+      </div>
+    </div>
+  );
 }

Also applies to: 18-24


19-24: Minor: simplify alignment or add left content placeholder

justify-between with only right-side content + ml-auto is redundant. Use justify-end (as above) unless a left region will be added imminently. Low-priority polish.

src/app/(dashboard)/invoices/me/_components/invoice-me-links.tsx (2)

41-50: Harden creation flow: trim, guard re-entry, handle errors, and disable when invalid

Prevent whitespace-only labels, avoid duplicate submits while loading, and surface failures. Also disable the button until valid.

-  const handleCreateInvoiceMeLink = async () => {
-    if (newLinkName.length === 0) {
-      setError("Link label is required");
-      return;
-    }
-    await createInvoiceMeLink({ label: newLinkName });
-    setIsCreating(false);
-    setNewLinkName("");
-    setError(null);
-  };
+  const handleCreateInvoiceMeLink = async () => {
+    const label = newLinkName.trim();
+    if (label.length === 0) {
+      setError("Link label is required");
+      return;
+    }
+    if (isCreatingInvoiceMeLink) return;
+    try {
+      await createInvoiceMeLink({ label });
+      setIsCreating(false);
+      setNewLinkName("");
+      setError(null);
+    } catch {
+      setError("Failed to create link. Please try again.");
+    }
+  };
@@
-                  onKeyDown={async (e) => {
-                    if (e.key === "Enter") {
-                      await handleCreateInvoiceMeLink();
-                    }
-                  }}
+                  onKeyDown={async (e) => {
+                    if (e.key === "Enter" && !isCreatingInvoiceMeLink) {
+                      await handleCreateInvoiceMeLink();
+                    }
+                  }}
@@
-              <Button
+              <Button
                 onClick={async () => {
                   await handleCreateInvoiceMeLink();
                 }}
                 className="bg-primary hover:bg-primary/80 text-primary-foreground"
-                disabled={isCreatingInvoiceMeLink}
+                disabled={isCreatingInvoiceMeLink || newLinkName.trim().length === 0}
               >

Also applies to: 75-79, 83-91


10-10: Optional: derive origin after mount to avoid hydration mismatch

Server-rendered client components will compute origin as empty string. Deriving it post-mount avoids transient mismatches in children that render URLs.

-import { useState } from "react";
+import { useEffect, useState } from "react";
@@
-  const origin = typeof window !== "undefined" ? window.location.origin : "";
+  // Compute on mount to avoid SSR/CSR mismatch.
+  const [origin, setOrigin] = useState("");
+  useEffect(() => {
+    setOrigin(window.location.origin);
+  }, []);

Also applies to: 17-17, 37-37

src/app/(dashboard)/subscriptions/page.tsx (1)

16-22: Add a semantic main landmark

Improves accessibility and structure while keeping UI unchanged.

   return (
-    <>
+    <main className="flex flex-col">
       <PageTitle>Subscription Plans</PageTitle>
       <PageDescription>Manage your subscription plans</PageDescription>
       <SubscriptionPlansList initialSubscriptionPlans={subscriptionPlans} />
-    </>
+    </main>
   );
src/app/(dashboard)/ecommerce/sales/_components/index.tsx (1)

30-30: Minor: ensure non-null data passed to table

Guard against a transient undefined (typing or future changes) without affecting behavior.

-  return <ClientPaymentsTable clientPayments={data} />;
+  return <ClientPaymentsTable clientPayments={data ?? initialClientPayments} />;
src/app/(dashboard)/layout.tsx (1)

11-15: Add a main landmark around page content

Improves accessibility and clarifies structure; no visual change.

-        <Topbar />
-        <div className="bg-muted/30 w-full h-full relative flex flex-col px-4 py-2">
-          {children}
-          <Footer />
-        </div>
+        <Topbar />
+        <div className="bg-muted/30 w-full h-full relative flex flex-col px-4 py-2">
+          <main role="main" className="flex-1">
+            {children}
+          </main>
+          <Footer />
+        </div>
src/app/layout.tsx (1)

43-43: Guard GTM when env var is missing

Avoid rendering the GTM component with an undefined ID.

-        <GoogleTagManager gtmId={process.env.NEXT_PUBLIC_GTM_ID as string} />
+        {process.env.NEXT_PUBLIC_GTM_ID && (
+          <GoogleTagManager gtmId={process.env.NEXT_PUBLIC_GTM_ID} />
+        )}
src/app/(dashboard)/invoices/_components/invoices-tabs.tsx (1)

22-32: Consider listening to hash changes for browser navigation.

The current implementation reads the hash only on initial mount. If users use browser back/forward buttons to navigate between tabs, the component won't sync with the URL hash.

Consider adding a hash change listener:

  useEffect(() => {
    const hash = window.location.hash.replace("#", "");
    if (hash === "received" || hash === "sent") {
      setActiveTab(hash);
    }
+
+   const handleHashChange = () => {
+     const newHash = window.location.hash.replace("#", "");
+     if (newHash === "received" || newHash === "sent") {
+       setActiveTab(newHash);
+     }
+   };
+
+   window.addEventListener("hashchange", handleHashChange);
+   return () => window.removeEventListener("hashchange", handleHashChange);
  }, []);
src/components/page-elements.tsx (1)

8-16: Consider safer className composition.

The current className concatenation may include a trailing space when className is undefined. While Tailwind ignores this, using a utility like clsx or cn would be more robust.

+import { cn } from "@/lib/utils";
+
 export function PageTitle({ children, className }: PageTitleProps) {
   return (
     <h1
-      className={`text-4xl font-bold tracking-tight text-foreground ${className}`}
+      className={cn("text-4xl font-bold tracking-tight text-foreground", className)}
     >
       {children}
     </h1>
   );
 }
src/context/auth/index.tsx (1)

34-38: Re-enable background session refresh (SWR) with initialData.

With enabled: !initialSession, dashboards that pass any initialSession never revalidate, so tab/window changes won’t reflect login/logout/expiry. Prefer always enabled with stale‑while‑revalidate:

-  const { data, isLoading } = api.auth.getSessionInfo.useQuery(undefined, {
-    enabled: !initialSession,
-    refetchOnWindowFocus: false,
-    initialData: initialSession ?? undefined,
-  });
+  const { data, isLoading } = api.auth.getSessionInfo.useQuery(undefined, {
+    initialData: initialSession ?? undefined,
+    refetchOnWindowFocus: true,
+    refetchOnReconnect: true,
+    staleTime: 60_000,
+  });

Keeps UX snappy while handling token refresh/remote sign‑out.

Also applies to: 40-44

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

163-166: Format monetary values; avoid raw toString().

Raw numbers risk precision/UX issues. At minimum, use locale formatting; ideally use a shared helper for fiat vs token decimals.

-                        {item.price.toString()}
+                        {Number(item.price).toLocaleString(undefined, { maximumFractionDigits: 6 })}
@@
-                        {(item.quantity * item.price).toString()}
+                        {(item.quantity * item.price).toLocaleString(undefined, { maximumFractionDigits: 6 })}
@@
-                      {Number(invoice.amount).toString()}
+                      {Number(invoice.amount).toLocaleString(undefined, { maximumFractionDigits: 6 })}
@@
-                        {Number(invoice.amount).toString()}
+                        {Number(invoice.amount).toLocaleString(undefined, { maximumFractionDigits: 6 })}

If you have a currency formatter util, prefer that here.

Also applies to: 181-189


41-46: Add aria-label on the back link.

Improve keyboard/screen‑reader context:

-<Link href="/invoices" className="text-foreground">
+<Link href="/invoices" className="text-foreground" aria-label="Back to Invoices">

154-170: Prefer real types over casting as InvoiceItem[].

Import the invoice item type from your server/types to avoid drift and runtime surprises.

src/app/(dashboard)/s/[id]/page.tsx (2)

18-21: Preserve return URL on auth redirect.

Improve UX by redirecting back post‑signin. Append a return parameter.

-  if (!user) redirect("/signin");
+  if (!user) {
+    // Redirect back here after successful sign‑in
+    redirect(`/signin?returnTo=${encodeURIComponent(`/dashboard/s/${params.id}`)}`);
+  }

8-11: Use dynamic metadata with plan label.

Better SEO/UX: include the plan label in the title/description via generateMetadata.

-export const metadata: Metadata = {
-  title: "Subscribe | EasyInvoice",
-  description: "Subscribe to a service provider",
-};
+export async function generateMetadata(
+  { params }: { params: { id: string } }
+): Promise<Metadata> {
+  const plan = await getSubscriptionPlan(params.id);
+  if (!plan) return { title: "Subscribe | EasyInvoice" };
+  return {
+    title: `Subscribe to ${plan.label} | EasyInvoice`,
+    description: `Subscribe to ${plan.label}`,
+  };
+}

Also applies to: 29-35

src/app/(dashboard)/s/[id]/_components/subscription-plan-preview.tsx (4)

76-79: Clipboard: handle failures and add accessible labels.

Await copy, catch errors, and add aria‑labels to icon‑only buttons.

-  const copyToClipboard = (text: string, label: string) => {
-    navigator.clipboard.writeText(text);
-    toast.success(`${label} copied to clipboard`);
-  };
+  const copyToClipboard = async (text: string, label: string) => {
+    try {
+      await navigator.clipboard.writeText(text);
+      toast.success(`${label} copied to clipboard`);
+    } catch {
+      toast.error(`Failed to copy ${label}`);
+    }
+  };
@@
-                <Button
+                <Button
                   type="button"
                   variant="ghost"
                   size="sm"
                   className="absolute right-1 top-1 h-6 w-6 p-0"
+                  aria-label="Copy recipient email"
                   onClick={() => copyToClipboard(recipientEmail, "Email")}
                 >
@@
-                <Button
+                <Button
                   type="button"
                   variant="ghost"
                   size="sm"
                   className="absolute right-1 top-1 h-6 w-6 p-0"
+                  aria-label="Copy recipient address"
                   onClick={() =>
                     copyToClipboard(subscriptionPlan.recipient, "Address")
                   }
                 >

Also applies to: 191-198, 213-223


119-123: Status chip should reflect actual state.

“Ready to Subscribe” appears even when not connected/not ready. Make it dynamic.

-  <div className="px-3 py-1 bg-blue-50 text-blue-700 text-sm rounded-full">
-    Ready to Subscribe
-  </div>
+  <div className="px-3 py-1 bg-blue-50 text-blue-700 text-sm rounded-full">
+    {isConnected ? "Ready to Subscribe" : "Connect Wallet"}
+  </div>

Also applies to: 228-233


233-245: Wire useDisconnect() to disconnect the wallet when user clicks the logout icon.

The LogOut icon semantically represents disconnect/logout functionality, but currently calls open() which only opens the wallet modal. AppKit exposes disconnect() via the useDisconnect hook. Import and use it instead:

const { disconnect } = useDisconnect();

Then replace onClick={() => open()} with onClick={() => disconnect()} at line 235.

If you prefer keeping the wallet modal behavior, rename the icon/action to reflect "Open wallet" instead.


17-17: Consider using direct imports from ethers/lib/utils to reduce client bundle size.

The file is a client component ("use client") importing the entire utils object, which pulls unnecessary code into the browser bundle. While ethers/lib/utils is a valid import path in v5 and is preferable to importing the full object, be aware that ethers v5's CommonJS/UMD distribution has poor tree-shaking, so the bundle reduction will be modest. Alternatively, if viem is already integrated elsewhere, prefer viem's parseUnits/formatUnits utilities.

The same pattern appears across multiple client components—consider this a candidate for systematic refactoring.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 88e4810 and 2fcae90.

📒 Files selected for processing (77)
  • src/app/(auth)/layout.tsx (1 hunks)
  • src/app/(auth)/signin/page.tsx (1 hunks)
  • src/app/(dashboard)/crypto-to-fiat/_components/compliance-form.tsx (1 hunks)
  • src/app/(dashboard)/crypto-to-fiat/_components/compliance-status.tsx (1 hunks)
  • src/app/(dashboard)/crypto-to-fiat/page.tsx (1 hunks)
  • src/app/(dashboard)/dashboard/_components/dashboard-navigation.tsx (1 hunks)
  • src/app/(dashboard)/dashboard/page.tsx (1 hunks)
  • src/app/(dashboard)/dashboard/receipts/_components/receipts.tsx (1 hunks)
  • src/app/(dashboard)/dashboard/receipts/page.tsx (1 hunks)
  • src/app/(dashboard)/dashboard/subscriptions/_components/subscriptions.tsx (3 hunks)
  • src/app/(dashboard)/dashboard/subscriptions/page.tsx (1 hunks)
  • src/app/(dashboard)/ecommerce/manage/page.tsx (1 hunks)
  • src/app/(dashboard)/ecommerce/sales/_components/index.tsx (1 hunks)
  • src/app/(dashboard)/ecommerce/sales/page.tsx (1 hunks)
  • src/app/(dashboard)/home/page.tsx (1 hunks)
  • src/app/(dashboard)/i/[id]/page.tsx (1 hunks)
  • src/app/(dashboard)/invoices/[ID]/_components/payment-section.tsx (1 hunks)
  • src/app/(dashboard)/invoices/[ID]/page.tsx (1 hunks)
  • src/app/(dashboard)/invoices/_components/invoices-received.tsx (2 hunks)
  • src/app/(dashboard)/invoices/_components/invoices-sent.tsx (2 hunks)
  • src/app/(dashboard)/invoices/_components/invoices-tabs.tsx (1 hunks)
  • src/app/(dashboard)/invoices/create/page.tsx (1 hunks)
  • src/app/(dashboard)/invoices/me/_components/invoice-me-links.tsx (2 hunks)
  • src/app/(dashboard)/invoices/me/page.tsx (1 hunks)
  • src/app/(dashboard)/invoices/page.tsx (1 hunks)
  • src/app/(dashboard)/layout.tsx (1 hunks)
  • src/app/(dashboard)/payments/batch/_components/batch-payout.tsx (1 hunks)
  • src/app/(dashboard)/payments/batch/page.tsx (1 hunks)
  • src/app/(dashboard)/payments/direct/_components/direct-payout.tsx (1 hunks)
  • src/app/(dashboard)/payments/direct/page.tsx (1 hunks)
  • src/app/(dashboard)/payments/page.tsx (1 hunks)
  • src/app/(dashboard)/payments/recurring/_components/recurring-payments-navigation.tsx (1 hunks)
  • src/app/(dashboard)/payments/recurring/_components/view-recurring-payments.tsx (3 hunks)
  • src/app/(dashboard)/payments/recurring/create/page.tsx (1 hunks)
  • src/app/(dashboard)/payments/recurring/layout.tsx (1 hunks)
  • src/app/(dashboard)/payments/recurring/page.tsx (2 hunks)
  • src/app/(dashboard)/s/[id]/_components/subscription-plan-preview.tsx (1 hunks)
  • src/app/(dashboard)/s/[id]/page.tsx (1 hunks)
  • src/app/(dashboard)/subscriptions/_components/payments-table.tsx (2 hunks)
  • src/app/(dashboard)/subscriptions/_components/subscribers-table.tsx (3 hunks)
  • src/app/(dashboard)/subscriptions/page.tsx (1 hunks)
  • src/app/(dashboard)/subscriptions/payments/page.tsx (1 hunks)
  • src/app/(dashboard)/subscriptions/subscribers/page.tsx (1 hunks)
  • src/app/crypto-to-fiat/page.tsx (0 hunks)
  • src/app/dashboard/get-paid/page.tsx (0 hunks)
  • src/app/dashboard/layout.tsx (0 hunks)
  • src/app/dashboard/pay/page.tsx (0 hunks)
  • src/app/ecommerce/layout.tsx (0 hunks)
  • src/app/ecommerce/manage/page.tsx (0 hunks)
  • src/app/ecommerce/sales/page.tsx (0 hunks)
  • src/app/globals.css (3 hunks)
  • src/app/i/[id]/page.tsx (0 hunks)
  • src/app/invoice-me/page.tsx (0 hunks)
  • src/app/invoices/[ID]/page.tsx (0 hunks)
  • src/app/invoices/create/page.tsx (0 hunks)
  • src/app/layout.tsx (3 hunks)
  • src/app/page.tsx (1 hunks)
  • src/app/payouts/batch/page.tsx (0 hunks)
  • src/app/payouts/layout.tsx (0 hunks)
  • src/app/payouts/recurring/layout.tsx (0 hunks)
  • src/app/payouts/single/page.tsx (0 hunks)
  • src/app/s/[id]/page.tsx (0 hunks)
  • src/app/subscription-plans/page.tsx (0 hunks)
  • src/components/background-wrapper.tsx (0 hunks)
  • src/components/ecommerce/ecommerce-navigation.tsx (0 hunks)
  • src/components/footer.tsx (1 hunks)
  • src/components/invoice/invoice-creator.tsx (1 hunks)
  • src/components/invoice/invoice-form/invoice-form.tsx (1 hunks)
  • src/components/navigation/sidebar.tsx (1 hunks)
  • src/components/navigation/topbar.tsx (1 hunks)
  • src/components/page-elements.tsx (1 hunks)
  • src/components/payout-navigation.tsx (0 hunks)
  • src/components/recurring-payment-status-badge.tsx (1 hunks)
  • src/components/subscription-plan-preview.tsx (0 hunks)
  • src/components/subscription-plans/subscription-plan-tabs.tsx (0 hunks)
  • src/context/auth/index.tsx (1 hunks)
  • tailwind.config.ts (1 hunks)
💤 Files with no reviewable changes (22)
  • src/components/payout-navigation.tsx
  • src/app/subscription-plans/page.tsx
  • src/app/dashboard/get-paid/page.tsx
  • src/components/subscription-plans/subscription-plan-tabs.tsx
  • src/app/payouts/recurring/layout.tsx
  • src/app/payouts/single/page.tsx
  • src/app/invoice-me/page.tsx
  • src/app/payouts/batch/page.tsx
  • src/app/crypto-to-fiat/page.tsx
  • src/components/background-wrapper.tsx
  • src/app/dashboard/layout.tsx
  • src/app/dashboard/pay/page.tsx
  • src/app/payouts/layout.tsx
  • src/components/subscription-plan-preview.tsx
  • src/app/s/[id]/page.tsx
  • src/app/invoices/create/page.tsx
  • src/components/ecommerce/ecommerce-navigation.tsx
  • src/app/ecommerce/sales/page.tsx
  • src/app/i/[id]/page.tsx
  • src/app/ecommerce/layout.tsx
  • src/app/invoices/[ID]/page.tsx
  • src/app/ecommerce/manage/page.tsx
🧰 Additional context used
🧠 Learnings (1)
📚 Learning: 2025-08-08T09:52:43.700Z
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.

Applied to files:

  • src/app/(dashboard)/i/[id]/page.tsx
  • src/app/(dashboard)/invoices/create/page.tsx
  • src/app/(dashboard)/s/[id]/page.tsx
🧬 Code graph analysis (30)
src/app/(dashboard)/payments/batch/page.tsx (2)
src/components/page-elements.tsx (2)
  • PageTitle (8-16)
  • PageDescription (22-26)
src/app/(dashboard)/payments/batch/_components/batch-payout.tsx (1)
  • BatchPayout (78-637)
src/components/navigation/topbar.tsx (3)
src/context/auth/index.tsx (1)
  • useAuthContext (51-57)
src/components/mode-toggle.tsx (1)
  • ModeToggle (7-65)
src/components/user-menu.tsx (1)
  • UserMenu (22-80)
src/app/(dashboard)/ecommerce/manage/page.tsx (3)
src/server/auth/index.ts (1)
  • getCurrentSession (96-103)
src/components/page-elements.tsx (2)
  • PageTitle (8-16)
  • PageDescription (22-26)
src/app/(dashboard)/ecommerce/manage/_components/index.tsx (1)
  • EcommerceManage (14-54)
src/app/(dashboard)/dashboard/receipts/page.tsx (1)
src/server/auth/index.ts (1)
  • getCurrentSession (96-103)
src/components/recurring-payment-status-badge.tsx (1)
src/server/db/schema.ts (1)
  • RecurringPaymentStatusType (84-85)
src/app/(dashboard)/payments/recurring/layout.tsx (2)
src/components/page-elements.tsx (2)
  • PageTitle (8-16)
  • PageDescription (22-26)
src/app/(dashboard)/payments/recurring/_components/recurring-payments-navigation.tsx (1)
  • RecurringPaymentsNavigation (8-32)
src/app/(dashboard)/ecommerce/sales/page.tsx (3)
src/server/auth/index.ts (1)
  • getCurrentSession (96-103)
src/components/page-elements.tsx (2)
  • PageTitle (8-16)
  • PageDescription (22-26)
src/app/(dashboard)/ecommerce/sales/_components/index.tsx (1)
  • EcommerceSales (12-31)
src/app/(dashboard)/dashboard/subscriptions/page.tsx (1)
src/server/auth/index.ts (1)
  • getCurrentSession (96-103)
src/app/layout.tsx (2)
src/server/auth/index.ts (1)
  • getCurrentSession (96-103)
src/context/auth/index.tsx (1)
  • AuthProvider (27-49)
src/app/(dashboard)/invoices/me/page.tsx (3)
src/server/auth/index.ts (1)
  • getCurrentSession (96-103)
src/components/page-elements.tsx (2)
  • PageTitle (8-16)
  • PageDescription (22-26)
src/app/(dashboard)/invoices/me/_components/invoice-me-links.tsx (1)
  • InvoiceMeLinks (16-113)
src/app/(dashboard)/payments/direct/page.tsx (2)
src/components/page-elements.tsx (2)
  • PageTitle (8-16)
  • PageDescription (22-26)
src/app/(dashboard)/payments/direct/_components/direct-payout.tsx (1)
  • DirectPayment (61-446)
src/app/(dashboard)/layout.tsx (3)
src/components/navigation/sidebar.tsx (1)
  • Sidebar (19-286)
src/components/navigation/topbar.tsx (1)
  • Topbar (7-26)
src/components/footer.tsx (1)
  • Footer (3-17)
src/app/(dashboard)/invoices/page.tsx (3)
src/server/auth/index.ts (1)
  • getCurrentSession (96-103)
src/components/page-elements.tsx (2)
  • PageTitle (8-16)
  • PageDescription (22-26)
src/app/(dashboard)/invoices/_components/invoices-tabs.tsx (1)
  • InvoicesTabs (15-50)
src/app/(dashboard)/subscriptions/subscribers/page.tsx (3)
src/server/auth/index.ts (1)
  • getCurrentSession (96-103)
src/components/page-elements.tsx (2)
  • PageTitle (8-16)
  • PageDescription (22-26)
src/app/(dashboard)/subscriptions/_components/subscribers-table.tsx (1)
  • SubscribersTable (134-276)
src/app/(auth)/signin/page.tsx (3)
src/server/auth/index.ts (1)
  • getCurrentSession (96-103)
src/components/header.tsx (1)
  • Header (10-104)
src/components/footer.tsx (1)
  • Footer (3-17)
src/app/(dashboard)/subscriptions/page.tsx (3)
src/server/auth/index.ts (1)
  • getCurrentSession (96-103)
src/components/page-elements.tsx (2)
  • PageTitle (8-16)
  • PageDescription (22-26)
src/app/(dashboard)/subscriptions/_components/subscription-plans-list.tsx (1)
  • SubscriptionPlansList (15-64)
src/app/page.tsx (1)
src/server/auth/index.ts (1)
  • getCurrentSession (96-103)
src/app/(dashboard)/dashboard/subscriptions/_components/subscriptions.tsx (1)
src/components/recurring-payment-status-badge.tsx (1)
  • RecurringPaymentStatusBadge (18-26)
src/app/(dashboard)/crypto-to-fiat/page.tsx (3)
src/server/auth/index.ts (1)
  • getCurrentSession (96-103)
src/components/page-elements.tsx (2)
  • PageTitle (8-16)
  • PageDescription (22-26)
src/app/(dashboard)/crypto-to-fiat/_components/crypto-to-fiat.tsx (1)
  • CryptoToFiat (5-29)
src/app/(dashboard)/i/[id]/page.tsx (4)
src/app/(dashboard)/i/[id]/helpers.ts (1)
  • getInvoiceMeLink (4-13)
src/lib/helpers/invoice.ts (1)
  • getInvoiceCount (5-27)
src/components/page-elements.tsx (1)
  • PageTitle (8-16)
src/components/invoice/invoice-creator.tsx (1)
  • InvoiceCreator (32-147)
src/app/(dashboard)/subscriptions/payments/page.tsx (3)
src/server/auth/index.ts (1)
  • getCurrentSession (96-103)
src/components/page-elements.tsx (2)
  • PageTitle (8-16)
  • PageDescription (22-26)
src/app/(dashboard)/subscriptions/_components/payments-table.tsx (1)
  • PaymentsTable (109-252)
src/app/(dashboard)/ecommerce/sales/_components/index.tsx (1)
src/app/(dashboard)/ecommerce/sales/_components/blocks/client-payments-table.tsx (1)
  • ClientPaymentsTable (162-267)
src/app/(dashboard)/invoices/[ID]/page.tsx (5)
src/app/(dashboard)/invoices/[ID]/helpers.ts (1)
  • getInvoice (4-13)
src/components/page-elements.tsx (2)
  • PageTitle (8-16)
  • PageDescription (22-26)
src/app/(dashboard)/invoices/[ID]/_components/payment-section.tsx (1)
  • PaymentSection (105-690)
src/lib/date-utils.ts (1)
  • formatDate (9-17)
src/lib/constants/currencies.ts (1)
  • formatCurrencyLabel (94-139)
src/app/(dashboard)/s/[id]/_components/subscription-plan-preview.tsx (4)
src/server/db/schema.ts (1)
  • SubscriptionPlan (487-487)
src/lib/hooks/use-create-recurring-payment.ts (1)
  • useCreateRecurringPayment (22-127)
src/lib/constants/currencies.ts (2)
  • formatCurrencyLabel (94-139)
  • PayoutCurrency (46-46)
src/components/payment-secured-using-request.tsx (1)
  • PaymentSecuredUsingRequest (1-13)
src/context/auth/index.tsx (2)
src/server/db/schema.ts (1)
  • User (484-484)
src/server/auth/index.ts (1)
  • SessionValidationResult (105-107)
src/app/(dashboard)/invoices/_components/invoices-tabs.tsx (3)
src/server/db/schema.ts (1)
  • Request (483-483)
src/app/(dashboard)/invoices/_components/invoices-sent.tsx (1)
  • InvoicesSent (47-141)
src/app/(dashboard)/invoices/_components/invoices-received.tsx (1)
  • InvoicesReceived (62-282)
src/app/(dashboard)/home/page.tsx (1)
src/server/auth/index.ts (1)
  • getCurrentSession (96-103)
src/app/(dashboard)/invoices/create/page.tsx (4)
src/server/auth/index.ts (1)
  • getCurrentSession (96-103)
src/lib/helpers/invoice.ts (1)
  • getInvoiceCount (5-27)
src/components/page-elements.tsx (2)
  • PageTitle (8-16)
  • PageDescription (22-26)
src/components/invoice/invoice-creator.tsx (1)
  • InvoiceCreator (32-147)
src/app/(dashboard)/s/[id]/page.tsx (4)
src/server/auth/index.ts (1)
  • getCurrentSession (96-103)
src/app/(dashboard)/s/[id]/helpers.ts (1)
  • getSubscriptionPlan (4-13)
src/components/page-elements.tsx (1)
  • PageTitle (8-16)
src/app/(dashboard)/s/[id]/_components/subscription-plan-preview.tsx (1)
  • SubscriptionPlanPreview (28-289)
src/app/(dashboard)/payments/recurring/_components/view-recurring-payments.tsx (1)
src/components/recurring-payment-status-badge.tsx (1)
  • RecurringPaymentStatusBadge (18-26)
🔇 Additional comments (45)
src/app/(dashboard)/payments/recurring/create/page.tsx (1)

1-1: LGTM! Import refactor aligns with PR objectives.

The change from an absolute import path to a relative ./_components/ path successfully implements the PR's goal of moving page-specific components into dedicated _components folders to reduce coupling with the top-level components directory.

src/app/(dashboard)/crypto-to-fiat/_components/compliance-status.tsx (1)

2-2: LGTM! Import path standardized.

The change from relative to absolute import path aligns with the project's import conventions and improves maintainability.

src/app/(dashboard)/crypto-to-fiat/_components/compliance-form.tsx (1)

34-34: LGTM! Import path standardized.

Consistent with the import refactor applied across the crypto-to-fiat components.

src/app/(dashboard)/crypto-to-fiat/page.tsx (1)

1-20: LGTM! Clean page structure with proper authentication guard.

The page follows the established dashboard pattern with session-based authentication and appropriate redirection for unauthenticated users.

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

33-56: Well-structured component implementation.

The render logic correctly implements the invoice-me flow with proper data fetching, error handling, and component composition. The use of PageTitle and InvoiceCreator aligns with the new dashboard patterns introduced in this PR.

src/app/(dashboard)/dashboard/receipts/_components/receipts.tsx (1)

12-12: LGTM: Import path standardization.

The change to an absolute import path aligns with the broader refactor to use path aliases consistently.

src/app/(dashboard)/payments/direct/_components/direct-payout.tsx (1)

49-49: LGTM: Import path updated to use absolute alias.

This change is consistent with the project's move toward absolute import paths.

src/components/footer.tsx (1)

5-6: LGTM: Layout styling updates for new dashboard structure.

The mt-auto class appropriately pushes the footer to the bottom of the flex container. The bg-transparent class is explicit but may be redundant unless it's overriding an existing background style.

src/app/(dashboard)/dashboard/receipts/page.tsx (1)

10-10: LGTM: Auth redirect updated appropriately.

The redirect to /signin aligns with the new authentication flow introduced in this PR.

src/app/(auth)/layout.tsx (1)

1-7: LGTM: Clean authentication layout implementation.

The layout provides a minimal, appropriate wrapper for authentication pages with proper flex styling and full viewport height.

src/app/(dashboard)/subscriptions/_components/subscribers-table.tsx (4)

3-4: LGTM: Import paths standardized to absolute aliases.

The updates to absolute import paths for CompletedPayments and FrequencyBadge align with the broader refactor pattern.


7-7: LGTM: StatCard import standardized.


16-16: LGTM: EmptyState import standardized.


25-25: LGTM: TableHeadCell import standardized.

src/components/invoice/invoice-form/invoice-form.tsx (1)

41-42: All import paths verified and correctly resolve to existing files.

The import adjustments are valid and reflect proper file restructuring. The Alert component is correctly located at src/components/ui/alert.tsx, and BankAccountForm is correctly located in the blocks subdirectory. No issues found.

src/app/(dashboard)/subscriptions/_components/payments-table.tsx (1)

3-24: LGTM! Import path refactoring improves maintainability.

The migration from relative imports to absolute @/components/... paths reduces coupling and improves code maintainability, aligning with the PR's goal of restructuring the layout.

src/app/(dashboard)/dashboard/_components/dashboard-navigation.tsx (1)

13-17: Simplified tab logic looks correct.

The updated logic correctly maps pathname to either "subscriptions" or "receipts" tab, aligning with the removal of "Get Paid" and "Pay" tabs mentioned in the PR objectives.

src/app/globals.css (1)

36-37: LGTM! Highlight color tokens properly configured.

The new --highlight and --highlight-foreground CSS variables are correctly defined for both light and dark themes, following the established pattern for other color tokens.

Also applies to: 68-69

tailwind.config.ts (1)

51-54: LGTM! Tailwind configuration matches CSS variables.

The highlight color group correctly maps to the CSS variables defined in globals.css, enabling theme-aware highlight styling throughout the application.

src/app/(dashboard)/dashboard/subscriptions/page.tsx (1)

6-16: LGTM! Authentication guard and data fetching properly implemented.

The page correctly guards against unauthenticated access by redirecting to /signin and fetches subscription data server-side before rendering.

src/app/(dashboard)/dashboard/page.tsx (1)

3-5: LGTM! Default dashboard redirect aligns with PR objectives.

Redirecting to /dashboard/subscriptions makes subscriptions the primary dashboard view, consistent with the PR's goal to simplify navigation and remove the "Get Paid" and "Pay" tabs.

src/app/(dashboard)/payments/recurring/page.tsx (1)

13-15: Auth guard looks correct.

Server-side redirect on missing session is appropriate for app router pages.

src/app/(dashboard)/ecommerce/sales/page.tsx (1)

7-15: LGTM: server auth guard + initial data pattern matches the new layout.

Consistent with other dashboard pages; clean composition.

src/app/(dashboard)/invoices/_components/invoices-received.tsx (1)

143-148: No issues found—code correctly uses ethers v5 APIs.

The project is on ethers v5 (^5.7.2), and the code at lines 143–148 correctly uses providers.Web3Provider and getSigner(), which are the appropriate v5 APIs. The conditional suggestion to migrate to v6's BrowserProvider does not apply. The implementation is consistent with other parts of the codebase that follow the same v5 pattern.

src/app/(dashboard)/payments/batch/_components/batch-payout.tsx (1)

61-61: LGTM! Import path normalization.

The change from a relative path to an absolute alias aligns with the PR's goal of moving shared components to centralized locations and reducing coupling between page-specific components.

src/app/(dashboard)/subscriptions/payments/page.tsx (1)

1-31: LGTM! Well-structured server component.

The authentication guard and parallel data fetching patterns are consistent with other dashboard pages. The component properly redirects unauthenticated users and efficiently loads data before rendering.

src/app/(dashboard)/subscriptions/subscribers/page.tsx (1)

1-29: LGTM! Consistent implementation.

The page follows the same well-established pattern as the payments page, with proper authentication guards and parallel data fetching.

src/app/(dashboard)/payments/recurring/_components/view-recurring-payments.tsx (1)

3-4: LGTM! Improved component specificity.

The import path normalization and the replacement of a generic badge with RecurringPaymentStatusBadge improves code clarity and uses a component specifically designed for recurring payment statuses.

Also applies to: 32-32, 216-216

src/app/(dashboard)/invoices/_components/invoices-sent.tsx (1)

3-4: LGTM! Import path cleanup.

The migration to absolute imports for shared UI components and the reorganization of InvoiceRow's location improve code maintainability and align with the PR's restructuring objectives.

Also applies to: 7-8, 16-16, 26-26

src/app/(dashboard)/invoices/create/page.tsx (1)

1-32: LGTM! Proper authentication and data flow.

The page correctly guards against unauthenticated access, fetches the necessary invoice count, and safely handles potentially null user fields with appropriate fallbacks.

src/app/(dashboard)/invoices/me/page.tsx (1)

1-25: LGTM! Clean and consistent implementation.

The page follows the established authentication and data-fetching patterns consistently with other dashboard pages.

src/app/(auth)/signin/page.tsx (1)

18-19: Routes and assets are valid—verification confirms all concerns are satisfied

The /home redirect target exists at src/app/(dashboard)/home/page.tsx, the /login/google OAuth route is properly configured with handlers at src/app/login/google/route.ts and src/app/login/google/callback/route.ts, and the Google icon asset is present at public/google-icon.svg. No action required.

src/app/(dashboard)/payments/recurring/layout.tsx (1)

1-19: LGTM! Clean layout component.

The layout component follows the established pattern for dashboard pages with proper separation of concerns—title, description, navigation, and content.

src/app/(dashboard)/dashboard/subscriptions/_components/subscriptions.tsx (2)

3-30: LGTM! Import updates align with component reorganization.

The import path updates and the switch from StatusBadge to RecurringPaymentStatusBadge are consistent with the broader refactoring.


111-111: Correct usage of the renamed component.

The replacement of StatusBadge with RecurringPaymentStatusBadge maintains the same functionality with improved naming clarity.

src/app/(dashboard)/ecommerce/manage/page.tsx (1)

7-23: LGTM! Standard authentication and data-fetching pattern.

The authentication guard and server-side data fetching are implemented correctly. Error handling is delegated to Next.js error boundaries, which is an acceptable pattern for server components.

src/components/recurring-payment-status-badge.tsx (1)

14-26: LGTM! Improved naming clarity.

Renaming from StatusBadge to RecurringPaymentStatusBadge makes the component's purpose more explicit and reduces potential naming conflicts with other badge components.

src/components/page-elements.tsx (1)

22-26: LGTM! Clean and simple description component.

The PageDescription component is straightforward and follows good practices.

src/app/page.tsx (1)

4-12: The /home route is correctly defined and accessible.

Verification confirms that /app/(dashboard)/home/page.tsx exists in the codebase. In Next.js, route groups like (dashboard) don't affect URL paths, so this file renders at the /home route. The redirect logic in the reviewed code is correct and will successfully route authenticated users to the existing /home route.

src/app/(dashboard)/invoices/page.tsx (2)

8-12: Auth redirect in RSC is correct.
Guard is consistent with other dashboard pages.


14-17: No issues found. The code already follows the recommended pattern.

The TRPC transformer is configured with superjson on both server and client, which handles Date serialization. The Request type contains a createdAt timestamp field that becomes a Date object, but the data is passed as initialData to client queries with refetchOnMount: true, ensuring continuous synchronization through the properly configured superjson transformer. This matches the recommended "hydrate client queries with initialData" approach mentioned in the original review comment.

src/app/(dashboard)/home/page.tsx (1)

23-27: Auth redirect looks good.
Pattern matches other pages.

src/context/auth/index.tsx (1)

3-6: Good use of type‑only imports in a client module.
Avoids bundling server code into the client.

src/app/(dashboard)/s/[id]/page.tsx (2)

23-25: LGTM: Proper 404 for missing plan.

notFound() here is correct for absent/invalid IDs.


21-22: No issues found — helpers.ts correctly uses the server tRPC client.

The file already imports from @/trpc/server (line 2), which is the server-safe tRPC client. This prevents bundling client code into the server route.

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 (7)
src/app/(dashboard)/home/page.tsx (4)

36-41: Fix stale route and Link/Button nesting.

The href /invoice/create should be /invoices/create, and the nested <Link><Button> pattern violates a11y best practices.

Apply this diff:

-        <Link href="/invoice/create">
-          <Button className="bg-highlight hover:bg-highlight/90 text-[hsl(var(--highlight-foreground))]">
+        <Button asChild className="bg-highlight hover:bg-highlight/90 text-[hsl(var(--highlight-foreground))]">
+          <Link href="/invoices/create">
             <Plus className="mr-2 h-4 w-4" />
             Create Your First Invoice
-          </Button>
-        </Link>
+          </Link>
+        </Button>

108-113: Fix stale route.

The href /invoice/create should be /invoices/create.

Apply this diff:

           <Button
             className="w-full bg-highlight hover:bg-highlight/90 text-highlight-foreground"
             asChild
           >
-            <Link href="/invoice/create">Create New Invoice</Link>
+            <Link href="/invoices/create">Create New Invoice</Link>
           </Button>

128-135: Fix stale route and Link/Button nesting.

The href /invoice should be /invoices, and the nested <Link><Button> pattern violates a11y best practices.

Apply this diff:

-          <Link href="/invoice">
-            <Button
-              variant="outline"
-              className="w-full border-highlight/30 hover:bg-highlight/10"
-            >
-              View All Invoices
-            </Button>
-          </Link>
+          <Button
+            asChild
+            variant="outline"
+            className="w-full border-highlight/30 hover:bg-highlight/10"
+          >
+            <Link href="/invoices">View All Invoices</Link>
+          </Button>

148-155: Fix Link/Button nesting.

The nested <Link><Button> pattern violates a11y best practices.

Apply this diff:

-          <Link href="/payments">
-            <Button
-              variant="outline"
-              className="w-full border-highlight/30 hover:bg-highlight/10"
-            >
-              Manage Payments
-            </Button>
-          </Link>
+          <Button
+            asChild
+            variant="outline"
+            className="w-full border-highlight/30 hover:bg-highlight/10"
+          >
+            <Link href="/payments">Manage Payments</Link>
+          </Button>
src/app/(dashboard)/s/[id]/_components/subscription-plan-preview.tsx (1)

79-88: Clarify error messages for loading and unauthenticated states.

When canSubscribeToPlan is false due to isSessionLoading or !isAuthenticated, the generic message "You cannot subscribe to this plan" is misleading. Users should see distinct messages for loading, unauthenticated, and ownership cases.

Consider this refinement:

   const handleStartSubscription = async () => {
+    if (isSessionLoading) {
+      toast.info("Checking your session, please wait…");
+      return;
+    }
+    
+    if (!isAuthenticated) {
+      toast.error("Please sign in to subscribe");
+      return;
+    }
+    
     if (isOwnPlan) {
       toast.error("You cannot subscribe to your own plan");
       return;
     }

-    if (!canSubscribeToPlan) {
-      toast.error("You cannot subscribe to this plan");
-      return;
-    }

     if (!address || !isConnected) {
src/app/(dashboard)/invoices/[ID]/page.tsx (2)

30-31: Auth guard added — resolves prior critical.

requireAuth() gates the page before fetching data. This addresses the earlier unauthenticated access risk. Good fix.


221-227: Mask all but last 4 for account numbers and IBAN (privacy).

Current masking hides only leading digits and leaves letters/IBAN exposed. Replace with an alphanumeric-wide mask to show only the last 4.

Apply this diff:

-  {paymentDetailsData.paymentDetails.accountNumber && (
-    <div className="text-sm">
-      {paymentDetailsData.paymentDetails.accountNumber.replace(
-        /^\d+(?=\d{4})/,
-        "****",
-      )}
-    </div>
-  )}
+  {paymentDetailsData.paymentDetails.accountNumber && (
+    <div className="text-sm">
+      {paymentDetailsData.paymentDetails.accountNumber.replace(
+        /[A-Za-z0-9](?=(?:.*[A-Za-z0-9]){4})/g,
+        "•",
+      )}
+    </div>
+  )}
@@
-  {paymentDetailsData.paymentDetails.iban && (
-    <div className="text-sm">
-      {paymentDetailsData.paymentDetails.iban}
-    </div>
-  )}
+  {paymentDetailsData.paymentDetails.iban && (
+    <div className="text-sm">
+      {paymentDetailsData.paymentDetails.iban.replace(
+        /[A-Za-z0-9](?=(?:.*[A-Za-z0-9]){4})/g,
+        "•",
+      )}
+    </div>
+  )}

If preferred, I can open a follow-up issue to track this hardening.

Also applies to: 239-243

🧹 Nitpick comments (3)
src/app/(dashboard)/invoices/[ID]/page.tsx (3)

156-170: Format amounts and derive Subtotal from items.

Use localized number formatting and compute subtotal from line items to avoid mismatch with invoice.amount and improve readability.

Apply this diff:

@@
-export default async function PaymentPage({
+export default async function PaymentPage({
@@
-  const paymentDetailsData = invoice.paymentDetailsId
+  const paymentDetailsData = invoice.paymentDetailsId
     ? await api.compliance.getPaymentDetailsById.query(invoice.paymentDetailsId)
     : null;
 
+  // Derived values and formatting
+  const items = (invoice.items as InvoiceItem[]) || [];
+  const computedSubtotal = items.reduce(
+    (sum, { quantity, price }) => sum + quantity * price,
+    0,
+  );
+  const fmt = new Intl.NumberFormat(undefined, {
+    minimumFractionDigits: 2,
+    maximumFractionDigits: 8,
+  });
@@
-                  {(invoice.items as InvoiceItem[]).map((item, index) => (
+                  {items.map((item, index) => (
                     <tr key={`invoice-item-${item.description}-${index}`}>
@@
-                      <td className="py-3 text-right text-sm">
-                        {item.price.toString()}
-                      </td>
+                      <td className="py-3 text-right text-sm">
+                        {fmt.format(item.price)}
+                      </td>
@@
-                      <td className="py-3 text-right text-sm">
-                        {(item.quantity * item.price).toString()}
-                      </td>
+                      <td className="py-3 text-right text-sm">
+                        {fmt.format(item.quantity * item.price)}
+                      </td>
                   ))}
@@
-                    <span className="text-sm">
-                      {Number(invoice.amount).toString()}
-                    </span>
+                    <span className="text-sm">
+                      {fmt.format(computedSubtotal)}
+                    </span>
@@
-                      <div className="text-sm text-right font-medium">
-                        {Number(invoice.amount).toString()}
-                      </div>
+                      <div className="text-sm text-right font-medium">
+                        {fmt.format(Number(invoice.amount))}
+                      </div>

Also applies to: 175-197


43-47: Add an accessible label to the icon-only back link.

Improve screen reader support with an aria-label.

-<Link href="/invoices" className="text-foreground">
+<Link href="/invoices" className="text-foreground" aria-label="Back to invoices">
   <ArrowLeft className="h-6 w-6" />
 </Link>

141-153: Add scope="col" to table headers.

Small a11y win for better header associations.

-<th className="text-xs text-muted-foreground text-left pb-3">
+<th scope="col" className="text-xs text-muted-foreground text-left pb-3">
   DESCRIPTION
 </th>
-<th className="text-xs text-muted-foreground text-right pb-3">
+<th scope="col" className="text-xs text-muted-foreground text-right pb-3">
   QTY
 </th>
-<th className="text-xs text-muted-foreground text-right pb-3">
+<th scope="col" className="text-xs text-muted-foreground text-right pb-3">
   PRICE
 </th>
-<th className="text-xs text-muted-foreground text-right pb-3">
+<th scope="col" className="text-xs text-muted-foreground text-right pb-3">
   AMOUNT
 </th>
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 2fcae90 and 022796b.

📒 Files selected for processing (27)
  • src/app/(auth)/signin/page.tsx (1 hunks)
  • src/app/(dashboard)/crypto-to-fiat/page.tsx (1 hunks)
  • src/app/(dashboard)/dashboard/_components/dashboard-navigation.tsx (1 hunks)
  • src/app/(dashboard)/dashboard/layout.tsx (1 hunks)
  • src/app/(dashboard)/dashboard/receipts/page.tsx (1 hunks)
  • src/app/(dashboard)/dashboard/subscriptions/page.tsx (1 hunks)
  • src/app/(dashboard)/ecommerce/manage/page.tsx (1 hunks)
  • src/app/(dashboard)/ecommerce/sales/page.tsx (1 hunks)
  • src/app/(dashboard)/home/page.tsx (1 hunks)
  • src/app/(dashboard)/i/[id]/page.tsx (1 hunks)
  • src/app/(dashboard)/invoices/[ID]/page.tsx (1 hunks)
  • src/app/(dashboard)/invoices/create/page.tsx (1 hunks)
  • src/app/(dashboard)/invoices/me/page.tsx (1 hunks)
  • src/app/(dashboard)/invoices/page.tsx (1 hunks)
  • src/app/(dashboard)/payments/batch/page.tsx (1 hunks)
  • src/app/(dashboard)/payments/direct/page.tsx (1 hunks)
  • src/app/(dashboard)/payments/recurring/create/_components/blocks/create-recurring-payment-form.tsx (1 hunks)
  • src/app/(dashboard)/payments/recurring/create/page.tsx (1 hunks)
  • src/app/(dashboard)/payments/recurring/page.tsx (1 hunks)
  • src/app/(dashboard)/s/[id]/_components/subscription-plan-preview.tsx (1 hunks)
  • src/app/(dashboard)/s/[id]/page.tsx (1 hunks)
  • src/app/(dashboard)/subscriptions/page.tsx (1 hunks)
  • src/app/(dashboard)/subscriptions/payments/page.tsx (1 hunks)
  • src/app/(dashboard)/subscriptions/subscribers/page.tsx (1 hunks)
  • src/app/layout.tsx (3 hunks)
  • src/components/navigation/sidebar.tsx (1 hunks)
  • src/lib/auth.ts (1 hunks)
✅ Files skipped from review due to trivial changes (1)
  • src/app/(dashboard)/ecommerce/manage/page.tsx
🚧 Files skipped from review as they are similar to previous changes (11)
  • src/app/(dashboard)/ecommerce/sales/page.tsx
  • src/app/(dashboard)/payments/direct/page.tsx
  • src/app/(dashboard)/payments/batch/page.tsx
  • src/app/(dashboard)/subscriptions/page.tsx
  • src/app/(dashboard)/s/[id]/page.tsx
  • src/app/(dashboard)/i/[id]/page.tsx
  • src/app/(dashboard)/crypto-to-fiat/page.tsx
  • src/app/(dashboard)/subscriptions/subscribers/page.tsx
  • src/app/(auth)/signin/page.tsx
  • src/components/navigation/sidebar.tsx
  • src/app/(dashboard)/invoices/create/page.tsx
🧰 Additional context used
🧠 Learnings (2)
📚 Learning: 2025-10-01T11:39:24.398Z
Learnt from: bassgeta
PR: RequestNetwork/easy-invoice#154
File: src/app/ecommerce/manage/page.tsx:7-15
Timestamp: 2025-10-01T11:39:24.398Z
Learning: In the Easy Invoice codebase, `getCurrentSession()` from `server/auth/index.ts` returns a discriminated union type `SessionValidationResult` that always includes both `session` and `user` properties. When unauthenticated, it returns `{ session: null, user: null }`. Destructuring `const { user } = await getCurrentSession()` is safe and is the standard pattern used across multiple pages. Do not flag this pattern as unsafe.

Applied to files:

  • src/app/layout.tsx
  • src/lib/auth.ts
📚 Learning: 2025-10-22T08:44:20.482Z
Learnt from: bassgeta
PR: RequestNetwork/easy-invoice#164
File: src/app/(dashboard)/s/[id]/_components/subscription-plan-preview.tsx:66-75
Timestamp: 2025-10-22T08:44:20.482Z
Learning: In the EasyInvoice codebase, amounts are normalized to 18 decimals internally for all calculations. The pattern is: parse user-facing amounts with 18 decimals using utils.parseUnits(), perform calculations with ethers BigNumber, and format back with 18 decimals using utils.formatUnits(). This normalization strategy avoids floating-point arithmetic issues and maintains consistency across different token types.

Applied to files:

  • src/app/(dashboard)/s/[id]/_components/subscription-plan-preview.tsx
🧬 Code graph analysis (13)
src/app/layout.tsx (2)
src/server/auth/index.ts (1)
  • getCurrentSession (96-103)
src/context/auth/index.tsx (1)
  • AuthProvider (27-49)
src/app/(dashboard)/payments/recurring/page.tsx (1)
src/lib/auth.ts (1)
  • requireAuth (4-8)
src/app/(dashboard)/invoices/page.tsx (3)
src/lib/auth.ts (1)
  • requireAuth (4-8)
src/components/page-elements.tsx (2)
  • PageTitle (8-16)
  • PageDescription (22-26)
src/app/(dashboard)/invoices/_components/invoices-tabs.tsx (1)
  • InvoicesTabs (15-50)
src/lib/auth.ts (1)
src/server/auth/index.ts (1)
  • getCurrentSession (96-103)
src/app/(dashboard)/dashboard/layout.tsx (2)
src/components/page-elements.tsx (2)
  • PageTitle (8-16)
  • PageDescription (22-26)
src/app/(dashboard)/dashboard/_components/dashboard-navigation.tsx (1)
  • DashboardNavigation (8-32)
src/app/(dashboard)/invoices/me/page.tsx (3)
src/lib/auth.ts (1)
  • requireAuth (4-8)
src/components/page-elements.tsx (2)
  • PageTitle (8-16)
  • PageDescription (22-26)
src/app/(dashboard)/invoices/me/_components/invoice-me-links.tsx (1)
  • InvoiceMeLinks (16-113)
src/app/(dashboard)/home/page.tsx (1)
src/lib/auth.ts (1)
  • requireAuth (4-8)
src/app/(dashboard)/dashboard/receipts/page.tsx (2)
src/lib/auth.ts (1)
  • requireAuth (4-8)
src/app/(dashboard)/dashboard/receipts/_components/receipts.tsx (1)
  • DashboardReceipts (66-187)
src/app/(dashboard)/subscriptions/payments/page.tsx (3)
src/lib/auth.ts (1)
  • requireAuth (4-8)
src/components/page-elements.tsx (2)
  • PageTitle (8-16)
  • PageDescription (22-26)
src/app/(dashboard)/subscriptions/_components/payments-table.tsx (1)
  • PaymentsTable (109-252)
src/app/(dashboard)/s/[id]/_components/subscription-plan-preview.tsx (5)
src/server/db/schema.ts (1)
  • SubscriptionPlan (487-487)
src/context/auth/index.tsx (1)
  • useAuthContext (51-57)
src/lib/hooks/use-create-recurring-payment.ts (1)
  • useCreateRecurringPayment (22-127)
src/lib/constants/currencies.ts (2)
  • formatCurrencyLabel (94-139)
  • PayoutCurrency (46-46)
src/components/payment-secured-using-request.tsx (1)
  • PaymentSecuredUsingRequest (1-13)
src/app/(dashboard)/payments/recurring/create/page.tsx (2)
src/lib/auth.ts (1)
  • requireAuth (4-8)
src/app/(dashboard)/payments/recurring/create/_components/create-recurring-payment.tsx (1)
  • CreateRecurringPayment (12-130)
src/app/(dashboard)/dashboard/subscriptions/page.tsx (1)
src/lib/auth.ts (1)
  • requireAuth (4-8)
src/app/(dashboard)/invoices/[ID]/page.tsx (6)
src/lib/auth.ts (1)
  • requireAuth (4-8)
src/app/(dashboard)/invoices/[ID]/helpers.ts (1)
  • getInvoice (4-13)
src/components/page-elements.tsx (2)
  • PageTitle (8-16)
  • PageDescription (22-26)
src/app/(dashboard)/invoices/[ID]/_components/payment-section.tsx (1)
  • PaymentSection (105-690)
src/lib/date-utils.ts (1)
  • formatDate (9-17)
src/lib/constants/currencies.ts (1)
  • formatCurrencyLabel (94-139)
🔇 Additional comments (14)
src/app/(dashboard)/payments/recurring/create/_components/blocks/create-recurring-payment-form.tsx (1)

66-66: LGTM! Route correction aligns with dashboard restructure.

The navigation target update from "/payouts/recurring" to "/payments/recurring" is consistent with the new routing structure.

src/app/(dashboard)/dashboard/receipts/page.tsx (1)

1-11: LGTM! Clean server component implementation.

The page correctly enforces authentication, fetches receipts data, and passes it to the presentation component. Implementation follows established patterns.

src/lib/auth.ts (1)

4-8: LGTM! Clean authentication helper implementation.

The requireAuth helper correctly retrieves the session, redirects unauthenticated users to /signin, and returns the authenticated user. The destructuring pattern is safe in this codebase.

Based on learnings.

src/app/(dashboard)/payments/recurring/page.tsx (1)

1-10: LGTM! Consistent authentication refactor.

The page correctly adopts the new requireAuth() pattern, replacing the previous manual session check. The authentication flow is now consistent with other dashboard pages.

src/app/(dashboard)/dashboard/_components/dashboard-navigation.tsx (1)

22-28: LGTM! Grid column count now matches tab count.

The grid-cols-2 correctly matches the two rendered tabs, addressing the previous review concern about the grid-cols-4 mismatch.

src/app/(dashboard)/dashboard/subscriptions/page.tsx (1)

1-11: LGTM! Consistent authentication refactor.

The page correctly adopts the new requireAuth() pattern, consistent with other dashboard pages in this PR.

src/app/(dashboard)/invoices/page.tsx (1)

9-12: LGTM! Efficient parallel data fetching.

Using Promise.all to fetch both invoice datasets in parallel is a good practice that reduces the total loading time.

src/app/(dashboard)/dashboard/layout.tsx (1)

4-19: LGTM! Clean layout composition.

The dashboard layout is straightforward and well-structured, composing the title, description, navigation, and child content appropriately.

src/app/(dashboard)/subscriptions/payments/page.tsx (1)

1-26: LGTM! Clean server-side page implementation.

The authentication guard, parallel data fetching, and component integration follow best practices. The use of Promise.all efficiently loads both payments and subscription plans before rendering.

src/app/(dashboard)/payments/recurring/create/page.tsx (1)

1-12: LGTM! Standard Next.js page pattern.

The metadata export and authentication guard are correctly implemented. The page cleanly delegates to the CreateRecurringPayment client component.

src/app/layout.tsx (2)

31-37: LGTM! Session initialization correctly handles unauthenticated state.

The code now passes null to AuthProvider when unauthenticated (line 37), which enables client-side revalidation. This addresses the previous concern about enabled: !initialSession staying false.


55-57: LGTM! AuthProvider integration is correct.

The provider receives the properly computed initialSessionForProvider and correctly wraps the application tree within the existing provider hierarchy.

src/app/(dashboard)/invoices/me/page.tsx (1)

1-20: LGTM! Clean and consistent page implementation.

The authentication enforcement, data fetching, and component integration follow the established patterns throughout the dashboard. The page correctly provides initialLinks to enable optimistic UI updates.

src/app/(dashboard)/s/[id]/_components/subscription-plan-preview.tsx (1)

231-287: LGTM! Clear conditional rendering for wallet connection states.

The UI properly handles the different states (AppKit not ready, wallet not connected, wallet connected) with appropriate loading indicators and action buttons. The user experience flow is intuitive.

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 (3)
src/app/(dashboard)/home/page.tsx (2)

62-95: Consider externalizing hard-coded statistics.

The stats (2017, 1B+, $30M, 20K) are hard-coded. If these values need updates or localization, consider moving them to a config file or environment variables.


37-37: Minor: Inconsistent color reference syntax.

Line 37 uses text-[hsl(var(--highlight-foreground))] (arbitrary value) while line 112 uses text-highlight-foreground (theme color). If highlight-foreground is defined in your Tailwind config, prefer the theme color syntax for consistency.

Also applies to: 112-112

src/app/(dashboard)/dashboard/_components/dashboard-navigation.tsx (1)

13-13: Consider more specific path matching for robustness.

The pathname.includes("/subscriptions") check will match any path containing the substring "subscriptions", including potential future routes like /dashboard/subscriptions-archive. For more precise routing, consider an exact match or prefix check.

For example:

-    if (pathname.includes("/subscriptions")) {
+    if (pathname === "/dashboard/subscriptions" || pathname.startsWith("/dashboard/subscriptions/")) {
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 022796b and 113a097.

📒 Files selected for processing (4)
  • src/app/(dashboard)/dashboard/_components/dashboard-navigation.tsx (1 hunks)
  • src/app/(dashboard)/home/page.tsx (1 hunks)
  • src/app/(dashboard)/payments/page.tsx (1 hunks)
  • src/components/navigation/sidebar.tsx (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/components/navigation/sidebar.tsx
🧰 Additional context used
🧬 Code graph analysis (1)
src/app/(dashboard)/home/page.tsx (1)
src/lib/auth.ts (1)
  • requireAuth (4-8)
⏰ 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 (6)
src/app/(dashboard)/payments/page.tsx (1)

10-11: Previous redirect typo has been fixed—changes look good!

The redirect now correctly points to /payments/direct (fixing the previous /payouts/direct typo from the earlier review). The function name and metadata are consistent with the payments route.

src/app/(dashboard)/home/page.tsx (3)

1-22: LGTM! Clean imports and proper auth guard.

The component follows the established pattern for dashboard pages, correctly invoking requireAuth() to ensure authenticated access. The import structure is well-organized.


36-44: Previous Link/Button nesting issue resolved correctly.

The Button now uses the asChild pattern with Link as a child, which satisfies accessibility requirements and avoids nesting interactive elements. The route /invoices/create is correct.


111-116: Previous Link/Button nesting issues resolved correctly.

All Quick Action buttons now use the asChild pattern properly, and the routes are correct (/invoices/create, /invoices, /payments). This addresses the accessibility and routing concerns from the previous review.

Also applies to: 131-137, 150-156

src/app/(dashboard)/dashboard/_components/dashboard-navigation.tsx (2)

12-18: Verify fallback behavior for dashboard root.

The logic falls back to highlighting the "receipts" tab for any path that doesn't include "/subscriptions". This includes the dashboard root (/dashboard) if it exists as a standalone route. Please confirm that this is the intended UX or that the root path redirects to one of these tab pages.


22-29: LGTM! Grid layout now matches tab count.

The grid column count has been correctly updated to grid-cols-2 to match the two rendered tabs, addressing the previous review feedback.

Copy link
Member

@MantisClone MantisClone left a comment

Choose a reason for hiding this comment

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

Initial high-level review complete ✅ - I unresolved several comments 🔍

  1. The old Payment Page was intentionally not authenticated. Unclear if this behavior should change in the new Dashboard layout. Let's discuss tomorrow.
  2. The Payout pages should appear under the/payouts path. Reserve /payments for the new "Payments" page (seen in the design as "Transactions").

@MantisClone
Copy link
Member

  1. The old Payment Page was intentionally not authenticated. Unclear if this behavior should change in the new Dashboard layout. Let's discuss tomorrow.

We decided that now is the time to add authentication to the Payment Page for a few reasons:

  1. Dashboard is a real app where we want users to create accounts
  2. It was difficult to keep the Payment Page unauthenticated in the past. Authenticating it simplifies the design.
  3. It's easier to attribute payments to users.
  4. It'll be safe to show rich details on the Payment Page that only authenticated users should see.

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 (4)
src/app/login/google/callback/route.ts (1)

99-103: Consider logging errors for easier debugging.

The catch block silently discards all errors, making it difficult to diagnose OAuth failures in production (e.g., Google API issues, token validation failures, database errors).

Consider adding error logging:

-  } catch (_) {
+  } catch (error) {
+    console.error("Google OAuth callback error:", error);
     return new Response(null, {
       status: 400,
     });
   }
src/app/(dashboard)/payouts/recurring/create/page.tsx (1)

4-7: Consider aligning terminology for consistency.

The title uses "Recurring Payments" while the layout and file paths use "Recurring Payouts". For consistency across the UI and better SEO, consider updating the title to match:

 export const metadata = {
-  title: "Recurring Payments | Easy Invoice",
-  description: "Create recurring payments using Easy Invoice",
+  title: "Create Recurring Payout | Easy Invoice",
+  description: "Create recurring payouts using Easy Invoice",
 };
src/components/navigation/sidebar.tsx (2)

87-101: Consider adding aria-expanded for improved accessibility.

The collapsible section toggle buttons would benefit from aria-expanded attributes to improve screen reader navigation. This helps assistive technology users understand whether each module section is expanded or collapsed.

Example for the Billing section:

 <button
   type="button"
   onClick={() => toggleSection("billing")}
+  aria-expanded={expandedSections.billing}
   className="flex items-center justify-between w-full px-3 py-2 text-foreground hover:bg-muted rounded-lg transition-colors"
 >

Apply similar changes to the Ecommerce, Subscriptions, and Payouts toggle buttons.

Also applies to: 139-153, 181-195, 233-247


86-282: Consider extracting module rendering logic to reduce duplication.

All four modules (Billing, Ecommerce, Subscriptions, Payouts) follow nearly identical patterns. Consider extracting this into a reusable component or configuration-driven approach to improve maintainability.

Example configuration-driven approach:

const modules = [
  {
    id: "billing" as Section,
    label: "Billing",
    icon: FileText,
    links: [
      { href: "/invoices", label: "View Invoices" },
      { href: "/invoices/create", label: "Create Invoice" },
      { href: "/invoices/me", label: "InvoiceMe Link" },
    ],
  },
  // ... other modules
];

Then map over this configuration to render each module, reducing repetition and making future updates easier.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 79839fa and a86ae00.

📒 Files selected for processing (15)
  • src/app/(dashboard)/dashboard/subscriptions/_components/subscriptions.tsx (3 hunks)
  • src/app/(dashboard)/payouts/batch/_components/batch-payout.tsx (1 hunks)
  • src/app/(dashboard)/payouts/batch/page.tsx (1 hunks)
  • src/app/(dashboard)/payouts/direct/_components/direct-payout.tsx (1 hunks)
  • src/app/(dashboard)/payouts/direct/page.tsx (1 hunks)
  • src/app/(dashboard)/payouts/page.tsx (1 hunks)
  • src/app/(dashboard)/payouts/recurring/_components/recurring-payments-navigation.tsx (1 hunks)
  • src/app/(dashboard)/payouts/recurring/_components/view-recurring-payments.tsx (3 hunks)
  • src/app/(dashboard)/payouts/recurring/create/_components/blocks/create-recurring-payment-form.tsx (1 hunks)
  • src/app/(dashboard)/payouts/recurring/create/page.tsx (1 hunks)
  • src/app/(dashboard)/payouts/recurring/layout.tsx (1 hunks)
  • src/app/(dashboard)/payouts/recurring/page.tsx (1 hunks)
  • src/app/(dashboard)/subscriptions/_components/subscribers-table.tsx (3 hunks)
  • src/app/login/google/callback/route.ts (1 hunks)
  • src/components/navigation/sidebar.tsx (1 hunks)
🧰 Additional context used
🧬 Code graph analysis (7)
src/app/(dashboard)/payouts/recurring/page.tsx (2)
src/lib/auth.ts (1)
  • requireAuth (4-8)
src/app/(dashboard)/payouts/recurring/_components/view-recurring-payments.tsx (1)
  • ViewRecurringPayments (41-352)
src/app/(dashboard)/payouts/recurring/_components/view-recurring-payments.tsx (1)
src/components/recurring-payment-status-badge.tsx (1)
  • RecurringPaymentStatusBadge (18-26)
src/app/(dashboard)/payouts/direct/page.tsx (3)
src/lib/auth.ts (1)
  • requireAuth (4-8)
src/components/page-elements.tsx (2)
  • PageTitle (8-16)
  • PageDescription (22-26)
src/app/(dashboard)/payouts/direct/_components/direct-payout.tsx (1)
  • DirectPayment (61-446)
src/app/(dashboard)/payouts/recurring/create/page.tsx (2)
src/lib/auth.ts (1)
  • requireAuth (4-8)
src/app/(dashboard)/payouts/recurring/create/_components/create-recurring-payment.tsx (1)
  • CreateRecurringPayment (12-130)
src/app/(dashboard)/payouts/batch/page.tsx (7)
src/app/(dashboard)/payouts/direct/page.tsx (1)
  • metadata (5-8)
src/app/(dashboard)/payouts/page.tsx (1)
  • metadata (4-8)
src/app/(dashboard)/payouts/recurring/create/page.tsx (1)
  • metadata (4-7)
src/app/(dashboard)/payouts/recurring/page.tsx (1)
  • metadata (5-8)
src/lib/auth.ts (1)
  • requireAuth (4-8)
src/components/page-elements.tsx (2)
  • PageTitle (8-16)
  • PageDescription (22-26)
src/app/(dashboard)/payouts/batch/_components/batch-payout.tsx (1)
  • BatchPayout (78-637)
src/app/(dashboard)/dashboard/subscriptions/_components/subscriptions.tsx (1)
src/components/recurring-payment-status-badge.tsx (1)
  • RecurringPaymentStatusBadge (18-26)
src/app/(dashboard)/payouts/recurring/layout.tsx (2)
src/components/page-elements.tsx (2)
  • PageTitle (8-16)
  • PageDescription (22-26)
src/app/(dashboard)/payouts/recurring/_components/recurring-payments-navigation.tsx (1)
  • RecurringPaymentsNavigation (8-32)
🔇 Additional comments (19)
src/app/(dashboard)/payouts/page.tsx (1)

11-11: LGTM!

The redirect target has been correctly updated to align with the new route structure.

src/app/(dashboard)/payouts/direct/_components/direct-payout.tsx (1)

49-49: LGTM!

The import path has been correctly normalized to use the absolute alias, consistent with the PR's goal of standardizing import paths across the codebase.

src/app/(dashboard)/payouts/batch/_components/batch-payout.tsx (1)

61-61: LGTM!

The import path normalization matches the pattern applied in the direct payout component, maintaining consistency across the payout flows.

src/app/(dashboard)/payouts/batch/page.tsx (1)

1-21: LGTM! Authentication correctly enforced.

The page structure follows Next.js best practices with proper authentication guard placement. The requireAuth() call ensures unauthenticated users are redirected before rendering, aligning with the PR objective to add authentication to payout pages.

Minor note: The function name BatchPayoutSlot differs slightly from the naming pattern used in DirectPaymentPage (direct/page.tsx), though both are valid.

src/app/(dashboard)/payouts/direct/page.tsx (1)

1-19: LGTM! Clean implementation with proper authentication.

The page follows the same well-structured pattern as the batch payout page, correctly enforcing authentication before rendering. The implementation aligns with the PR's objective to add authentication to payout pages and follows Next.js App Router conventions.

src/app/login/google/callback/route.ts (1)

63-98: ****

The divergent redirects are intentional and correct. The root page (src/app/page.tsx) redirects all authenticated users to "/home", so existing users sent to "/" ultimately reach the same dashboard destination as new users sent directly to "/home". This is a routing optimization—not an oversight—where existing users are validated through the root page logic before proceeding to the dashboard.

src/app/(dashboard)/subscriptions/_components/subscribers-table.tsx (1)

3-4: LGTM! Import path normalization improves maintainability.

The import paths have been updated from relative to absolute paths, which improves code maintainability and aligns with the PR's objective to reorganize the codebase structure. The imports are consistent and correctly reference the new locations.

Also applies to: 7-7, 16-16, 25-25

src/app/(dashboard)/payouts/recurring/create/_components/blocks/create-recurring-payment-form.tsx (1)

66-66: LGTM! Route redirect correctly updated.

The redirect path has been updated to /payments/recurring, which is consistent with the navigation component changes and correctly addresses the route renaming in this PR.

src/app/(dashboard)/payouts/recurring/_components/recurring-payments-navigation.tsx (1)

24-27: LGTM! Navigation routes correctly updated.

The navigation links have been updated from /payouts/recurring to /payments/recurring, which is consistent with the broader route restructuring in this PR. The changes maintain proper navigation functionality across the application.

src/app/(dashboard)/payouts/recurring/_components/view-recurring-payments.tsx (1)

3-4: LGTM! Import paths and component usage updated consistently.

The import paths have been normalized to absolute paths, and the StatusBadge component has been correctly replaced with RecurringPaymentStatusBadge. The component interface remains the same, ensuring no breaking changes in functionality.

Also applies to: 32-32, 216-216

src/app/(dashboard)/dashboard/subscriptions/_components/subscriptions.tsx (1)

3-8: LGTM! Import paths normalized and component updated consistently.

The import paths have been successfully normalized to absolute paths, and the badge component has been consistently updated from StatusBadge to RecurringPaymentStatusBadge at line 111. These changes align with the broader refactoring effort across the dashboard components and maintain functional compatibility.

Also applies to: 19-19, 21-22, 30-30, 111-111

src/app/(dashboard)/payouts/recurring/create/page.tsx (2)

1-2: LGTM!

The imports follow the expected pattern with absolute paths for shared utilities and relative paths for page-specific components.


8-12: LGTM!

The authentication guard pattern is correctly implemented. The page properly enforces authentication before rendering the create form.

src/app/(dashboard)/payouts/recurring/page.tsx (2)

1-3: LGTM!

The imports are correctly structured for a server component that performs authentication and data fetching.


9-16: LGTM!

The page correctly implements the authentication guard and server-side data fetching pattern. Passing the initial data to the client component allows for immediate rendering with the ability to refetch client-side.

src/app/(dashboard)/payouts/recurring/layout.tsx (1)

4-19: Layout structure looks good.

The layout component correctly uses the PageTitle, PageDescription, and navigation components with appropriate spacing. The implementation follows Next.js App Router layout conventions properly.

src/components/navigation/sidebar.tsx (3)

56-66: Homepage link correctly updated.

The previous review flagged that checking pathname === "/" would be dead code within the dashboard layout. This has been correctly resolved by linking to /home with the appropriate pathname check.


19-47: Well-implemented sidebar state management and auto-expansion logic.

The component correctly uses React hooks and manages expandable sections with clean state updates. The useEffect auto-expansion based on pathname provides good UX by opening relevant modules as users navigate, while the manual toggle preserves user preferences by only expanding (never collapsing) sections.


273-276: No action needed—active state logic is intentional.

Sub-routes exist under /payouts/recurring (specifically /payouts/recurring/create), confirming that the pathname.startsWith() logic is the correct design choice. This approach intentionally highlights the parent link when navigating to any sub-route, which is standard UX behavior.

@rodrigopavezi
Copy link
Member

PR Review: Navigation Rework and Route Restructuring

Summary

This PR implements a comprehensive navigation and routing refactor, introducing Next.js 14 route groups (auth) and (dashboard), implementing a new authentication system with session management, and reorganizing the entire component structure. The changes include moving components from src/components/ to route-specific _components/ folders and implementing a new sidebar/topbar navigation system.


🔒 Security Review

✅ Strengths:

  • Robust Authentication System: Implements secure session-based authentication using @oslojs/crypto for token generation and SHA-256 hashing
  • CSRF Protection: Middleware includes proper CSRF protection by validating Origin and Host headers
  • Secure Cookie Configuration: Session cookies are properly configured with httpOnly, sameSite: "lax", and secure in production
  • Input Validation: Comprehensive Zod schemas for form validation across all user inputs (invoices, compliance, bank accounts, etc.)
  • Environment Variable Validation: Proper validation of sensitive environment variables with clear error messages
  • Session Management: Automatic session renewal and proper cleanup on logout

⚠️ Areas of Concern:

  • Session Token Length: 20-byte session tokens (160 bits) are acceptable but consider 32 bytes (256 bits) for enhanced security
  • Session Expiration: 30-day session expiration is quite long for a financial application - consider shorter periods
  • Webhook Security: While webhook endpoints skip CSRF checks (line 24 in middleware), ensure webhook signature validation is implemented

🔧 Recommendations:

  1. Consider implementing rate limiting for authentication endpoints
  2. Add session invalidation on password/security changes
  3. Consider implementing 2FA for enhanced security
  4. Add audit logging for sensitive operations

📊 Code Quality Review

✅ Excellent Practices:

  • Clean Architecture: Well-organized route groups and component co-location following Next.js 14 best practices
  • Type Safety: Comprehensive TypeScript usage with proper type definitions
  • Consistent Patterns: Uniform use of tRPC for API calls and React Hook Form with Zod validation
  • Component Organization: Logical separation of concerns with _components folders
  • Error Handling: Proper error boundaries and validation throughout

✅ Modern Stack Implementation:

  • Next.js 14 App Router: Proper use of route groups and layouts
  • Server Components: Appropriate use of server-side rendering where beneficial
  • Client Components: Proper "use client" directives only where needed

⚠️ Minor Improvements:

  • File Naming: Some inconsistency in component naming conventions (kebab-case vs camelCase)
  • Import Organization: Could benefit from consistent import ordering
  • Component Size: Some components (like sidebar.tsx) are quite large and could be split

🐛 Bug Analysis

✅ No Critical Bugs Found

⚠️ Potential Issues:

  1. Route Redirect Logic: In src/app/page.tsx, the redirect logic could create redirect loops if session validation fails
  2. Missing Error Boundaries: Some new route components lack error boundary implementations
  3. Hydration Concerns: The auth context initialization might cause hydration mismatches if server and client states differ
  4. Navigation State: Sidebar expansion state is not persisted across page refreshes

🔧 Suggested Fixes:

// In src/app/page.tsx - add error handling
export default async function RootPage() {
  try {
    const { user } = await getCurrentSession();
    if (user) {
      return redirect("/home");
    }
    return redirect("/signin");
  } catch (error) {
    console.error("Session validation error:", error);
    return redirect("/signin");
  }
}

📋 Migration Completeness

✅ Comprehensive File Migration:

  • All components properly moved to new locations
  • Route structure completely reorganized
  • Authentication system fully implemented
  • Navigation components created and integrated

✅ Import Updates:

  • All import paths appear to be correctly updated
  • No broken references detected in the codebase

🎯 Overall Assessment

Grade: A-

This is a well-executed architectural refactor that significantly improves the application's organization and maintainability. The new authentication system is robust, the route structure follows modern Next.js patterns, and the code quality is high throughout.

Key Achievements:

  • ✅ Successful migration to Next.js 14 App Router with route groups
  • ✅ Implementation of secure session-based authentication
  • ✅ Clean component organization with co-location
  • ✅ Comprehensive input validation and security measures
  • ✅ Modern, responsive navigation system

Deployment Readiness: ✅ Ready for deployment with minor recommendations addressed


📝 Action Items

Before Merge:

  • Consider reducing session expiration time for enhanced security
  • Add error boundaries to new route components
  • Test authentication flow thoroughly in production environment

Post-Merge:

  • Monitor authentication performance and session management
  • Consider implementing additional security features (2FA, audit logs)
  • Optimize large components for better maintainability

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: 0

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
src/app/(dashboard)/dashboard/subscriptions/_components/subscriptions.tsx (1)

43-57: Add missing header column for the cancel button.

The table header defines 11 columns, but SubscriptionRow renders 12 cells (including the cancel button at lines 149-192). This causes header-to-data misalignment.

Add a header cell for the Actions column:

     <TableHeadCell>Payment History</TableHeadCell>
+    <TableHeadCell>Actions</TableHeadCell>
   </TableRow>
src/app/(dashboard)/invoices/_components/invoices-received.tsx (1)

108-111: Fix the network reset logic.

The condition on line 109 checks the length before the filter operation on line 108, so lastSelectedNetwork is only reset when the original array had exactly 1 item. If a user unselects an invoice when 2+ are selected, the network constraint persists incorrectly.

Apply this fix to check the new array length after filtering:

     } else {
-      setSelectedInvoices(selectedInvoices.filter((i) => i.id !== invoice.id));
-      if (selectedInvoices.length === 1) {
+      const newSelection = selectedInvoices.filter((i) => i.id !== invoice.id);
+      setSelectedInvoices(newSelection);
+      if (newSelection.length === 0) {
         setLastSelectedNetwork(null);
       }
     }
🧹 Nitpick comments (4)
src/app/(dashboard)/invoices/_components/invoices-received.tsx (4)

35-35: Consider increasing the polling interval to reduce server load.

A 3-second polling interval for invoice data is aggressive and may cause unnecessary server load, especially as user count grows. Invoice status doesn't typically change frequently enough to warrant sub-5-second updates.

Consider one of these approaches:

  • Increase the interval to 10-30 seconds for better performance at scale
  • Implement a WebSocket or Server-Sent Events solution for real-time updates when invoice status changes
  • Use a longer interval with manual refresh capability
-const RETRIEVE_ALL_INVOICES_POLLING_INTERVAL = 3000;
+const RETRIEVE_ALL_INVOICES_POLLING_INTERVAL = 15000; // 15 seconds

Also applies to: 78-78


92-92: Validate paymentCurrency format before extracting network.

The string split assumes a specific format without validation. If paymentCurrency doesn't contain a "-" or has a different structure, this will silently produce undefined or an unexpected value.

Consider adding validation:

-    const invoiceNetwork = invoice.paymentCurrency.split("-")[1];
+    const parts = invoice.paymentCurrency.split("-");
+    if (parts.length < 2) {
+      toast.error("Invalid payment currency format");
+      return false;
+    }
+    const invoiceNetwork = parts[1];

189-191: Consider validating invoice amounts before calculation.

The reduce operation doesn't validate that invoice.amount exists. While Number(null) returns 0 (masking missing data), Number(undefined) returns NaN which would break toLocaleString().

Add a filter or validation:

                 {selectedInvoices
-                  .reduce((sum, invoice) => sum + Number(invoice.amount), 0)
+                  .reduce((sum, invoice) => sum + (Number(invoice.amount) || 0), 0)
                   .toLocaleString()}

Or filter out invalid amounts explicitly before the reduce.


132-135: Validate chainId lookup before switching networks.

If lastSelectedNetwork doesn't exist in NETWORK_TO_ID, targetChainId will be undefined, potentially causing issues in switchToChainId.

Add validation:

     try {
       const targetChainId =
         NETWORK_TO_ID[lastSelectedNetwork as keyof typeof NETWORK_TO_ID];
+      
+      if (!targetChainId) {
+        toast.error(`Unsupported network: ${lastSelectedNetwork}`);
+        setIsPayingInvoices(false);
+        return;
+      }
 
       await switchToChainId(targetChainId);
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between a86ae00 and 662c21f.

📒 Files selected for processing (6)
  • src/app/(dashboard)/dashboard/subscriptions/_components/subscriptions.tsx (3 hunks)
  • src/app/(dashboard)/invoices/_components/invoices-received.tsx (2 hunks)
  • src/app/(dashboard)/invoices/_components/invoices-sent.tsx (1 hunks)
  • src/app/(dashboard)/payouts/recurring/_components/view-recurring-payments.tsx (2 hunks)
  • src/app/(dashboard)/subscriptions/_components/payments-table.tsx (1 hunks)
  • src/app/(dashboard)/subscriptions/_components/subscribers-table.tsx (3 hunks)
🚧 Files skipped from review as they are similar to previous changes (3)
  • src/app/(dashboard)/subscriptions/_components/payments-table.tsx
  • src/app/(dashboard)/invoices/_components/invoices-sent.tsx
  • src/app/(dashboard)/subscriptions/_components/subscribers-table.tsx
🧰 Additional context used
🧬 Code graph analysis (2)
src/app/(dashboard)/dashboard/subscriptions/_components/subscriptions.tsx (1)
src/components/recurring-payment-status-badge.tsx (1)
  • RecurringPaymentStatusBadge (18-26)
src/app/(dashboard)/payouts/recurring/_components/view-recurring-payments.tsx (1)
src/components/recurring-payment-status-badge.tsx (1)
  • RecurringPaymentStatusBadge (18-26)
⏰ 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/(dashboard)/dashboard/subscriptions/_components/subscriptions.tsx (3)

3-29: LGTM! Import refactoring aligns with the new component structure.

The updated import paths for RecurringPaymentStatusBadge, StatCard, EmptyState, Pagination, TableHeadCell, and other UI components correctly reflect the centralized component structure introduced in this PR.


108-108: LGTM! Badge component usage is correct.

The RecurringPaymentStatusBadge correctly receives the status prop and aligns with the component signature shown in the relevant snippets.


201-331: Component logic is solid. Verify error boundary coverage.

The data fetching, filtering, calculations, and pagination logic are all correctly implemented. The use of initialData provides good UX by preventing loading states.

However, per the PR review comments, some new route components lack error boundaries. Please verify that appropriate error boundary coverage has been added for this subscriptions view.

src/app/(dashboard)/invoices/_components/invoices-received.tsx (1)

3-3: LGTM! Import path consolidation aligns with the refactoring.

The migration to absolute paths for UI components (StatCard, EmptyState, Pagination, TableHeadCell) and the updated InvoiceRow sibling import improve maintainability and align with the PR's component consolidation objectives.

Also applies to: 7-8, 16-16, 32-32

src/app/(dashboard)/payouts/recurring/_components/view-recurring-payments.tsx (1)

3-6: The import paths are correct; the original review comment is based on flawed analysis.

The verification confirms all imports resolve correctly to actual files at the specified locations. The AI summary contained inaccurate dependency paths (listing subscriptions/_components/ when files are actually at components/recurring-payments/), which led to the false concern. The code correctly uses absolute @/ paths, RecurringPaymentStatusBadge is properly imported and used at line 216, and there are no cross-page component coupling issues or duplication.

Likely an incorrect or invalid review comment.

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: 0

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/app/(dashboard)/dashboard/subscriptions/_components/subscriptions.tsx (1)

253-287: Fix disclaimer positioning to avoid potential overlap.

The disclaimers at lines 265-270 and 279-284 use absolute positioning with -bottom-5, placing them outside the StatCard boundary. This could cause the text to overlap with the table below, especially on smaller screens or when the gap spacing changes.

Consider one of these alternatives:

  • Move disclaimers below the StatCards grid (outside the relative containers)
  • Use margin-top on the table/card to ensure sufficient spacing
  • Place disclaimers inside the StatCard with proper padding

Example fix — move disclaimers after the grid:

       </div>
     </div>
+    {(hasNonUsdCommitments || hasNonUsdSpent) && (
+      <div className="text-center space-y-1">
+        {hasNonUsdCommitments && (
+          <p className="text-xs text-muted-foreground">
+            * Subscription Commitments excludes non-USD denominated subscriptions
+          </p>
+        )}
+        {hasNonUsdSpent && (
+          <p className="text-xs text-muted-foreground">
+            * Total Spent excludes non-USD denominated payments
+          </p>
+        )}
+      </div>
+    )}

     <Card className="border border-border">

Then remove the absolute positioned disclaimers from lines 265-270 and 279-284.

🧹 Nitpick comments (2)
src/app/(dashboard)/dashboard/subscriptions/_components/subscriptions.tsx (2)

77-83: Consider using a structured logging service.

The try/catch correctly prevents crashes, but console.error may not be ideal for production monitoring. Consider using a structured logging service (e.g., Sentry, LogRocket) to track cancellation failures for debugging and user support.


217-249: Consider consolidating the two loops for efficiency.

The separate loops for commitmentTotal and spentTotal are clear but iterate over potentially overlapping subscription sets. If performance becomes a concern with large datasets, consider consolidating into a single loop that checks subscription status and payment state to calculate both totals simultaneously.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 662c21f and 38e093a.

📒 Files selected for processing (2)
  • src/app/(dashboard)/dashboard/subscriptions/_components/subscriptions.tsx (4 hunks)
  • src/app/(dashboard)/invoices/_components/invoices-received.tsx (3 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/app/(dashboard)/invoices/_components/invoices-received.tsx
🧰 Additional context used
🧬 Code graph analysis (1)
src/app/(dashboard)/dashboard/subscriptions/_components/subscriptions.tsx (2)
src/components/ui/table/table-head-cell.tsx (1)
  • TableHeadCell (9-13)
src/components/recurring-payment-status-badge.tsx (1)
  • RecurringPaymentStatusBadge (18-26)
⏰ 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 (3)
src/app/(dashboard)/dashboard/subscriptions/_components/subscriptions.tsx (3)

3-29: LGTM! Import updates align with UI component centralization.

The import path updates correctly reflect the migration to centralized UI components (RecurringPaymentStatusBadge, EmptyState, Pagination, TableHeadCell, etc.) as outlined in the PR objectives.


109-109: LGTM! Correctly migrated to RecurringPaymentStatusBadge.

The component usage aligns with the interface shown in the relevant code snippets and properly passes the subscription status.


296-327: LGTM! EmptyState and Pagination correctly implemented.

The colSpan={12} correctly matches the 12 table columns, EmptyState is shown only when appropriate, and pagination logic with slicing is accurate.

@rodrigopavezi rodrigopavezi self-requested a review October 28, 2025 10:20
@bassgeta bassgeta merged commit ce77bbc into main Oct 28, 2025
5 checks passed
@bassgeta bassgeta deleted the feat/161-nav-rework branch October 28, 2025 11:56
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 - Refactor Navigation Layout to look more like "Dashboard"

4 participants