From 28d4d752e99e7454465b44bf2b175ac5600ab62f Mon Sep 17 00:00:00 2001 From: Chuma McPhoy Date: Mon, 2 Dec 2024 21:11:53 +0100 Subject: [PATCH] feat(LOY-53): CurrentRewards - replace show more functionality with pagination --- .../EarnAndBurn/JourneyTable/Client.tsx | 2 +- .../Rewards/CurrentLevel/Client.tsx | 61 ++---- .../Rewards/CurrentLevel/current.module.css | 6 + .../Rewards/CurrentLevel/index.tsx | 10 +- .../Pagination/index.tsx | 2 +- .../Pagination/pagination.module.css | 2 +- server/routers/contentstack/reward/input.ts | 7 - server/routers/contentstack/reward/query.ts | 173 ++++++++---------- .../components/myPages/myPage/accountPage.ts | 3 +- .../components/myPages/myPage/earnAndBurn.ts | 13 -- types/components/myPages/pagination.ts | 12 ++ 11 files changed, 124 insertions(+), 167 deletions(-) rename components/{Blocks/DynamicContent/Points/EarnAndBurn/JourneyTable => MyPages}/Pagination/index.tsx (96%) rename components/{Blocks/DynamicContent/Points/EarnAndBurn/JourneyTable => MyPages}/Pagination/pagination.module.css (95%) create mode 100644 types/components/myPages/pagination.ts diff --git a/components/Blocks/DynamicContent/Points/EarnAndBurn/JourneyTable/Client.tsx b/components/Blocks/DynamicContent/Points/EarnAndBurn/JourneyTable/Client.tsx index eca762942..76fb8fa06 100644 --- a/components/Blocks/DynamicContent/Points/EarnAndBurn/JourneyTable/Client.tsx +++ b/components/Blocks/DynamicContent/Points/EarnAndBurn/JourneyTable/Client.tsx @@ -6,9 +6,9 @@ import { useState } from "react" import { trpc } from "@/lib/trpc/client" import LoadingSpinner from "@/components/LoadingSpinner" +import Pagination from "@/components/MyPages/Pagination" import ClientTable from "./ClientTable" -import Pagination from "./Pagination" import { Transactions } from "@/types/components/myPages/myPage/earnAndBurn" diff --git a/components/Blocks/DynamicContent/Rewards/CurrentLevel/Client.tsx b/components/Blocks/DynamicContent/Rewards/CurrentLevel/Client.tsx index 936e340aa..84c6c942d 100644 --- a/components/Blocks/DynamicContent/Rewards/CurrentLevel/Client.tsx +++ b/components/Blocks/DynamicContent/Rewards/CurrentLevel/Client.tsx @@ -1,14 +1,11 @@ "use client" -import { trpc } from "@/lib/trpc/client" -import { Reward } from "@/server/routers/contentstack/reward/output" +import { useState } from "react" import Image from "@/components/Image" -import LoadingSpinner from "@/components/LoadingSpinner" +import Pagination from "@/components/MyPages/Pagination" import Grids from "@/components/TempDesignSystem/Grids" -import ShowMoreButton from "@/components/TempDesignSystem/ShowMoreButton" import Title from "@/components/TempDesignSystem/Text/Title" -import useLang from "@/hooks/useLang" import Redeem from "./Redeem" @@ -17,46 +14,21 @@ import styles from "./current.module.css" import type { CurrentRewardsClientProps } from "@/types/components/myPages/myPage/accountPage" export default function ClientCurrentRewards({ - initialCurrentRewards, + rewards, + pageSize, showRedeem, }: CurrentRewardsClientProps) { - const lang = useLang() - const { data, isFetching, fetchNextPage, hasNextPage, isLoading } = - trpc.contentstack.rewards.current.useInfiniteQuery( - { - limit: 3, - lang, - }, - { - getNextPageParam: (lastPage) => lastPage?.nextCursor, - initialData: { - pageParams: [undefined, 1], - pages: [initialCurrentRewards], - }, - } - ) - function loadMoreData() { - if (hasNextPage) { - fetchNextPage() - } - } - const filteredRewards = data?.pages.filter((page) => page?.rewards) ?? [] - const rewards = filteredRewards - .flatMap((page) => page?.rewards) - .filter((reward): reward is Reward => !!reward) + const [currentPage, setCurrentPage] = useState(1) - if (isLoading) { - return - } - - if (!rewards.length) { - return null - } + const totalPages = Math.ceil(rewards.length / pageSize) + const startIndex = (currentPage - 1) * pageSize + const endIndex = startIndex + pageSize + const currentRewards = rewards.slice(startIndex, endIndex) return ( -
+
- {rewards.map((reward, idx) => ( + {currentRewards.map((reward, idx) => (
))} - {hasNextPage && - (isFetching ? ( - - ) : ( - - ))} +
) } diff --git a/components/Blocks/DynamicContent/Rewards/CurrentLevel/current.module.css b/components/Blocks/DynamicContent/Rewards/CurrentLevel/current.module.css index a1023b0da..f2ff4ca62 100644 --- a/components/Blocks/DynamicContent/Rewards/CurrentLevel/current.module.css +++ b/components/Blocks/DynamicContent/Rewards/CurrentLevel/current.module.css @@ -123,3 +123,9 @@ display: flex; align-items: center; } + +.container { + display: flex; + flex-direction: column; + gap: var(--Spacing-x4); +} diff --git a/components/Blocks/DynamicContent/Rewards/CurrentLevel/index.tsx b/components/Blocks/DynamicContent/Rewards/CurrentLevel/index.tsx index 2d685ea9c..6f961f5ee 100644 --- a/components/Blocks/DynamicContent/Rewards/CurrentLevel/index.tsx +++ b/components/Blocks/DynamicContent/Rewards/CurrentLevel/index.tsx @@ -14,12 +14,9 @@ export default async function CurrentRewardsBlock({ subtitle, link, }: AccountPageComponentProps) { - const initialCurrentRewards = - await serverClient().contentstack.rewards.current({ - limit: 3, - }) + const rewardsResponse = await serverClient().contentstack.rewards.current() - if (!initialCurrentRewards) { + if (!rewardsResponse?.rewards.length) { return null } @@ -27,7 +24,8 @@ export default async function CurrentRewardsBlock({ diff --git a/components/Blocks/DynamicContent/Points/EarnAndBurn/JourneyTable/Pagination/index.tsx b/components/MyPages/Pagination/index.tsx similarity index 96% rename from components/Blocks/DynamicContent/Points/EarnAndBurn/JourneyTable/Pagination/index.tsx rename to components/MyPages/Pagination/index.tsx index ee1f1bf9a..02f8dd1f6 100644 --- a/components/Blocks/DynamicContent/Points/EarnAndBurn/JourneyTable/Pagination/index.tsx +++ b/components/MyPages/Pagination/index.tsx @@ -5,7 +5,7 @@ import styles from "./pagination.module.css" import { PaginationButtonProps, PaginationProps, -} from "@/types/components/myPages/myPage/earnAndBurn" +} from "@/types/components/myPages/pagination" function PaginationButton({ children, diff --git a/components/Blocks/DynamicContent/Points/EarnAndBurn/JourneyTable/Pagination/pagination.module.css b/components/MyPages/Pagination/pagination.module.css similarity index 95% rename from components/Blocks/DynamicContent/Points/EarnAndBurn/JourneyTable/Pagination/pagination.module.css rename to components/MyPages/Pagination/pagination.module.css index 33ba04a4d..4b17d7b9c 100644 --- a/components/Blocks/DynamicContent/Points/EarnAndBurn/JourneyTable/Pagination/pagination.module.css +++ b/components/MyPages/Pagination/pagination.module.css @@ -34,7 +34,7 @@ } .paginationButtonActive { - color: var(--WHITE); + color: var(--Base-Text-Inverted); background-color: var(--Base-Text-Accent); border-radius: var(--Corner-radius-Rounded); } diff --git a/server/routers/contentstack/reward/input.ts b/server/routers/contentstack/reward/input.ts index 30419835b..2ad7bcb79 100644 --- a/server/routers/contentstack/reward/input.ts +++ b/server/routers/contentstack/reward/input.ts @@ -1,6 +1,5 @@ import { z } from "zod" -import { Lang } from "@/constants/languages" import { MembershipLevelEnum } from "@/constants/membershipLevels" export const rewardsByLevelInput = z.object({ @@ -12,12 +11,6 @@ export const rewardsAllInput = z .object({ unique: z.boolean() }) .default({ unique: false }) -export const rewardsCurrentInput = z.object({ - limit: z.number().min(1).default(3), - cursor: z.number().optional().default(0), - lang: z.nativeEnum(Lang).optional(), -}) - export const rewardsUpdateInput = z.array( z.object({ rewardId: z.string(), diff --git a/server/routers/contentstack/reward/query.ts b/server/routers/contentstack/reward/query.ts index aca10edb7..6f62241e2 100644 --- a/server/routers/contentstack/reward/query.ts +++ b/server/routers/contentstack/reward/query.ts @@ -12,7 +12,6 @@ import { getAllLoyaltyLevels, getLoyaltyLevel } from "../loyaltyLevel/query" import { rewardsAllInput, rewardsByLevelInput, - rewardsCurrentInput, rewardsRedeemInput, rewardsUpdateInput, } from "./input" @@ -161,115 +160,105 @@ export const rewardQueryRouter = router({ getByLevelRewardSuccessCounter.add(1) return { level: loyaltyLevelsConfig, rewards: levelsWithRewards } }), - current: contentStackBaseWithProtectedProcedure - .input(rewardsCurrentInput) - .query(async function ({ input, ctx }) { - getCurrentRewardCounter.add(1) + current: contentStackBaseWithProtectedProcedure.query(async function ({ + ctx, + }) { + getCurrentRewardCounter.add(1) - const { limit, cursor } = input + const isNewEndpoint = env.USE_NEW_REWARDS_ENDPOINT + const endpoint = isNewEndpoint + ? api.endpoints.v1.Profile.Reward.reward + : api.endpoints.v1.Profile.reward - const isNewEndpoint = env.USE_NEW_REWARDS_ENDPOINT - const endpoint = isNewEndpoint - ? api.endpoints.v1.Profile.Reward.reward - : api.endpoints.v1.Profile.reward + const apiResponse = await api.get(endpoint, { + cache: undefined, // override defaultOptions + headers: { + Authorization: `Bearer ${ctx.session.token.access_token}`, + }, + next: { revalidate: ONE_HOUR }, + }) - const apiResponse = await api.get(endpoint, { - cache: undefined, // override defaultOptions - headers: { - Authorization: `Bearer ${ctx.session.token.access_token}`, - }, - next: { revalidate: ONE_HOUR }, + if (!apiResponse.ok) { + const text = await apiResponse.text() + getCurrentRewardFailCounter.add(1, { + error_type: "http_error", + error: JSON.stringify({ + status: apiResponse.status, + statusText: apiResponse.statusText, + text, + }), }) - - if (!apiResponse.ok) { - const text = await apiResponse.text() - getCurrentRewardFailCounter.add(1, { - error_type: "http_error", - error: JSON.stringify({ + console.error( + "api.reward error ", + JSON.stringify({ + error: { status: apiResponse.status, statusText: apiResponse.statusText, text, - }), + }, }) - console.error( - "api.reward error ", - JSON.stringify({ - error: { - status: apiResponse.status, - statusText: apiResponse.statusText, - text, - }, - }) - ) - return null - } + ) + return null + } - const data = await apiResponse.json() - const validatedApiRewards = isNewEndpoint - ? validateCategorizedRewardsSchema.safeParse(data) - : validateApiRewardSchema.safeParse(data) + const data = await apiResponse.json() + const validatedApiRewards = isNewEndpoint + ? validateCategorizedRewardsSchema.safeParse(data) + : validateApiRewardSchema.safeParse(data) - if (!validatedApiRewards.success) { - getCurrentRewardFailCounter.add(1, { - locale: ctx.lang, - error_type: "validation_error", - error: JSON.stringify(validatedApiRewards.error), + if (!validatedApiRewards.success) { + getCurrentRewardFailCounter.add(1, { + locale: ctx.lang, + error_type: "validation_error", + error: JSON.stringify(validatedApiRewards.error), + }) + console.error(validatedApiRewards.error) + console.error( + "contentstack.rewards validation error", + JSON.stringify({ + query: { locale: ctx.lang }, + error: validatedApiRewards.error, }) - console.error(validatedApiRewards.error) - console.error( - "contentstack.rewards validation error", - JSON.stringify({ - query: { locale: ctx.lang }, - error: validatedApiRewards.error, - }) - ) - return null - } + ) + return null + } - const rewardIds = validatedApiRewards.data - .map((reward) => reward?.rewardId) - .filter((rewardId): rewardId is string => !!rewardId) - .sort() + const rewardIds = validatedApiRewards.data + .map((reward) => reward?.rewardId) + .filter((rewardId): rewardId is string => !!rewardId) + .sort() - const slicedData = rewardIds.slice(cursor, limit + cursor) + const cmsRewards = await getCmsRewards(ctx.lang, rewardIds) - const cmsRewards = await getCmsRewards(ctx.lang, slicedData) + if (!cmsRewards) { + return null + } - if (!cmsRewards) { - return null - } + const wrappedSurprisesIds = validatedApiRewards.data + .filter( + (reward) => + reward.type === "coupon" && + reward.rewardType === "Surprise" && + "coupon" in reward && + reward.coupon?.some(({ unwrapped }) => !unwrapped) + ) + .map(({ rewardId }) => rewardId) - const nextCursor = - limit + cursor < rewardIds.length ? limit + cursor : undefined + const rewards = cmsRewards + .filter((reward) => !wrappedSurprisesIds.includes(reward.reward_id)) + .map((reward) => { + return { + ...reward, + id: validatedApiRewards.data.find( + ({ rewardId }) => rewardId === reward.reward_id + )?.id, + } + }) - const wrappedSurprisesIds = validatedApiRewards.data - .filter( - (reward) => - reward.type === "coupon" && - reward.rewardType === "Surprise" && - "coupon" in reward && - reward.coupon?.some(({ unwrapped }) => !unwrapped) - ) - .map(({ rewardId }) => rewardId) + getCurrentRewardSuccessCounter.add(1) - const rewards = cmsRewards - .filter((reward) => !wrappedSurprisesIds.includes(reward.reward_id)) - .map((reward) => { - return { - ...reward, - id: validatedApiRewards.data.find( - ({ rewardId }) => rewardId === reward.reward_id - )?.id, - } - }) - - getCurrentRewardSuccessCounter.add(1) - - return { - rewards, - nextCursor, - } - }), + return { rewards } + }), surprises: contentStackBaseWithProtectedProcedure.query(async ({ ctx }) => { getCurrentRewardCounter.add(1) diff --git a/types/components/myPages/myPage/accountPage.ts b/types/components/myPages/myPage/accountPage.ts index 525d18fce..7ad56b272 100644 --- a/types/components/myPages/myPage/accountPage.ts +++ b/types/components/myPages/myPage/accountPage.ts @@ -21,7 +21,8 @@ export type ContentProps = { } export interface CurrentRewardsClientProps { - initialCurrentRewards: { rewards: Reward[]; nextCursor: number | undefined } + rewards: Reward[] + pageSize: number showRedeem: boolean } diff --git a/types/components/myPages/myPage/earnAndBurn.ts b/types/components/myPages/myPage/earnAndBurn.ts index e7836f908..15efdaef6 100644 --- a/types/components/myPages/myPage/earnAndBurn.ts +++ b/types/components/myPages/myPage/earnAndBurn.ts @@ -31,19 +31,6 @@ export interface RowProps { transaction: Transaction } -export interface PaginationProps { - pageCount: number - isFetching: boolean - handlePageChange: (page: number) => void - currentPage: number -} - -export interface PaginationButtonProps { - disabled: boolean - isActive?: boolean - handleClick: () => void -} - export interface AwardPointsProps extends Pick {} export interface AwardPointsVariantProps diff --git a/types/components/myPages/pagination.ts b/types/components/myPages/pagination.ts new file mode 100644 index 000000000..e2ac5aac3 --- /dev/null +++ b/types/components/myPages/pagination.ts @@ -0,0 +1,12 @@ +export interface PaginationProps { + pageCount: number + isFetching?: boolean + handlePageChange: (page: number) => void + currentPage: number +} + +export interface PaginationButtonProps { + disabled: boolean + isActive?: boolean + handleClick: () => void +}