mirror of
https://github.com/jcreek/SvelteKitSaasBoilerplate.git
synced 2026-07-15 12:03:49 +00:00
Merge pull request #95 from jcreek/76-add-pagination-for-stripe-orders-on-account-page
76 add pagination for stripe orders on account page
This commit is contained in:
@@ -590,7 +590,12 @@ const getUserSubscriptions = async (userId: string) => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const getUserTransactions = async (userId: string) => {
|
const getUserTransactions = async (
|
||||||
|
userId: string,
|
||||||
|
perPage: number,
|
||||||
|
startingAfter: string | undefined = undefined,
|
||||||
|
endingBefore: string | undefined = undefined
|
||||||
|
) => {
|
||||||
try {
|
try {
|
||||||
// Step 1: Retrieve the customer ID associated with the user
|
// Step 1: Retrieve the customer ID associated with the user
|
||||||
const { data, error } = await supabaseAdmin
|
const { data, error } = await supabaseAdmin
|
||||||
@@ -610,10 +615,21 @@ const getUserTransactions = async (userId: string) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Step 2: Fetch charge transactions from Stripe
|
// Step 2: Fetch charge transactions from Stripe
|
||||||
const charges = await stripeClient.charges.list({ customer: customerId });
|
const params: stripe.ChargeListParams = {
|
||||||
|
customer: customerId,
|
||||||
|
limit: perPage
|
||||||
|
};
|
||||||
|
|
||||||
|
if (startingAfter) params.starting_after = startingAfter;
|
||||||
|
if (endingBefore) params.ending_before = endingBefore;
|
||||||
|
|
||||||
|
const charges = await stripeClient.charges.list(params);
|
||||||
|
|
||||||
|
const hasNextPage = charges.has_more;
|
||||||
|
|
||||||
// Step 3: Format transaction data
|
// Step 3: Format transaction data
|
||||||
const transactions = charges.data.map((charge) => ({
|
const transactions = charges.data.map((charge) => ({
|
||||||
|
id: charge.id,
|
||||||
amount: (charge.amount / 100).toFixed(2),
|
amount: (charge.amount / 100).toFixed(2),
|
||||||
currency: charge.currency.toUpperCase(),
|
currency: charge.currency.toUpperCase(),
|
||||||
description: charge.description ?? 'No description provided',
|
description: charge.description ?? 'No description provided',
|
||||||
@@ -626,7 +642,7 @@ const getUserTransactions = async (userId: string) => {
|
|||||||
receipt_url: charge.receipt_url
|
receipt_url: charge.receipt_url
|
||||||
}));
|
}));
|
||||||
|
|
||||||
return transactions;
|
return { transactions, hasNextPage };
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('An error occurred while retrieving transactions:', error);
|
console.error('An error occurred while retrieving transactions:', error);
|
||||||
throw new Error('Could not retrieve transactions');
|
throw new Error('Could not retrieve transactions');
|
||||||
|
|||||||
@@ -4,6 +4,8 @@ import logger from '$lib/utils/logger/logger';
|
|||||||
import { getUserSubscriptions, getUserTransactions } from '$lib/utils/supabase/admin';
|
import { getUserSubscriptions, getUserTransactions } from '$lib/utils/supabase/admin';
|
||||||
import { requestAccountDeletion } from '$lib/utils/supabase/admin';
|
import { requestAccountDeletion } from '$lib/utils/supabase/admin';
|
||||||
|
|
||||||
|
const transactionsPerPage = 5;
|
||||||
|
|
||||||
export const load: PageServerLoad = async ({ locals: { supabase, safeGetSession } }) => {
|
export const load: PageServerLoad = async ({ locals: { supabase, safeGetSession } }) => {
|
||||||
const { session } = await safeGetSession();
|
const { session } = await safeGetSession();
|
||||||
|
|
||||||
@@ -19,9 +21,21 @@ export const load: PageServerLoad = async ({ locals: { supabase, safeGetSession
|
|||||||
|
|
||||||
const subscriptions = await getUserSubscriptions(session.user.id);
|
const subscriptions = await getUserSubscriptions(session.user.id);
|
||||||
|
|
||||||
const transactions = await getUserTransactions(session.user.id);
|
const transactionsData = await getUserTransactions(session.user.id, transactionsPerPage);
|
||||||
|
|
||||||
return { session, user, subscriptions, transactions };
|
const transactions = transactionsData.transactions;
|
||||||
|
const firstTransactionId = transactions.length > 0 ? transactions[0].id : undefined;
|
||||||
|
const hasMoreThanOnePage = transactionsData.hasNextPage;
|
||||||
|
|
||||||
|
return {
|
||||||
|
session,
|
||||||
|
user,
|
||||||
|
subscriptions,
|
||||||
|
transactions,
|
||||||
|
firstTransactionId,
|
||||||
|
hasMoreThanOnePage,
|
||||||
|
pageSize: transactionsPerPage
|
||||||
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
export const actions: Actions = {
|
export const actions: Actions = {
|
||||||
@@ -78,5 +92,43 @@ export const actions: Actions = {
|
|||||||
return {
|
return {
|
||||||
name
|
name
|
||||||
};
|
};
|
||||||
|
},
|
||||||
|
paginate: async ({ request, locals: { safeGetSession } }) => {
|
||||||
|
const formData = await request.formData();
|
||||||
|
const pageSize = transactionsPerPage;
|
||||||
|
|
||||||
|
let startingAfter = formData.get('startingAfter')?.toString() || undefined;
|
||||||
|
if (startingAfter === 'undefined') {
|
||||||
|
startingAfter = undefined;
|
||||||
|
}
|
||||||
|
let endingBefore = formData.get('endingBefore')?.toString() || undefined;
|
||||||
|
if (endingBefore === 'undefined') {
|
||||||
|
endingBefore = undefined;
|
||||||
|
}
|
||||||
|
const firstTransactionId = formData.get('firstTransactionId')?.toString();
|
||||||
|
const hasMoreThanOnePage = formData.get('hasMoreThanOnePage') === 'true';
|
||||||
|
|
||||||
|
const { session } = await safeGetSession();
|
||||||
|
if (!session) return fail(401, { error: 'Not authenticated' });
|
||||||
|
|
||||||
|
try {
|
||||||
|
const { transactions, hasNextPage } = await getUserTransactions(
|
||||||
|
session.user.id,
|
||||||
|
pageSize,
|
||||||
|
startingAfter,
|
||||||
|
endingBefore
|
||||||
|
);
|
||||||
|
|
||||||
|
const isFirstPage = transactions.length > 0 && transactions[0].id === firstTransactionId;
|
||||||
|
|
||||||
|
return {
|
||||||
|
transactions: JSON.stringify(transactions),
|
||||||
|
hasNextPage: hasNextPage || (isFirstPage && hasMoreThanOnePage),
|
||||||
|
isFirstPage
|
||||||
|
};
|
||||||
|
} catch (error) {
|
||||||
|
logger.error('Failed to fetch transactions', { userId: session.user.id, error });
|
||||||
|
return fail(500, { error: 'Failed to load transactions' });
|
||||||
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -5,8 +5,8 @@
|
|||||||
export let data;
|
export let data;
|
||||||
export let form;
|
export let form;
|
||||||
|
|
||||||
let { session, user, subscriptions, transactions } = data;
|
let { session, user, subscriptions, transactions, firstTransactionId, hasMoreThanOnePage } = data;
|
||||||
$: ({ session, supabase, user } = data);
|
$: ({ session, user, transactions, firstTransactionId, hasMoreThanOnePage } = data);
|
||||||
|
|
||||||
let profileForm: HTMLFormElement;
|
let profileForm: HTMLFormElement;
|
||||||
let loading = false;
|
let loading = false;
|
||||||
@@ -39,6 +39,22 @@
|
|||||||
loading = false;
|
loading = false;
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
let startingAfter: string | undefined = undefined;
|
||||||
|
let endingBefore: string | undefined = undefined;
|
||||||
|
let hasNextPage = hasMoreThanOnePage;
|
||||||
|
let hasPreviousPage = false;
|
||||||
|
|
||||||
|
const handlePaginationSubmit: SubmitFunction = () => {
|
||||||
|
loading = true;
|
||||||
|
|
||||||
|
return async ({ result }) => {
|
||||||
|
transactions = JSON.parse(result.data.transactions);
|
||||||
|
hasNextPage = result.data.hasNextPage;
|
||||||
|
hasPreviousPage = !result.data.isFirstPage;
|
||||||
|
loading = false;
|
||||||
|
};
|
||||||
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div
|
<div
|
||||||
@@ -126,8 +142,9 @@
|
|||||||
<!-- Billing History -->
|
<!-- Billing History -->
|
||||||
<section class="space-y-4">
|
<section class="space-y-4">
|
||||||
<h2 class="text-xl font-semibold">Billing History</h2>
|
<h2 class="text-xl font-semibold">Billing History</h2>
|
||||||
<ul class="space-y-3">
|
|
||||||
{#if transactions}
|
{#if transactions && transactions.length > 0}
|
||||||
|
<ul class="space-y-3">
|
||||||
{#each transactions as transaction}
|
{#each transactions as transaction}
|
||||||
<li class="flex justify-between p-4 border border-gray-200 rounded-md">
|
<li class="flex justify-between p-4 border border-gray-200 rounded-md">
|
||||||
<div>
|
<div>
|
||||||
@@ -146,8 +163,58 @@
|
|||||||
{/if}
|
{/if}
|
||||||
</li>
|
</li>
|
||||||
{/each}
|
{/each}
|
||||||
{/if}
|
</ul>
|
||||||
</ul>
|
{:else}
|
||||||
|
<p class="text-gray-600">No transactions found.</p>
|
||||||
|
{/if}
|
||||||
|
|
||||||
|
<!-- Pagination Form -->
|
||||||
|
<form method="post" action="?/paginate" use:enhance={handlePaginationSubmit}>
|
||||||
|
<div
|
||||||
|
class="flex justify-center items-center space-x-2 mt-4"
|
||||||
|
role="navigation"
|
||||||
|
aria-label="Transactions pagination"
|
||||||
|
>
|
||||||
|
<input type="hidden" name="startingAfter" value={startingAfter} />
|
||||||
|
<input type="hidden" name="endingBefore" value={endingBefore} />
|
||||||
|
<input type="hidden" name="firstTransactionId" value={firstTransactionId} />
|
||||||
|
<input type="hidden" name="hasMoreThanOnePage" value={hasMoreThanOnePage} />
|
||||||
|
|
||||||
|
<button
|
||||||
|
type="submit"
|
||||||
|
name="direction"
|
||||||
|
value="previous"
|
||||||
|
class="btn btn-outline"
|
||||||
|
disabled={!hasPreviousPage || loading}
|
||||||
|
aria-label="View previous page of transactions"
|
||||||
|
on:click={() => {
|
||||||
|
if (transactions.length) {
|
||||||
|
endingBefore = transactions[0].id;
|
||||||
|
startingAfter = undefined;
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Previous
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<button
|
||||||
|
type="submit"
|
||||||
|
name="direction"
|
||||||
|
value="next"
|
||||||
|
class="btn btn-outline"
|
||||||
|
disabled={!hasNextPage || loading}
|
||||||
|
aria-label="View next page of transactions"
|
||||||
|
on:click={() => {
|
||||||
|
if (transactions.length) {
|
||||||
|
startingAfter = transactions[transactions.length - 1].id;
|
||||||
|
endingBefore = undefined;
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Next
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
</section>
|
</section>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user