diff --git a/packages/atlas/src/MainLayout.tsx b/packages/atlas/src/MainLayout.tsx index 7e0e436dcf..c01db037dc 100644 --- a/packages/atlas/src/MainLayout.tsx +++ b/packages/atlas/src/MainLayout.tsx @@ -45,6 +45,21 @@ const LoadablePlaygroundLayout = loadable( ) export const MainLayout: FC = () => { + return ( + <> + + + + } /> + } /> + } /> + } /> + + + ) +} + +const MiscUtils = () => { useTimeMismatchWarning() const scrollPosition = useRef(0) const location = useLocation() @@ -111,15 +126,5 @@ export const MainLayout: FC = () => { }, parseInt(transitions.timings.routing) + ROUTING_ANIMATION_OFFSET) }, [location, cachedLocation, locationState, navigationType, clearOverlays]) - return ( - <> - - - } /> - } /> - } /> - } /> - - - ) + return null } diff --git a/packages/atlas/src/components/MinimizedPlayer/MinimizedPlayer.tsx b/packages/atlas/src/components/MinimizedPlayer/MinimizedPlayer.tsx index 4e1e373f21..53ba388f29 100644 --- a/packages/atlas/src/components/MinimizedPlayer/MinimizedPlayer.tsx +++ b/packages/atlas/src/components/MinimizedPlayer/MinimizedPlayer.tsx @@ -1,4 +1,4 @@ -import { forwardRef, useEffect, useState } from 'react' +import { forwardRef, useEffect, useMemo, useState } from 'react' import { VideoPlayer, VideoPlayerProps } from '@/components/_video/VideoPlayer' import { useMediaMatch } from '@/hooks/useMediaMatch' @@ -30,23 +30,26 @@ export const MinimizedPlayer = forwardRef( const inView = isAllowed && mdMatch && !wasPausedOnTop ? isInView || forceExit || hasError : true + const actions = useMemo( + () => ({ + onPause: () => { + setIsPaused(true) + videoPlayerProps.onPause?.() + }, + onPlay: () => { + setIsPaused(false) + videoPlayerProps.onPlay?.() + }, + onError: () => setHasError(true), + onMinimizedExit: () => setForceExit(true), + }), + // eslint-disable-next-line react-hooks/exhaustive-deps + [videoPlayerProps.onPlay, videoPlayerProps.onPause] + ) + return ( - { - setIsPaused(true) - videoPlayerProps.onPause?.() - }} - onPlay={() => { - setIsPaused(false) - videoPlayerProps.onPlay?.() - }} - onError={() => setHasError(true)} - onMinimizedExit={() => setForceExit(true)} - {...videoPlayerProps} - /> + ) } diff --git a/packages/atlas/src/components/_auth/LogInModal/LogInModal.tsx b/packages/atlas/src/components/_auth/LogInModal/LogInModal.tsx index 0352e0df3f..ea952e841d 100644 --- a/packages/atlas/src/components/_auth/LogInModal/LogInModal.tsx +++ b/packages/atlas/src/components/_auth/LogInModal/LogInModal.tsx @@ -73,8 +73,8 @@ export const LogInModal = () => { } catch (error) { if (error.message === LogInErrors.ArtifactsNotFound) { displaySnackbar({ - title: `We can't find ${atlasConfig.general.appName} membership associated with this email`, - description: `Make sure that you are using the same email that you used to create your membership on ${atlasConfig.general.appName}.`, + title: `We can't find ${atlasConfig.general.appName} membership associated with these credentials`, + description: `Make sure that you are using the same email and password that you used to create your membership on ${atlasConfig.general.appName}.`, iconType: 'error', }) setError('email', { type: 'custom', message: 'Incorrect email or password.' }) diff --git a/packages/atlas/src/components/_video/VideoTile/VideoTile.tsx b/packages/atlas/src/components/_video/VideoTile/VideoTile.tsx index e4e899651e..554b1dd059 100644 --- a/packages/atlas/src/components/_video/VideoTile/VideoTile.tsx +++ b/packages/atlas/src/components/_video/VideoTile/VideoTile.tsx @@ -1,6 +1,11 @@ +import styled from '@emotion/styled' import { FC, memo, useState } from 'react' import useResizeObserver from 'use-resize-observer' +import { FlexBox } from '@/components/FlexBox' +import { SkeletonLoader } from '@/components/_loaders/SkeletonLoader' +import { square } from '@/styles' + import { VideoTileContainer } from './VideoTile.styles' import { VideoThumbnail, VideoThumbnailProps } from '../VideoThumbnail' @@ -62,6 +67,21 @@ export const VideoTile: FC = memo( }, }) + if (loadingDetails) { + return ( + + + + + + + + + + + ) + } + return ( = memo( } ) +const StyledThumbnailSkeleton = styled(SkeletonLoader)` + min-width: unset; + min-height: unset; + width: 100%; + aspect-ratio: 16/9; +` + +const AvatarSkeleton = styled(SkeletonLoader)` + ${square(32)} +` + VideoTile.displayName = 'VideoTile' diff --git a/packages/atlas/src/components/_video/VideoTileViewer/VideoTileViewer.tsx b/packages/atlas/src/components/_video/VideoTileViewer/VideoTileViewer.tsx index 1aac3b2d06..9baecaa309 100644 --- a/packages/atlas/src/components/_video/VideoTileViewer/VideoTileViewer.tsx +++ b/packages/atlas/src/components/_video/VideoTileViewer/VideoTileViewer.tsx @@ -1,4 +1,4 @@ -import { FC, useCallback } from 'react' +import { FC, useCallback, useMemo } from 'react' import { useNavigate } from 'react-router' import { useBasicVideo } from '@/api/hooks/video' @@ -44,17 +44,39 @@ export const VideoTileViewer: FC = ({ const channelHref = absoluteRoutes.viewer.channel(video?.channel.id) - const handleCopyVideoURLClick = useCallback(() => { - copyToClipboard(videoHref ? location.origin + videoHref : '', 'Video URL copied to clipboard') - }, [videoHref, copyToClipboard]) + const contextMenuItems = useMemo( + () => [ + { + nodeStart: , + onClick: () => copyToClipboard(videoHref ? location.origin + videoHref : '', 'Video URL copied to clipboard'), + label: 'Copy video URL', + }, + ], + [copyToClipboard, videoHref] + ) + + const isNft = !!video?.nft + const slots = useMemo( + () => ({ + bottomRight: { + element: video?.duration ? ( + + ) : null, + }, + bottomLeft: isNft + ? { + element: , + } + : undefined, + center: { + element: , + type: 'hover', + } as const, + }), + [isNft, video?.duration] + ) - const contextMenuItems = [ - { - nodeStart: , - onClick: handleCopyVideoURLClick, - label: 'Copy video URL', - }, - ] + const onAvatarClick = useCallback(() => navigate(channelHref), [channelHref, navigate]) return ( = ({ detailsVariant={detailsVariant} videoHref={videoHref} channelHref={channelHref} - onChannelAvatarClick={() => navigate(channelHref)} + onChannelAvatarClick={onAvatarClick} loadingDetails={loading || !video} loadingThumbnail={isLoadingThumbnail} thumbnailUrls={thumbnailPhotoUrls} views={video?.viewsNum} createdAt={video?.createdAt} - slots={{ - bottomRight: { - element: video?.duration ? ( - - ) : null, - }, - bottomLeft: - video && video?.nft - ? { - element: , - } - : undefined, - center: { - element: , - type: 'hover', - }, - }} + slots={slots} channelAvatarUrls={avatarPhotoUrls} loadingAvatar={isLoadingAvatar} channelTitle={video?.channel?.title} diff --git a/packages/atlas/src/hooks/useGetAssetUrl.ts b/packages/atlas/src/hooks/useGetAssetUrl.ts index 3be292c8b2..01d10bb77b 100644 --- a/packages/atlas/src/hooks/useGetAssetUrl.ts +++ b/packages/atlas/src/hooks/useGetAssetUrl.ts @@ -10,6 +10,7 @@ import { ConsoleLogger, DistributorEventEntry, SentryLogger, UserEventsLogger } import { withTimeout } from '@/utils/misc' const workingUrlMap = new Map() +const assetsWithNoDistributions: string[] = [] export const getSingleAssetUrl = async ( urls: string[] | null | undefined, @@ -18,7 +19,7 @@ export const getSingleAssetUrl = async ( timeout?: number, opts?: AssetTestOptions ): Promise => { - if (!urls || !urls.length) { + if (!urls || !urls.length || (id && assetsWithNoDistributions.includes(id))) { return } @@ -90,6 +91,7 @@ export const getSingleAssetUrl = async ( urls, error, }) + return undefined }) }) } @@ -108,9 +110,12 @@ export const useGetAssetUrl = (urls: string[] | undefined | null, type: AssetTyp setUrl(undefined) setIsLoading(true) const resolvedUrl = await getSingleAssetUrl(urls, id, type, userBenchmarkTime.current ?? undefined, opts) + setIsLoading(false) if (resolvedUrl) { setUrl(resolvedUrl) + } else if (id) { + assetsWithNoDistributions.push(id) } } diff --git a/packages/atlas/src/utils/getVideoCodec.ts b/packages/atlas/src/utils/getVideoCodec.ts index 841fc45667..baad20587a 100644 --- a/packages/atlas/src/utils/getVideoCodec.ts +++ b/packages/atlas/src/utils/getVideoCodec.ts @@ -22,7 +22,7 @@ async function fetchPartialContent(url: string, range: { start: number; end: num export const getVideoCodec = async (url: string): string => { const fetchRange = { start: 0, end: 8192 } - const arrayBuffer = await fetchPartialContent(url, fetchRange) + const arrayBuffer = await fetchPartialContent(url, fetchRange).catch(() => undefined) if (arrayBuffer) { try { arrayBuffer.fileStart = 0 diff --git a/packages/atlas/src/views/viewer/HomeView.tsx b/packages/atlas/src/views/viewer/HomeView.tsx index 54e2b05e11..7df0e55765 100644 --- a/packages/atlas/src/views/viewer/HomeView.tsx +++ b/packages/atlas/src/views/viewer/HomeView.tsx @@ -1,6 +1,5 @@ import styled from '@emotion/styled' -import { FC, useEffect, useState } from 'react' -import { useLocation } from 'react-router-dom' +import { FC, useState } from 'react' import { useGetCuratedHompageVideosQuery } from '@/api/queries/__generated__/videos.generated' import { Section } from '@/components/Section/Section' @@ -11,29 +10,15 @@ import { publicCryptoVideoFilter } from '@/config/contentFilter' import { useBreakpointKey } from '@/hooks/useBreakpointKey' import { useHeadTags } from '@/hooks/useHeadTags' import { useVideoGridRows } from '@/hooks/useVideoGridRows' -import { getCorrectLoginModal } from '@/providers/auth/auth.helpers' -import { useAuthStore } from '@/providers/auth/auth.store' import { DEFAULT_VIDEO_GRID, sizes } from '@/styles' import { createPlaceholderData } from '@/utils/data' import { InfiniteLoadingOffsets } from '@/utils/loading.contants' export const HomeView: FC = () => { const [hasMoreVideos, setHasMoreVideos] = useState(true) - const location = useLocation() - const { - actions: { setAuthModalOpenName }, - } = useAuthStore() - const headTags = useHeadTags() const { columns, fetchMore, tiles, loading, skipVideoIds } = useHomeVideos() - useEffect(() => { - if (location.state?.['redirectTo']) { - setAuthModalOpenName(getCorrectLoginModal()) - } - // eslint-disable-next-line react-hooks/exhaustive-deps - }, []) - return ( diff --git a/packages/atlas/src/views/viewer/VideoView/VideoView.tsx b/packages/atlas/src/views/viewer/VideoView/VideoView.tsx index 5a38268328..8e7234fff2 100644 --- a/packages/atlas/src/views/viewer/VideoView/VideoView.tsx +++ b/packages/atlas/src/views/viewer/VideoView/VideoView.tsx @@ -65,14 +65,8 @@ const DISABLE_VIEWS = true export const VideoView: FC = () => { const { id } = useParams() - const { memberId, isLoggedIn } = useUser() - const [showReportDialog, setShowReportDialog] = useState(false) - const [reactionFee, setReactionFee] = useState() const [availableTracks, setAvailableTracks] = useState([]) - const { openNftPutOnSale, openNftAcceptBid, openNftChangePrice, openNftPurchase, openNftSettlement, cancelNftSale } = - useNftActions() const { trackPageView } = useSegmentAnalytics() - const reactionPopoverDismissed = usePersonalDataStore((state) => state.reactionPopoverDismissed) const { loading, video, error } = useFullVideo( id ?? '', { @@ -92,12 +86,7 @@ export const VideoView: FC = () => { } ) const [isInView, ref] = useIntersectionObserver() - const [videoReactionProcessing, setVideoReactionProcessing] = useState(false) const [isCommenting, setIsCommenting] = useState(false) - const nftWidgetProps = useNftWidget(video) - const { likeOrDislikeVideo } = useReactionTransactions() - const { withdrawBid } = useNftTransactions() - const { trackLikeAdded, trackDislikeAdded } = useSegmentAnalytics() const [canPrefetchNew, setCanPrefetchNew] = useState(false) const mdMatch = useMediaMatch('md') @@ -107,10 +96,6 @@ export const VideoView: FC = () => { cinematicView, actions: { updateWatchedVideos }, } = usePersonalDataStore((state) => state) - const videoCategory = video?.category ? video.category.id : null - const belongsToCategories = videoCategory - ? displayCategories.filter((category) => category.videoCategories.includes(videoCategory)) - : null const { anyOverlaysOpen } = useOverlayManager() const { ref: playerRef, inView: isPlayerInView } = useInView() @@ -169,41 +154,23 @@ export const VideoView: FC = () => { const [isShareDialogOpen, setShareDialogOpen] = useState(false) + const handleShare = useCallback(() => { + setShareDialogOpen(true) + }, []) + const savedVideoTimestamp = watchedVideos?.find((v) => v.id === video?.id)?.timestamp const startTimestamp = useVideoStartTimestamp(video?.duration, savedVideoTimestamp) const channelId = video?.channel?.id - const channelName = video?.channel?.title const videoId = video?.id - const numberOfLikes = video?.reactions.filter(({ reaction }) => reaction === 'LIKE').length - const numberOfDislikes = video?.reactions.filter(({ reaction }) => reaction === 'UNLIKE').length const videoNotAvailable = !loading && !video - const reactionStepperState = useMemo(() => { - if (!video) { - return 'loading' - } - if (videoReactionProcessing) { - return 'processing' - } - const myReaction = video?.reactions.find(({ member: { id } }) => id === memberId) - if (myReaction) { - if (myReaction.reaction === 'LIKE') { - return 'liked' - } - if (myReaction.reaction === 'UNLIKE') { - return 'disliked' - } - } - return 'default' - }, [memberId, videoReactionProcessing, video]) - // Save the video timestamp // disabling eslint for this line since debounce is an external fn and eslint can't figure out its args, so it will complain. // eslint-disable-next-line react-hooks/exhaustive-deps const handleTimeUpdate = useCallback( throttle((time) => { - if (!canPrefetchNew) { + if (!canPrefetchNew && time > 5_000) { setCanPrefetchNew(true) } if (video?.id) { @@ -220,40 +187,6 @@ export const VideoView: FC = () => { } }, [video?.id, handleTimeUpdate, updateWatchedVideos]) - const { getTxFee: getReactionFee } = useFee('reactToVideoTx') - - const handleCalculateFeeForPopover = async (reaction: VideoReaction) => { - if (!memberId || !video?.id) return - const fee = await getReactionFee([memberId, video?.id, reaction]) - setReactionFee(fee) - } - - const handleReact = useCallback( - async (reaction: VideoReaction) => { - if (video?.id) { - setVideoReactionProcessing(true) - const fee = reactionFee || (await getReactionFee([memberId || '', video?.id, reaction])) - const reacted = await likeOrDislikeVideo(video.id, reaction, video.title, fee) - reaction === 'like' - ? trackLikeAdded(video.id, memberId ?? 'no data') - : trackDislikeAdded(video.id, memberId ?? 'no data') - setVideoReactionProcessing(false) - return reacted - } - return false - }, - [ - getReactionFee, - likeOrDislikeVideo, - memberId, - reactionFee, - trackLikeAdded, - trackDislikeAdded, - video?.id, - video?.title, - ] - ) - // use Media Session API to provide rich metadata to the browser useEffect(() => { const supported = 'mediaSession' in navigator @@ -277,10 +210,6 @@ export const VideoView: FC = () => { } }, [thumbnailUrls, video]) - const handleShare = () => { - setShareDialogOpen(true) - } - const handleAddVideoView = useCallback(() => { if (!videoId || !channelId) { return @@ -299,7 +228,116 @@ export const VideoView: FC = () => { } const isCinematic = cinematicView || !mdMatch - const sideItems = ( + return ( + <> + {headTags} + + + + + {videoNotAvailable ? ( + + ) : !loading && video ? ( + setShareDialogOpen(false)} + onAddVideoView={handleAddVideoView} + isShareDialogOpen={isShareDialogOpen} + isVideoPending={!video?.media?.isAccepted} + videoId={video?.id} + autoplay + videoUrls={mediaUrls} + onEnd={handleVideoEnd} + onTimeUpdated={handleTimeUpdate} + startTime={startTimestamp} + isPlayNextDisabled={pausePlayNext} + ref={playerRef} + availableTextTracks={availableTracks} + /> + ) : ( + + )} + + {!isCinematic && ( + <> + {!videoNotAvailable ? ( + + ) : mdMatch ? ( + + ) : null} + {!videoNotAvailable && ( + + )} + + )} + + {!isCinematic && } + + + + {isCinematic && !(!mdMatch && videoNotAvailable) && ( + + + {!videoNotAvailable ? ( + + ) : mdMatch ? ( + + ) : null} + {!videoNotAvailable && ( + + )} + + + + )} + + + ) +} + +const SideItems = ({ + video, + loading, + canStartPrefetch, +}: { + video: ReturnType['video'] + loading: boolean + canStartPrefetch: boolean +}) => { + const { id } = useParams() + const { openNftPutOnSale, openNftAcceptBid, openNftChangePrice, openNftPurchase, openNftSettlement, cancelNftSale } = + useNftActions() + const channelId = video?.channel?.id + const channelName = video?.channel?.title + const videoNotAvailable = !loading && !video + + const nftWidgetProps = useNftWidget(video) + const { withdrawBid } = useNftTransactions() + + const mdMatch = useMediaMatch('md') + const { cinematicView } = usePersonalDataStore((state) => state) + const videoCategory = video?.category ? video.category.id : null + const belongsToCategories = videoCategory + ? displayCategories.filter((category) => category.videoCategories.includes(videoCategory)) + : null + return ( {videoNotAvailable ? mdMatch && ( @@ -326,7 +364,7 @@ export const VideoView: FC = () => { channelName={channelName} videoId={id} type="channel" - shouldPrefetch={canPrefetchNew} + shouldPrefetch={canStartPrefetch} /> {belongsToCategories?.map((category) => ( { ))} ) +} + +const DetailsItems = ({ + video, + handleShare, +}: { + video: ReturnType['video'] + handleShare: () => void +}) => { + const mdMatch = useMediaMatch('md') + + const { memberId, isLoggedIn } = useUser() + const [showReportDialog, setShowReportDialog] = useState(false) + const [reactionFee, setReactionFee] = useState() + const reactionPopoverDismissed = usePersonalDataStore((state) => state.reactionPopoverDismissed) + + const channelId = video?.channel?.id + const numberOfLikes = video?.reactions.filter(({ reaction }) => reaction === 'LIKE').length + const numberOfDislikes = video?.reactions.filter(({ reaction }) => reaction === 'UNLIKE').length + const videoCategory = video?.category ? video.category.id : null + + const [videoReactionProcessing, setVideoReactionProcessing] = useState(false) + const { likeOrDislikeVideo } = useReactionTransactions() + const { trackLikeAdded, trackDislikeAdded } = useSegmentAnalytics() + const belongsToCategories = videoCategory + ? displayCategories.filter((category) => category.videoCategories.includes(videoCategory)) + : null + + const { getTxFee: getReactionFee } = useFee('reactToVideoTx') + + const reactionStepperState = useMemo(() => { + if (!video) { + return 'loading' + } + if (videoReactionProcessing) { + return 'processing' + } + const myReaction = video?.reactions.find(({ member: { id } }) => id === memberId) + if (myReaction) { + if (myReaction.reaction === 'LIKE') { + return 'liked' + } + if (myReaction.reaction === 'UNLIKE') { + return 'disliked' + } + } + return 'default' + }, [memberId, videoReactionProcessing, video]) + + const handleCalculateFeeForPopover = async (reaction: VideoReaction) => { + if (!memberId || !video?.id) return + const fee = await getReactionFee([memberId, video?.id, reaction]) + setReactionFee(fee) + } + + const handleReact = useCallback( + async (reaction: VideoReaction) => { + if (video?.id) { + setVideoReactionProcessing(true) + const fee = reactionFee || (await getReactionFee([memberId || '', video?.id, reaction])) + const reacted = await likeOrDislikeVideo(video.id, reaction, video.title, fee) + reaction === 'like' + ? trackLikeAdded(video.id, memberId ?? 'no data') + : trackDislikeAdded(video.id, memberId ?? 'no data') + setVideoReactionProcessing(false) + return reacted + } + return false + }, + [ + getReactionFee, + likeOrDislikeVideo, + memberId, + reactionFee, + trackLikeAdded, + trackDislikeAdded, + video?.id, + video?.title, + ] + ) - const detailsItems = videoNotAvailable ? ( - mdMatch && - ) : ( + return ( <> {video ? ( @@ -416,81 +532,6 @@ export const VideoView: FC = () => { ) - - return ( - <> - {headTags} - - - - - {videoNotAvailable ? ( - - ) : !loading && video ? ( - setShareDialogOpen(false)} - onAddVideoView={handleAddVideoView} - isShareDialogOpen={isShareDialogOpen} - isVideoPending={!video?.media?.isAccepted} - videoId={video?.id} - autoplay - videoUrls={mediaUrls} - onEnd={handleVideoEnd} - onTimeUpdated={handleTimeUpdate} - startTime={startTimestamp} - isPlayNextDisabled={pausePlayNext} - ref={playerRef} - availableTextTracks={availableTracks} - /> - ) : ( - - )} - - {!isCinematic && ( - <> - {detailsItems} - {!videoNotAvailable && ( - - )} - - )} - - {!isCinematic && sideItems} - - - - {isCinematic && !(!mdMatch && videoNotAvailable) && ( - - - {detailsItems} - {!videoNotAvailable && ( - - )} - - {sideItems} - - )} - - - ) } const useIntersectionObserver = (options: IntersectionObserverInit = {}): [boolean, RefObject] => { diff --git a/packages/atlas/src/views/viewer/ViewerLayout.tsx b/packages/atlas/src/views/viewer/ViewerLayout.tsx index 60642fb089..4f6c0654d7 100644 --- a/packages/atlas/src/views/viewer/ViewerLayout.tsx +++ b/packages/atlas/src/views/viewer/ViewerLayout.tsx @@ -15,6 +15,8 @@ import { atlasConfig } from '@/config' import { absoluteRoutes, relativeRoutes } from '@/config/routes' import { useMediaMatch } from '@/hooks/useMediaMatch' import { useSegmentAnalytics } from '@/hooks/useSegmentAnalytics' +import { getCorrectLoginModal } from '@/providers/auth/auth.helpers' +import { useAuthStore } from '@/providers/auth/auth.store' import { useSearchStore } from '@/providers/search' import { useUser } from '@/providers/user/user.hooks' import { media, transitions } from '@/styles' @@ -82,62 +84,11 @@ const locationToPageName = { } export const ViewerLayout: FC = () => { - const location = useLocation() - const locationState = location.state as RoutingState const { isLoggedIn } = useUser() - const [searchParams] = useSearchParams() - const { trackPageView } = useSegmentAnalytics() - const navigate = useNavigate() - const mdMatch = useMediaMatch('md') - const searchOpen = useSearchStore((state) => state.searchOpen) + const location = useLocation() + const locationState = location.state as RoutingState const displayedLocation = locationState?.overlaidLocation || location - const afterGoogleRedirect = useRef(false) - - useEffect(() => { - if (!location.pathname.includes('studio')) { - const pageName = - location.pathname === '/' - ? 'Homepage' - : Object.entries(locationToPageName).find(([key]) => location.pathname.includes(key))?.[1] - - //pages below will be tracked by the view components in order to include the additional params - if (['Channel', 'Category', 'Video'].some((page) => pageName?.includes(page))) { - return - } - const [query, referrerChannel, utmSource, utmCampaign, gState, gCode] = [ - searchParams.get('query'), - searchParams.get('referrerId'), - searchParams.get('utm_source'), - searchParams.get('utm_campaign'), - searchParams.get('state'), - searchParams.get('code'), - ] - if (gState || gCode) { - afterGoogleRedirect.current = true - } - - // had to include this timeout to make sure the page title is updated - const trackRequestTimeout = setTimeout( - () => - trackPageView(pageName || 'Unknown page', { - ...(location.pathname === absoluteRoutes.viewer.ypp() - ? { - referrerChannel: referrerChannel || undefined, - utm_source: utmSource || undefined, - utm_campaign: utmCampaign || undefined, - } - : {}), - ...(location.pathname === absoluteRoutes.viewer.search() ? { searchQuery: query } : {}), - }), - 1000 - ) - - return () => { - clearTimeout(trackRequestTimeout) - } - } - }, [location.pathname, searchParams, trackPageView]) return ( <> @@ -204,7 +155,8 @@ export const ViewerLayout: FC = () => { - {!mdMatch && !searchOpen && } + + ) } @@ -221,3 +173,76 @@ const MainContainer = styled.main` : 'var(--size-topbar-height) var(--size-global-horizontal-padding) 0'}; } ` + +const BottomNavWrapper = () => { + const searchOpen = useSearchStore((state) => state.searchOpen) + const mdMatch = useMediaMatch('md') + if (!mdMatch && !searchOpen) { + return + } + return null +} + +const MiscUtils = () => { + const location = useLocation() + const [searchParams] = useSearchParams() + const { trackPageView } = useSegmentAnalytics() + const { + actions: { setAuthModalOpenName }, + } = useAuthStore() + const afterGoogleRedirect = useRef(false) + + useEffect(() => { + if (location.state?.['redirectTo']) { + setAuthModalOpenName(getCorrectLoginModal()) + } + // eslint-disable-next-line react-hooks/exhaustive-deps + }, []) + + useEffect(() => { + if (!location.pathname.includes('studio')) { + const pageName = + location.pathname === '/' + ? 'Homepage' + : Object.entries(locationToPageName).find(([key]) => location.pathname.includes(key))?.[1] + + //pages below will be tracked by the view components in order to include the additional params + if (['Channel', 'Category', 'Video'].some((page) => pageName?.includes(page))) { + return + } + const [query, referrerChannel, utmSource, utmCampaign, gState, gCode] = [ + searchParams.get('query'), + searchParams.get('referrerId'), + searchParams.get('utm_source'), + searchParams.get('utm_campaign'), + searchParams.get('state'), + searchParams.get('code'), + ] + if (gState || gCode) { + afterGoogleRedirect.current = true + } + + // had to include this timeout to make sure the page title is updated + const trackRequestTimeout = setTimeout( + () => + trackPageView(pageName || 'Unknown page', { + ...(location.pathname === absoluteRoutes.viewer.ypp() + ? { + referrerChannel: referrerChannel || undefined, + utm_source: utmSource || undefined, + utm_campaign: utmCampaign || undefined, + } + : {}), + ...(location.pathname === absoluteRoutes.viewer.search() ? { searchQuery: query } : {}), + }), + 1000 + ) + + return () => { + clearTimeout(trackRequestTimeout) + } + } + }, [location.pathname, searchParams, trackPageView]) + + return null +}