Files
web/apps/scandic-web/components/Blocks/DynamicContent/Rewards/CurrentRewards/Client.tsx
Chuma Mcphoy (We Ahead) 1ef6fd02c1 Merged in feat/LOY-55-Filter-Modal (pull request #1509)
feat(LOY-55): Add FilterRewardsModal

* feat(LOY-55): Add rewards filtering functionality

- Implement dynamic rewards filtering by category and membership level
- Create FilterRewardsModal component for filtering rewards
- Add useFilteredRewards hook to handle filtering logic
- Update rewards schema and constants to support new filtering features
- Remove hardcoded page size and replace with constant

* fix(LOY-55): reuse existing tier to friend map

* refactor(LOY-55): fix checkbox onChange type safety

* refactor(LOY-55): Improve rewards filtering type safety and validation

* refactor(LOY-55): Update filter modal border color using design token


Approved-by: Christian Andolf
2025-03-12 13:29:35 +00:00

159 lines
4.8 KiB
TypeScript

"use client"
import { useRef, useState } from "react"
import { REWARDS_PER_PAGE } from "@/constants/rewards"
import { trpc } from "@/lib/trpc/client"
import { RewardIcon } from "@/components/Blocks/DynamicContent/Rewards/RewardIcon"
import ScriptedRewardText from "@/components/Blocks/DynamicContent/Rewards/ScriptedRewardText"
import Pagination from "@/components/MyPages/Pagination"
import ExpirationDate from "@/components/Rewards/ExpirationDate"
import Grids from "@/components/TempDesignSystem/Grids"
import Title from "@/components/TempDesignSystem/Text/Title"
import { useFilteredRewards } from "@/hooks/rewards/useFilteredRewards"
import useLang from "@/hooks/useLang"
import { getEarliestExpirationDate } from "@/utils/rewards"
import Redeem from "../Redeem"
import FilterRewardsModal from "./FilterRewardsModal"
import styles from "./current.module.css"
import type { CurrentRewardsClientProps } from "@/types/components/myPages/myPage/accountPage"
import type { RewardCategory } from "@/types/components/myPages/rewards"
import type { MembershipLevelEnum } from "@/constants/membershipLevels"
import type {
Reward,
RewardWithRedeem,
} from "@/server/routers/contentstack/reward/output"
export default function ClientCurrentRewards({
rewards: initialData,
showRedeem,
membershipNumber,
}: CurrentRewardsClientProps) {
const lang = useLang()
const containerRef = useRef<HTMLDivElement>(null)
const [currentPage, setCurrentPage] = useState(1)
const [selectedCategories, setSelectedCategories] = useState<
RewardCategory[]
>([])
const [selectedLevels, setSelectedLevels] = useState<MembershipLevelEnum[]>(
[]
)
const { data } = trpc.contentstack.rewards.current.useQuery<{
rewards: (Reward | RewardWithRedeem)[]
}>(
{
lang,
},
{
initialData: { rewards: initialData },
}
)
const {
filteredRewards,
total,
availableTierLevels,
availableCategories,
hasFilterableOptions,
} = useFilteredRewards(
data?.rewards ?? [],
selectedCategories,
selectedLevels
)
if (!data) {
return null
}
function handleCategoriesChange(categories: RewardCategory[]) {
setSelectedCategories(categories)
setCurrentPage(1)
}
function handleLevelsChange(levels: MembershipLevelEnum[]) {
setSelectedLevels(levels)
setCurrentPage(1)
}
const startIndex = (currentPage - 1) * REWARDS_PER_PAGE
const endIndex = startIndex + REWARDS_PER_PAGE
const paginatedRewards = filteredRewards.slice(startIndex, endIndex)
const totalPages = Math.ceil(total / REWARDS_PER_PAGE)
function handlePageChange(page: number) {
requestAnimationFrame(() => {
setCurrentPage(page)
containerRef.current?.scrollIntoView({
behavior: "smooth",
block: "start",
inline: "nearest",
})
})
}
return (
<div ref={containerRef} className={styles.container}>
{showRedeem && hasFilterableOptions ? (
<FilterRewardsModal
selectedCategories={selectedCategories}
selectedLevels={selectedLevels}
availableCategories={availableCategories}
availableTierLevels={availableTierLevels}
onCategoriesChange={handleCategoriesChange}
onLevelsChange={handleLevelsChange}
/>
) : null}
<Grids.Stackable>
{paginatedRewards.map((reward, idx) => {
const earliestExpirationDate =
"coupons" in reward
? getEarliestExpirationDate(reward.coupons)
: null
return (
<article className={styles.card} key={`${reward.reward_id}-${idx}`}>
<div className={styles.content}>
<RewardIcon rewardId={reward.reward_id} />
{showRedeem && (
<ScriptedRewardText
rewardType={reward.rewardType}
rewardTierLevel={reward.rewardTierLevel}
/>
)}
<Title
as="h4"
level="h3"
textAlign="center"
textTransform="regular"
>
{reward.label}
</Title>
{earliestExpirationDate ? (
<ExpirationDate expirationDate={earliestExpirationDate} />
) : null}
</div>
{showRedeem && "redeem_description" in reward && (
<div className={styles.btnContainer}>
<Redeem reward={reward} membershipNumber={membershipNumber} />
</div>
)}
</article>
)
})}
</Grids.Stackable>
{totalPages > 1 && (
<Pagination
pageCount={totalPages}
currentPage={currentPage}
handlePageChange={handlePageChange}
/>
)}
</div>
)
}