Curation wave leaderboard filter n sort#2034
Conversation
Signed-off-by: Simo <simo@6529.io>
Signed-off-by: Simo <simo@6529.io>
Signed-off-by: Simo <simo@6529.io>
📝 WalkthroughWalkthroughAdds price-based filtering and a curation-drop modal to leaderboard flows, updates header measurement/layout logic for responsive action rendering, forwards price filters through leaderboard components and data hooks, replaces progressive debounce with batch-error-aware drop processing, and expands tests across these areas. Changes
Sequence Diagram(s)sequenceDiagram
participant User
participant WaveLeaderboardHeader
participant MyStreamWaveLeaderboard
participant WaveLeaderboardCurationDropModal
participant WaveDropCreate
participant API
User->>WaveLeaderboardHeader: Click "Create drop"
WaveLeaderboardHeader->>MyStreamWaveLeaderboard: onCreateDrop()
MyStreamWaveLeaderboard->>MyStreamWaveLeaderboard: check isCurationWave & canCreateDrop
alt curation wave & eligible
MyStreamWaveLeaderboard->>WaveLeaderboardCurationDropModal: open modal (isOpen=true)
WaveLeaderboardCurationDropModal->>WaveLeaderboardCurationDropModal: trap focus, disable body scroll
User->>WaveDropCreate: submit artwork
WaveDropCreate->>API: POST create drop
API-->>WaveDropCreate: success
WaveDropCreate->>WaveLeaderboardCurationDropModal: onSuccess -> onClose
WaveLeaderboardCurationDropModal->>MyStreamWaveLeaderboard: onClose()
WaveLeaderboardCurationDropModal->>WaveLeaderboardCurationDropModal: restore body scroll, restore focus
end
sequenceDiagram
participant User
participant WaveLeaderboardPriceFilters
participant WaveLeaderboardHeader
participant MyStreamWaveLeaderboard
participant useWaveDropsLeaderboard
participant API
User->>WaveLeaderboardHeader: Toggle price filters / enter min/max
WaveLeaderboardHeader->>WaveLeaderboardPriceFilters: show inputs
User->>WaveLeaderboardPriceFilters: set min/max
WaveLeaderboardPriceFilters->>WaveLeaderboardHeader: debounce -> onPriceRangeChange({min,max})
WaveLeaderboardHeader->>MyStreamWaveLeaderboard: propagate price change
MyStreamWaveLeaderboard->>useWaveDropsLeaderboard: pass minPrice/maxPrice/priceCurrency
useWaveDropsLeaderboard->>API: fetch drops with min_price/max_price/price_currency
API-->>useWaveDropsLeaderboard: return filtered results
useWaveDropsLeaderboard-->>WaveLeaderboardHeader: update list rendering
Estimated code review effort🎯 4 (Complex) | ⏱️ ~60 minutes Possibly related PRs
Suggested reviewers
Poem
🚥 Pre-merge checks | ✅ 2 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 8f90adf20c
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
There was a problem hiding this comment.
Actionable comments posted: 2
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
hooks/useWaveDropsLeaderboard.ts (1)
107-124:⚠️ Potential issue | 🟡 MinorNormalize empty currency to
undefinedto avoid cache key fragmentation.A whitespace-only currency becomes
""in the query key but is omitted from request params, so semantically identical requests can hash to different cache keys.Suggested fix
- const normalizedPriceCurrency = useMemo( - () => priceCurrency?.trim() ?? undefined, - [priceCurrency] - ); + const normalizedPriceCurrency = useMemo(() => { + const trimmed = priceCurrency?.trim(); + return trimmed ? trimmed : undefined; + }, [priceCurrency]);🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@hooks/useWaveDropsLeaderboard.ts` around lines 107 - 124, The normalizedPriceCurrency currently uses priceCurrency?.trim() ?? undefined which leaves a trimmed empty string ("") instead of undefined and can fragment the query key; update the useMemo for normalizedPriceCurrency (in useWaveDropsLeaderboard.ts) to explicitly convert a trimmed empty string to undefined (e.g. compute const t = priceCurrency?.trim(); return t === "" ? undefined : t) so the queryKey generation (where normalizedPriceCurrency is used) treats empty/whitespace currency as undefined rather than "".
🧹 Nitpick comments (4)
__tests__/components/waves/CreateDrop.test.tsx (1)
156-158: Consider adding a test for the error path.The test verifies
onAllDropsAddedis called on success, but there's no corresponding test verifying it is not called when a batch error occurs. This would ensure thehasBatchErrorsReflogic works correctly in both directions.💡 Suggested test case
it("does not call onAllDropsAdded when submission fails", async () => { const onAllDropsAdded = jest.fn(); commonApiPostMock.mockRejectedValueOnce(new Error("Network error")); render( <AuthContext.Provider value={{ setToast: jest.fn() } as any}> <ReactQueryWrapperContext.Provider value={{ waitAndInvalidateDrops: jest.fn() } as any} > <CreateDrop activeDrop={null} onCancelReplyQuote={() => {}} onDropAddedToQueue={jest.fn()} wave={wave} dropId={null} fixedDropMode={"BOTH" as any} privileges={{} as any} onAllDropsAdded={onAllDropsAdded} /> </ReactQueryWrapperContext.Provider> </AuthContext.Provider> ); await userEvent.click(screen.getByText("submit")); await waitFor(() => expect(commonApiPostMock).toHaveBeenCalled()); expect(onAllDropsAdded).not.toHaveBeenCalled(); });🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@__tests__/components/waves/CreateDrop.test.tsx` around lines 156 - 158, Add a new test that asserts the error path: render CreateDrop (using the same props as existing tests, including onAllDropsAdded jest.fn()), mock commonApiPostMock to reject (e.g., mockRejectedValueOnce(new Error("Network error"))), trigger the submit (userEvent.click on "submit"), wait for commonApiPostMock to have been called, and then assert that onAllDropsAdded was not called; this verifies the hasBatchErrorsRef logic in CreateDrop prevents calling onAllDropsAdded on failure.components/waves/CreateCurationDropContent.tsx (1)
390-397: Consider renaming the prop for clarity.Passing
tw-w-fullthrough thepaddingprop works but is semantically misleading sincetw-w-fullisn't padding-related. The PrimaryButton'spaddingprop appears to be a general className extension point, but naming it "padding" suggests it should only contain padding values.This is a minor naming concern that doesn't affect functionality.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@components/waves/CreateCurationDropContent.tsx` around lines 390 - 397, The prop name padding on PrimaryButton is misleading because it accepts general classes like tw-w-full; rename the prop to something semantically accurate (e.g., className, extraClass, or containerClass) by updating the PrimaryButton component's prop type and JSX usage (replace padding with the new prop name inside the PrimaryButton implementation and where it is applied to the element), then update this call site (the PrimaryButton in CreateCurationDropContent with onClicked={onDrop} loading={submitting} disabled={!canSubmit}) to pass the new prop instead of padding; optionally add a short compatibility alias (keep padding as a deprecated prop that maps to the new prop with a TODO) to avoid breaking other callers and update any prop typings and tests accordingly.__tests__/components/waves/leaderboard/create/WaveLeaderboardCurationDropModal.test.tsx (1)
72-96: Use the real pre-test overflow baseline instead of hardcoding"scroll".Capture the current value from
document.body.style.overflowbefore rendering, then assert restoration to that captured value. This avoids environment-coupled assumptions.Proposed test-hardening diff
- const originalOverflow = "scroll"; - document.body.style.overflow = originalOverflow; + const originalOverflow = document.body.style.overflow;🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@__tests__/components/waves/leaderboard/create/WaveLeaderboardCurationDropModal.test.tsx` around lines 72 - 96, The test in WaveLeaderboardCurationDropModal.test.tsx hardcodes originalOverflow as "scroll"; capture the real baseline by reading document.body.style.overflow into a variable (e.g., originalOverflow) before rendering the component, then assert after closing that document.body.style.overflow equals that captured value; update the assertions in the test that references WaveLeaderboardCurationDropModal to use the captured originalOverflow instead of the literal "scroll".components/brain/my-stream/MyStreamWaveLeaderboard.tsx (1)
67-69: Decouple local filter/modal state from parent remount behavior.Consider resetting curation-local state on
wave.idchange in-component. This prevents stale price filters/modal state if this component is ever reused without a remount key.Suggested hardening
const [isCurationDropModalOpen, setIsCurationDropModalOpen] = useState(false); const [minPrice, setMinPrice] = useState<number | undefined>(undefined); const [maxPrice, setMaxPrice] = useState<number | undefined>(undefined); + + useEffect(() => { + setIsCurationDropModalOpen(false); + setMinPrice(undefined); + setMaxPrice(undefined); + }, [wave.id]);Also applies to: 200-212
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@components/brain/my-stream/MyStreamWaveLeaderboard.tsx` around lines 67 - 69, The component keeps curation/filter UI state (e.g., minPrice, maxPrice and modal-related state) that can become stale when the parent reuses the component for a different wave; add a useEffect that listens for changes to wave.id and resets those local states to their initial values (call setMinPrice(undefined), setMaxPrice(undefined) and also reset/close any modal state such as setIsFilterModalOpen(false), setIsCurationModalOpen(false), setSelectedCuration(undefined) or similar variables used around lines 200-212) so the component clears filters and modals whenever wave.id changes.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@components/waves/leaderboard/create/WaveLeaderboardCurationDropModal.tsx`:
- Around line 49-103: The modal in WaveLeaderboardCurationDropModal lacks focus
trapping and initial-focus handling, so implement focus management: either swap
the native <dialog> for an accessible dialog primitive (e.g., HeadlessUI/Dialog
or react-aria/Dialog) or add a focus-trap effect in the
WaveLeaderboardCurationDropModal component that (1) sets initial focus to a
known element (use the close button with aria-label "Close modal" or the heading
with id "leaderboard-drop-art-title"), (2) traps Tab/Shift+Tab inside the panel
referenced by data-testid "curation-drop-modal-panel", (3) closes on Escape by
calling onClose, and (4) restores focus to the previously focused element when
closed; ensure the close button and WaveDropCreate remain focusable and wired to
the trap/restore logic.
In `@hooks/useWaveDropsLeaderboard.ts`:
- Around line 166-174: The code currently forwards inverted price ranges
(normalizedMinPrice > normalizedMaxPrice) to params, causing empty results;
update the logic in useWaveDropsLeaderboard (around normalizedMinPrice,
normalizedMaxPrice, params) to detect when both normalizedMinPrice and
normalizedMaxPrice are numbers and normalizedMinPrice > normalizedMaxPrice, then
either swap them (set min_price = normalizedMaxPrice.toString() and max_price =
normalizedMinPrice.toString()) or skip both filters—apply the chosen
normalization before assigning params["min_price"] and params["max_price"] so
the API never receives an inverted range.
---
Outside diff comments:
In `@hooks/useWaveDropsLeaderboard.ts`:
- Around line 107-124: The normalizedPriceCurrency currently uses
priceCurrency?.trim() ?? undefined which leaves a trimmed empty string ("")
instead of undefined and can fragment the query key; update the useMemo for
normalizedPriceCurrency (in useWaveDropsLeaderboard.ts) to explicitly convert a
trimmed empty string to undefined (e.g. compute const t = priceCurrency?.trim();
return t === "" ? undefined : t) so the queryKey generation (where
normalizedPriceCurrency is used) treats empty/whitespace currency as undefined
rather than "".
---
Nitpick comments:
In `@__tests__/components/waves/CreateDrop.test.tsx`:
- Around line 156-158: Add a new test that asserts the error path: render
CreateDrop (using the same props as existing tests, including onAllDropsAdded
jest.fn()), mock commonApiPostMock to reject (e.g., mockRejectedValueOnce(new
Error("Network error"))), trigger the submit (userEvent.click on "submit"), wait
for commonApiPostMock to have been called, and then assert that onAllDropsAdded
was not called; this verifies the hasBatchErrorsRef logic in CreateDrop prevents
calling onAllDropsAdded on failure.
In
`@__tests__/components/waves/leaderboard/create/WaveLeaderboardCurationDropModal.test.tsx`:
- Around line 72-96: The test in WaveLeaderboardCurationDropModal.test.tsx
hardcodes originalOverflow as "scroll"; capture the real baseline by reading
document.body.style.overflow into a variable (e.g., originalOverflow) before
rendering the component, then assert after closing that
document.body.style.overflow equals that captured value; update the assertions
in the test that references WaveLeaderboardCurationDropModal to use the captured
originalOverflow instead of the literal "scroll".
In `@components/brain/my-stream/MyStreamWaveLeaderboard.tsx`:
- Around line 67-69: The component keeps curation/filter UI state (e.g.,
minPrice, maxPrice and modal-related state) that can become stale when the
parent reuses the component for a different wave; add a useEffect that listens
for changes to wave.id and resets those local states to their initial values
(call setMinPrice(undefined), setMaxPrice(undefined) and also reset/close any
modal state such as setIsFilterModalOpen(false), setIsCurationModalOpen(false),
setSelectedCuration(undefined) or similar variables used around lines 200-212)
so the component clears filters and modals whenever wave.id changes.
In `@components/waves/CreateCurationDropContent.tsx`:
- Around line 390-397: The prop name padding on PrimaryButton is misleading
because it accepts general classes like tw-w-full; rename the prop to something
semantically accurate (e.g., className, extraClass, or containerClass) by
updating the PrimaryButton component's prop type and JSX usage (replace padding
with the new prop name inside the PrimaryButton implementation and where it is
applied to the element), then update this call site (the PrimaryButton in
CreateCurationDropContent with onClicked={onDrop} loading={submitting}
disabled={!canSubmit}) to pass the new prop instead of padding; optionally add a
short compatibility alias (keep padding as a deprecated prop that maps to the
new prop with a TODO) to avoid breaking other callers and update any prop
typings and tests accordingly.
ℹ️ Review info
Configuration used: defaults
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (25)
__tests__/components/brain/my-stream/MyStreamWave.test.tsx__tests__/components/brain/my-stream/MyStreamWaveLeaderboard.test.tsx__tests__/components/waves/CreateCurationDropContent.supportedUrls.test.tsx__tests__/components/waves/CreateDrop.test.tsx__tests__/components/waves/leaderboard/create/WaveLeaderboardCurationDropModal.test.tsx__tests__/components/waves/leaderboard/header/WaveleaderboardHeader.test.tsx__tests__/components/waves/leaderboard/header/WaveleaderboardSort.test.tsx__tests__/components/waves/leaderboard/header/waveLeaderboardHeaderLayout.test.ts__tests__/hooks/useWaveDropsLeaderboard.extra.test.tscomponents/brain/BrainMobile.tsxcomponents/brain/my-stream/MyStreamWave.tsxcomponents/brain/my-stream/MyStreamWaveLeaderboard.tsxcomponents/waves/CreateCurationDropContent.tsxcomponents/waves/CreateCurationDropUrlInput.tsxcomponents/waves/CreateDrop.tsxcomponents/waves/leaderboard/create/WaveLeaderboardCurationDropModal.tsxcomponents/waves/leaderboard/drops/WaveLeaderboardDrops.tsxcomponents/waves/leaderboard/gallery/WaveLeaderboardGallery.tsxcomponents/waves/leaderboard/grid/WaveLeaderboardGrid.tsxcomponents/waves/leaderboard/header/WaveleaderboardHeader.tsxcomponents/waves/leaderboard/header/WaveleaderboardSort.tsxcomponents/waves/leaderboard/header/useLeaderboardHeaderControlMeasurements.tscomponents/waves/leaderboard/header/waveLeaderboardHeaderLayout.tshooks/useProgressiveDebounce.tshooks/useWaveDropsLeaderboard.ts
💤 Files with no reviewable changes (1)
- hooks/useProgressiveDebounce.ts
There was a problem hiding this comment.
Actionable comments posted: 1
🧹 Nitpick comments (1)
__tests__/hooks/useWaveDropsLeaderboard.extra.test.ts (1)
120-146: Consider also asserting canonicalized bounds in thequeryKey.This test validates swapped request params, but adding query-key assertions would prevent cache fragmentation regressions for inverted bounds.
Test addition
@@ const call = (queryClientMock.prefetchInfiniteQuery as jest.Mock).mock .calls[0][0]; + expect(call.queryKey[1].min_price).toBe(0.5); + expect(call.queryKey[1].max_price).toBe(2.75); await call.queryFn({ pageParam: null });🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@__tests__/hooks/useWaveDropsLeaderboard.extra.test.ts` around lines 120 - 146, The test currently asserts that inverted min/max are swapped in the request params but doesn't assert the canonicalized bounds are used in the queryKey, which can cause cache fragmentation; update the test (around useWaveDropsLeaderboard renderHook and the captured prefetchInfiniteQuery call via queryClientMock.prefetchInfiniteQuery.mock.calls[0][0]) to also assert that call.queryKey (or the first mock argument representing the query key) contains the canonicalized parameters (min_price: "0.5", max_price: "2.75", price_currency: "ETH", waveId: "2", sort: WaveDropsLeaderboardSort.PRICE) so the key is identical regardless of input order. Ensure you reference the same captured call object (the variable named call in the test) when adding the queryKey assertion.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@hooks/useWaveDropsLeaderboard.ts`:
- Around line 107-110: The cache key and request params are using inconsistent
canonicalized price filters causing duplicate caches; update
useWaveDropsLeaderboard to compute canonicalized filter values once (e.g.,
replace the current normalizedPriceCurrency useMemo and compute
normalizedPriceLower/normalizedPriceUpper as trimmed/undefined or
numeric-normalized bounds) and use those same normalized symbols for both the
queryKey and the API request params; ensure empty/whitespace currency becomes
undefined (not "") and the price lower/upper are normalized in the same order
for key and params so the queryKey exactly matches the request parameters.
---
Nitpick comments:
In `@__tests__/hooks/useWaveDropsLeaderboard.extra.test.ts`:
- Around line 120-146: The test currently asserts that inverted min/max are
swapped in the request params but doesn't assert the canonicalized bounds are
used in the queryKey, which can cause cache fragmentation; update the test
(around useWaveDropsLeaderboard renderHook and the captured
prefetchInfiniteQuery call via
queryClientMock.prefetchInfiniteQuery.mock.calls[0][0]) to also assert that
call.queryKey (or the first mock argument representing the query key) contains
the canonicalized parameters (min_price: "0.5", max_price: "2.75",
price_currency: "ETH", waveId: "2", sort: WaveDropsLeaderboardSort.PRICE) so the
key is identical regardless of input order. Ensure you reference the same
captured call object (the variable named call in the test) when adding the
queryKey assertion.
ℹ️ Review info
Configuration used: defaults
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (9)
__tests__/components/waves/leaderboard/create/WaveLeaderboardCurationDropModal.test.tsx__tests__/components/waves/leaderboard/header/WaveleaderboardHeader.test.tsx__tests__/components/waves/leaderboard/header/waveLeaderboardHeaderLayout.test.ts__tests__/hooks/useWaveDropsLeaderboard.extra.test.tscomponents/waves/leaderboard/create/WaveLeaderboardCurationDropModal.tsxcomponents/waves/leaderboard/header/WaveleaderboardHeader.tsxcomponents/waves/leaderboard/header/useLeaderboardHeaderControlMeasurements.tscomponents/waves/leaderboard/header/waveLeaderboardHeaderLayout.tshooks/useWaveDropsLeaderboard.ts
|
There was a problem hiding this comment.
♻️ Duplicate comments (1)
hooks/useWaveDropsLeaderboard.ts (1)
94-95:⚠️ Potential issue | 🟠 MajorNormalize whitespace-only
priceCurrencytoundefinedbefore canonicalization.At Line 94,
priceCurrency?.trim() ?? undefinedpreserves""for whitespace-only input. That empty string is kept inqueryKeyat Line 141, but omitted from API params at Line 189, so cache key and request params diverge.Suggested fix
- const normalizedPriceCurrency = priceCurrency?.trim() ?? undefined; + const trimmedPriceCurrency = priceCurrency?.trim(); + const normalizedPriceCurrency = trimmedPriceCurrency + ? trimmedPriceCurrency + : undefined;Also applies to: 141-141, 189-191
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@hooks/useWaveDropsLeaderboard.ts` around lines 94 - 95, Normalize whitespace-only priceCurrency to undefined by replacing the current normalization (normalizedPriceCurrency = priceCurrency?.trim() ?? undefined) with logic that treats an empty trimmed string as undefined; update the value used in the query key construction (where normalizedPriceCurrency is referenced) so the queryKey and the API params use the same canonical value, and ensure the same normalizedPriceCurrency variable is used when building the request params in the function (so priceCurrency, normalizedPriceCurrency, and the queryKey generation are consistent).
🧹 Nitpick comments (1)
components/waves/leaderboard/header/WaveleaderboardHeader.tsx (1)
702-704: Includewave.idin the price-filter key to avoid cross-wave draft carryover.If
minPrice/maxPriceare unchanged between waves, the current key can reuse the same component instance and preserve prior draft input state.Suggested refactor
- key={`${minPrice ?? ""}|${maxPrice ?? ""}`} + key={`${wave.id}|${minPrice ?? ""}|${maxPrice ?? ""}`}🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@components/waves/leaderboard/header/WaveleaderboardHeader.tsx` around lines 702 - 704, The WaveLeaderboardPriceFilters component key currently uses only minPrice and maxPrice which can cause retained draft state across different waves; update the key for WaveLeaderboardPriceFilters to also include wave.id (for example key={`${wave.id}|${minPrice ?? ""}|${maxPrice ?? ""}`) so each wave renders a fresh instance tied to its waveId, preventing cross-wave draft carryover while keeping minPrice/maxPrice in the key.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Duplicate comments:
In `@hooks/useWaveDropsLeaderboard.ts`:
- Around line 94-95: Normalize whitespace-only priceCurrency to undefined by
replacing the current normalization (normalizedPriceCurrency =
priceCurrency?.trim() ?? undefined) with logic that treats an empty trimmed
string as undefined; update the value used in the query key construction (where
normalizedPriceCurrency is referenced) so the queryKey and the API params use
the same canonical value, and ensure the same normalizedPriceCurrency variable
is used when building the request params in the function (so priceCurrency,
normalizedPriceCurrency, and the queryKey generation are consistent).
---
Nitpick comments:
In `@components/waves/leaderboard/header/WaveleaderboardHeader.tsx`:
- Around line 702-704: The WaveLeaderboardPriceFilters component key currently
uses only minPrice and maxPrice which can cause retained draft state across
different waves; update the key for WaveLeaderboardPriceFilters to also include
wave.id (for example key={`${wave.id}|${minPrice ?? ""}|${maxPrice ?? ""}`) so
each wave renders a fresh instance tied to its waveId, preventing cross-wave
draft carryover while keeping minPrice/maxPrice in the key.
ℹ️ Review info
Configuration used: defaults
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (3)
__tests__/hooks/useWaveDropsLeaderboard.extra.test.tscomponents/waves/leaderboard/header/WaveleaderboardHeader.tsxhooks/useWaveDropsLeaderboard.ts



Summary by CodeRabbit
New Features
Improvements
Tests