diff --git a/packages/common/src/services/ApiService.ts b/packages/common/src/services/ApiService.ts index 481e8db4a..919822364 100644 --- a/packages/common/src/services/ApiService.ts +++ b/packages/common/src/services/ApiService.ts @@ -246,13 +246,16 @@ export default class ApiService { return (await getDataOrThrow(response)) as AdSchedule; }; - getMediaAds = async (url: string, mediaId: string): Promise => { - const urlWithQuery = createURL(url, { - media_id: mediaId, + getAppContentSearch = async (siteId: string, searchQuery: string | undefined) => { + const pathname = `/v2/sites/${siteId}/app_content/media/search`; + + const url = createURL(`${env.APP_API_BASE_URL}${pathname}`, { + search_query: searchQuery, }); - const response = await fetch(urlWithQuery, { credentials: 'omit' }); + const response = await fetch(url); + const data = (await getDataOrThrow(response)) as Playlist; - return (await getDataOrThrow(response)) as AdSchedule; + return this.transformPlaylist(data); }; } diff --git a/packages/common/src/services/ConfigService.ts b/packages/common/src/services/ConfigService.ts index 6c17f682e..c775a87c8 100644 --- a/packages/common/src/services/ConfigService.ts +++ b/packages/common/src/services/ConfigService.ts @@ -22,6 +22,7 @@ export default class ConfigService { id: '', siteName: '', description: '', + siteId: '', assets: { banner: '/images/logo.png', }, diff --git a/packages/common/src/stores/ConfigStore.ts b/packages/common/src/stores/ConfigStore.ts index 854a60977..4a71a4115 100644 --- a/packages/common/src/stores/ConfigStore.ts +++ b/packages/common/src/stores/ConfigStore.ts @@ -21,6 +21,7 @@ export const useConfigStore = createStore('ConfigStore', () => ({ siteName: '', description: '', player: '', + siteId: '', assets: {}, content: [], menu: [], diff --git a/packages/common/src/utils/configSchema.ts b/packages/common/src/utils/configSchema.ts index af6ffc382..e564a4eac 100644 --- a/packages/common/src/utils/configSchema.ts +++ b/packages/common/src/utils/configSchema.ts @@ -50,6 +50,7 @@ export const configSchema: SchemaOf = object({ description: string().defined(), analyticsToken: string().nullable(), adSchedule: string().nullable(), + siteId: string().defined(), assets: object({ banner: string().notRequired().nullable(), }).notRequired(), diff --git a/packages/common/types/config.ts b/packages/common/types/config.ts index 7e60bfee8..fa1a790f2 100644 --- a/packages/common/types/config.ts +++ b/packages/common/types/config.ts @@ -22,6 +22,7 @@ export type Config = { custom?: Record; contentSigningService?: ContentSigningConfig; contentProtection?: ContentProtection; + siteId: string; }; export type ContentSigningConfig = { diff --git a/packages/hooks-react/src/useSearch.ts b/packages/hooks-react/src/useSearch.ts new file mode 100644 index 000000000..76d0acb43 --- /dev/null +++ b/packages/hooks-react/src/useSearch.ts @@ -0,0 +1,54 @@ +import { useQuery, type UseQueryResult } from 'react-query'; +import { shallow } from '@jwp/ott-common/src/utils/compare'; +import ApiService from '@jwp/ott-common/src/services/ApiService'; +import { getModule } from '@jwp/ott-common/src/modules/container'; +import type { ApiError } from '@jwp/ott-common/src/utils/api'; +import usePlaylist from '@jwp/ott-hooks-react/src/usePlaylist'; +import { CACHE_TIME, STALE_TIME } from '@jwp/ott-common/src/constants'; +import { useConfigStore } from '@jwp/ott-common/src/stores/ConfigStore'; +import { isTruthyCustomParamValue } from '@jwp/ott-common/src/utils/common'; +import type { Playlist } from '@jwp/ott-common/types/playlist'; +import { generatePlaylistPlaceholder } from '@jwp/ott-common/src/utils/collection'; + +const placeholderData = generatePlaylistPlaceholder(30); + +const useAppContentSearch = ({ siteId, enabled, query }: { query: string; siteId: string; enabled: boolean }) => { + const apiService = getModule(ApiService); + + const appContentSearchQuery: UseQueryResult = useQuery( + ['app-search', query], + async () => { + const searchResult = await apiService.getAppContentSearch(siteId, query); + + return searchResult; + }, + { + placeholderData: enabled ? placeholderData : undefined, + enabled: enabled, + staleTime: STALE_TIME, + cacheTime: CACHE_TIME, + }, + ); + + return appContentSearchQuery; +}; + +export const useSearch = (query: string) => { + const { config } = useConfigStore(({ config }) => ({ config }), shallow); + + const siteId = config?.siteId; + const searchPlaylist = config?.features?.searchPlaylist; + const hasAppContentSearch = isTruthyCustomParamValue(config?.custom?.appContentSearch); + + const playlistQuery = usePlaylist(searchPlaylist || '', { search: query || '' }, !hasAppContentSearch, !!query); + // New app content search flow + const appContentSearchQuery = useAppContentSearch({ siteId, enabled: hasAppContentSearch, query }); + + return hasAppContentSearch + ? { data: appContentSearchQuery.data, isFetching: appContentSearchQuery.isFetching, error: appContentSearchQuery.error } + : { + isFetching: playlistQuery.isFetching, + error: playlistQuery.error, + data: playlistQuery.data, + }; +}; diff --git a/packages/ui-react/src/containers/Layout/Layout.tsx b/packages/ui-react/src/containers/Layout/Layout.tsx index a9d8bbbc3..845f79cc3 100644 --- a/packages/ui-react/src/containers/Layout/Layout.tsx +++ b/packages/ui-react/src/containers/Layout/Layout.tsx @@ -10,7 +10,7 @@ import { useConfigStore } from '@jwp/ott-common/src/stores/ConfigStore'; import { useProfileStore } from '@jwp/ott-common/src/stores/ProfileStore'; import ProfileController from '@jwp/ott-common/src/controllers/ProfileController'; import { modalURLFromLocation } from '@jwp/ott-ui-react/src/utils/location'; -import { unicodeToChar } from '@jwp/ott-common/src/utils/common'; +import { isTruthyCustomParamValue, unicodeToChar } from '@jwp/ott-common/src/utils/common'; import { ACCESS_MODEL } from '@jwp/ott-common/src/constants'; import useSearchQueryUpdater from '@jwp/ott-ui-react/src/hooks/useSearchQueryUpdater'; import { useProfiles, useSelectProfile } from '@jwp/ott-hooks-react/src/useProfiles'; @@ -40,7 +40,7 @@ const Layout = () => { const userMenuTitleId = useOpaqueId('usermenu-title'); const isLoggedIn = !!useAccountStore(({ user }) => user); const favoritesEnabled = !!config.features?.favoritesList; - const { menu, assets, siteName, description, features, styling } = config; + const { menu, assets, siteName, description, features, styling, custom } = config; const metaDescription = description || t('default_description'); const { footerText: configFooterText } = styling || {}; const footerText = configFooterText || unicodeToChar(env.APP_FOOTER_TEXT); @@ -48,6 +48,9 @@ const Layout = () => { const profileController = getModule(ProfileController, false); const { searchPlaylist } = features || {}; + const hasAppContentSearch = isTruthyCustomParamValue(custom?.appContentSearch); + const searchEnabled = !!searchPlaylist || hasAppContentSearch; + const currentLanguage = useMemo(() => supportedLanguages.find(({ code }) => code === i18n.language), [i18n.language, supportedLanguages]); const { @@ -160,7 +163,7 @@ const Layout = () => {
setSideBarOpen(true)} logoSrc={banner} - searchEnabled={!!searchPlaylist} + searchEnabled={searchEnabled} searchBarProps={{ query: searchQuery, onQueryChange: (event) => updateSearchQuery(event.target.value), diff --git a/packages/ui-react/src/pages/Home/Home.test.tsx b/packages/ui-react/src/pages/Home/Home.test.tsx index bba69ce89..c4b711670 100644 --- a/packages/ui-react/src/pages/Home/Home.test.tsx +++ b/packages/ui-react/src/pages/Home/Home.test.tsx @@ -9,6 +9,7 @@ describe('Home Component tests', () => { test('Home test', () => { useConfigStore.setState({ config: { + siteId: 'test', description: '', integrations: {}, assets: {}, diff --git a/packages/ui-react/src/pages/Search/Search.tsx b/packages/ui-react/src/pages/Search/Search.tsx index 81366cb42..c43896d41 100644 --- a/packages/ui-react/src/pages/Search/Search.tsx +++ b/packages/ui-react/src/pages/Search/Search.tsx @@ -10,7 +10,7 @@ import { useConfigStore } from '@jwp/ott-common/src/stores/ConfigStore'; import { mediaURL } from '@jwp/ott-common/src/utils/urlFormatting'; import useFirstRender from '@jwp/ott-hooks-react/src/useFirstRender'; import useSearchQueryUpdater from '@jwp/ott-ui-react/src/hooks/useSearchQueryUpdater'; -import usePlaylist from '@jwp/ott-hooks-react/src/usePlaylist'; +import { useSearch } from '@jwp/ott-hooks-react/src/useSearch'; import useOpaqueId from '@jwp/ott-hooks-react/src/useOpaqueId'; import CardGrid from '../../components/CardGrid/CardGrid'; @@ -29,7 +29,7 @@ const Search = () => { const { updateSearchQuery } = useSearchQueryUpdater(); const params = useParams(); const query = params['*']; - const { isFetching, error, data: playlist } = usePlaylist(features?.searchPlaylist || '', { search: query || '' }, true, !!query); + const { isFetching, error, data: playlist } = useSearch(query || ''); // User const { user, subscription } = useAccountStore(({ user, subscription }) => ({ user, subscription }), shallow);