From 9c0e51cbd1a70ef1f34e8f5cd2503f14722ccb36 Mon Sep 17 00:00:00 2001 From: Dylan Jeffers Date: Thu, 7 May 2026 09:49:41 -0700 Subject: [PATCH 1/4] [Contests] Use per-user endpoint for profile tab + has-contest check The profile Contests tab and the upstream useUserHasRemixContest gate both fetched the global remix-contests list and filtered client-side by host userId. Hosts whose contests didn't fall on page 1 of the global list silently rendered as "no contests" unless we paginated up to a safety cap of 5 pages. Switch both to the new GET /v1/users/{id}/contests endpoint via a new useUserRemixContests hook. The has-contest gate becomes a single pageSize=1 query, and the tab drops the per-row HostedContestCard filter and the auto-pagination effect. SDK regen picked up the new users.getContestsByUser method and the GetContestsByUserStatusEnum. Co-Authored-By: Claude Opus 4.7 (1M context) --- .../common/src/api/tan-query/events/index.ts | 1 + .../events/useUserHasRemixContest.ts | 83 ++-------- .../tan-query/events/useUserRemixContests.ts | 143 ++++++++++++++++++ .../common/src/api/tan-query/queryKeys.ts | 1 + .../api/generated/default/apis/UsersApi.ts | 71 +++++++++ .../components/desktop/ContestsTab.tsx | 71 ++------- 6 files changed, 243 insertions(+), 127 deletions(-) create mode 100644 packages/common/src/api/tan-query/events/useUserRemixContests.ts diff --git a/packages/common/src/api/tan-query/events/index.ts b/packages/common/src/api/tan-query/events/index.ts index d7f7bb75b0f..58487ae1827 100644 --- a/packages/common/src/api/tan-query/events/index.ts +++ b/packages/common/src/api/tan-query/events/index.ts @@ -9,6 +9,7 @@ export * from './useFollowEvent' export * from './useRemixContest' export * from './useRemixContestWinners' export * from './useUserHasRemixContest' +export * from './useUserRemixContests' // Mutations export * from './useCreateEvent' diff --git a/packages/common/src/api/tan-query/events/useUserHasRemixContest.ts b/packages/common/src/api/tan-query/events/useUserHasRemixContest.ts index e458e59f287..ba526ad1adb 100644 --- a/packages/common/src/api/tan-query/events/useUserHasRemixContest.ts +++ b/packages/common/src/api/tan-query/events/useUserHasRemixContest.ts @@ -1,78 +1,27 @@ -import { useEffect, useMemo } from 'react' - -import { EventEntityTypeEnum, EventEventTypeEnum } from '@audius/sdk' -import { useQueryClient } from '@tanstack/react-query' - import { ID } from '~/models' -import { Event } from '~/models/Event' -import { useAllRemixContests } from './useAllRemixContests' -import { getEventIdsByEntityIdQueryKey, getEventQueryKey } from './utils' +import { useUserRemixContests } from './useUserRemixContests' -const PAGE_SIZE = 50 -const MAX_PAGES_TO_LOAD = 5 +const PAGE_SIZE = 1 /** - * Returns whether the given user hosts any remix contest. The discovery - * endpoint behind `useAllRemixContests` doesn't yet support filtering by - * host userId, so the global list is paginated client-side and matched - * against `event.userId`. Mirrors the pagination cap used by the profile - * Contests tab so a profile can decide whether to show the tab at all. - * - * The events returned by the SDK are primed into the React Query cache by - * `useAllRemixContests`, so the per-track lookup here is a synchronous cache - * read in practice. + * Returns whether the given user hosts any remix contest. Calls the + * per-user endpoint (`useUserRemixContests`) with `pageSize: 1` so a + * positive answer is one row, and a negative answer is one query — no + * client-side scanning of the global list. */ export const useUserHasRemixContest = (hostUserId: ID | null | undefined) => { - const queryClient = useQueryClient() - const enabled = hostUserId != null - const { - data: trackIds, - isPending, - isFetching, - hasNextPage, - isFetchingNextPage, - fetchNextPage - } = useAllRemixContests({ pageSize: PAGE_SIZE }, { enabled }) - - const loadedPages = trackIds ? Math.ceil(trackIds.length / PAGE_SIZE) : 0 - - useEffect(() => { - if ( - hostUserId != null && - hasNextPage && - !isFetchingNextPage && - loadedPages < MAX_PAGES_TO_LOAD - ) { - fetchNextPage() - } - }, [hostUserId, hasNextPage, isFetchingNextPage, loadedPages, fetchNextPage]) - - const hasContest = useMemo(() => { - if (!hostUserId || !trackIds) return false - return trackIds.some((trackId) => { - const eventIds = queryClient.getQueryData( - getEventIdsByEntityIdQueryKey({ - entityId: trackId, - entityType: EventEntityTypeEnum.Track, - eventType: EventEventTypeEnum.RemixContest - }) - ) - const eventId = eventIds?.[0] - if (!eventId) return false - const event = queryClient.getQueryData(getEventQueryKey(eventId)) - return event?.userId === hostUserId - }) - }, [hostUserId, trackIds, queryClient]) - - // Match-not-yet-found is ambiguous while pages are still being fetched — - // surface that so callers can hold off on hiding the tab and avoid a - // late "tab appears" flash for hosts whose contests sit on later pages. - const isResolving = - isPending || - (!hasContest && - (isFetching || (hasNextPage && loadedPages < MAX_PAGES_TO_LOAD))) + const { data: trackIds, isPending, isFetching } = useUserRemixContests( + { userId: hostUserId, pageSize: PAGE_SIZE }, + { enabled } + ) + + const hasContest = (trackIds?.length ?? 0) > 0 + // While the first page is still loading the answer is ambiguous; surface + // that so callers can hold off on hiding the tab and avoid a late + // "tab appears" flash. + const isResolving = isPending || (!hasContest && isFetching) return { hasContest, isPending: isResolving } } diff --git a/packages/common/src/api/tan-query/events/useUserRemixContests.ts b/packages/common/src/api/tan-query/events/useUserRemixContests.ts new file mode 100644 index 00000000000..9d0a7d0d77d --- /dev/null +++ b/packages/common/src/api/tan-query/events/useUserRemixContests.ts @@ -0,0 +1,143 @@ +import { + EventEntityTypeEnum, + EventEventTypeEnum, + GetContestsByUserStatusEnum, + Id, + OptionalHashId, + Event as SDKEvent +} from '@audius/sdk' +import { useInfiniteQuery, useQueryClient } from '@tanstack/react-query' + +import { eventMetadataFromSDK } from '~/adapters/event' +import { getRemixesQueryKey } from '~/api/tan-query/remixes/useRemixes' +import { useQueryContext } from '~/api/tan-query/utils' +import { primeRelatedData } from '~/api/tan-query/utils/primeRelatedData' +import { ID } from '~/models' +import { removeNullable } from '~/utils' + +import { QUERY_KEYS } from '../queryKeys' +import { QueryKey, QueryOptions } from '../types' + +import { getEventIdsByEntityIdQueryKey, getEventQueryKey } from './utils' + +const DEFAULT_PAGE_SIZE = 25 + +export type UserRemixContestStatus = GetContestsByUserStatusEnum + +type UseUserRemixContestsArgs = { + userId: ID | null | undefined + pageSize?: number + /** + * Filter by contest status. Defaults to `'all'` (the backend's default), + * which returns active contests first (ordered by soonest-ending end_date) + * followed by ended contests (most-recently-ended first). + */ + status?: UserRemixContestStatus +} + +export const getUserRemixContestsQueryKey = ({ + userId, + pageSize = DEFAULT_PAGE_SIZE, + status = GetContestsByUserStatusEnum.All +}: UseUserRemixContestsArgs) => + [ + QUERY_KEYS.userRemixContests, + { userId, pageSize, status } + ] as unknown as QueryKey + +/** + * Hook to fetch remix contest events hosted by a specific user with infinite + * query support. Calls the dedicated endpoint + * `GET /v1/users/{id}/contests` (SDK: `users.getContestsByUser`), which returns + * events ordered with currently-active contests first (by soonest-ending + * end_date) followed by ended contests. + * + * Each page is mapped to the remix contest's parent track ID + * (`event.entityId`) so consumers like `ContestCard` can receive a + * `trackId` prop and resolve the event internally via `useRemixContest`. + */ +export const useUserRemixContests = ( + { + userId, + pageSize = DEFAULT_PAGE_SIZE, + status = GetContestsByUserStatusEnum.All + }: UseUserRemixContestsArgs, + options?: QueryOptions +) => { + const { audiusSdk } = useQueryContext() + const queryClient = useQueryClient() + + return useInfiniteQuery({ + queryKey: getUserRemixContestsQueryKey({ userId, pageSize, status }), + initialPageParam: 0, + getNextPageParam: (lastPage: ID[], allPages) => { + if (lastPage.length < pageSize) return undefined + return allPages.length * pageSize + }, + queryFn: async ({ pageParam }) => { + const sdk = await audiusSdk() + const { data, related } = await sdk.users.getContestsByUser({ + id: Id.parse(userId), + limit: pageSize, + offset: pageParam as number, + status + }) + + // Prime related tracks + users (full objects, delivered alongside the + // event list on the per-user endpoint, same shape as the discovery + // endpoint). + primeRelatedData({ related, queryClient }) + + // Prime useRemixes({ trackId, pageSize: 0, isContestEntry: true }) so + // ContestCard's entry-count badge doesn't fire a count-only request + // per card. + const entryCounts = related?.entryCounts ?? {} + for (const [hashedTrackId, count] of Object.entries(entryCounts)) { + const trackId = OptionalHashId.parse(hashedTrackId) + if (!trackId) continue + queryClient.setQueryData( + getRemixesQueryKey({ + trackId, + pageSize: 0, + isContestEntry: true + }), + { + pages: [{ count, tracks: [] }], + pageParams: [0] + } as unknown as never + ) + } + + if (!data) return [] + + return data + .map((sdkEvent: SDKEvent) => { + const event = eventMetadataFromSDK(sdkEvent) + if (!event) return null + // Prime the per-event cache so useEvent hits immediately downstream. + queryClient.setQueryData(getEventQueryKey(event.eventId), event) + // useRemixContest resolves via useEventIdsByEntityId keyed by + // (entityId, entityType=Track, eventType=RemixContest). Prime that + // lookup too so the card doesn't have to re-fetch the event list. + if ( + event.entityId && + event.entityType === EventEntityTypeEnum.Track + ) { + queryClient.setQueryData( + getEventIdsByEntityIdQueryKey({ + entityId: event.entityId, + entityType: EventEntityTypeEnum.Track, + eventType: EventEventTypeEnum.RemixContest + }), + [event.eventId] + ) + } + return event.entityId ?? null + }) + .filter(removeNullable) + }, + enabled: !!userId && options?.enabled !== false, + select: (data) => data.pages.flat(), + ...options + }) +} diff --git a/packages/common/src/api/tan-query/queryKeys.ts b/packages/common/src/api/tan-query/queryKeys.ts index 00f6adaae43..1183b8dddee 100644 --- a/packages/common/src/api/tan-query/queryKeys.ts +++ b/packages/common/src/api/tan-query/queryKeys.ts @@ -97,6 +97,7 @@ export const QUERY_KEYS = { events: 'events', eventsByEntityId: 'eventsByEntityId', remixContestsList: 'remixContestsList', + userRemixContests: 'userRemixContests', walletOwner: 'walletOwner', tokenPrice: 'tokenPrice', usdcBalance: 'usdcBalance', diff --git a/packages/sdk/src/sdk/api/generated/default/apis/UsersApi.ts b/packages/sdk/src/sdk/api/generated/default/apis/UsersApi.ts index 94ca5d3e120..c2b8fb2e693 100644 --- a/packages/sdk/src/sdk/api/generated/default/apis/UsersApi.ts +++ b/packages/sdk/src/sdk/api/generated/default/apis/UsersApi.ts @@ -46,6 +46,7 @@ import type { PurchasesCountResponse, PurchasesResponse, RelatedArtistResponse, + RemixContestsResponse, RemixersCountResponse, RemixersResponse, Reposts, @@ -139,6 +140,8 @@ import { PurchasesResponseToJSON, RelatedArtistResponseFromJSON, RelatedArtistResponseToJSON, + RemixContestsResponseFromJSON, + RemixContestsResponseToJSON, RemixersCountResponseFromJSON, RemixersCountResponseToJSON, RemixersResponseFromJSON, @@ -314,6 +317,13 @@ export interface GetConnectedWalletsRequest { id: string; } +export interface GetContestsByUserRequest { + id: string; + offset?: number; + limit?: number; + status?: GetContestsByUserStatusEnum; +} + export interface GetFollowersRequest { id: string; offset?: number; @@ -1705,6 +1715,58 @@ export class UsersApi extends runtime.BaseAPI { return await response.value(); } + /** + * @hidden + * Get the remix contests hosted by a single user, ordered with currently-active contests first (by soonest-ending end_date) followed by ended contests (most-recently-ended first). Mirrors the response shape of `GET /events/remix-contests` (data + related users / tracks / entry_counts). + * Get contests hosted by user + */ + async getContestsByUserRaw(params: GetContestsByUserRequest, initOverrides?: RequestInit | runtime.InitOverrideFunction): Promise> { + if (params.id === null || params.id === undefined) { + throw new runtime.RequiredError('id','Required parameter params.id was null or undefined when calling getContestsByUser.'); + } + + const queryParameters: any = {}; + + if (params.offset !== undefined) { + queryParameters['offset'] = params.offset; + } + + if (params.limit !== undefined) { + queryParameters['limit'] = params.limit; + } + + if (params.status !== undefined) { + queryParameters['status'] = params.status; + } + + const headerParameters: runtime.HTTPHeaders = {}; + + if (!headerParameters["Authorization"] && this.configuration && this.configuration.accessToken) { + const token = await this.configuration.accessToken("OAuth2", ["read"]); + if (token) { + headerParameters["Authorization"] = token; + } + } + + const response = await this.request({ + path: `/users/{id}/contests`.replace(`{${"id"}}`, encodeURIComponent(String(params.id))), + method: 'GET', + headers: headerParameters, + query: queryParameters, + }, initOverrides); + + return new runtime.JSONApiResponse(response, (jsonValue) => RemixContestsResponseFromJSON(jsonValue)); + } + + /** + * Get the remix contests hosted by a single user, ordered with currently-active contests first (by soonest-ending end_date) followed by ended contests (most-recently-ended first). Mirrors the response shape of `GET /events/remix-contests` (data + related users / tracks / entry_counts). + * Get contests hosted by user + */ + async getContestsByUser(params: GetContestsByUserRequest, initOverrides?: RequestInit | runtime.InitOverrideFunction): Promise { + const response = await this.getContestsByUserRaw(params, initOverrides); + return await response.value(); + } + /** * @hidden * All users that follow the provided user @@ -5233,6 +5295,15 @@ export const GetAudioTransactionsSortDirectionEnum = { Desc: 'desc' } as const; export type GetAudioTransactionsSortDirectionEnum = typeof GetAudioTransactionsSortDirectionEnum[keyof typeof GetAudioTransactionsSortDirectionEnum]; +/** + * @export + */ +export const GetContestsByUserStatusEnum = { + Active: 'active', + Ended: 'ended', + All: 'all' +} as const; +export type GetContestsByUserStatusEnum = typeof GetContestsByUserStatusEnum[keyof typeof GetContestsByUserStatusEnum]; /** * @export */ diff --git a/packages/web/src/pages/profile-page/components/desktop/ContestsTab.tsx b/packages/web/src/pages/profile-page/components/desktop/ContestsTab.tsx index 62e2cb38952..98bfef2ad2c 100644 --- a/packages/web/src/pages/profile-page/components/desktop/ContestsTab.tsx +++ b/packages/web/src/pages/profile-page/components/desktop/ContestsTab.tsx @@ -1,7 +1,7 @@ -import { useEffect, useMemo } from 'react' +import { useMemo } from 'react' -import { useAllRemixContests, useRemixContest } from '@audius/common/api' -import { ID, User } from '@audius/common/models' +import { useUserRemixContests } from '@audius/common/api' +import { User } from '@audius/common/models' import { Box, Flex, LoadingSpinner } from '@audius/harmony' import { ContestCard } from 'components/contest-card/ContestCard' @@ -9,8 +9,6 @@ import { ContestCard } from 'components/contest-card/ContestCard' import { EmptyTab } from './EmptyTab' import styles from './ProfilePage.module.css' -const MAX_PAGES_TO_LOAD = 5 - const messages = { emptyContests: 'hosted any contests' } @@ -20,64 +18,25 @@ type ContestsTabProps = { isOwner: boolean } -/** - * Per-row guard: render a `` only when the resolved remix - * contest event for `trackId` is hosted by `hostUserId`. Lets the parent - * tab iterate over the global remix-contest list (which doesn't yet - * support a host-userId filter at the API layer) and keep only the rows - * for this profile. The event has already been primed into the React - * Query cache by `useAllRemixContests`, so this is a synchronous cache - * read in practice. - */ -const HostedContestCard = ({ - trackId, - hostUserId -}: { - trackId: ID - hostUserId: ID -}) => { - const { data: contest } = useRemixContest(trackId) - if (!contest || contest.userId !== hostUserId) return null - return -} - /** * Profile "Contests" tab. Lists the contests hosted by this profile as a * grid of `ContestCard`s that link out to the dedicated contest page. * Matches Figma 2864-13286. * - * The discovery endpoint behind `useAllRemixContests` doesn't yet - * support filtering by host userId, so we paginate the global list and - * filter client-side by `event.userId === profile.user_id`. Active - * contests come first (by soonest-ending end_date), then ended. + * Calls `GET /v1/users/{id}/contests` (via `useUserRemixContests`), which + * returns only this artist's contests with active first (by soonest-ending + * end_date) followed by ended. */ export const ContestsTab = ({ profile }: ContestsTabProps) => { const { user_id: hostUserId, name } = profile - const { - data: trackIds, - isPending, - isFetching, - hasNextPage, - isFetchingNextPage, - fetchNextPage - } = useAllRemixContests({ pageSize: 50 }) + const { data: trackIds, isPending, isFetching } = useUserRemixContests({ + userId: hostUserId, + pageSize: 50 + }) const contestTrackIds = useMemo(() => trackIds ?? [], [trackIds]) - // The discovery endpoint can't filter by host userId yet, so the - // client filters globally fetched pages. Without auto-pagination an - // artist whose contests aren't on the first 50 rows of the global - // list (active first, then ended) reads as "no contests" — including - // ended-contest-only artists. Pull additional pages opportunistically - // up to a safety cap so a typical profile resolves correctly. - const loadedPages = trackIds ? Math.ceil(trackIds.length / 50) : 0 - useEffect(() => { - if (hasNextPage && !isFetchingNextPage && loadedPages < MAX_PAGES_TO_LOAD) { - fetchNextPage() - } - }, [hasNextPage, isFetchingNextPage, loadedPages, fetchNextPage]) - if (isPending && contestTrackIds.length === 0) { return ( @@ -88,10 +47,6 @@ export const ContestsTab = ({ profile }: ContestsTabProps) => { ) } - // The list itself can be non-empty while every row gets filtered out - // (none belong to this host), so the empty-state check happens both - // before and after filtering. We can't filter ahead of render without - // breaking React Query hook ordering — see HostedContestCard. if (!isFetching && contestTrackIds.length === 0) { return ( @@ -118,11 +73,7 @@ export const ContestsTab = ({ profile }: ContestsTabProps) => { }} > {contestTrackIds.map((trackId) => ( - + ))} ) From 0984b98a819246a8242f3e145c09a801858237b4 Mon Sep 17 00:00:00 2001 From: Dylan Jeffers Date: Thu, 7 May 2026 09:53:38 -0700 Subject: [PATCH 2/4] profile: switch mobile Contests tab to per-user endpoint Mirrors the desktop change in this branch: drops the global useAllRemixContests fetch + per-row HostedContestCard host filter + auto-pagination, replacing them with useUserRemixContests, so the mobile profile reads from GET /v1/users/{id}/contests directly. Co-Authored-By: Claude Opus 4.7 (1M context) --- .../components/mobile/ContestsTab.tsx | 60 ++++--------------- 1 file changed, 12 insertions(+), 48 deletions(-) diff --git a/packages/web/src/pages/profile-page/components/mobile/ContestsTab.tsx b/packages/web/src/pages/profile-page/components/mobile/ContestsTab.tsx index 9c564e59b6a..0315ae62fa3 100644 --- a/packages/web/src/pages/profile-page/components/mobile/ContestsTab.tsx +++ b/packages/web/src/pages/profile-page/components/mobile/ContestsTab.tsx @@ -1,70 +1,38 @@ -import { useEffect, useMemo } from 'react' +import { useMemo } from 'react' -import { useAllRemixContests, useRemixContest } from '@audius/common/api' -import { ID, User } from '@audius/common/models' +import { useUserRemixContests } from '@audius/common/api' +import { User } from '@audius/common/models' import { Box, Flex, LoadingSpinner } from '@audius/harmony' import { ContestCard } from 'components/contest-card/ContestCard' import { EmptyTab } from './EmptyTab' -const MAX_PAGES_TO_LOAD = 5 - type ContestsTabProps = { profile: User isOwner: boolean } -/** - * Per-row guard: render a `` only when the resolved remix - * contest event for `trackId` is hosted by `hostUserId`. See the desktop - * ContestsTab for the full rationale; the mobile component is a thin - * wrapper around the same cards with a stacked single-column layout. - */ -const HostedContestCard = ({ - trackId, - hostUserId -}: { - trackId: ID - hostUserId: ID -}) => { - const { data: contest } = useRemixContest(trackId) - if (!contest || contest.userId !== hostUserId) return null - return -} - /** * Profile "Contests" tab on mobile. Lists contests hosted by this * profile as a stacked grid of `ContestCard`s. Matches Figma 2864-13286 * (the desktop layout collapses to a single column on narrow shells — * mobile reuses the same card so the visual treatment stays consistent). + * + * Calls `GET /v1/users/{id}/contests` (via `useUserRemixContests`), which + * returns only this artist's contests with active first (by soonest-ending + * end_date) followed by ended. */ export const ContestsTab = ({ profile, isOwner }: ContestsTabProps) => { const { user_id: hostUserId, name } = profile - const { - data: trackIds, - isPending, - isFetching, - hasNextPage, - isFetchingNextPage, - fetchNextPage - } = useAllRemixContests({ pageSize: 50 }) + const { data: trackIds, isPending, isFetching } = useUserRemixContests({ + userId: hostUserId, + pageSize: 50 + }) const contestTrackIds = useMemo(() => trackIds ?? [], [trackIds]) - // Auto-paginate the global contest list (the discovery endpoint - // doesn't filter by host yet) up to a safety cap. Without this, an - // artist whose hosted contests sit beyond the first page reads as - // "no contests" until the user scrolls — and ended-only artists - // never showed up. - const loadedPages = trackIds ? Math.ceil(trackIds.length / 50) : 0 - useEffect(() => { - if (hasNextPage && !isFetchingNextPage && loadedPages < MAX_PAGES_TO_LOAD) { - fetchNextPage() - } - }, [hasNextPage, isFetchingNextPage, loadedPages, fetchNextPage]) - if (isPending && contestTrackIds.length === 0) { return ( @@ -88,11 +56,7 @@ export const ContestsTab = ({ profile, isOwner }: ContestsTabProps) => { return ( {contestTrackIds.map((trackId) => ( - + ))} ) From cb9ab4791cb1626d8fd8ad9bb1ac3e56ffb5f68b Mon Sep 17 00:00:00 2001 From: Dylan Jeffers Date: Thu, 7 May 2026 10:37:46 -0700 Subject: [PATCH 3/4] fix: restore filter behavior on All search tab The search-explore-page useEffect had an else-if branch that cleared all URL params except query whenever URL params changed while on the All tab. Since searchParams is in the deps array, setting any filter on the All tab would immediately rewrite the URL and drop the filter. The branch contradicted categories.all.filters (which lists all 7 filters) and the rendered filter UI for the All tab. Removing it. Co-Authored-By: Claude Opus 4.7 (1M context) --- .../components/desktop/SearchExplorePage.tsx | 10 +--------- .../components/mobile/SearchExplorePage.tsx | 10 +--------- 2 files changed, 2 insertions(+), 18 deletions(-) diff --git a/packages/web/src/pages/search-explore-page/components/desktop/SearchExplorePage.tsx b/packages/web/src/pages/search-explore-page/components/desktop/SearchExplorePage.tsx index 6b0f9aa6690..0fe2a3416f7 100644 --- a/packages/web/src/pages/search-explore-page/components/desktop/SearchExplorePage.tsx +++ b/packages/web/src/pages/search-explore-page/components/desktop/SearchExplorePage.tsx @@ -140,20 +140,12 @@ const SearchExplorePage = ({ newParams.delete('query') } setSearchParams(newParams, { replace: true }) - } else if (categoryKey === SearchTabs.ALL.toLowerCase()) { - // clear filters when searching all - const newParams = new URLSearchParams() - if (debouncedValue) { - newParams.set('query', debouncedValue) - } - setSearchParams(newParams, { replace: true }) } }, [ debouncedValue, setSearchParams, searchParams, - previousDebouncedValue, - categoryKey + previousDebouncedValue ]) const filterKeys: string[] = categories[categoryKey].filters diff --git a/packages/web/src/pages/search-explore-page/components/mobile/SearchExplorePage.tsx b/packages/web/src/pages/search-explore-page/components/mobile/SearchExplorePage.tsx index 8a8cb74c7d2..b306912dc6b 100644 --- a/packages/web/src/pages/search-explore-page/components/mobile/SearchExplorePage.tsx +++ b/packages/web/src/pages/search-explore-page/components/mobile/SearchExplorePage.tsx @@ -115,20 +115,12 @@ const SearchExplorePage = ({ newParams.delete('query') } setSearchParams(newParams, { replace: true }) - } else if (categoryKey === SearchTabs.ALL.toLowerCase()) { - // clear filters when searching all - const newParams = new URLSearchParams() - if (debouncedValue) { - newParams.set('query', debouncedValue) - } - setSearchParams(newParams, { replace: true }) } }, [ debouncedValue, setSearchParams, searchParams, - previousDebouncedValue, - categoryKey + previousDebouncedValue ]) const { setCenter, setRight } = useContext(NavContext)! From 1cfaacb85e5102ccbdd96dd5e47c95bdf0891971 Mon Sep 17 00:00:00 2001 From: Dylan Jeffers Date: Thu, 7 May 2026 13:12:04 -0700 Subject: [PATCH 4/4] chore: fix prettier formatting on profile and search-explore pages Co-Authored-By: Claude Opus 4.7 (1M context) --- .../pages/profile-page/components/desktop/ContestsTab.tsx | 6 +++++- .../pages/profile-page/components/mobile/ContestsTab.tsx | 6 +++++- .../components/desktop/SearchExplorePage.tsx | 7 +------ .../components/mobile/SearchExplorePage.tsx | 7 +------ 4 files changed, 12 insertions(+), 14 deletions(-) diff --git a/packages/web/src/pages/profile-page/components/desktop/ContestsTab.tsx b/packages/web/src/pages/profile-page/components/desktop/ContestsTab.tsx index 98bfef2ad2c..b49345b498d 100644 --- a/packages/web/src/pages/profile-page/components/desktop/ContestsTab.tsx +++ b/packages/web/src/pages/profile-page/components/desktop/ContestsTab.tsx @@ -30,7 +30,11 @@ type ContestsTabProps = { export const ContestsTab = ({ profile }: ContestsTabProps) => { const { user_id: hostUserId, name } = profile - const { data: trackIds, isPending, isFetching } = useUserRemixContests({ + const { + data: trackIds, + isPending, + isFetching + } = useUserRemixContests({ userId: hostUserId, pageSize: 50 }) diff --git a/packages/web/src/pages/profile-page/components/mobile/ContestsTab.tsx b/packages/web/src/pages/profile-page/components/mobile/ContestsTab.tsx index 0315ae62fa3..19bd097414b 100644 --- a/packages/web/src/pages/profile-page/components/mobile/ContestsTab.tsx +++ b/packages/web/src/pages/profile-page/components/mobile/ContestsTab.tsx @@ -26,7 +26,11 @@ type ContestsTabProps = { export const ContestsTab = ({ profile, isOwner }: ContestsTabProps) => { const { user_id: hostUserId, name } = profile - const { data: trackIds, isPending, isFetching } = useUserRemixContests({ + const { + data: trackIds, + isPending, + isFetching + } = useUserRemixContests({ userId: hostUserId, pageSize: 50 }) diff --git a/packages/web/src/pages/search-explore-page/components/desktop/SearchExplorePage.tsx b/packages/web/src/pages/search-explore-page/components/desktop/SearchExplorePage.tsx index 0fe2a3416f7..14bf13436e8 100644 --- a/packages/web/src/pages/search-explore-page/components/desktop/SearchExplorePage.tsx +++ b/packages/web/src/pages/search-explore-page/components/desktop/SearchExplorePage.tsx @@ -141,12 +141,7 @@ const SearchExplorePage = ({ } setSearchParams(newParams, { replace: true }) } - }, [ - debouncedValue, - setSearchParams, - searchParams, - previousDebouncedValue - ]) + }, [debouncedValue, setSearchParams, searchParams, previousDebouncedValue]) const filterKeys: string[] = categories[categoryKey].filters diff --git a/packages/web/src/pages/search-explore-page/components/mobile/SearchExplorePage.tsx b/packages/web/src/pages/search-explore-page/components/mobile/SearchExplorePage.tsx index b306912dc6b..410af39ebc3 100644 --- a/packages/web/src/pages/search-explore-page/components/mobile/SearchExplorePage.tsx +++ b/packages/web/src/pages/search-explore-page/components/mobile/SearchExplorePage.tsx @@ -116,12 +116,7 @@ const SearchExplorePage = ({ } setSearchParams(newParams, { replace: true }) } - }, [ - debouncedValue, - setSearchParams, - searchParams, - previousDebouncedValue - ]) + }, [debouncedValue, setSearchParams, searchParams, previousDebouncedValue]) const { setCenter, setRight } = useContext(NavContext)! const { setHeader } = useContext(HeaderContext)