diff --git a/tubearchivist/www/next.config.js b/tubearchivist/www/next.config.js index b2a1d34..9d73944 100644 --- a/tubearchivist/www/next.config.js +++ b/tubearchivist/www/next.config.js @@ -1,5 +1,3 @@ -const { withPlaiceholder } = require("@plaiceholder/next"); - /** @type {import('next').NextConfig} */ const nextConfig = { images: { @@ -8,4 +6,4 @@ const nextConfig = { reactStrictMode: true, }; -module.exports = withPlaiceholder(nextConfig); +module.exports = nextConfig; diff --git a/tubearchivist/www/src/components/BoxedContent.tsx b/tubearchivist/www/src/components/BoxedContent.tsx new file mode 100644 index 0000000..e383ab4 --- /dev/null +++ b/tubearchivist/www/src/components/BoxedContent.tsx @@ -0,0 +1,3 @@ +export const BoxedContent: React.FC = ({ children }) => ( +
{children}
+); diff --git a/tubearchivist/www/src/components/VideoList.tsx b/tubearchivist/www/src/components/VideoList.tsx index 6655bc7..f56a0ca 100644 --- a/tubearchivist/www/src/components/VideoList.tsx +++ b/tubearchivist/www/src/components/VideoList.tsx @@ -1,18 +1,22 @@ +import { useSession } from "next-auth/react"; import NextImage from "next/image"; import { useState } from "react"; -import ReactPlayer from "react-player/file"; import { useQuery } from "react-query"; -import IconClose from "../images/icon-close.svg"; import IconPlay from "../images/icon-play.svg"; import { TA_BASE_URL } from "../lib/constants"; import { getVideos } from "../lib/getVideos"; -import { formatNumbers } from "../lib/utils"; -import { Datum, Videos } from "../types/video"; +import type { Datum } from "../types/video"; +import { VideoPlayer } from "./VideoPlayer"; + +type ViewStyle = "grid" | "list"; export const VideoList = () => { const [selectedVideoUrl, setSelectedVideoUrl] = useState(); - const { data: queryData } = useQuery("videos", getVideos); - const { data: videos } = queryData; + const [viewStyle, setViewStyle] = useState("grid"); + const { data: session } = useSession(); + const { data, error, isLoading } = useQuery("videos", () => + getVideos(session.ta_token.token) + ); const handleSelectedVideo = (video: Datum) => { setSelectedVideoUrl(video); @@ -22,12 +26,16 @@ export const VideoList = () => { setSelectedVideoUrl(undefined); }; - if (!videos) { + const handleSetViewstyle = (selectedViewStyle: ViewStyle) => { + setViewStyle(selectedViewStyle); + }; + + if (!isLoading && !data?.data) { return (

No videos found...

- If you`'`ve already added a channel or playlist, try going to the{" "} + If you've already added a channel or playlist, try going to the{" "} downloads page to start the scan and download tasks.

@@ -37,63 +45,10 @@ export const VideoList = () => { return ( <> - {selectedVideoUrl && ( - <> -
-
- -
- - {/* ${watchStatusIndicator} - ${castButton} - */} -
- views icon - - {formatNumbers( - selectedVideoUrl.stats.view_count.toString() - )} - - | - thumbs-up - - {formatNumbers( - selectedVideoUrl.stats.like_count.toString() - )} - -
-
-

- - {selectedVideoUrl.channel.channel_name} - -

- {/* ${playlist} */} -
- -

{selectedVideoUrl.title}

-
-
-
-
- - )} +
@@ -151,25 +106,24 @@ export const VideoList = () => { /> console.log("grid view")} - data-origin="home" - data-value="grid" + onClick={() => handleSetViewstyle("grid")} alt="grid view" /> console.log("list view")} - data-origin="home" - data-value="list" + onClick={() => handleSetViewstyle("list")} alt="list view" />
-
- {videos && - videos?.map((video) => { +
+ {data && + data.data?.map((video) => { return ( -
+
handleSelectedVideo(video)} diff --git a/tubearchivist/www/src/components/VideoPlayer.tsx b/tubearchivist/www/src/components/VideoPlayer.tsx new file mode 100644 index 0000000..469bc9d --- /dev/null +++ b/tubearchivist/www/src/components/VideoPlayer.tsx @@ -0,0 +1,61 @@ +import NextImage from "next/image"; +import ReactPlayer from "react-player"; +import IconClose from "../images/icon-close.svg"; +import { formatNumbers } from "../lib/utils"; + +export const VideoPlayer = ({ selectedVideoUrl, handleRemoveVideoPlayer }) => { + if (!selectedVideoUrl) return; + return ( + <> + + + ); +}; diff --git a/tubearchivist/www/src/lib/getChannels.ts b/tubearchivist/www/src/lib/getChannels.ts index 117ee63..bb2fbb8 100644 --- a/tubearchivist/www/src/lib/getChannels.ts +++ b/tubearchivist/www/src/lib/getChannels.ts @@ -1,15 +1,19 @@ import { Channel } from "../types/channel"; -export const getChannels = async (): Promise => { - return await fetch( +export const getChannels = async (token: string): Promise => { + const response = await fetch( `${process.env.NEXT_PUBLIC_TUBEARCHIVIST_URL}/api/channel/`, { headers: { Accept: "application/json", "Content-Type": "application/json", - Authorization: `Token b4d4330462c7fc16c51873e45579b29a1a12fc90`, + Authorization: `Token ${token}`, mode: "no-cors", }, } - ).then((res) => res.json()); + ); + if (!response.ok) { + throw new Error("Error getting channel information"); + } + return response.json(); }; diff --git a/tubearchivist/www/src/lib/getVideos.ts b/tubearchivist/www/src/lib/getVideos.ts index 16bf031..7739105 100644 --- a/tubearchivist/www/src/lib/getVideos.ts +++ b/tubearchivist/www/src/lib/getVideos.ts @@ -1,15 +1,26 @@ import { Videos } from "../types/video"; -export const getVideos = async (): Promise => { - return await fetch( +export const getVideos = async (token: string): Promise => { + if (!token) { + throw new Error("Missing API token in request to get videos"); + } + const response = await fetch( `${process.env.NEXT_PUBLIC_TUBEARCHIVIST_URL}/api/video/`, { headers: { Accept: "application/json", "Content-Type": "application/json", - Authorization: `Token b4d4330462c7fc16c51873e45579b29a1a12fc90`, + Authorization: `Token ${token}`, mode: "no-cors", }, } - ).then((res) => res.json()); + ); + + if (!response.ok) { + throw new Error("Failed to fetch videos"); + } + + return response.json(); }; + +// b4d4330462c7fc16c51873e45579b29a1a12fc90 diff --git a/tubearchivist/www/src/pages/_app.tsx b/tubearchivist/www/src/pages/_app.tsx index cdabb5b..02d158a 100644 --- a/tubearchivist/www/src/pages/_app.tsx +++ b/tubearchivist/www/src/pages/_app.tsx @@ -1,40 +1,16 @@ -import type { AppProps } from "next/app"; import { SessionProvider } from "next-auth/react"; -import Script, { ScriptProps } from "next/script"; +import type { AppProps } from "next/app"; +import { useState } from "react"; import { Hydrate, QueryClient, QueryClientProvider } from "react-query"; import { ReactQueryDevtools } from "react-query/devtools"; -import "../styles/globals.css"; import "../styles/dark.css"; // TODO: Setup themeing the React way -import { useState } from "react"; - -// TODO: Do these scripts need to be on every page? - -type ClientOnlyScriptProps = { - src: string; -} & ScriptProps; - -/** - * This wraps next/script and returns early if `window` is not detected - * due to next using SSR - */ -const ClientOnlyScript = ({ src, ...props }: ClientOnlyScriptProps) => { - if (typeof window === "undefined") { - return; - } - return