From 5f3060c5919b6cfe8c2e34cc4b1395d0c012c299 Mon Sep 17 00:00:00 2001 From: m5r Date: Sun, 3 Oct 2021 20:24:46 +0200 Subject: [PATCH] paginate payments history --- .../components/billing/billing-history.tsx | 89 ++++++++++++++++++- app/settings/hooks/use-payments-history.ts | 35 ++++++++ app/settings/hooks/use-subscription.ts | 3 - app/settings/pages/settings/billing.tsx | 8 +- app/settings/queries/get-payments.ts | 47 ++++++++-- 5 files changed, 168 insertions(+), 14 deletions(-) create mode 100644 app/settings/hooks/use-payments-history.ts 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, + }; });