From 1de9e64407c9bf6dc6c082f02c6b6812e11209a1 Mon Sep 17 00:00:00 2001
From: StephDietz <steph.dietz@vercel.com>
Date: Wed, 6 Sep 2023 10:14:41 -0500
Subject: [PATCH 1/9] add pagination and search bar to the invoice table

---
 dashboard/15-final/app/lib/dummy-data.tsx    | 49 ++++++++++++
 dashboard/15-final/app/ui/invoices/table.tsx | 81 ++++++++++++++++----
 2 files changed, 117 insertions(+), 13 deletions(-)

diff --git a/dashboard/15-final/app/lib/dummy-data.tsx b/dashboard/15-final/app/lib/dummy-data.tsx
index 40b6887a..ef1d411b 100644
--- a/dashboard/15-final/app/lib/dummy-data.tsx
+++ b/dashboard/15-final/app/lib/dummy-data.tsx
@@ -87,6 +87,55 @@ export const invoices: Invoice[] = [
     status: "paid",
     date: "2023-06-01",
   },
+  {
+    id: 8,
+    customerId: 3,
+    amount: 1250,
+    status: "paid",
+    date: "2023-06-02",
+  },
+  {
+    id: 9,
+    customerId: 1,
+    amount: 8945,
+    status: "paid",
+    date: "2023-06-01",
+  },
+  {
+    id: 10,
+    customerId: 2,
+    amount: 500,
+    status: "paid",
+    date: "2023-08-01",
+  },
+  {
+    id: 11,
+    customerId: 3,
+    amount: 8945,
+    status: "paid",
+    date: "2023-06-01",
+  },
+  {
+    id: 12,
+    customerId: 3,
+    amount: 8945,
+    status: "paid",
+    date: "2023-06-01",
+  },
+  {
+    id: 13,
+    customerId: 4,
+    amount: 8945,
+    status: "paid",
+    date: "2023-10-01",
+  },
+  {
+    id: 14,
+    customerId: 5,
+    amount: 1000,
+    status: "paid",
+    date: "2022-06-12",
+  },
 ];
 
 export const revenue: Revenue[] = [
diff --git a/dashboard/15-final/app/ui/invoices/table.tsx b/dashboard/15-final/app/ui/invoices/table.tsx
index 466b48ef..d3530dbd 100644
--- a/dashboard/15-final/app/ui/invoices/table.tsx
+++ b/dashboard/15-final/app/ui/invoices/table.tsx
@@ -1,3 +1,5 @@
+"use client";
+
 import { invoices, customers } from "@/app/lib/dummy-data";
 import { Customer } from "@/app/lib/definitions";
 
@@ -6,8 +8,14 @@ import {
   PencilSquareIcon,
   ClockIcon,
   CheckCircleIcon,
+  MagnifyingGlassIcon,
+  ChevronLeftIcon,
+  ChevronRightIcon
 } from "@heroicons/react/24/outline";
 import DeleteInvoice from "@/app/ui/invoices/delete-invoice-button";
+import { useState } from "react";
+
+const ITEMS_PER_PAGE = 10;
 
 function renderInvoiceStatus(status: string) {
   if (status === "pending") {
@@ -28,11 +36,36 @@ function renderInvoiceStatus(status: string) {
 }
 
 export default function InvoicesTable() {
+  const [searchTerm, setSearchTerm] = useState('');
+  const [currentPage, setCurrentPage] = useState(1);
+
+  const filteredInvoices = invoices.filter(invoice => {
+    const customer = getCustomerById(invoice.customerId);
+
+    const invoiceMatches = Object.values(invoice).some(value =>
+      value?.toString().toLowerCase().includes(searchTerm.toLowerCase())
+    );
+
+    const customerMatches = customer && Object.values(customer).some(value =>
+      value?.toString().toLowerCase().includes(searchTerm.toLowerCase())
+    );
+
+    return invoiceMatches || customerMatches;
+  });
+
+  const paginatedInvoices = filteredInvoices.slice(
+    (currentPage - 1) * ITEMS_PER_PAGE,
+    currentPage * ITEMS_PER_PAGE
+  );
+
   function getCustomerById(customerId: number): Customer | null {
     const customer = customers.find((customer) => customer.id === customerId);
     return customer ? customer : null;
   }
 
+  const totalPages = Math.ceil(filteredInvoices.length / ITEMS_PER_PAGE);
+  const pageNumbers = Array.from({ length: totalPages }).map((_, index) => index + 1);
+
   return (
     <div className="w-full">
       <div className="flex w-full items-center justify-between">
@@ -44,7 +77,17 @@ export default function InvoicesTable() {
           Add Invoice
         </Link>
       </div>
-      <div className="mt-8">
+      <div className="relative flex items-center py-2 px-2 mt-8">
+        <MagnifyingGlassIcon className="h-5 text-gray-400" />
+        <input
+          type="text"
+          placeholder="Search..."
+          value={searchTerm}
+          onChange={e => setSearchTerm(e.target.value)}
+          className="absolute pl-8 bg-transparent inset-0 border p-2 w-full rounded-md border-gray-300 text-sm"
+        />
+      </div>
+      <div className="mt-4">
         <div className="overflow-x-auto">
           <div className="overflow-hidden rounded-md border">
             <table className="min-w-full divide-y divide-gray-300">
@@ -72,13 +115,10 @@ export default function InvoicesTable() {
                   <th scope="col" className="relative py-3.5 pl-3 pr-4 sm:pr-6">
                     <span className="sr-only">Edit</span>
                   </th>
-                  {/* <th scope="col" className="relative py-3.5 pl-3 pr-4 sm:pr-6">
-                    <span className="sr-only">View</span>
-                  </th> */}
                 </tr>
               </thead>
               <tbody className="divide-y divide-gray-200 text-gray-500">
-                {invoices.map((invoice) => (
+                {paginatedInvoices.map((invoice) => (
                   <tr key={invoice.id}>
                     <td className="whitespace-nowrap py-4 pl-4 pr-3 text-sm font-medium text-black sm:pl-6">
                       {invoice.id}
@@ -116,14 +156,6 @@ export default function InvoicesTable() {
                       </Link>
                       <DeleteInvoice id={invoice.id} />
                     </td>
-                    {/* <td className="relative whitespace-nowrap py-4 pl-3 pr-4 text-right text-sm font-medium sm:pr-6">
-                      <Link
-                        href={`/dashboard/invoices/${invoice.id}`}
-                        className="text-blue-600 hover:text-blue-900"
-                      >
-                        View<span className="sr-only">, {invoice.id}</span>
-                      </Link>
-                    </td> */}
                   </tr>
                 ))}
               </tbody>
@@ -131,6 +163,29 @@ export default function InvoicesTable() {
           </div>
         </div>
       </div>
+      <div className="flex items-center justify-end mt-4">
+        <button
+          onClick={() => setCurrentPage((prev) => Math.max(prev - 1, 1))}
+          disabled={currentPage === 1}
+          className="border border-gray-300 rounded-l-md h-8 w-8 flex items-center justify-center"
+        >
+          <ChevronLeftIcon className="w-4" />
+        </button>
+        <>
+          {pageNumbers.map(page => (
+            <button onClick={()=>{setCurrentPage(page)}} className={`${currentPage === page ? 'bg-blue-600 text-white border-blue-600' : 'border-gray-300'} border-y border-r text-sm h-8 w-8 flex items-center justify-center`} key={page}>
+              {page}
+            </button>
+          ))}
+        </>
+        <button
+          onClick={() => setCurrentPage((prev) => Math.min(prev + 1, Math.ceil(filteredInvoices.length / ITEMS_PER_PAGE)))}
+          disabled={currentPage === Math.ceil(filteredInvoices.length / ITEMS_PER_PAGE)}
+          className="border border-gray-300 rounded-r-md border-l-0 h-8 w-8 flex items-center justify-center"
+        >
+          <ChevronRightIcon className="w-4" />
+        </button>
+      </div>
     </div>
   );
 }

From 31df8edc0bb0389af65d3382eb67ff51db5859d4 Mon Sep 17 00:00:00 2001
From: StephDietz <steph.dietz@vercel.com>
Date: Wed, 6 Sep 2023 13:33:54 -0500
Subject: [PATCH 2/9] make table component server

---
 .../15-final/app/dashboard/invoices/page.tsx  | 11 ++-
 .../15-final/app/ui/invoices/pagination.tsx   | 58 ++++++++++++++++
 .../15-final/app/ui/invoices/table-search.tsx | 69 +++++++++++++++++++
 dashboard/15-final/app/ui/invoices/table.tsx  | 53 +++-----------
 4 files changed, 145 insertions(+), 46 deletions(-)
 create mode 100644 dashboard/15-final/app/ui/invoices/pagination.tsx
 create mode 100644 dashboard/15-final/app/ui/invoices/table-search.tsx

diff --git a/dashboard/15-final/app/dashboard/invoices/page.tsx b/dashboard/15-final/app/dashboard/invoices/page.tsx
index 393ab7d5..bbd5a228 100644
--- a/dashboard/15-final/app/dashboard/invoices/page.tsx
+++ b/dashboard/15-final/app/dashboard/invoices/page.tsx
@@ -1,9 +1,16 @@
 import InvoicesTable from "@/app/ui/invoices/table";
 
-export default function Page() {
+export default function Page({
+  searchParams,
+}: {
+  searchParams: {
+    q: string;
+    page: string;
+  };
+}) {
   return (
     <div>
-      <InvoicesTable />
+      <InvoicesTable searchParams={searchParams} />
     </div>
   );
 }
diff --git a/dashboard/15-final/app/ui/invoices/pagination.tsx b/dashboard/15-final/app/ui/invoices/pagination.tsx
new file mode 100644
index 00000000..37b6ef10
--- /dev/null
+++ b/dashboard/15-final/app/ui/invoices/pagination.tsx
@@ -0,0 +1,58 @@
+'use client';
+
+import { ChevronLeftIcon, ChevronRightIcon } from '@heroicons/react/24/outline';
+import { usePathname, useRouter } from 'next/navigation';
+import { useTransition } from 'react';
+
+export default function PaginationButtons({
+  totalPages,
+  currentPage,
+}: {
+  totalPages: number;
+  currentPage: number;
+}) {
+  const { replace } = useRouter();
+  const pathname = usePathname();
+  const [isPending, startTransition] = useTransition();
+  const pageNumbers = Array.from({ length: totalPages }, (_, i) => i + 1);
+
+  function onPageChange(page: number) {
+    const params = new URLSearchParams(window.location.search);
+    params.set('page', page.toString());
+
+    startTransition(() => {
+      replace(`${pathname}?${params.toString()}`);
+    });
+  }
+
+  return (
+    <div className="flex items-center justify-end mt-4">
+        <button
+          onClick={() => onPageChange(currentPage - 1)}
+          disabled={currentPage === 1}
+          className="border border-gray-300 rounded-l-md h-8 w-8 flex items-center justify-center"
+        >
+          <ChevronLeftIcon className="w-4" />
+        </button>
+        <>
+          {pageNumbers.map((page) => (
+        <button
+          key={page}
+          onClick={() => onPageChange(page)}
+          className={`${currentPage === page ? 'bg-blue-600 text-white border-blue-600' : 'border-gray-300'} border-y border-r text-sm h-8 w-8 flex items-center justify-center`}
+          disabled={page === currentPage}
+        >
+          {page}
+        </button>
+      ))}
+        </>
+        <button
+          onClick={() => onPageChange(currentPage + 1)}
+          disabled={currentPage === totalPages}
+          className="border border-gray-300 rounded-r-md border-l-0 h-8 w-8 flex items-center justify-center"
+        >
+          <ChevronRightIcon className="w-4" />
+        </button>
+    </div>
+  );
+}
\ No newline at end of file
diff --git a/dashboard/15-final/app/ui/invoices/table-search.tsx b/dashboard/15-final/app/ui/invoices/table-search.tsx
new file mode 100644
index 00000000..62ce9e37
--- /dev/null
+++ b/dashboard/15-final/app/ui/invoices/table-search.tsx
@@ -0,0 +1,69 @@
+'use client';
+
+import { MagnifyingGlassIcon } from '@heroicons/react/24/outline';
+import { usePathname, useRouter } from 'next/navigation';
+import { useTransition } from 'react';
+
+export default function TableSearch({ disabled }: { disabled?: boolean }) {
+  const { replace } = useRouter();
+  const pathname = usePathname();
+  const [isPending, startTransition] = useTransition();
+
+  function handleSearch(term: string) {
+    const params = new URLSearchParams(window.location.search);
+    if (term) {
+      params.set('q', term);
+    } else {
+      params.delete('q');
+    }
+
+    startTransition(() => {
+      replace(`${pathname}?${params.toString()}`);
+    });
+  }
+
+  return (
+    <div className="relative mb-5 max-w-md">
+      <label htmlFor="search" className="sr-only">
+        Search
+      </label>
+      <div className="relative flex items-center py-2 px-2 mt-8">
+        <MagnifyingGlassIcon className="h-5 text-gray-400" />
+        <input
+          type="text"
+          placeholder="Search..."
+          onChange={(e) => handleSearch(e.target.value)}
+          className="absolute pl-8 bg-transparent inset-0 border p-2 w-full rounded-md border-gray-300 text-sm"
+        />
+      </div>
+      {isPending && <LoadingIcon />}
+    </div>
+  );
+}
+
+function LoadingIcon() {
+  return (
+    <div className="absolute right-0 top-0 bottom-0 flex items-center justify-center">
+      <svg
+        className="animate-spin -ml-1 mr-3 h-5 w-5 text-gray-700"
+        xmlns="http://www.w3.org/2000/svg"
+        fill="none"
+        viewBox="0 0 24 24"
+      >
+        <circle
+          className="opacity-25"
+          cx="12"
+          cy="12"
+          r="10"
+          stroke="currentColor"
+          stroke-width="4"
+        />
+        <path
+          className="opacity-75"
+          fill="currentColor"
+          d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"
+        />
+      </svg>
+    </div>
+  );
+}
\ No newline at end of file
diff --git a/dashboard/15-final/app/ui/invoices/table.tsx b/dashboard/15-final/app/ui/invoices/table.tsx
index d3530dbd..4635de38 100644
--- a/dashboard/15-final/app/ui/invoices/table.tsx
+++ b/dashboard/15-final/app/ui/invoices/table.tsx
@@ -1,5 +1,3 @@
-"use client";
-
 import { invoices, customers } from "@/app/lib/dummy-data";
 import { Customer } from "@/app/lib/definitions";
 
@@ -7,13 +5,11 @@ import Link from "next/link";
 import {
   PencilSquareIcon,
   ClockIcon,
-  CheckCircleIcon,
-  MagnifyingGlassIcon,
-  ChevronLeftIcon,
-  ChevronRightIcon
+  CheckCircleIcon
 } from "@heroicons/react/24/outline";
 import DeleteInvoice from "@/app/ui/invoices/delete-invoice-button";
-import { useState } from "react";
+import TableSearch from "./table-search";
+import PaginationButtons from "./pagination";
 
 const ITEMS_PER_PAGE = 10;
 
@@ -35,9 +31,9 @@ function renderInvoiceStatus(status: string) {
   }
 }
 
-export default function InvoicesTable() {
-  const [searchTerm, setSearchTerm] = useState('');
-  const [currentPage, setCurrentPage] = useState(1);
+export default function InvoicesTable({searchParams}) {
+  const searchTerm = searchParams.q ?? '';
+  const currentPage = parseInt(searchParams.page ?? '1');
 
   const filteredInvoices = invoices.filter(invoice => {
     const customer = getCustomerById(invoice.customerId);
@@ -51,7 +47,7 @@ export default function InvoicesTable() {
     );
 
     return invoiceMatches || customerMatches;
-  });
+  }); 
 
   const paginatedInvoices = filteredInvoices.slice(
     (currentPage - 1) * ITEMS_PER_PAGE,
@@ -77,16 +73,7 @@ export default function InvoicesTable() {
           Add Invoice
         </Link>
       </div>
-      <div className="relative flex items-center py-2 px-2 mt-8">
-        <MagnifyingGlassIcon className="h-5 text-gray-400" />
-        <input
-          type="text"
-          placeholder="Search..."
-          value={searchTerm}
-          onChange={e => setSearchTerm(e.target.value)}
-          className="absolute pl-8 bg-transparent inset-0 border p-2 w-full rounded-md border-gray-300 text-sm"
-        />
-      </div>
+      <TableSearch/>
       <div className="mt-4">
         <div className="overflow-x-auto">
           <div className="overflow-hidden rounded-md border">
@@ -163,29 +150,7 @@ export default function InvoicesTable() {
           </div>
         </div>
       </div>
-      <div className="flex items-center justify-end mt-4">
-        <button
-          onClick={() => setCurrentPage((prev) => Math.max(prev - 1, 1))}
-          disabled={currentPage === 1}
-          className="border border-gray-300 rounded-l-md h-8 w-8 flex items-center justify-center"
-        >
-          <ChevronLeftIcon className="w-4" />
-        </button>
-        <>
-          {pageNumbers.map(page => (
-            <button onClick={()=>{setCurrentPage(page)}} className={`${currentPage === page ? 'bg-blue-600 text-white border-blue-600' : 'border-gray-300'} border-y border-r text-sm h-8 w-8 flex items-center justify-center`} key={page}>
-              {page}
-            </button>
-          ))}
-        </>
-        <button
-          onClick={() => setCurrentPage((prev) => Math.min(prev + 1, Math.ceil(filteredInvoices.length / ITEMS_PER_PAGE)))}
-          disabled={currentPage === Math.ceil(filteredInvoices.length / ITEMS_PER_PAGE)}
-          className="border border-gray-300 rounded-r-md border-l-0 h-8 w-8 flex items-center justify-center"
-        >
-          <ChevronRightIcon className="w-4" />
-        </button>
-      </div>
+      <PaginationButtons totalPages={totalPages} currentPage={currentPage} />
     </div>
   );
 }

From 868a9d97b8d306b976aa22b5b6b9541581d4e113 Mon Sep 17 00:00:00 2001
From: StephDietz <steph.dietz@vercel.com>
Date: Wed, 6 Sep 2023 13:54:31 -0500
Subject: [PATCH 3/9] fix eslint

---
 dashboard/15-final/app/ui/invoices/table.tsx | 5 ++++-
 1 file changed, 4 insertions(+), 1 deletion(-)

diff --git a/dashboard/15-final/app/ui/invoices/table.tsx b/dashboard/15-final/app/ui/invoices/table.tsx
index 4635de38..6f9ed2fa 100644
--- a/dashboard/15-final/app/ui/invoices/table.tsx
+++ b/dashboard/15-final/app/ui/invoices/table.tsx
@@ -31,7 +31,10 @@ function renderInvoiceStatus(status: string) {
   }
 }
 
-export default function InvoicesTable({searchParams}) {
+export default function InvoicesTable(searchParams: {
+    q: string;
+    page: string;
+  }) {
   const searchTerm = searchParams.q ?? '';
   const currentPage = parseInt(searchParams.page ?? '1');
 

From ece34c82227bf7664bc0dff534de37c47587b238 Mon Sep 17 00:00:00 2001
From: StephDietz <steph.dietz@vercel.com>
Date: Wed, 6 Sep 2023 13:57:16 -0500
Subject: [PATCH 4/9] eslint

---
 dashboard/15-final/app/ui/invoices/table.tsx | 6 +-----
 1 file changed, 1 insertion(+), 5 deletions(-)

diff --git a/dashboard/15-final/app/ui/invoices/table.tsx b/dashboard/15-final/app/ui/invoices/table.tsx
index 6f9ed2fa..5115ffa4 100644
--- a/dashboard/15-final/app/ui/invoices/table.tsx
+++ b/dashboard/15-final/app/ui/invoices/table.tsx
@@ -31,10 +31,7 @@ function renderInvoiceStatus(status: string) {
   }
 }
 
-export default function InvoicesTable(searchParams: {
-    q: string;
-    page: string;
-  }) {
+export default function InvoicesTable(searchParams: {q: string, page: string}) {
   const searchTerm = searchParams.q ?? '';
   const currentPage = parseInt(searchParams.page ?? '1');
 
@@ -63,7 +60,6 @@ export default function InvoicesTable(searchParams: {
   }
 
   const totalPages = Math.ceil(filteredInvoices.length / ITEMS_PER_PAGE);
-  const pageNumbers = Array.from({ length: totalPages }).map((_, index) => index + 1);
 
   return (
     <div className="w-full">

From 8f745096d69c63a20a35f120f27c06f21b0b706c Mon Sep 17 00:00:00 2001
From: StephDietz <steph.dietz@vercel.com>
Date: Wed, 6 Sep 2023 14:02:51 -0500
Subject: [PATCH 5/9] eslint

---
 dashboard/15-final/app/ui/invoices/table.tsx | 9 ++++++++-
 1 file changed, 8 insertions(+), 1 deletion(-)

diff --git a/dashboard/15-final/app/ui/invoices/table.tsx b/dashboard/15-final/app/ui/invoices/table.tsx
index 5115ffa4..2fea028b 100644
--- a/dashboard/15-final/app/ui/invoices/table.tsx
+++ b/dashboard/15-final/app/ui/invoices/table.tsx
@@ -31,7 +31,14 @@ function renderInvoiceStatus(status: string) {
   }
 }
 
-export default function InvoicesTable(searchParams: {q: string, page: string}) {
+export default function InvoicesTable({
+  searchParams,
+}: {
+  searchParams: {
+    q: string;
+    page: string;
+  };
+}) {
   const searchTerm = searchParams.q ?? '';
   const currentPage = parseInt(searchParams.page ?? '1');
 

From 55de9d0c92299398bdfc3614276b402f7e4c4d61 Mon Sep 17 00:00:00 2001
From: Michael Novotny <manovotny@gmail.com>
Date: Wed, 6 Sep 2023 16:01:32 -0500
Subject: [PATCH 6/9] Updates pagination url to not use state or router and
 moves pagination component up above the table

---
 dashboard/15-final/app/lib/dummy-data.tsx     | 44 ++++-----
 .../15-final/app/ui/invoices/pagination.tsx   | 91 +++++++++++--------
 .../15-final/app/ui/invoices/table-search.tsx | 12 +--
 dashboard/15-final/app/ui/invoices/table.tsx  | 36 +++++---
 4 files changed, 101 insertions(+), 82 deletions(-)

diff --git a/dashboard/15-final/app/lib/dummy-data.tsx b/dashboard/15-final/app/lib/dummy-data.tsx
index 038dd56f..2ab62b2f 100644
--- a/dashboard/15-final/app/lib/dummy-data.tsx
+++ b/dashboard/15-final/app/lib/dummy-data.tsx
@@ -95,53 +95,53 @@ export const invoices: Invoice[] = [
     date: '2023-06-01',
   },
   {
-    id: 8,
+    id: 9,
     customerId: 3,
     amount: 1250,
-    status: "paid",
-    date: "2023-06-02",
+    status: 'paid',
+    date: '2023-06-02',
   },
   {
-    id: 9,
+    id: 10,
     customerId: 1,
     amount: 8945,
-    status: "paid",
-    date: "2023-06-01",
+    status: 'paid',
+    date: '2023-06-01',
   },
   {
-    id: 10,
+    id: 11,
     customerId: 2,
     amount: 500,
-    status: "paid",
-    date: "2023-08-01",
+    status: 'paid',
+    date: '2023-08-01',
   },
   {
-    id: 11,
+    id: 12,
     customerId: 3,
     amount: 8945,
-    status: "paid",
-    date: "2023-06-01",
+    status: 'paid',
+    date: '2023-06-01',
   },
   {
-    id: 12,
+    id: 13,
     customerId: 3,
     amount: 8945,
-    status: "paid",
-    date: "2023-06-01",
+    status: 'paid',
+    date: '2023-06-01',
   },
   {
-    id: 13,
+    id: 14,
     customerId: 4,
     amount: 8945,
-    status: "paid",
-    date: "2023-10-01",
+    status: 'paid',
+    date: '2023-10-01',
   },
   {
-    id: 14,
-    customerId: 5,
+    id: 15,
+    customerId: 3,
     amount: 1000,
-    status: "paid",
-    date: "2022-06-12",
+    status: 'paid',
+    date: '2022-06-12',
   },
 ];
 
diff --git a/dashboard/15-final/app/ui/invoices/pagination.tsx b/dashboard/15-final/app/ui/invoices/pagination.tsx
index 37b6ef10..7b52fc50 100644
--- a/dashboard/15-final/app/ui/invoices/pagination.tsx
+++ b/dashboard/15-final/app/ui/invoices/pagination.tsx
@@ -1,8 +1,9 @@
 'use client';
 
 import { ChevronLeftIcon, ChevronRightIcon } from '@heroicons/react/24/outline';
-import { usePathname, useRouter } from 'next/navigation';
-import { useTransition } from 'react';
+import { usePathname, useSearchParams } from 'next/navigation';
+import clsx from 'clsx';
+import Link from 'next/link';
 
 export default function PaginationButtons({
   totalPages,
@@ -11,48 +12,60 @@ export default function PaginationButtons({
   totalPages: number;
   currentPage: number;
 }) {
-  const { replace } = useRouter();
   const pathname = usePathname();
-  const [isPending, startTransition] = useTransition();
+  const searchParams = useSearchParams();
   const pageNumbers = Array.from({ length: totalPages }, (_, i) => i + 1);
 
-  function onPageChange(page: number) {
-    const params = new URLSearchParams(window.location.search);
-    params.set('page', page.toString());
+  const createPageUrl = (pageNumber) => {
+    const newSearchParams = new URLSearchParams(searchParams.toString());
+    newSearchParams.set('page', pageNumber);
+    return `${pathname}?${newSearchParams.toString()}`;
+  };
 
-    startTransition(() => {
-      replace(`${pathname}?${params.toString()}`);
-    });
-  }
+  const PreviousTag = currentPage === 1 ? 'p' : Link;
+  const NextTag = currentPage === totalPages ? 'p' : Link;
 
   return (
-    <div className="flex items-center justify-end mt-4">
-        <button
-          onClick={() => onPageChange(currentPage - 1)}
-          disabled={currentPage === 1}
-          className="border border-gray-300 rounded-l-md h-8 w-8 flex items-center justify-center"
-        >
-          <ChevronLeftIcon className="w-4" />
-        </button>
-        <>
-          {pageNumbers.map((page) => (
-        <button
-          key={page}
-          onClick={() => onPageChange(page)}
-          className={`${currentPage === page ? 'bg-blue-600 text-white border-blue-600' : 'border-gray-300'} border-y border-r text-sm h-8 w-8 flex items-center justify-center`}
-          disabled={page === currentPage}
-        >
-          {page}
-        </button>
-      ))}
-        </>
-        <button
-          onClick={() => onPageChange(currentPage + 1)}
-          disabled={currentPage === totalPages}
-          className="border border-gray-300 rounded-r-md border-l-0 h-8 w-8 flex items-center justify-center"
-        >
-          <ChevronRightIcon className="w-4" />
-        </button>
+    <div className="flex items-center justify-end">
+      <PreviousTag
+        href={createPageUrl(currentPage - 1)}
+        className={clsx(
+          'flex h-8 w-8 items-center justify-center rounded-l-md border border-gray-300',
+          {
+            'text-gray-300': currentPage === 1,
+          },
+        )}
+      >
+        <ChevronLeftIcon className="w-4" />
+      </PreviousTag>
+      {pageNumbers.map((page) => {
+        const PageTag = page === currentPage ? 'p' : Link;
+        return (
+          <PageTag
+            key={page}
+            href={createPageUrl(page)}
+            className={clsx(
+              'flex h-8 w-8 items-center justify-center border-y border-r border-gray-300 text-sm',
+              {
+                'border-blue-600 bg-blue-600 text-white': currentPage === page,
+              },
+            )}
+          >
+            {page}
+          </PageTag>
+        );
+      })}
+      <NextTag
+        href={createPageUrl(currentPage + 1)}
+        className={clsx(
+          'flex h-8 w-8 items-center justify-center rounded-r-md border border-l-0 border-gray-300',
+          {
+            'text-gray-300': currentPage === totalPages,
+          },
+        )}
+      >
+        <ChevronRightIcon className="w-4" />
+      </NextTag>
     </div>
   );
-}
\ No newline at end of file
+}
diff --git a/dashboard/15-final/app/ui/invoices/table-search.tsx b/dashboard/15-final/app/ui/invoices/table-search.tsx
index 62ce9e37..057c48b2 100644
--- a/dashboard/15-final/app/ui/invoices/table-search.tsx
+++ b/dashboard/15-final/app/ui/invoices/table-search.tsx
@@ -23,17 +23,17 @@ export default function TableSearch({ disabled }: { disabled?: boolean }) {
   }
 
   return (
-    <div className="relative mb-5 max-w-md">
+    <div className="relative max-w-md flex-grow">
       <label htmlFor="search" className="sr-only">
         Search
       </label>
-      <div className="relative flex items-center py-2 px-2 mt-8">
+      <div className="relative flex items-center px-2 py-2">
         <MagnifyingGlassIcon className="h-5 text-gray-400" />
         <input
           type="text"
           placeholder="Search..."
           onChange={(e) => handleSearch(e.target.value)}
-          className="absolute pl-8 bg-transparent inset-0 border p-2 w-full rounded-md border-gray-300 text-sm"
+          className="absolute inset-0 w-full rounded-md border border-gray-300 bg-transparent p-2 pl-8 text-sm"
         />
       </div>
       {isPending && <LoadingIcon />}
@@ -43,9 +43,9 @@ export default function TableSearch({ disabled }: { disabled?: boolean }) {
 
 function LoadingIcon() {
   return (
-    <div className="absolute right-0 top-0 bottom-0 flex items-center justify-center">
+    <div className="absolute bottom-0 right-0 top-0 flex items-center justify-center">
       <svg
-        className="animate-spin -ml-1 mr-3 h-5 w-5 text-gray-700"
+        className="-ml-1 mr-3 h-5 w-5 animate-spin text-gray-700"
         xmlns="http://www.w3.org/2000/svg"
         fill="none"
         viewBox="0 0 24 24"
@@ -66,4 +66,4 @@ function LoadingIcon() {
       </svg>
     </div>
   );
-}
\ No newline at end of file
+}
diff --git a/dashboard/15-final/app/ui/invoices/table.tsx b/dashboard/15-final/app/ui/invoices/table.tsx
index b1cb4d31..84b7c8a1 100644
--- a/dashboard/15-final/app/ui/invoices/table.tsx
+++ b/dashboard/15-final/app/ui/invoices/table.tsx
@@ -5,11 +5,11 @@ import Link from 'next/link';
 import {
   PencilSquareIcon,
   ClockIcon,
-  CheckCircleIcon
-} from "@heroicons/react/24/outline";
-import DeleteInvoice from "@/app/ui/invoices/delete-invoice-button";
-import TableSearch from "./table-search";
-import PaginationButtons from "./pagination";
+  CheckCircleIcon,
+} from '@heroicons/react/24/outline';
+import DeleteInvoice from '@/app/ui/invoices/delete-invoice-button';
+import TableSearch from './table-search';
+import PaginationButtons from './pagination';
 
 const ITEMS_PER_PAGE = 10;
 
@@ -42,23 +42,27 @@ export default function InvoicesTable({
   const searchTerm = searchParams.q ?? '';
   const currentPage = parseInt(searchParams.page ?? '1');
 
-  const filteredInvoices = invoices.filter(invoice => {
+  const filteredInvoices = invoices.filter((invoice) => {
     const customer = getCustomerById(invoice.customerId);
 
-    const invoiceMatches = Object.values(invoice).some(value =>
-      value?.toString().toLowerCase().includes(searchTerm.toLowerCase())
+    const invoiceMatches = Object.values(invoice).some(
+      (value) =>
+        value?.toString().toLowerCase().includes(searchTerm.toLowerCase()),
     );
 
-    const customerMatches = customer && Object.values(customer).some(value =>
-      value?.toString().toLowerCase().includes(searchTerm.toLowerCase())
-    );
+    const customerMatches =
+      customer &&
+      Object.values(customer).some(
+        (value) =>
+          value?.toString().toLowerCase().includes(searchTerm.toLowerCase()),
+      );
 
     return invoiceMatches || customerMatches;
-  }); 
+  });
 
   const paginatedInvoices = filteredInvoices.slice(
     (currentPage - 1) * ITEMS_PER_PAGE,
-    currentPage * ITEMS_PER_PAGE
+    currentPage * ITEMS_PER_PAGE,
   );
 
   function getCustomerById(customerId: number): Customer | null {
@@ -79,7 +83,10 @@ export default function InvoicesTable({
           Add Invoice
         </Link>
       </div>
-      <TableSearch/>
+      <div className="mt-8 flex items-center justify-between">
+        <TableSearch />
+        <PaginationButtons totalPages={totalPages} currentPage={currentPage} />
+      </div>
       <div className="mt-4">
         <div className="overflow-x-auto">
           <div className="overflow-hidden rounded-md border">
@@ -156,7 +163,6 @@ export default function InvoicesTable({
           </div>
         </div>
       </div>
-      <PaginationButtons totalPages={totalPages} currentPage={currentPage} />
     </div>
   );
 }

From a373c552fca8d4add3fa3b11ff8145337a30ef96 Mon Sep 17 00:00:00 2001
From: StephDietz <steph.dietz@vercel.com>
Date: Wed, 6 Sep 2023 20:59:17 -0500
Subject: [PATCH 7/9] fix eslint typing error

---
 dashboard/15-final/app/ui/invoices/pagination.tsx | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/dashboard/15-final/app/ui/invoices/pagination.tsx b/dashboard/15-final/app/ui/invoices/pagination.tsx
index 7b52fc50..983f12e5 100644
--- a/dashboard/15-final/app/ui/invoices/pagination.tsx
+++ b/dashboard/15-final/app/ui/invoices/pagination.tsx
@@ -16,9 +16,9 @@ export default function PaginationButtons({
   const searchParams = useSearchParams();
   const pageNumbers = Array.from({ length: totalPages }, (_, i) => i + 1);
 
-  const createPageUrl = (pageNumber) => {
+  const createPageUrl = (pageNumber: number) => {
     const newSearchParams = new URLSearchParams(searchParams.toString());
-    newSearchParams.set('page', pageNumber);
+    newSearchParams.set('page', pageNumber.toString());
     return `${pathname}?${newSearchParams.toString()}`;
   };
 

From fa750cebceab14eb9a1c8ab6aec45753f636804e Mon Sep 17 00:00:00 2001
From: StephDietz <steph.dietz@vercel.com>
Date: Thu, 7 Sep 2023 09:18:16 -0500
Subject: [PATCH 8/9] resolve github convos

---
 dashboard/15-final/app/dashboard/invoices/page.tsx  |  2 +-
 dashboard/15-final/app/ui/invoices/pagination.tsx   | 12 ++++++------
 dashboard/15-final/app/ui/invoices/table-search.tsx |  7 ++++---
 dashboard/15-final/app/ui/invoices/table.tsx        |  2 +-
 4 files changed, 12 insertions(+), 11 deletions(-)

diff --git a/dashboard/15-final/app/dashboard/invoices/page.tsx b/dashboard/15-final/app/dashboard/invoices/page.tsx
index 11c66e6c..d46e3cdc 100644
--- a/dashboard/15-final/app/dashboard/invoices/page.tsx
+++ b/dashboard/15-final/app/dashboard/invoices/page.tsx
@@ -4,7 +4,7 @@ export default function Page({
   searchParams,
 }: {
   searchParams: {
-    q: string;
+    query: string;
     page: string;
   };
 }) {
diff --git a/dashboard/15-final/app/ui/invoices/pagination.tsx b/dashboard/15-final/app/ui/invoices/pagination.tsx
index 983f12e5..2cd3416d 100644
--- a/dashboard/15-final/app/ui/invoices/pagination.tsx
+++ b/dashboard/15-final/app/ui/invoices/pagination.tsx
@@ -22,12 +22,12 @@ export default function PaginationButtons({
     return `${pathname}?${newSearchParams.toString()}`;
   };
 
-  const PreviousTag = currentPage === 1 ? 'p' : Link;
-  const NextTag = currentPage === totalPages ? 'p' : Link;
+  const PreviousPageTag = currentPage === 1 ? 'p' : Link;
+  const NextPageTag = currentPage === totalPages ? 'p' : Link;
 
   return (
     <div className="flex items-center justify-end">
-      <PreviousTag
+      <PreviousPageTag
         href={createPageUrl(currentPage - 1)}
         className={clsx(
           'flex h-8 w-8 items-center justify-center rounded-l-md border border-gray-300',
@@ -37,7 +37,7 @@ export default function PaginationButtons({
         )}
       >
         <ChevronLeftIcon className="w-4" />
-      </PreviousTag>
+      </PreviousPageTag>
       {pageNumbers.map((page) => {
         const PageTag = page === currentPage ? 'p' : Link;
         return (
@@ -55,7 +55,7 @@ export default function PaginationButtons({
           </PageTag>
         );
       })}
-      <NextTag
+      <NextPageTag
         href={createPageUrl(currentPage + 1)}
         className={clsx(
           'flex h-8 w-8 items-center justify-center rounded-r-md border border-l-0 border-gray-300',
@@ -65,7 +65,7 @@ export default function PaginationButtons({
         )}
       >
         <ChevronRightIcon className="w-4" />
-      </NextTag>
+      </NextPageTag>
     </div>
   );
 }
diff --git a/dashboard/15-final/app/ui/invoices/table-search.tsx b/dashboard/15-final/app/ui/invoices/table-search.tsx
index 057c48b2..38123c3b 100644
--- a/dashboard/15-final/app/ui/invoices/table-search.tsx
+++ b/dashboard/15-final/app/ui/invoices/table-search.tsx
@@ -1,16 +1,17 @@
 'use client';
 
 import { MagnifyingGlassIcon } from '@heroicons/react/24/outline';
-import { usePathname, useRouter } from 'next/navigation';
+import { usePathname, useRouter, useSearchParams } from 'next/navigation';
 import { useTransition } from 'react';
 
-export default function TableSearch({ disabled }: { disabled?: boolean }) {
+export default function TableSearch() {
   const { replace } = useRouter();
+  const searchParams = useSearchParams()!;
   const pathname = usePathname();
   const [isPending, startTransition] = useTransition();
 
   function handleSearch(term: string) {
-    const params = new URLSearchParams(window.location.search);
+    const params = new URLSearchParams(searchParams);
     if (term) {
       params.set('q', term);
     } else {
diff --git a/dashboard/15-final/app/ui/invoices/table.tsx b/dashboard/15-final/app/ui/invoices/table.tsx
index 84b7c8a1..534723a8 100644
--- a/dashboard/15-final/app/ui/invoices/table.tsx
+++ b/dashboard/15-final/app/ui/invoices/table.tsx
@@ -35,7 +35,7 @@ export default function InvoicesTable({
   searchParams,
 }: {
   searchParams: {
-    q: string;
+    query: string;
     page: string;
   };
 }) {

From 340c70ce97759c52992d88d3a0025d82fcbf9eb4 Mon Sep 17 00:00:00 2001
From: StephDietz <steph.dietz@vercel.com>
Date: Thu, 7 Sep 2023 09:44:01 -0500
Subject: [PATCH 9/9] update all 'q' to be 'query'

---
 dashboard/15-final/app/ui/invoices/table-search.tsx | 4 ++--
 dashboard/15-final/app/ui/invoices/table.tsx        | 2 +-
 2 files changed, 3 insertions(+), 3 deletions(-)

diff --git a/dashboard/15-final/app/ui/invoices/table-search.tsx b/dashboard/15-final/app/ui/invoices/table-search.tsx
index 38123c3b..3d2efd9d 100644
--- a/dashboard/15-final/app/ui/invoices/table-search.tsx
+++ b/dashboard/15-final/app/ui/invoices/table-search.tsx
@@ -13,9 +13,9 @@ export default function TableSearch() {
   function handleSearch(term: string) {
     const params = new URLSearchParams(searchParams);
     if (term) {
-      params.set('q', term);
+      params.set('query', term);
     } else {
-      params.delete('q');
+      params.delete('query');
     }
 
     startTransition(() => {
diff --git a/dashboard/15-final/app/ui/invoices/table.tsx b/dashboard/15-final/app/ui/invoices/table.tsx
index 534723a8..0048b9f5 100644
--- a/dashboard/15-final/app/ui/invoices/table.tsx
+++ b/dashboard/15-final/app/ui/invoices/table.tsx
@@ -39,7 +39,7 @@ export default function InvoicesTable({
     page: string;
   };
 }) {
-  const searchTerm = searchParams.q ?? '';
+  const searchTerm = searchParams.query ?? '';
   const currentPage = parseInt(searchParams.page ?? '1');
 
   const filteredInvoices = invoices.filter((invoice) => {