# Sidebar Navigation Consolidation > **For Claude:** REQUIRED SUB-SKILL: Use superpowers:executing-plans to implement this plan task-by-task. **Goal:** Simplify sidebar by removing expandable sub-menus — make Support and Billing flat links, creating a combined Billing page. **Architecture:** Remove children from navigation items, create a new combined Billing page view that composes existing PaymentMethods and InvoicesList components, update route structure to add `/account/billing` page. **Tech Stack:** Next.js 15, React 19, Tailwind CSS, existing billing/support feature components --- ### Task 1: Make Support a flat sidebar link **Files:** - Modify: `apps/portal/src/components/organisms/AppShell/navigation.ts:46-49` **Step 1: Update navigation config** Change the Support entry from expandable (with children) to a flat link: ```ts // Before (lines 46-49): { name: "Support", icon: ChatBubbleLeftRightIcon, children: [ { name: "Cases", href: "/account/support" }, { name: "New Case", href: "/account/support/new" }, ], }, // After: { name: "Support", href: "/account/support", icon: ChatBubbleLeftRightIcon }, ``` **Step 2: Remove auto-expand logic for Support in AppShell** File: `apps/portal/src/components/organisms/AppShell/AppShell.tsx:112` Remove the line: ```ts if (pathname.startsWith("/account/support")) next.add("Support"); ``` **Step 3: Verify** Run: `pnpm type-check` Expected: PASS — no type errors **Step 4: Commit** ```bash git add apps/portal/src/components/organisms/AppShell/navigation.ts apps/portal/src/components/organisms/AppShell/AppShell.tsx git commit -m "refactor: make Support a flat sidebar link" ``` --- ### Task 2: Create combined Billing page view **Files:** - Create: `apps/portal/src/features/billing/views/BillingOverview.tsx` **Step 1: Create the combined billing view** This view composes existing `PaymentMethodsContainer` content and `InvoicesList` into one page. We reuse the existing components directly — payment methods section on top, invoices below. ```tsx "use client"; import { useState } from "react"; import { CreditCardIcon } from "@heroicons/react/24/outline"; import { PageLayout } from "@/components/templates/PageLayout"; import { ErrorBoundary } from "@/components/molecules"; import { useSession } from "@/features/auth/hooks"; import { useAuthStore } from "@/features/auth/stores/auth.store"; import { isApiError } from "@/core/api"; import { openSsoLink } from "@/features/billing/utils/sso"; import { usePaymentRefresh } from "@/features/billing/hooks/usePaymentRefresh"; import { PaymentMethodCard, usePaymentMethods, useCreatePaymentMethodsSsoLink, } from "@/features/billing"; import type { PaymentMethodList } from "@customer-portal/domain/payments"; import { InlineToast } from "@/components/atoms/inline-toast"; import { Button } from "@/components/atoms/button"; import { Skeleton } from "@/components/atoms/loading-skeleton"; import { InvoicesList } from "@/features/billing/components/InvoiceList/InvoiceList"; import { logger } from "@/core/logger"; function PaymentMethodsSkeleton() { return (
{Array.from({ length: 2 }).map((_, i) => (
))}
); } function PaymentMethodsSection({ paymentMethodsData, onManage, isPending, }: { paymentMethodsData: PaymentMethodList; onManage: () => void; isPending: boolean; }) { const hasMethods = paymentMethodsData.paymentMethods.length > 0; return (

Payment Methods

{hasMethods ? `${paymentMethodsData.paymentMethods.length} payment method${paymentMethodsData.paymentMethods.length === 1 ? "" : "s"} on file` : "No payment methods on file"}

{hasMethods && (
{paymentMethodsData.paymentMethods.map(paymentMethod => ( ))}
)}
); } export function BillingOverview() { const [error, setError] = useState(null); const { isAuthenticated } = useSession(); const paymentMethodsQuery = usePaymentMethods(); const { data: paymentMethodsData, isLoading: isLoadingPaymentMethods, isFetching: isFetchingPaymentMethods, error: paymentMethodsError, } = paymentMethodsQuery; const createPaymentMethodsSsoLink = useCreatePaymentMethodsSsoLink(); const { hasCheckedAuth } = useAuthStore(); const paymentRefresh = usePaymentRefresh({ refetch: async () => { const result = await paymentMethodsQuery.refetch(); return { data: result.data }; }, hasMethods: data => Boolean(data && (data.totalCount > 0 || data.paymentMethods.length > 0)), attachFocusListeners: true, }); const openPaymentMethods = async () => { if (!isAuthenticated) { setError("Please log in to access payment methods."); return; } setError(null); try { const ssoLink = await createPaymentMethodsSsoLink.mutateAsync(); openSsoLink(ssoLink.url, { newTab: true }); } catch (err: unknown) { logger.error("Failed to open payment methods", err); if ( isApiError(err) && "response" in err && typeof err.response === "object" && err.response !== null && "status" in err.response && err.response.status === 401 ) { setError("Authentication failed. Please log in again."); } else { setError("Unable to access payment methods. Please try again later."); } } }; const isPaymentLoading = !hasCheckedAuth || isLoadingPaymentMethods || isFetchingPaymentMethods; const combinedError = error ? new Error(error) : paymentMethodsError instanceof Error ? paymentMethodsError : paymentMethodsError ? new Error(String(paymentMethodsError)) : null; return ( } title="Billing" error={combinedError}>
{/* Payment Methods Section */} {isPaymentLoading && } {!isPaymentLoading && paymentMethodsData && ( void openPaymentMethods()} isPending={createPaymentMethodsSsoLink.isPending} /> )} {/* Invoices Section */}

