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:
Chuma Mcphoy (We Ahead)
2025-03-12 13:29:35 +00:00
parent 2e887aaff8
commit 1ef6fd02c1
12 changed files with 594 additions and 12 deletions

View File

@@ -0,0 +1,79 @@
import { useMemo } from "react"
import { isMembershipLevel } from "@/utils/membershipLevels"
import { isRewardCategory } from "@/utils/rewards"
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 function useFilteredRewards(
rewards: (Reward | RewardWithRedeem)[],
selectedCategories: RewardCategory[] = [],
selectedLevels: MembershipLevelEnum[] = []
) {
const availableCategories = Array.from(
new Set(
rewards
.flatMap((reward) => reward.categories || [])
.filter((category) => isRewardCategory(category))
)
).sort()
const availableTierLevels = Array.from(
new Set(
rewards
.map((reward) => reward.rewardTierLevel)
.filter(
(level): level is MembershipLevelEnum =>
typeof level === "string" && isMembershipLevel(level)
)
)
)
const hasFilterableOptions =
availableCategories.length > 0 || availableTierLevels.length > 0
const filteredRewards = useMemo(() => {
const hasSelectedCategoryFilter = selectedCategories.length > 0
const hasSelectedLevelFilter = selectedLevels.length > 0
if (!hasSelectedCategoryFilter && !hasSelectedLevelFilter) {
return rewards
}
const useOrLogic = hasSelectedCategoryFilter && hasSelectedLevelFilter
return rewards.filter((reward) => {
const matchesCategory =
!hasSelectedCategoryFilter ||
(reward.categories?.some(
(category) =>
isRewardCategory(category) && selectedCategories.includes(category)
) ??
false)
const matchesLevel =
!hasSelectedLevelFilter ||
(reward.rewardTierLevel &&
isMembershipLevel(reward.rewardTierLevel) &&
selectedLevels.includes(reward.rewardTierLevel))
// Apply OR logic if both filters are active, otherwise AND
return useOrLogic
? matchesCategory || matchesLevel
: matchesCategory && matchesLevel
})
}, [rewards, selectedCategories, selectedLevels])
return {
filteredRewards,
total: filteredRewards.length,
availableTierLevels,
availableCategories,
hasFilterableOptions,
}
}