diff --git a/packages/web/src/pages/search-page-v2/BpmFilter.tsx b/packages/web/src/pages/search-page-v2/BpmFilter.tsx index 673e4250a48..80da6fba614 100644 --- a/packages/web/src/pages/search-page-v2/BpmFilter.tsx +++ b/packages/web/src/pages/search-page-v2/BpmFilter.tsx @@ -19,7 +19,7 @@ import { useSearchParams } from 'react-router-dom-v5-compat' import { useBpmMaskedInput } from 'hooks/useBpmMaskedInput' -import { useUpdateSearchParams } from './utils' +import { useUpdateSearchParams } from './hooks' const MIN_BPM = 1 const MAX_BPM = 999 diff --git a/packages/web/src/pages/search-page-v2/SearchFilters.tsx b/packages/web/src/pages/search-page-v2/SearchFilters.tsx index 70b7ae6c707..5e75dacd2b7 100644 --- a/packages/web/src/pages/search-page-v2/SearchFilters.tsx +++ b/packages/web/src/pages/search-page-v2/SearchFilters.tsx @@ -16,8 +16,9 @@ import { Mood } from '@audius/sdk' import { useSearchParams } from 'react-router-dom-v5-compat' import { BpmFilter } from './BpmFilter' +import { useUpdateSearchParams } from './hooks' +import { MOODS } from './moods' import { Filter } from './types' -import { MOODS, useUpdateSearchParams } from './utils' const messages = { genre: 'Genre', diff --git a/packages/web/src/pages/search-page-v2/SearchHeader.tsx b/packages/web/src/pages/search-page-v2/SearchHeader.tsx index 196ea67cfed..e5054cf4e06 100644 --- a/packages/web/src/pages/search-page-v2/SearchHeader.tsx +++ b/packages/web/src/pages/search-page-v2/SearchHeader.tsx @@ -1,16 +1,6 @@ import { ChangeEvent, useCallback } from 'react' -import { Maybe } from '@audius/common/utils' -import { - Flex, - IconAlbum, - IconNote, - IconPlaylists, - IconUser, - RadioGroup, - SelectablePill, - Text -} from '@audius/harmony' +import { Flex, RadioGroup, SelectablePill, Text } from '@audius/harmony' import { CSSObject, useTheme } from '@emotion/react' import { capitalize } from 'lodash' @@ -18,42 +8,13 @@ import Header from 'components/header/desktop/Header' import { useIsMobile } from 'hooks/useIsMobile' import { filters } from './SearchFilters' -import { Category } from './types' - -export const categories = { - all: { filters: [] }, - profiles: { icon: IconUser, filters: ['genre', 'isVerified'] }, - tracks: { - icon: IconNote, - filters: [ - 'genre', - 'mood', - 'key', - 'bpm', - 'isPremium', - 'hasDownloads', - 'isVerified' - ] - }, - albums: { - icon: IconAlbum, - filters: ['genre', 'mood', 'isPremium', 'hasDownloads', 'isVerified'] - }, - playlists: { icon: IconPlaylists, filters: ['genre', 'mood', 'isVerified'] } -} satisfies Record - -export type CategoryKey = keyof typeof categories - -type SearchHeaderProps = { - category?: CategoryKey - setCategory: (category: CategoryKey) => void - title: string - query: Maybe -} - -export const SearchHeader = (props: SearchHeaderProps) => { - const { category: categoryKey = 'all', setCategory, query, title } = props +import { categories } from './categories' +import { useSearchCategory, useSearchParams } from './hooks' +import { Category, CategoryKey } from './types' +export const SearchHeader = () => { + const { query } = useSearchParams() + const [categoryKey, setCategory] = useSearchCategory() const isMobile = useIsMobile() const { color } = useTheme() @@ -113,8 +74,7 @@ export const SearchHeader = (props: SearchHeaderProps) => { ) : (
diff --git a/packages/web/src/pages/search-page-v2/SearchPageV2.tsx b/packages/web/src/pages/search-page-v2/SearchPageV2.tsx index 28721a7875c..4c6550445bc 100644 --- a/packages/web/src/pages/search-page-v2/SearchPageV2.tsx +++ b/packages/web/src/pages/search-page-v2/SearchPageV2.tsx @@ -1,13 +1,9 @@ -import { useCallback, useContext, useEffect } from 'react' +import { useContext, useEffect } from 'react' import { SearchCategory } from '@audius/common/src/api/search' import { Flex, useTheme } from '@audius/harmony' -import { intersection, isEmpty } from 'lodash' -import { generatePath } from 'react-router-dom' import { useSearchParams } from 'react-router-dom-v5-compat' -import { useHistoryContext } from 'app/HistoryProvider' -import { RouterContext } from 'components/animated-switch/RouterContextProvider' import MobilePageContainer from 'components/mobile-page-container/MobilePageContainer' import NavContext, { CenterPreset, @@ -16,53 +12,20 @@ import NavContext, { } from 'components/nav/mobile/NavContext' import Page from 'components/page/Page' import { useIsMobile } from 'hooks/useIsMobile' -import { - SEARCH_BASE_ROUTE, - SEARCH_PAGE, - fullSearchResultsPageV2 -} from 'utils/route' +import { fullSearchResultsPageV2 } from 'utils/route' import { RecentSearches } from './RecentSearches' import { SearchCatalogTile } from './SearchCatalogTile' -import { CategoryKey, SearchHeader, categories } from './SearchHeader' +import { SearchHeader } from './SearchHeader' import { SearchResults } from './SearchResults' -import { useSearchCategory } from './utils' - -const useShowSearchResults = () => { - const [urlSearchParams] = useSearchParams() - const query = urlSearchParams.get('query') - const genre = urlSearchParams.get('genre') - const mood = urlSearchParams.get('mood') - const bpm = urlSearchParams.get('bpm') - const key = urlSearchParams.get('key') - const isVerified = urlSearchParams.get('isVerified') - const hasDownloads = urlSearchParams.get('hasDownloads') - const isPremium = urlSearchParams.get('isPremium') - - return ( - query || - genre || - mood || - isVerified || - hasDownloads || - bpm || - key || - isPremium - ) -} +import { useSearchCategory, useShowSearchResults } from './hooks' export const SearchPageV2 = () => { const isMobile = useIsMobile() - const category = useSearchCategory() - const { history } = useHistoryContext() + const [category] = useSearchCategory() const [urlSearchParams] = useSearchParams() const query = urlSearchParams.get('query') - const genre = urlSearchParams.get('genre') - const mood = urlSearchParams.get('mood') - const isPremium = urlSearchParams.get('isPremium') - const hasDownloads = urlSearchParams.get('hasDownloads') const showSearchResults = useShowSearchResults() - const { setStackReset } = useContext(RouterContext) const { color } = useTheme() // Set nav header @@ -73,57 +36,7 @@ export const SearchPageV2 = () => { setRight(RightPreset.SEARCH) }, [setLeft, setCenter, setRight]) - const setCategory = useCallback( - (newCategory: CategoryKey) => { - // Do not animate on mobile - setStackReset(true) - - const commonFilters = intersection( - categories[category]?.filters ?? [], - categories[newCategory]?.filters ?? [] - ) - const commonFilterParams = { - ...(query && { query }), - ...(genre && commonFilters.includes('genre') && { genre }), - ...(mood && commonFilters.includes('mood') && { mood }), - ...(isPremium && commonFilters.includes('isPremium') && { isPremium }), - ...(hasDownloads && - commonFilters.includes('hasDownloads') && { hasDownloads }) - } - - const pathname = - newCategory === 'all' - ? generatePath(SEARCH_BASE_ROUTE) - : generatePath(SEARCH_PAGE, { category: newCategory }) - - history.push({ - pathname, - search: !isEmpty(commonFilterParams) - ? new URLSearchParams(commonFilterParams).toString() - : undefined, - state: {} - }) - }, - [ - category, - genre, - hasDownloads, - history, - isPremium, - mood, - query, - setStackReset - ] - ) - - const header = ( - - ) + const header = const PageComponent = isMobile ? MobilePageContainer : Page diff --git a/packages/web/src/pages/search-page-v2/SearchResults.tsx b/packages/web/src/pages/search-page-v2/SearchResults.tsx index 7466ca13e6b..b0c4fd50015 100644 --- a/packages/web/src/pages/search-page-v2/SearchResults.tsx +++ b/packages/web/src/pages/search-page-v2/SearchResults.tsx @@ -1,12 +1,12 @@ +import { useSearchCategory } from './hooks' import { AlbumResultsPage } from './search-results/AlbumResults' import { AllResults } from './search-results/AllResults' import { PlaylistResultsPage } from './search-results/PlaylistResults' import { ProfileResultsPage } from './search-results/ProfileResults' import { TrackResultsPage } from './search-results/TrackResults' -import { useSearchCategory } from './utils' export const SearchResults = () => { - const category = useSearchCategory() + const [category] = useSearchCategory() switch (category) { case 'profiles': diff --git a/packages/web/src/pages/search-page-v2/SortMethodFilterButton.tsx b/packages/web/src/pages/search-page-v2/SortMethodFilterButton.tsx index 79c498093d8..69942aabe81 100644 --- a/packages/web/src/pages/search-page-v2/SortMethodFilterButton.tsx +++ b/packages/web/src/pages/search-page-v2/SortMethodFilterButton.tsx @@ -1,6 +1,6 @@ import { FilterButton } from '@audius/harmony' -import { useSearchParams, useUpdateSearchParams } from './utils' +import { useSearchParams, useUpdateSearchParams } from './hooks' const messages = { sortOptionsLabel: 'Sort By' diff --git a/packages/web/src/pages/search-page-v2/categories.ts b/packages/web/src/pages/search-page-v2/categories.ts new file mode 100644 index 00000000000..293871c71dc --- /dev/null +++ b/packages/web/src/pages/search-page-v2/categories.ts @@ -0,0 +1,25 @@ +import { IconAlbum, IconNote, IconPlaylists, IconUser } from '@audius/harmony' + +import { Category } from './types' + +export const categories = { + all: { filters: [] }, + profiles: { icon: IconUser, filters: ['genre', 'isVerified'] }, + tracks: { + icon: IconNote, + filters: [ + 'genre', + 'mood', + 'key', + 'bpm', + 'isPremium', + 'hasDownloads', + 'isVerified' + ] + }, + albums: { + icon: IconAlbum, + filters: ['genre', 'mood', 'isPremium', 'hasDownloads', 'isVerified'] + }, + playlists: { icon: IconPlaylists, filters: ['genre', 'mood', 'isVerified'] } +} satisfies Record diff --git a/packages/web/src/pages/search-page-v2/hooks.ts b/packages/web/src/pages/search-page-v2/hooks.ts new file mode 100644 index 00000000000..3f5cf771fc5 --- /dev/null +++ b/packages/web/src/pages/search-page-v2/hooks.ts @@ -0,0 +1,203 @@ +import { useCallback, useContext, useMemo } from 'react' + +import { + SearchCategory, + useGetSearchResults as useGetSearchResultsApi +} from '@audius/common/api' +import { Status } from '@audius/common/models' +import { SearchSortMethod, accountSelectors } from '@audius/common/store' +import { Genre, Mood } from '@audius/sdk' +import { intersection, isEmpty } from 'lodash' +import { useSelector } from 'react-redux' +import { generatePath, useRouteMatch } from 'react-router-dom' +import { useSearchParams as useParams } from 'react-router-dom-v5-compat' + +import { useHistoryContext } from 'app/HistoryProvider' +import { RouterContext } from 'components/animated-switch/RouterContextProvider' +import { useIsMobile } from 'hooks/useIsMobile' +import { SEARCH_BASE_ROUTE, SEARCH_PAGE } from 'utils/route' + +import { categories } from './categories' +import { CategoryKey, CategoryView, SearchResultsType } from './types' +import { ALL_RESULTS_LIMIT, urlSearchParamsToObject } from './utils' + +const { getAccountStatus, getUserId } = accountSelectors + +export const useGetSearchResults = ( + category: C +): SearchResultsType => { + const { query, ...filters } = useSearchParams() + + const accountStatus = useSelector(getAccountStatus) + const currentUserId = useSelector(getUserId) + + const params = { + query: query || '', + ...filters, + category, + currentUserId, + limit: category === 'all' ? ALL_RESULTS_LIMIT : undefined, + offset: 0 + } + + // TODO: Properly type data when `shallow` is true + const { data, status } = useGetSearchResultsApi(params, { + // We pass shallow here because the top level search results don't care + // about the actual entities, just the ids. The nested componets pull + // the entities from the cache. This prevents unnecessary re-renders at the top + shallow: true, + debounce: 500, + // Only search when the account has finished loading, + // or if the user is not logged in + disabled: accountStatus === Status.LOADING || accountStatus === Status.IDLE + }) + + if (category === 'all') { + return { data: data as any, status } as SearchResultsType + } else { + return { + data: data?.[category as Exclude] as any, + status + } as SearchResultsType + } +} + +export const useShowSearchResults = () => { + const { query, genre, mood, isPremium, hasDownloads, isVerified, bpm, key } = + useSearchParams() + + return ( + query || + genre || + mood || + isVerified || + hasDownloads || + bpm || + key || + isPremium + ) +} + +export const useSearchCategory = () => { + const isMobile = useIsMobile() + const routeMatch = useRouteMatch<{ category: string }>(SEARCH_PAGE) + const categoryParam = routeMatch?.params.category as CategoryView + + const category = isMobile ? categoryParam ?? 'profiles' : categoryParam + + const { history } = useHistoryContext() + const { query, genre, mood, isPremium, hasDownloads, isVerified } = + useSearchParams() + const { setStackReset } = useContext(RouterContext) + + const setCategory = useCallback( + (newCategory: CategoryKey) => { + // Do not animate on mobile + setStackReset(true) + + const commonFilters = intersection( + categories[category]?.filters ?? [], + categories[newCategory]?.filters ?? [] + ) + const commonFilterParams = { + ...(query && { query }), + ...(genre && commonFilters.includes('genre') && { genre }), + ...(mood && commonFilters.includes('mood') && { mood }), + ...(isPremium && + commonFilters.includes('isPremium') && { + isPremium: String(isPremium) + }), + ...(hasDownloads && + commonFilters.includes('hasDownloads') && { + hasDownloads: String(hasDownloads) + }), + ...(isVerified && + commonFilters.includes('isVerified') && { + isVerified: String(isVerified) + }) + } + + const pathname = + newCategory === 'all' + ? generatePath(SEARCH_BASE_ROUTE) + : generatePath(SEARCH_PAGE, { category: newCategory }) + + history.push({ + pathname, + search: !isEmpty(commonFilterParams) + ? new URLSearchParams(commonFilterParams).toString() + : undefined, + state: {} + }) + }, + [ + category, + genre, + hasDownloads, + history, + isPremium, + isVerified, + mood, + query, + setStackReset + ] + ) + + return [category || CategoryView.ALL, setCategory] as const +} + +export const useSearchParams = () => { + const [urlSearchParams] = useParams() + + const query = urlSearchParams.get('query') + const sortMethod = urlSearchParams.get('sortMethod') as SearchSortMethod + const genre = urlSearchParams.get('genre') + const mood = urlSearchParams.get('mood') + const bpm = urlSearchParams.get('bpm') + const key = urlSearchParams.get('key') + const isVerified = urlSearchParams.get('isVerified') + const hasDownloads = urlSearchParams.get('hasDownloads') + const isPremium = urlSearchParams.get('isPremium') + + const searchParams = useMemo( + () => ({ + query, + genre: (genre || undefined) as Genre, + mood: (mood || undefined) as Mood, + bpm: bpm || undefined, + key: key || undefined, + isVerified: isVerified === 'true', + hasDownloads: hasDownloads === 'true', + isPremium: isPremium === 'true', + sortMethod: sortMethod || undefined + }), + [ + query, + genre, + mood, + bpm, + key, + isVerified, + hasDownloads, + isPremium, + sortMethod + ] + ) + return searchParams +} + +export const useUpdateSearchParams = (key: string) => { + const [searchParams, setUrlSearchParams] = useParams() + return (value: string) => { + if (value) { + setUrlSearchParams({ + ...urlSearchParamsToObject(searchParams), + [key]: value + }) + } else { + const { [key]: ignored, ...params } = + urlSearchParamsToObject(searchParams) + setUrlSearchParams(params) + } + } +} diff --git a/packages/web/src/pages/search-page-v2/moods.tsx b/packages/web/src/pages/search-page-v2/moods.tsx new file mode 100644 index 00000000000..dee46ccd95e --- /dev/null +++ b/packages/web/src/pages/search-page-v2/moods.tsx @@ -0,0 +1,121 @@ +import { Mood } from '@audius/sdk' + +import { MoodInfo } from './types' + +export const MOODS: Record = { + Peaceful: { + label: Mood.PEACEFUL, + value: Mood.PEACEFUL, + icon: + }, + Romantic: { + label: Mood.ROMANTIC, + value: Mood.ROMANTIC, + icon: + }, + Sentimental: { + label: Mood.SENTIMENTAL, + value: Mood.SENTIMENTAL, + icon: + }, + Tender: { + label: Mood.TENDER, + value: Mood.TENDER, + icon: + }, + Easygoing: { + label: Mood.EASYGOING, + value: Mood.EASYGOING, + icon: + }, + Yearning: { + label: Mood.YEARNING, + value: Mood.YEARNING, + icon: + }, + Sophisticated: { + label: Mood.SOPHISTICATED, + value: Mood.SOPHISTICATED, + icon: + }, + Sensual: { + label: Mood.SENSUAL, + value: Mood.SENSUAL, + icon: + }, + Cool: { + label: Mood.COOL, + value: Mood.COOL, + icon: + }, + Gritty: { + label: Mood.GRITTY, + value: Mood.GRITTY, + icon: + }, + Melancholy: { + label: Mood.MELANCHOLY, + value: Mood.MELANCHOLY, + icon: + }, + Serious: { + label: Mood.SERIOUS, + value: Mood.SERIOUS, + icon: + }, + Brooding: { + label: Mood.BROODING, + value: Mood.BROODING, + icon: + }, + Fiery: { + label: Mood.FIERY, + value: Mood.FIERY, + icon: + }, + Defiant: { + label: Mood.DEFIANT, + value: Mood.DEFIANT, + icon: + }, + Aggressive: { + label: Mood.AGGRESSIVE, + value: Mood.AGGRESSIVE, + icon: + }, + Rowdy: { + label: Mood.ROWDY, + value: Mood.ROWDY, + icon: + }, + Excited: { + label: Mood.EXCITED, + value: Mood.EXCITED, + icon: + }, + Energizing: { + label: Mood.ENERGIZING, + value: Mood.ENERGIZING, + icon: + }, + Empowering: { + label: Mood.EMPOWERING, + value: Mood.EMPOWERING, + icon: + }, + Stirring: { + label: Mood.STIRRING, + value: Mood.STIRRING, + icon: + }, + Upbeat: { + label: Mood.UPBEAT, + value: Mood.UPBEAT, + icon: + }, + Other: { + label: Mood.OTHER, + value: Mood.OTHER, + icon: + } +} diff --git a/packages/web/src/pages/search-page-v2/search-results/AlbumResults.tsx b/packages/web/src/pages/search-page-v2/search-results/AlbumResults.tsx index 5dfc8f8b309..3aa1b2ccb14 100644 --- a/packages/web/src/pages/search-page-v2/search-results/AlbumResults.tsx +++ b/packages/web/src/pages/search-page-v2/search-results/AlbumResults.tsx @@ -12,7 +12,7 @@ import { useIsMobile } from 'hooks/useIsMobile' import { NoResultsTile } from '../NoResultsTile' import { SortMethodFilterButton } from '../SortMethodFilterButton' -import { useGetSearchResults, useSearchParams } from '../utils' +import { useGetSearchResults, useSearchParams } from '../hooks' const { addItem: addRecentSearch } = searchActions diff --git a/packages/web/src/pages/search-page-v2/search-results/AllResults.tsx b/packages/web/src/pages/search-page-v2/search-results/AllResults.tsx index 780496574d3..719888177f8 100644 --- a/packages/web/src/pages/search-page-v2/search-results/AllResults.tsx +++ b/packages/web/src/pages/search-page-v2/search-results/AllResults.tsx @@ -7,7 +7,7 @@ import { Flex, Text } from '@audius/harmony' import { useIsMobile } from 'hooks/useIsMobile' import { NoResultsTile } from '../NoResultsTile' -import { useGetSearchResults } from '../utils' +import { useGetSearchResults } from '../hooks' import { AlbumResults } from './AlbumResults' import { PlaylistResults } from './PlaylistResults' diff --git a/packages/web/src/pages/search-page-v2/search-results/PlaylistResults.tsx b/packages/web/src/pages/search-page-v2/search-results/PlaylistResults.tsx index 114b04990b6..0a80ccedf73 100644 --- a/packages/web/src/pages/search-page-v2/search-results/PlaylistResults.tsx +++ b/packages/web/src/pages/search-page-v2/search-results/PlaylistResults.tsx @@ -12,7 +12,7 @@ import { useIsMobile } from 'hooks/useIsMobile' import { NoResultsTile } from '../NoResultsTile' import { SortMethodFilterButton } from '../SortMethodFilterButton' -import { useGetSearchResults, useSearchParams } from '../utils' +import { useGetSearchResults, useSearchParams } from '../hooks' const { addItem: addRecentSearch } = searchActions diff --git a/packages/web/src/pages/search-page-v2/search-results/ProfileResults.tsx b/packages/web/src/pages/search-page-v2/search-results/ProfileResults.tsx index 6a62352d035..cd059edb664 100644 --- a/packages/web/src/pages/search-page-v2/search-results/ProfileResults.tsx +++ b/packages/web/src/pages/search-page-v2/search-results/ProfileResults.tsx @@ -12,7 +12,7 @@ import { useIsMobile } from 'hooks/useIsMobile' import { NoResultsTile } from '../NoResultsTile' import { SortMethodFilterButton } from '../SortMethodFilterButton' -import { useGetSearchResults, useSearchParams } from '../utils' +import { useGetSearchResults, useSearchParams } from '../hooks' const { addItem: addRecentSearch } = searchActions diff --git a/packages/web/src/pages/search-page-v2/search-results/TrackResults.tsx b/packages/web/src/pages/search-page-v2/search-results/TrackResults.tsx index 29dc2f1fa89..985e97008f2 100644 --- a/packages/web/src/pages/search-page-v2/search-results/TrackResults.tsx +++ b/packages/web/src/pages/search-page-v2/search-results/TrackResults.tsx @@ -22,8 +22,9 @@ import { useMainContentRef } from 'pages/MainContentContext' import { NoResultsTile } from '../NoResultsTile' import { SortMethodFilterButton } from '../SortMethodFilterButton' +import { useSearchParams } from '../hooks' import { ViewLayout, viewLayoutOptions } from '../types' -import { ALL_RESULTS_LIMIT, useSearchParams } from '../utils' +import { ALL_RESULTS_LIMIT } from '../utils' const { makeGetLineupMetadatas } = lineupSelectors const { getBuffering, getPlaying } = playerSelectors diff --git a/packages/web/src/pages/search-page-v2/types.ts b/packages/web/src/pages/search-page-v2/types.ts index 7d648aadbf5..8b386b25cf6 100644 --- a/packages/web/src/pages/search-page-v2/types.ts +++ b/packages/web/src/pages/search-page-v2/types.ts @@ -1,4 +1,12 @@ +import { + SearchCategory, + useGetSearchResults as useGetSearchResultsApi +} from '@audius/common/api' +import { ID } from '@audius/common/models' import { IconComponent } from '@audius/harmony' +import { Mood } from '@audius/sdk' + +import { categories } from './categories' export type ViewLayout = 'grid' | 'list' export const viewLayoutOptions: { label: string; value: ViewLayout }[] = [ @@ -29,3 +37,25 @@ export type Category = { filters: Filter[] icon?: IconComponent } + +export type MoodInfo = { + label: Mood + value: Mood + icon: JSX.Element +} + +export type SearchResultsApiType = ReturnType + +export type SearchResultsType = { + status: SearchResultsApiType['status'] + data: C extends 'all' + ? { + users: ID[] + tracks: ID[] + playlists: ID[] + albums: ID[] + } + : ID[] +} + +export type CategoryKey = keyof typeof categories diff --git a/packages/web/src/pages/search-page-v2/utils.tsx b/packages/web/src/pages/search-page-v2/utils.tsx index 6eee2f4b60c..b6e3a4e4516 100644 --- a/packages/web/src/pages/search-page-v2/utils.tsx +++ b/packages/web/src/pages/search-page-v2/utils.tsx @@ -1,129 +1,6 @@ -import { useMemo } from 'react' - -import { - SearchCategory, - useGetSearchResults as useGetSearchResultsApi -} from '@audius/common/api' -import { ID, Status } from '@audius/common/models' -import { SearchSortMethod, accountSelectors } from '@audius/common/store' -import { Genre, Mood } from '@audius/sdk' -import { useSelector } from 'react-redux' -import { useRouteMatch } from 'react-router-dom' -import { useSearchParams as useParams } from 'react-router-dom-v5-compat' - -import { useIsMobile } from 'hooks/useIsMobile' -import { SEARCH_PAGE } from 'utils/route' - -import { CategoryView } from './types' - -const { getAccountStatus, getUserId } = accountSelectors - -type SearchResultsApiType = ReturnType - export const ALL_RESULTS_LIMIT = 12 -type SearchResultsType = { - status: SearchResultsApiType['status'] - data: C extends 'all' - ? { - users: ID[] - tracks: ID[] - playlists: ID[] - albums: ID[] - } - : ID[] -} - -export const useGetSearchResults = ( - category: C -): SearchResultsType => { - const { query, ...filters } = useSearchParams() - - const accountStatus = useSelector(getAccountStatus) - const currentUserId = useSelector(getUserId) - - const params = { - query: query || '', - ...filters, - category, - currentUserId, - limit: category === 'all' ? ALL_RESULTS_LIMIT : undefined, - offset: 0 - } - - // TODO: Properly type data when `shallow` is true - const { data, status } = useGetSearchResultsApi(params, { - // We pass shallow here because the top level search results don't care - // about the actual entities, just the ids. The nested componets pull - // the entities from the cache. This prevents unnecessary re-renders at the top - shallow: true, - debounce: 500, - // TODO: do we need this on mobile too - // Only search when the account has finished loading, - // or if the user is not logged in - disabled: accountStatus === Status.LOADING || accountStatus === Status.IDLE - }) - - if (category === 'all') { - return { data: data as any, status } as SearchResultsType - } else { - return { - data: data?.[category as Exclude] as any, - status - } as SearchResultsType - } -} - -export const useSearchCategory = () => { - const isMobile = useIsMobile() - const routeMatch = useRouteMatch<{ category: string }>(SEARCH_PAGE) - const categoryParam = routeMatch?.params.category as CategoryView - - const category = isMobile ? categoryParam ?? 'profiles' : categoryParam - return category || CategoryView.ALL -} - -export const useSearchParams = () => { - const [urlSearchParams] = useParams() - - const query = urlSearchParams.get('query') - const sortMethod = urlSearchParams.get('sortMethod') as SearchSortMethod - const genre = urlSearchParams.get('genre') - const mood = urlSearchParams.get('mood') - const bpm = urlSearchParams.get('bpm') - const key = urlSearchParams.get('key') - const isVerified = urlSearchParams.get('isVerified') - const hasDownloads = urlSearchParams.get('hasDownloads') - const isPremium = urlSearchParams.get('isPremium') - - const searchParams = useMemo( - () => ({ - query, - genre: (genre || undefined) as Genre, - mood: (mood || undefined) as Mood, - bpm: bpm || undefined, - key: key || undefined, - isVerified: isVerified === 'true', - hasDownloads: hasDownloads === 'true', - isPremium: isPremium === 'true', - sortMethod: sortMethod || undefined - }), - [ - query, - genre, - mood, - bpm, - key, - isVerified, - hasDownloads, - isPremium, - sortMethod - ] - ) - return searchParams -} - -const urlSearchParamsToObject = ( +export const urlSearchParamsToObject = ( searchParams: URLSearchParams ): Record => [...searchParams.entries()].reduce( @@ -133,143 +10,3 @@ const urlSearchParamsToObject = ( }), {} ) - -export const useUpdateSearchParams = (key: string) => { - const [searchParams, setUrlSearchParams] = useParams() - return (value: string) => { - if (value) { - setUrlSearchParams({ - ...urlSearchParamsToObject(searchParams), - [key]: value - }) - } else { - const { [key]: ignored, ...params } = - urlSearchParamsToObject(searchParams) - setUrlSearchParams(params) - } - } -} - -type MoodInfo = { - label: Mood - value: Mood - icon: JSX.Element -} - -export const MOODS: Record = { - Peaceful: { - label: Mood.PEACEFUL, - value: Mood.PEACEFUL, - icon: - }, - Romantic: { - label: Mood.ROMANTIC, - value: Mood.ROMANTIC, - icon: - }, - Sentimental: { - label: Mood.SENTIMENTAL, - value: Mood.SENTIMENTAL, - icon: - }, - Tender: { - label: Mood.TENDER, - value: Mood.TENDER, - icon: - }, - Easygoing: { - label: Mood.EASYGOING, - value: Mood.EASYGOING, - icon: - }, - Yearning: { - label: Mood.YEARNING, - value: Mood.YEARNING, - icon: - }, - Sophisticated: { - label: Mood.SOPHISTICATED, - value: Mood.SOPHISTICATED, - icon: - }, - Sensual: { - label: Mood.SENSUAL, - value: Mood.SENSUAL, - icon: - }, - Cool: { - label: Mood.COOL, - value: Mood.COOL, - icon: - }, - Gritty: { - label: Mood.GRITTY, - value: Mood.GRITTY, - icon: - }, - Melancholy: { - label: Mood.MELANCHOLY, - value: Mood.MELANCHOLY, - icon: - }, - Serious: { - label: Mood.SERIOUS, - value: Mood.SERIOUS, - icon: - }, - Brooding: { - label: Mood.BROODING, - value: Mood.BROODING, - icon: - }, - Fiery: { - label: Mood.FIERY, - value: Mood.FIERY, - icon: - }, - Defiant: { - label: Mood.DEFIANT, - value: Mood.DEFIANT, - icon: - }, - Aggressive: { - label: Mood.AGGRESSIVE, - value: Mood.AGGRESSIVE, - icon: - }, - Rowdy: { - label: Mood.ROWDY, - value: Mood.ROWDY, - icon: - }, - Excited: { - label: Mood.EXCITED, - value: Mood.EXCITED, - icon: - }, - Energizing: { - label: Mood.ENERGIZING, - value: Mood.ENERGIZING, - icon: - }, - Empowering: { - label: Mood.EMPOWERING, - value: Mood.EMPOWERING, - icon: - }, - Stirring: { - label: Mood.STIRRING, - value: Mood.STIRRING, - icon: - }, - Upbeat: { - label: Mood.UPBEAT, - value: Mood.UPBEAT, - icon: - }, - Other: { - label: Mood.OTHER, - value: Mood.OTHER, - icon: - } -}