From e71e6b4d51a0f03d7e79376ff69c5c1ba47a5d53 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=80=9CAnton?= <“alantukh@“jwplayer.com> Date: Thu, 30 Jun 2022 20:29:15 +0200 Subject: [PATCH] feat(project): support watchlists - Parse query param function fix - Review warning fixes --- src/components/Alert/Alert.test.tsx | 2 +- src/components/Alert/Alert.tsx | 9 ++++----- .../Alert/__snapshots__/Alert.test.tsx.snap | 2 +- src/components/Video/Video.tsx | 8 ++++++++ src/i18n/locales/en_US/common.json | 1 + src/i18n/locales/en_US/video.json | 5 +---- src/i18n/locales/nl_NL/common.json | 4 ++++ src/i18n/locales/nl_NL/video.json | 5 +---- src/screens/Movie/Movie.tsx | 17 +---------------- src/screens/Series/Series.tsx | 15 +-------------- src/stores/FavoritesController.ts | 5 +++-- src/stores/FavoritesStore.ts | 10 ++++++---- src/stores/WatchHistoryController.ts | 10 +++------- src/utils/formatting.ts | 5 +++-- test-e2e/codecept.desktop.js | 2 +- test-e2e/codecept.mobile.js | 2 +- 16 files changed, 40 insertions(+), 62 deletions(-) diff --git a/src/components/Alert/Alert.test.tsx b/src/components/Alert/Alert.test.tsx index d7e2e8764..ec2085a2d 100644 --- a/src/components/Alert/Alert.test.tsx +++ b/src/components/Alert/Alert.test.tsx @@ -9,7 +9,7 @@ vi.mock('../Dialog/Dialog', () => ({ describe('', () => { test('renders and matches snapshot', () => { - const { container } = render(); + const { container } = render(); expect(container).toMatchSnapshot(); }); }); diff --git a/src/components/Alert/Alert.tsx b/src/components/Alert/Alert.tsx index 96df7eaa7..fe3c967c2 100644 --- a/src/components/Alert/Alert.tsx +++ b/src/components/Alert/Alert.tsx @@ -8,18 +8,17 @@ import styles from './Alert.module.scss'; type Props = { open: boolean; - title: string; - body: string; + message: string | null; onClose: () => void; }; -const Alert: React.FC = ({ open, title, body, onClose }: Props) => { +const Alert: React.FC = ({ open, message, onClose }: Props) => { const { t } = useTranslation('common'); return ( -

{title}

-

{body}

+

{t('alert.title')}

+

{message}

); diff --git a/src/components/Alert/__snapshots__/Alert.test.tsx.snap b/src/components/Alert/__snapshots__/Alert.test.tsx.snap index e0252f898..1528c022e 100644 --- a/src/components/Alert/__snapshots__/Alert.test.tsx.snap +++ b/src/components/Alert/__snapshots__/Alert.test.tsx.snap @@ -8,7 +8,7 @@ exports[` > renders and matches snapshot 1`] = `

- Title + alert.title

= ({ const handlePause = useCallback(() => setIsPlaying(false), []); const handleComplete = useCallback(() => onComplete && onComplete(), [onComplete]); + const { clearWarning, warning } = useFavoritesStore((state) => ({ + clearWarning: state.clearWarning, + warning: state.warning, + })); + const isLargeScreen = breakpoint >= Breakpoint.md; const isMobile = breakpoint === Breakpoint.xs; const imageSourceWidth = 640 * (window.devicePixelRatio > 1 || isLargeScreen ? 2 : 1); @@ -189,6 +196,7 @@ const Video: React.FC = ({ + {!!trailerItem && (

diff --git a/src/i18n/locales/en_US/common.json b/src/i18n/locales/en_US/common.json index d6d81b648..3ea1a920b 100644 --- a/src/i18n/locales/en_US/common.json +++ b/src/i18n/locales/en_US/common.json @@ -7,6 +7,7 @@ "confirm": "Yes" }, "alert": { + "title": "An error occurred", "close": "Close" }, "filter_videos_by_genre": "Filter videos by genre", diff --git a/src/i18n/locales/en_US/video.json b/src/i18n/locales/en_US/video.json index 834591784..61e230847 100644 --- a/src/i18n/locales/en_US/video.json +++ b/src/i18n/locales/en_US/video.json @@ -21,8 +21,5 @@ "video_not_found": "Video not found", "watch_trailer": "Watch the trailer", "share_video": "Share this video", - "favorites_warning": { - "title": "Maximum amount of favorite videos exceeded", - "body": "You can only add up to {{ count }} favorite videos. Please delete one and repeat the operation." - } + "favorites_warning": "Maximum amount of favorite videos exceeded. You can only add up to {{ count }} favorite videos. Please delete one and repeat the operation." } diff --git a/src/i18n/locales/nl_NL/common.json b/src/i18n/locales/nl_NL/common.json index 047b4c100..3a5911aaf 100644 --- a/src/i18n/locales/nl_NL/common.json +++ b/src/i18n/locales/nl_NL/common.json @@ -6,6 +6,10 @@ "close": "", "confirm": "" }, + "alert": { + "title": "", + "close": "" + }, "filter_videos_by_genre": "", "home": "", "live": "", diff --git a/src/i18n/locales/nl_NL/video.json b/src/i18n/locales/nl_NL/video.json index 3e96d8a72..646c914ce 100644 --- a/src/i18n/locales/nl_NL/video.json +++ b/src/i18n/locales/nl_NL/video.json @@ -21,8 +21,5 @@ "video_not_found": "", "watch_trailer": "", "share_video": "", - "favorites_warning": { - "title": "", - "body": "" - } + "favorites_warning": "" } diff --git a/src/screens/Movie/Movie.tsx b/src/screens/Movie/Movie.tsx index ea05858b7..608ee05a6 100644 --- a/src/screens/Movie/Movie.tsx +++ b/src/screens/Movie/Movie.tsx @@ -15,7 +15,6 @@ import type { PlaylistItem } from '#types/playlist'; import VideoComponent from '#src/components/Video/Video'; import ErrorPage from '#src/components/ErrorPage/ErrorPage'; import CardGrid from '#src/components/CardGrid/CardGrid'; -import Alert from '#src/components/Alert/Alert'; import useMedia from '#src/hooks/useMedia'; import { generateMovieJSONLD } from '#src/utils/structuredData'; import { copyToClipboard } from '#src/utils/dom'; @@ -26,7 +25,6 @@ import { addConfigParamToUrl } from '#src/utils/configOverride'; import usePlaylist from '#src/hooks/usePlaylist'; import useEntitlement from '#src/hooks/useEntitlement'; import StartWatchingButton from '#src/containers/StartWatchingButton/StartWatchingButton'; -import { MAX_WATCHLIST_ITEMS_COUNT } from '#src/config'; type MovieRouteParams = { id: string; @@ -59,10 +57,8 @@ const Movie = ({ match, location }: RouteComponentProps): JSX. const { data: playlist } = usePlaylist(features?.recommendationsPlaylist || '', { related_media_id: id }); // Favorite - const { isFavorited, toggleWarning, isWarningShown } = useFavoritesStore((state) => ({ + const { isFavorited } = useFavoritesStore((state) => ({ isFavorited: !!item && state.hasItem(item), - isWarningShown: state.isWarningShown, - toggleWarning: state.toggleWarning, })); // User, entitlement @@ -74,10 +70,6 @@ const Movie = ({ match, location }: RouteComponentProps): JSX. toggleFavorite(item); }, [item]); - const onToggleWarning = useCallback(() => { - toggleWarning(); - }, [toggleWarning]); - const goBack = () => item && history.push(videoUrl(item, searchParams.get('r'), false)); const onCardClick = (item: PlaylistItem) => history.push(cardUrl(item)); const onShareClick = (): void => { @@ -184,13 +176,6 @@ const Movie = ({ match, location }: RouteComponentProps): JSX. /> ) : undefined} - - diff --git a/src/screens/Series/Series.tsx b/src/screens/Series/Series.tsx index 3f727c536..bf2c90c9e 100644 --- a/src/screens/Series/Series.tsx +++ b/src/screens/Series/Series.tsx @@ -9,7 +9,6 @@ import styles from './Series.module.scss'; import useEntitlement from '#src/hooks/useEntitlement'; import CardGrid from '#src/components/CardGrid/CardGrid'; -import { MAX_WATCHLIST_ITEMS_COUNT } from '#src/config'; import useBlurImageUpdater from '#src/hooks/useBlurImageUpdater'; import { episodeURL } from '#src/utils/formatting'; import Filter from '#src/components/Filter/Filter'; @@ -28,7 +27,6 @@ import { useAccountStore } from '#src/stores/AccountStore'; import { useFavoritesStore } from '#src/stores/FavoritesStore'; import { toggleFavorite } from '#src/stores/FavoritesController'; import StartWatchingButton from '#src/containers/StartWatchingButton/StartWatchingButton'; -import Alert from '#src/components/Alert/Alert'; type SeriesRouteParams = { id: string; @@ -64,10 +62,8 @@ const Series = ({ match, location }: RouteComponentProps): JS const filteredPlaylist = useMemo(() => filterSeries(seriesPlaylist.playlist, seasonFilter), [seriesPlaylist, seasonFilter]); // Favorite - const { isFavorited, toggleWarning, isWarningShown } = useFavoritesStore((state) => ({ + const { isFavorited } = useFavoritesStore((state) => ({ isFavorited: !!item && state.hasItem(item), - isWarningShown: state.isWarningShown, - toggleWarning: state.toggleWarning, })); const watchHistoryDictionary = useWatchHistoryStore((state) => state.getDictionary()); @@ -81,9 +77,6 @@ const Series = ({ match, location }: RouteComponentProps): JS toggleFavorite(item); }, [item]); - const onToggleWarning = useCallback(() => { - toggleWarning(); - }, [toggleWarning]); const goBack = () => item && seriesPlaylist && history.push(episodeURL(seriesPlaylist, item.mediaid, false)); const onCardClick = (item: PlaylistItem) => seriesPlaylist && history.push(episodeURL(seriesPlaylist, item.mediaid)); const onShareClick = (): void => { @@ -207,12 +200,6 @@ const Series = ({ match, location }: RouteComponentProps): JS isLoggedIn={!!user} hasSubscription={!!subscription} /> - diff --git a/src/stores/FavoritesController.ts b/src/stores/FavoritesController.ts index 72f9b564b..43ff13f27 100644 --- a/src/stores/FavoritesController.ts +++ b/src/stores/FavoritesController.ts @@ -6,6 +6,7 @@ import { useConfigStore } from '#src/stores/ConfigStore'; import type { Favorite, SerializedFavorite } from '#types/favorite'; import type { PlaylistItem } from '#types/playlist'; import { MAX_WATCHLIST_ITEMS_COUNT } from '#src/config'; +import i18n from '#src/i18n/config'; const PERSIST_KEY_FAVORITES = `favorites${window.configId ? `-${window.configId}` : ''}`; @@ -65,7 +66,7 @@ export const removeItem = (item: PlaylistItem) => { }; export const toggleFavorite = (item: PlaylistItem | undefined) => { - const { favorites, hasItem, toggleWarning } = useFavoritesStore.getState(); + const { favorites, hasItem, setWarning } = useFavoritesStore.getState(); if (!item) { return; @@ -81,7 +82,7 @@ export const toggleFavorite = (item: PlaylistItem | undefined) => { // If we exceed the max available number of favorites, we show a warning if (favorites?.length >= MAX_WATCHLIST_ITEMS_COUNT) { - toggleWarning(); + setWarning(i18n.t('video:favorites_warning', { count: MAX_WATCHLIST_ITEMS_COUNT })); return; } diff --git a/src/stores/FavoritesStore.ts b/src/stores/FavoritesStore.ts index bc92b628d..ddca64fee 100644 --- a/src/stores/FavoritesStore.ts +++ b/src/stores/FavoritesStore.ts @@ -6,16 +6,18 @@ import type { Playlist, PlaylistItem } from '#types/playlist'; type FavoritesState = { favorites: Favorite[]; - isWarningShown: boolean; + warning: string | null; hasItem: (item: PlaylistItem) => boolean; getPlaylist: () => Playlist; - toggleWarning: () => void; + setWarning: (warning: string) => void; + clearWarning: () => void; }; export const useFavoritesStore = createStore('FavoritesState', (set, get) => ({ favorites: [], - isWarningShown: false, - toggleWarning: () => set({ isWarningShown: !get().isWarningShown }), + warning: null, + setWarning: (message: string | null) => set({ warning: message }), + clearWarning: () => set({ warning: null }), hasItem: (item: PlaylistItem) => get().favorites.some((favoriteItem) => favoriteItem.mediaid === item.mediaid), getPlaylist: () => ({ diff --git a/src/stores/WatchHistoryController.ts b/src/stores/WatchHistoryController.ts index beee2fe65..fcac1d38c 100644 --- a/src/stores/WatchHistoryController.ts +++ b/src/stores/WatchHistoryController.ts @@ -80,13 +80,9 @@ export const saveItem = (item: PlaylistItem, videoProgress: number | null) => { const watchHistoryItem = createWatchHistoryItem(item, videoProgress); - let updatedHistory = watchHistory; - - updatedHistory = [watchHistoryItem, ...watchHistory.filter(({ mediaid }) => mediaid !== watchHistoryItem.mediaid)]; - - if (watchHistory.length > MAX_WATCHLIST_ITEMS_COUNT) { - updatedHistory = updatedHistory.slice(0, MAX_WATCHLIST_ITEMS_COUNT - 1); - } + const updatedHistory = watchHistory.filter(({ mediaid }) => mediaid !== watchHistoryItem.mediaid); + updatedHistory.unshift(watchHistoryItem); + updatedHistory.splice(MAX_WATCHLIST_ITEMS_COUNT); useWatchHistoryStore.setState({ watchHistory: updatedHistory }); diff --git a/src/utils/formatting.ts b/src/utils/formatting.ts index f84ee3da8..06a5c4c6e 100644 --- a/src/utils/formatting.ts +++ b/src/utils/formatting.ts @@ -39,8 +39,9 @@ export const addQueryParams = (url: string, queryParams: { [key: string]: string Object.keys(queryParams).forEach((key) => { const value = queryParams[key]; - // null or undefined - if (value == null || !value?.length) return; + if (value == null) return; + + if (typeof value === 'object' && !value?.length) return; const formattedValue = Array.isArray(value) ? value.join(',') : value; diff --git a/test-e2e/codecept.desktop.js b/test-e2e/codecept.desktop.js index 4bc599b2c..543d63e7a 100644 --- a/test-e2e/codecept.desktop.js +++ b/test-e2e/codecept.desktop.js @@ -38,7 +38,7 @@ exports.config = { enabled: true, }, screenshotOnFail: { - enabled: !process.env.CI, + enabled: true, }, allure: { enabled: true, diff --git a/test-e2e/codecept.mobile.js b/test-e2e/codecept.mobile.js index ae1d15054..a6c648d1b 100644 --- a/test-e2e/codecept.mobile.js +++ b/test-e2e/codecept.mobile.js @@ -40,7 +40,7 @@ exports.config = { enabled: true, }, screenshotOnFail: { - enabled: !process.env.CI, + enabled: true, }, allure: { enabled: true,