Invoices

); } export default BillingOverview; ``` **Step 2: Verify** Run: `pnpm type-check` Expected: PASS **Step 3: Commit** ```bash git add apps/portal/src/features/billing/views/BillingOverview.tsx git commit -m "feat: create combined BillingOverview view" ``` --- ### Task 3: Add /account/billing route and make sidebar flat **Files:** - Create: `apps/portal/src/app/account/billing/page.tsx` - Modify: `apps/portal/src/components/organisms/AppShell/navigation.ts:29-36` - Modify: `apps/portal/src/components/organisms/AppShell/AppShell.tsx:111` **Step 1: Create the billing page** ```tsx import { BillingOverview } from "@/features/billing/views/BillingOverview"; export default function AccountBillingPage() { return ; } ``` **Step 2: Update navigation config** Change Billing from expandable to flat: ```ts // Before (lines 29-36): { name: "Billing", icon: CreditCardIcon, children: [ { name: "Invoices", href: "/account/billing/invoices" }, { name: "Payment Methods", href: "/account/billing/payments" }, ], }, // After: { name: "Billing", href: "/account/billing", icon: CreditCardIcon }, ``` **Step 3: Remove auto-expand logic for Billing in AppShell** File: `apps/portal/src/components/organisms/AppShell/AppShell.tsx` Remove the line: ```ts if (pathname.startsWith("/account/billing")) next.add("Billing"); ``` **Step 4: Verify** Run: `pnpm type-check` Expected: PASS **Step 5: Commit** ```bash git add apps/portal/src/app/account/billing/page.tsx apps/portal/src/components/organisms/AppShell/navigation.ts apps/portal/src/components/organisms/AppShell/AppShell.tsx git commit -m "refactor: make Billing a flat sidebar link with combined page" ``` --- ### Task 4: Update Sidebar active-state matching for flat Billing and Support **Files:** - Modify: `apps/portal/src/components/organisms/AppShell/Sidebar.tsx:327` The current `SimpleNavItem` uses exact match (`pathname === item.href`) which won't highlight Billing when on `/account/billing/invoices/123`. Change to `startsWith` for path-based matching: ```ts // Before (line 327): const isActive = item.href ? pathname === item.href : false; // After: const isActive = item.href ? item.href === "/account" ? pathname === item.href : pathname.startsWith(item.href) : false; ``` This ensures: - Dashboard (`/account`) still uses exact match (doesn't highlight for every `/account/*` page) - Billing (`/account/billing`) highlights on `/account/billing`, `/account/billing/invoices/123`, etc. - Support (`/account/support`) highlights on `/account/support`, `/account/support/new`, `/account/support/123`, etc. **Step 1: Update Sidebar active matching** Apply the change above. **Step 2: Verify** Run: `pnpm type-check` Expected: PASS **Step 3: Commit** ```bash git add apps/portal/src/components/organisms/AppShell/Sidebar.tsx git commit -m "fix: use startsWith for sidebar active state on nested routes" ``` --- ### Task 5: Update backLink references in InvoiceDetail **Files:** - Modify: `apps/portal/src/features/billing/views/InvoiceDetail.tsx:84,99` Update the "Back to Invoices" links to point to the combined billing page: ```ts // Before: backLink={{ label: "Back to Invoices", href: "/account/billing/invoices" }} // After: backLink={{ label: "Back to Billing", href: "/account/billing" }} ``` Apply this on both lines 84 and 99. **Step 1: Apply changes** **Step 2: Verify** Run: `pnpm type-check` Expected: PASS **Step 3: Commit** ```bash git add apps/portal/src/features/billing/views/InvoiceDetail.tsx git commit -m "fix: update InvoiceDetail backLink to point to combined billing page" ``` --- ### Task 6: Update remaining billing route references **Files:** - Modify: `apps/portal/src/features/dashboard/utils/dashboard.utils.ts:43` — change `/account/billing/invoices/${activity.relatedId}` to keep as-is (invoice detail pages still live at `/account/billing/invoices/[id]`) - Modify: `apps/portal/src/features/dashboard/components/TaskList.tsx:66` — change `href="/account/billing/invoices"` to `href="/account/billing"` - Modify: `apps/portal/src/features/subscriptions/views/SubscriptionDetail.tsx:208` — change `href="/account/billing/invoices"` to `href="/account/billing"` - Modify: `apps/portal/src/features/billing/components/BillingSummary/BillingSummary.tsx:160,187` — change `href="/account/billing/invoices"` to `href="/account/billing"` Note: Keep `dashboard.utils.ts:43` unchanged — it links to a specific invoice detail page which still exists at `/account/billing/invoices/[id]`. Note: Keep `InvoiceTable.tsx:276` unchanged — it navigates to individual invoice detail pages. **Step 1: Apply changes to the 3 files listed above** **Step 2: Verify** Run: `pnpm type-check` Expected: PASS **Step 3: Commit** ```bash git add apps/portal/src/features/dashboard/components/TaskList.tsx apps/portal/src/features/subscriptions/views/SubscriptionDetail.tsx apps/portal/src/features/billing/components/BillingSummary/BillingSummary.tsx git commit -m "fix: update billing route references to use combined billing page" ``` --- ### Task 7: Clean up unused imports in navigation.ts **Files:** - Modify: `apps/portal/src/components/organisms/AppShell/navigation.ts` After removing children from both Billing and Support, the `NavigationChild` type and children-related interfaces may still be needed by other code (the Sidebar still supports expandable items generically). Check if `NavigationChild` is still used — if Subscriptions or any other item still has children, keep it. If no items have children anymore, remove unused types. **Step 1: Check if any navigation item still uses children** After our changes, review `baseNavigation` — none will have children. But `NavigationChild` and `children` field on `NavigationItem` are still referenced by `Sidebar.tsx` (the `ExpandableNavItem` component). These can stay for now since they're part of the generic nav system — removing the component is a larger cleanup. **Step 2: Verify full build** Run: `pnpm type-check && pnpm lint` Expected: PASS **Step 3: Final commit if any cleanup was needed** ```bash git add -A git commit -m "chore: sidebar consolidation cleanup" ```