167
resources/client/videos/watch-page/watch-page.tsx
Executable file
167
resources/client/videos/watch-page/watch-page.tsx
Executable file
@@ -0,0 +1,167 @@
|
||||
import React, {Fragment, useState} from 'react';
|
||||
import {MainNavbar} from '@app/main-navbar';
|
||||
import {useDarkThemeVariables} from '@common/ui/themes/use-dark-theme-variables';
|
||||
import {
|
||||
useWatchPageVideo,
|
||||
UseWatchPageVideoResponse,
|
||||
} from '@app/videos/requests/use-watch-page-video';
|
||||
import {Footer} from '@common/ui/footer/footer';
|
||||
import {PageErrorMessage} from '@common/errors/page-error-message';
|
||||
import {CommentList} from '@common/comments/comment-list/comment-list';
|
||||
import {NewCommentForm} from '@common/comments/new-comment-form';
|
||||
import {WatchPageTitleDetails} from '@app/videos/watch-page/watch-page-title-details';
|
||||
import {WatchPageAside} from '@app/videos/watch-page/watch-page-aside';
|
||||
import {AnimatePresence, m} from 'framer-motion';
|
||||
import {opacityAnimation} from '@common/ui/animation/opacity-animation';
|
||||
import {useScrollToTop} from '@common/ui/navigation/use-scroll-to-top';
|
||||
import {VideoPlayerSkeleton} from '@app/videos/video-player-skeleton';
|
||||
import {SiteVideoPlayer} from '@app/videos/site-video-player';
|
||||
import {useSettings} from '@common/core/settings/use-settings';
|
||||
import {useAuth} from '@common/auth/use-auth';
|
||||
import {useIsStreamingMode} from '@app/videos/use-is-streaming-mode';
|
||||
import {WatchPageAlternativeVideos} from '@app/videos/watch-page/watch-page-alternative-videos';
|
||||
import {AdHost} from '@common/admin/ads/ad-host';
|
||||
import {Episode} from '@app/titles/models/episode';
|
||||
import {Title} from '@app/titles/models/title';
|
||||
import {Video} from '@app/titles/models/video';
|
||||
import {PageMetaTags} from '@common/http/page-meta-tags';
|
||||
import {useLayoutEffect} from '@react-aria/utils';
|
||||
import {VideoThumbnail} from '@app/videos/video-thumbnail';
|
||||
import {Trans} from '@common/i18n/trans';
|
||||
import {Button} from '@common/ui/buttons/button';
|
||||
import {Link} from 'react-router-dom';
|
||||
|
||||
export function WatchPage() {
|
||||
const darkThemeVars = useDarkThemeVariables();
|
||||
useScrollToTop();
|
||||
|
||||
return (
|
||||
<Fragment>
|
||||
<MainNavbar />
|
||||
<div style={darkThemeVars} className="dark min-h-screen bg text">
|
||||
<div className="container mx-auto p-14 md:p-24">
|
||||
<Content />
|
||||
<Footer className="mt-48" />
|
||||
</div>
|
||||
</div>
|
||||
</Fragment>
|
||||
);
|
||||
}
|
||||
|
||||
function Content() {
|
||||
const {titles, comments} = useSettings();
|
||||
const {isLoggedIn, hasPermission} = useAuth();
|
||||
const query = useWatchPageVideo();
|
||||
const {data, isLoading} = query;
|
||||
const title = data?.title;
|
||||
const episode = data?.episode;
|
||||
const video = data?.video;
|
||||
let commentable: Episode | Title | Video | undefined = video;
|
||||
|
||||
if (!comments?.per_video) {
|
||||
commentable = episode || title;
|
||||
}
|
||||
|
||||
const shouldShowComments =
|
||||
title && video && titles.enable_comments && hasPermission('comments.view');
|
||||
|
||||
if (data || isLoading) {
|
||||
return (
|
||||
<Fragment key={video?.id || 'loading'}>
|
||||
<PageMetaTags query={query} />
|
||||
<VideoWrapper data={data} />
|
||||
<WatchPageAlternativeVideos data={data} />
|
||||
<AdHost slot="watch_top" className="pt-48" />
|
||||
<section className="mt-42 items-start gap-56 lg:flex">
|
||||
<div className="flex-auto">
|
||||
<WatchPageTitleDetails />
|
||||
{shouldShowComments && (
|
||||
<CommentList
|
||||
commentable={commentable!}
|
||||
className="mt-44"
|
||||
perPage={20}
|
||||
>
|
||||
{isLoggedIn && hasPermission('comments.create') && (
|
||||
<NewCommentForm
|
||||
commentable={commentable!}
|
||||
className="mb-14 mt-24"
|
||||
/>
|
||||
)}
|
||||
</CommentList>
|
||||
)}
|
||||
</div>
|
||||
<WatchPageAside />
|
||||
</section>
|
||||
</Fragment>
|
||||
);
|
||||
}
|
||||
|
||||
return <PageErrorMessage />;
|
||||
}
|
||||
|
||||
interface VideoWrapperProps {
|
||||
data?: UseWatchPageVideoResponse;
|
||||
}
|
||||
function VideoWrapper({data}: VideoWrapperProps) {
|
||||
const isStreamingMode = useIsStreamingMode();
|
||||
const {hasPermission} = useAuth();
|
||||
|
||||
const [isVisible, setIsVisible] = useState(false);
|
||||
useLayoutEffect(() => {
|
||||
setIsVisible(true);
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<AnimatePresence initial={false} mode="wait">
|
||||
{data?.video && isVisible ? (
|
||||
<m.div key="player" {...opacityAnimation}>
|
||||
{hasPermission('videos.play') ? (
|
||||
<SiteVideoPlayer
|
||||
title={data.title}
|
||||
episode={data.episode}
|
||||
video={data.video}
|
||||
relatedVideos={data.related_videos}
|
||||
autoPlay
|
||||
logPlays
|
||||
showEpisodeSelector={isStreamingMode}
|
||||
/>
|
||||
) : (
|
||||
<UpgradeMessage video={data.video} />
|
||||
)}
|
||||
</m.div>
|
||||
) : (
|
||||
<m.div className="relative" key="skeleton" {...opacityAnimation}>
|
||||
<VideoPlayerSkeleton animate />
|
||||
</m.div>
|
||||
)}
|
||||
</AnimatePresence>
|
||||
);
|
||||
}
|
||||
|
||||
interface UpgradeMessageProps {
|
||||
video: Video;
|
||||
}
|
||||
function UpgradeMessage({video}: UpgradeMessageProps) {
|
||||
return (
|
||||
<div className="relative flex aspect-video items-center justify-center bg-alt">
|
||||
<div className="blur">
|
||||
<VideoThumbnail video={video} />
|
||||
</div>
|
||||
<div className="absolute h-max w-max max-w-full rounded-lg bg-black/60 p-24 text-lg font-medium">
|
||||
<div>
|
||||
<Trans message="Your current plan does not allow watching videos. Upgrade to unlock this feature." />
|
||||
</div>
|
||||
<div className="mt-14 text-center">
|
||||
<Button
|
||||
variant="flat"
|
||||
color="primary"
|
||||
elementType={Link}
|
||||
to="/pricing"
|
||||
>
|
||||
<Trans message="Upgrade" />
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user