feat(#6): Add proper pagination support

This commit is contained in:
Josh Creek
2024-10-15 19:31:37 +01:00
parent 36a8620e70
commit 8221224255
3 changed files with 169 additions and 22 deletions
+2 -1
View File
@@ -49,7 +49,8 @@
"dependencies": {
"@supabase/ssr": "^0.4.0",
"@supabase/supabase-js": "^2.44.2",
"stripe": "^15.4.0"
"stripe": "^15.4.0",
"ts-debounce": "^4.0.0"
},
"engines": {
"node": ">=18.13.0"
+8
View File
@@ -17,6 +17,9 @@ importers:
stripe:
specifier: ^15.4.0
version: 15.12.0
ts-debounce:
specifier: ^4.0.0
version: 4.0.0
devDependencies:
'@sveltejs/adapter-auto':
specifier: ^3.0.0
@@ -3051,6 +3054,9 @@ packages:
peerDependencies:
typescript: '>=4.2.0'
ts-debounce@4.0.0:
resolution: {integrity: sha512-+1iDGY6NmOGidq7i7xZGA4cm8DAa6fqdYcvO5Z6yBevH++Bdo9Qt/mN0TzHUgcCcKv1gmh9+W5dHqz8pMWbCbg==}
ts-interface-checker@0.1.13:
resolution: {integrity: sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==}
@@ -6580,6 +6586,8 @@ snapshots:
dependencies:
typescript: 5.6.2
ts-debounce@4.0.0: {}
ts-interface-checker@0.1.13: {}
tslib@2.7.0: {}
+159 -21
View File
@@ -1,17 +1,108 @@
<script lang="ts">
import ProductsListItem from '$lib/components/products/ProductsListItem.svelte';
import { onMount } from 'svelte';
let products = [] as any;
import { debounce } from 'ts-debounce';
import ProductsListItem from '$lib/components/products/ProductsListItem.svelte';
let products = [] as any[];
let enablePagination = false;
let totalPages = 1;
let currentPage = 1;
const limit = 20;
let isLoading = false;
let errorMessage = '';
onMount(async () => {
const response = await fetch('/products?limit=20&offset=0');
const responseJson = await response.json();
products = responseJson.products;
var count = responseJson.count;
// enablePagination needs to be set to true if there are more than 20 products
// Debounced fetchProducts function to prevent rapid requests
const fetchProducts = debounce(async (page: number) => {
isLoading = true;
errorMessage = '';
// Ensure page number is within valid range
if (page < 1) {
page = 1;
} else if (page > totalPages) {
page = totalPages;
}
const offset = (page - 1) * limit;
try {
const response = await fetch(`/products?limit=${limit}&offset=${offset}`);
const responseJson = await response.json();
if (response.ok) {
products = responseJson.products;
const count = responseJson.count;
totalPages = Math.ceil(count / limit);
enablePagination = totalPages > 1;
currentPage = page;
} else {
throw new Error(responseJson.error || 'Failed to load products.');
}
} catch (error) {
console.error(error);
errorMessage = error.message || 'Failed to load products.';
} finally {
isLoading = false;
}
}, 300); // 300ms debounce delay
onMount(() => {
fetchProducts(currentPage);
});
// Generate pagination buttons with ellipsis for large numbers of pages
function getPageNumbers() {
const maxButtons = 5;
const pageNumbers = [];
if (totalPages <= maxButtons) {
// Show all pages as there aren't many
for (let i = 1; i <= totalPages; i++) {
pageNumbers.push(i);
}
} else {
let startPage = Math.max(currentPage - 2, 1);
let endPage = Math.min(currentPage + 2, totalPages);
if (currentPage <= 3) {
startPage = 1;
endPage = maxButtons;
} else if (currentPage >= totalPages - 2) {
startPage = totalPages - (maxButtons - 1);
endPage = totalPages;
}
// Add first page and ellipsis if needed
if (startPage > 1) {
pageNumbers.push(1);
if (startPage > 2) {
pageNumbers.push('...');
}
}
// Add page numbers
for (let i = startPage; i <= endPage; i++) {
pageNumbers.push(i);
}
// Add ellipsis and last page if needed
if (endPage < totalPages) {
if (endPage < totalPages - 1) {
pageNumbers.push('...');
}
pageNumbers.push(totalPages);
}
}
return pageNumbers;
}
function handlePageClick(pageNumber: number | string) {
if (typeof pageNumber === 'number' && pageNumber !== currentPage) {
fetchProducts(pageNumber);
}
}
</script>
<section class="py-24">
@@ -21,20 +112,67 @@
>
Available Products
</h2>
<div class="grid grid-cols-1 lg:grid-cols-3 gap-8">
{#each products as product (product.id)}
<ProductsListItem {product} />
{/each}
</div>
{#if enablePagination}
<div class="flex justify-center mt-12">
<div class="join">
<button class="join-item btn">1</button>
<button class="join-item btn btn-active">2</button>
<button class="join-item btn">3</button>
<button class="join-item btn">4</button>
</div>
{#if isLoading}
<p class="loading">Loading products...</p>
{:else if errorMessage}
<p class="error">{errorMessage}</p>
{:else}
<div class="grid grid-cols-1 lg:grid-cols-3 gap-8">
{#each products as product (product.id)}
<ProductsListItem {product} />
{/each}
</div>
{#if enablePagination}
<div class="flex justify-center mt-12">
<div class="join">
<!-- Previous Button -->
<button
class="join-item btn"
on:click={() => fetchProducts(currentPage - 1)}
disabled={currentPage === 1}
>
Previous
</button>
<!-- Page Number Buttons -->
{#each getPageNumbers() as pageNumber}
{#if pageNumber === '...'}
<span class="join-item btn">...</span>
{:else}
<button
class="join-item btn {currentPage === pageNumber ? 'btn-active' : ''}"
on:click={() => handlePageClick(pageNumber)}
>
{pageNumber}
</button>
{/if}
{/each}
<!-- Next Button -->
<button
class="join-item btn"
on:click={() => fetchProducts(currentPage + 1)}
disabled={currentPage === totalPages}
>
Next
</button>
</div>
</div>
{/if}
{/if}
</div>
</section>
<style>
.loading {
text-align: center;
margin: 20px 0;
}
.error {
color: red;
text-align: center;
margin: 20px 0;
}
</style>