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
This commit is contained in:
@@ -2,6 +2,7 @@
|
||||
|
||||
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"
|
||||
@@ -10,14 +11,18 @@ 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,
|
||||
@@ -25,13 +30,18 @@ import type {
|
||||
|
||||
export default function ClientCurrentRewards({
|
||||
rewards: initialData,
|
||||
pageSize,
|
||||
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)[]
|
||||
@@ -44,16 +54,36 @@ export default function ClientCurrentRewards({
|
||||
}
|
||||
)
|
||||
|
||||
const {
|
||||
filteredRewards,
|
||||
total,
|
||||
availableTierLevels,
|
||||
availableCategories,
|
||||
hasFilterableOptions,
|
||||
} = useFilteredRewards(
|
||||
data?.rewards ?? [],
|
||||
selectedCategories,
|
||||
selectedLevels
|
||||
)
|
||||
|
||||
if (!data) {
|
||||
return null
|
||||
}
|
||||
|
||||
const rewards = data.rewards
|
||||
function handleCategoriesChange(categories: RewardCategory[]) {
|
||||
setSelectedCategories(categories)
|
||||
setCurrentPage(1)
|
||||
}
|
||||
|
||||
const totalPages = Math.ceil(rewards.length / pageSize)
|
||||
const startIndex = (currentPage - 1) * pageSize
|
||||
const endIndex = startIndex + pageSize
|
||||
const currentRewards = rewards.slice(startIndex, endIndex)
|
||||
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(() => {
|
||||
@@ -68,8 +98,18 @@ export default function ClientCurrentRewards({
|
||||
|
||||
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>
|
||||
{currentRewards.map((reward, idx) => {
|
||||
{paginatedRewards.map((reward, idx) => {
|
||||
const earliestExpirationDate =
|
||||
"coupons" in reward
|
||||
? getEarliestExpirationDate(reward.coupons)
|
||||
@@ -93,12 +133,10 @@ export default function ClientCurrentRewards({
|
||||
>
|
||||
{reward.label}
|
||||
</Title>
|
||||
|
||||
{earliestExpirationDate ? (
|
||||
<ExpirationDate expirationDate={earliestExpirationDate} />
|
||||
) : null}
|
||||
</div>
|
||||
|
||||
{showRedeem && "redeem_description" in reward && (
|
||||
<div className={styles.btnContainer}>
|
||||
<Redeem reward={reward} membershipNumber={membershipNumber} />
|
||||
|
||||
Reference in New Issue
Block a user