diff --git a/app/settings/components/billing/billing-history.tsx b/app/settings/components/billing/billing-history.tsx
index f043e4e..fb8472f 100644
--- a/app/settings/components/billing/billing-history.tsx
+++ b/app/settings/components/billing/billing-history.tsx
@@ -1,7 +1,21 @@
-import useSubscription from "../../hooks/use-subscription";
+import { IoChevronBack, IoChevronForward } from "react-icons/io5";
+import clsx from "clsx";
+
+import usePaymentsHistory from "../../hooks/use-payments-history";
export default function BillingHistory() {
- const { payments } = useSubscription();
+ const {
+ payments,
+ count,
+ skip,
+ pagesNumber,
+ currentPage,
+ goToPreviousPage,
+ hasPreviousPage,
+ goToNextPage,
+ hasNextPage,
+ setPage,
+ } = usePaymentsHistory();
if (payments.length === 0) {
return null;
@@ -77,6 +91,77 @@ export default function BillingHistory() {
))}
+
+
+
+
+
+ Page 1 of{" "}
+ 4
+
+
+
+
+
+
+ Showing {skip + 1} to{" "}
+ {skip + payments.length} of{" "}
+ {count} results
+
+
+
+
+
+
+
diff --git a/app/settings/hooks/use-payments-history.ts b/app/settings/hooks/use-payments-history.ts
new file mode 100644
index 0000000..4f234f6
--- /dev/null
+++ b/app/settings/hooks/use-payments-history.ts
@@ -0,0 +1,35 @@
+import { useState } from "react";
+import { usePaginatedQuery } from "blitz";
+
+import getPayments from "../queries/get-payments";
+
+const itemsPerPage = 5;
+
+export default function usePaymentsHistory() {
+ const [skip, setSkip] = useState(0);
+ const [{ payments, hasMore, nextPage, count }] = usePaginatedQuery(getPayments, { skip, take: itemsPerPage });
+
+ const totalPages = Math.ceil(count / itemsPerPage);
+ const pagesNumber = Array(totalPages)
+ .fill(-1)
+ .map((_, i) => i + 1);
+ const currentPage = Math.floor((skip / count) * totalPages) + 1;
+ const hasPreviousPage = skip > 0;
+ const hasNextPage = hasMore && !!nextPage;
+ const goToPreviousPage = () => hasPreviousPage && setSkip(skip - itemsPerPage);
+ const goToNextPage = () => hasNextPage && setSkip(nextPage.skip);
+ const setPage = (pageNumber: number) => setSkip((pageNumber - 1) * itemsPerPage);
+
+ return {
+ payments,
+ count,
+ skip,
+ pagesNumber,
+ currentPage,
+ hasPreviousPage,
+ hasNextPage,
+ goToPreviousPage,
+ goToNextPage,
+ setPage,
+ };
+}
diff --git a/app/settings/hooks/use-subscription.ts b/app/settings/hooks/use-subscription.ts
index b45370a..af2c4b7 100644
--- a/app/settings/hooks/use-subscription.ts
+++ b/app/settings/hooks/use-subscription.ts
@@ -3,7 +3,6 @@ import { useQuery, useMutation, useSession } from "blitz";
import type { Subscription } from "db";
import getSubscription from "../queries/get-subscription";
-import getPayments from "../queries/get-payments";
import usePaddle from "./use-paddle";
import useCurrentUser from "../../core/hooks/use-current-user";
import updateSubscription from "../mutations/update-subscription";
@@ -20,7 +19,6 @@ export default function useSubscription({ initialData }: Params = {}) {
initialData,
refetchInterval: isWaitingForSubChange ? 1500 : false,
});
- const [payments] = useQuery(getPayments, null);
const [updateSubscriptionMutation] = useMutation(updateSubscription);
const resolve = useRef<() => void>();
@@ -107,7 +105,6 @@ export default function useSubscription({ initialData }: Params = {}) {
return {
subscription,
- payments,
subscribe,
updatePaymentMethod,
cancelSubscription,
diff --git a/app/settings/pages/settings/billing.tsx b/app/settings/pages/settings/billing.tsx
index 7e8b001..88c6394 100644
--- a/app/settings/pages/settings/billing.tsx
+++ b/app/settings/pages/settings/billing.tsx
@@ -4,13 +4,14 @@ import { GetServerSideProps, getSession, Routes } from "blitz";
import db, { Subscription, SubscriptionStatus } from "db";
import useSubscription from "../../hooks/use-subscription";
import useRequireOnboarding from "../../../core/hooks/use-require-onboarding";
+import usePaymentsHistory from "../../hooks/use-payments-history";
import SettingsLayout from "../../components/settings-layout";
import SettingsSection from "../../components/settings-section";
import Divider from "../../components/divider";
import PaddleLink from "../../components/billing/paddle-link";
import Plans from "../../components/billing/plans";
import BillingHistory from "../../components/billing/billing-history";
-import appLogger from "../../../../integrations/logger";
+import appLogger from "integrations/logger";
const logger = appLogger.child({ page: "/account/settings/billing" });
@@ -27,7 +28,8 @@ const Billing: BlitzPage = (props) => {
*/
useRequireOnboarding();
- const { subscription, cancelSubscription, updatePaymentMethod, payments } = useSubscription({
+ const { count: paymentsCount } = usePaymentsHistory();
+ const { subscription, cancelSubscription, updatePaymentMethod } = useSubscription({
initialData: props.subscription,
});
@@ -48,7 +50,7 @@ const Billing: BlitzPage = (props) => {
) : null}
- {payments.length > 0 ? (
+ {paymentsCount > 0 ? (
<>
diff --git a/app/settings/queries/get-payments.ts b/app/settings/queries/get-payments.ts
index 5aa9aef..deac7ab 100644
--- a/app/settings/queries/get-payments.ts
+++ b/app/settings/queries/get-payments.ts
@@ -1,21 +1,56 @@
-import { resolver } from "blitz";
+import { paginate, resolver } from "blitz";
+import { z } from "zod";
import db from "db";
import { getPayments } from "integrations/paddle";
-export default resolver.pipe(resolver.authorize(), async (_ = null, { session }) => {
+const Body = z.object({
+ skip: z.number().optional(),
+ take: z.number().optional(),
+});
+
+export default resolver.pipe(resolver.zod(Body), resolver.authorize(), async ({ skip, take }, { session }) => {
if (!session.orgId) {
- return [];
+ return {
+ payments: [],
+ nextPage: null,
+ hasMore: false,
+ count: 0,
+ };
}
const subscriptions = await db.subscription.findMany({ where: { organizationId: session.orgId } });
if (subscriptions.length === 0) {
- return [];
+ return {
+ payments: [],
+ nextPage: null,
+ hasMore: false,
+ count: 0,
+ };
}
const paymentsBySubscription = await Promise.all(
subscriptions.map((subscription) => getPayments({ subscriptionId: subscription.paddleSubscriptionId })),
);
- const payments = paymentsBySubscription.flat();
- return payments.sort((a, b) => b.payout_date.localeCompare(a.payout_date));
+ const unsortedPayments = paymentsBySubscription.flat();
+ const allPayments = Array.from(unsortedPayments).sort((a, b) => b.payout_date.localeCompare(a.payout_date));
+
+ const {
+ items: payments,
+ hasMore,
+ nextPage,
+ count,
+ } = await paginate({
+ skip,
+ take,
+ count: () => Promise.resolve(allPayments.length),
+ query: ({ skip, take }) => Promise.resolve(allPayments.slice(skip, skip + take)),
+ });
+
+ return {
+ payments,
+ nextPage,
+ hasMore,
+ count,
+ };
});