diff --git a/src/App.tsx b/src/App.tsx index 795cd607b2c1ea5f57161744db8ec6d3ee339e8f..9cca4f08faaaf4c2efee393ec44fb897ad2db7c4 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -4,8 +4,11 @@ import { RouterProvider, createBrowserRouter } from "react-router-dom"; import SidebarLayout from "./components/layouts/SidebarLayout"; import ProfileLayout from "./components/layouts/ProfileLayout"; import HomePage from "./pages/home"; +import PodcastPage from "./pages/podcast"; +import EpisodePage from "./pages/episode" import QueuePage from "./pages/queue"; import axios from "axios"; +import LibraryPage from "./pages/library"; export default function App(): JSX.Element { const userQueueLoader = async () => { @@ -43,18 +46,25 @@ export default function App(): JSX.Element { }, { path: "/library", - element: <h1>Hello library</h1>, + element: <LibraryPage/>, }, { - path: "/queue", + path: "/podcast/:podcastId", + element: <PodcastPage/>, + }, + { + path: "/episode/:episodeId", + element: <EpisodePage/>, + }, + { path: "/queue", element: <QueuePage />, loader: userQueueLoader, }, ], - }, - ], - }, - ]); + },] + } + ]) + return <RouterProvider router={router} />; } diff --git a/src/components/EpisodeHeader.tsx b/src/components/EpisodeHeader.tsx new file mode 100644 index 0000000000000000000000000000000000000000..3c1a7bd5cd0a30a19a120e259b84ea139a33e5e3 --- /dev/null +++ b/src/components/EpisodeHeader.tsx @@ -0,0 +1,74 @@ +import Placeholder from "../assets/placeholder_image.jpg"; +import PlayIcon from "../assets/play-icon.svg"; +import PlusIcon from "../assets/plus-icon.svg"; + + +export type headerProps = { + title: string, + description: string, + imageurl: string + } + + export default function EpisodeHeader({title, description, imageurl}: headerProps): JSX.Element { + const urlPrefix = "http://localhost:3000/images/" + return ( + <div className="block"> + <div className="w-[950px] inline-flex mt-[20px]"> + + <div className=""> + <div className="w-[225px] h-[225px]"> + <img className="rounded-[20px] w-[225px] h-[225px] object-cover object-center" + width={225} + height={225} + src={ urlPrefix + imageurl || Placeholder} + alt="podcast thumbnail" /> + </div> + + </div> + + <div className="ml-[30px]"> + <div className="block"> + + </div> + <div className="block"> + <h1 className="h1 my-[10px] leading-tight">{title}</h1> + </div> + + <div className="block"> + <p className="b3 text-gray-600 ">{description}</p> + </div> + </div> + + </div> + + <div className="block mt-[20px]"> + <button + data-te-toggle="tooltip" + title="play episode" + className=" w-[225px] h-[50px] bg-NAVY-5 text-white rounded-[32px] h4 leading-4"> + Play Episode + <img + className="inline ml-[45px]" + width={16} + height={16} + src={PlayIcon} + alt="" /> + </button> + + <button + data-te-toggle="tooltip" + title="add episode to queue" + className=" w-[225px] h-[50px] bg-NAVY-5 text-white rounded-[32px] h4 leading-4 ml-[30px]"> + Add To Queue + <img + className="inline ml-[45px]" + width={16} + height={16} + src={PlusIcon} + alt="" /> + </button> + + </div> + </div> + ); + } \ No newline at end of file diff --git a/src/components/EpisodeList.tsx b/src/components/EpisodeList.tsx new file mode 100644 index 0000000000000000000000000000000000000000..7859fa2bb3eb5b429e9352229cf26cc39f2d8189 --- /dev/null +++ b/src/components/EpisodeList.tsx @@ -0,0 +1,58 @@ +import Placeholder from "../assets/placeholder_image.jpg"; +import PlayIcon from "../assets/play-icon.svg"; +import PlusIcon from "../assets/plus-icon.svg"; + + +export type episodeProps = { + order: number, + title: string, + description: string, + imageurl: string + } + + export default function EpisodeList({order, title, description, imageurl}: episodeProps): JSX.Element { + const urlPrefix = "http://localhost:3000/images/" + return ( + <div className="group/item flex items-center w-[1100px] h-[110px] rounded-xl bg-white hover:bg-NAVY-5 text-black hover:text-white"> + + <h1 className="h2 ml-[30px] mr-[20px]">{order}</h1> + + <img + className="rounded-lg w-[75px] h-[75px] object-cover object-center" + width={75} + height={75} + src={ urlPrefix + imageurl || Placeholder} + alt="episode thumbnail" /> + + <div className="w-[650px] h-[59px] ml-5"> + <h2 className="h3 text-ellipsis whitespace-nowrap overflow-hidden">{title}</h2> + <p className="b4 text-ellipsis whitespace-nowrap overflow-hidden">{description}</p> + </div> + + <button + data-te-toggle="tooltip" + title="play episode" + className="invisible group-hover/item:visible flex items-center justify-center rounded-full bg-black w-[48px] h-[48px] ml-[70px] hover:bg-gray-600"> + <img + className="ml-[5px]" + width={18} + height={18} + src={PlayIcon} + alt="play episode"/> + </button> + + <button + data-te-toggle="tooltip" + title="add to queue" + className="invisible group-hover/item:visible flex items-center justify-center rounded-full bg-black w-[48px] h-[48px] ml-[30px] hover:bg-gray-600"> + <img + className="" + width={18} + height={18} + src={PlusIcon} + alt="play episode"/> + </button> + + </div> + ); + } \ No newline at end of file diff --git a/src/components/LibraryCard.tsx b/src/components/LibraryCard.tsx new file mode 100644 index 0000000000000000000000000000000000000000..aed670b2851681a144a8da05be3b60260264ef17 --- /dev/null +++ b/src/components/LibraryCard.tsx @@ -0,0 +1,58 @@ +// Asset imports +// import axios from "axios"; +import Placeholder from "../assets/placeholder_image.jpg"; +import SampleImage1 from "../assets/escape.jpg"; +import SampleImage2 from "../assets/hello.jpg"; +import SampleImage3 from "../assets/play.jpg"; + +export type cardProps = { + title: string; + imageurl: string; +}; + +export default function LibraryCard({ + title, +// imageurl +}: cardProps): JSX.Element { + return ( + <div className="w-[160px] h-[210px] rounded-xl overflow-hidden border-NAVY-5 border-2 shadow-[-2px_2px_4px_0_#5C67DE,2px_-2px_4px_0_#5C67DE,-2px_-2px_4px_0_#5C67DE,2px_2px_4px_0_#5C67DE] hover:shadow-[-2px_2px_4px_0_#F5D049,2px_-2px_4px_0_#F5D049,-2px_-2px_4px_0_#F5D049,2px_2px_4px_0_#F5D049] hover:bg-YELLOW-5 hover:border-YELLOW-1 group shrink-0 xl:w-[200px] xl:h-[288px]"> + + <div className="shrink-0 w-[160px] h-[160px] overflow-hidden grid grid-cols-2 grid-rows-2 xl:w-[200px] xl:h-[200px]"> + <img + className="object-cover object-center xl:w-[100px] xl:h-[100px] lg:w-[80px] lg:h-[80px]" + src={SampleImage1 || Placeholder} + width={100} + height={100} + alt="image1" + /> + <img + className="object-cover object-center xl:w-[100px] xl:h-[100px] lg:w-[80px] lg:h-[80px]" + src={SampleImage2 || Placeholder} + width={100} + height={100} + alt="image2" + /> + <img + className="object-cover object-center xl:w-[100px] xl:h-[100px] lg:w-[80px] lg:h-[80px]" + src={SampleImage3 || Placeholder} + width={100} + height={100} + alt="image3" + /> + <img + className="object-cover object-center xl:w-[100px] xl:h-[100px] lg:w-[80px] lg:h-[80px]" + src={SampleImage1 || Placeholder} + width={100} + height={100} + alt="image4" + /> + </div> + + <div className="pt-3 pb-5 px-1.5 w-full group-hover:text-NAVY-2 relative xl:pt-4 xl:pb-6 xl:px-2.5"> + <h4 className="h4 w-full text-ellipsis whitespace-nowrap overflow-hidden md:max-xl:text-xs"> + {title} + </h4> + </div> + </div> + ); +} \ No newline at end of file diff --git a/src/components/PodcastHeader.tsx b/src/components/PodcastHeader.tsx new file mode 100644 index 0000000000000000000000000000000000000000..9c95bb3c015f2504bca8b35caeaeda17b2b5d635 --- /dev/null +++ b/src/components/PodcastHeader.tsx @@ -0,0 +1,83 @@ +import Placeholder from "../assets/placeholder_image.jpg"; +import PlayIcon from "../assets/play-icon.svg"; +import PlusIcon from "../assets/plus-icon.svg"; + + +export type headerProps = { + category: string, + creator: string, + title: string, + description: string, + imageurl: string + } + + export default function PodcastHeader({category, creator, title, description, imageurl}: headerProps): JSX.Element { + const urlPrefix = "http://localhost:3000/images/" + return ( + <div className="block"> + <div className="w-[950px] inline-flex mt-[20px]"> + + <div className=""> + <div className="w-[225px] h-[225px]"> + <img className="rounded-[20px] object-cover object-center w-[225px] h-[225px]" + width={225} + height={225} + src={ urlPrefix + imageurl || Placeholder} + alt="podcast thumbnail" /> + </div> + + </div> + + <div className="ml-[30px]"> + <div className="block"> + <div className="inline-flex w-[100px] h-[32px] rounded-[32px] bg-NAVY-5 text-white align-middle items-center justify-center"> + <h3 className="h6 capitalize">{category.toLowerCase()}</h3> + </div> + + <div className="inline-flex ml-[10px]"> + <h3 className="h6 text-NAVY-5">Created By {creator}</h3> + </div> + </div> + + <div className="block"> + <h1 className="h1 my-[10px] leading-tight">{title}</h1> + </div> + + <div className="block"> + <p className="b3 text-gray-600 ">{description}</p> + </div> + </div> + + </div> + + <div className="block mt-[20px]"> + + <button + data-te-toggle="tooltip" + title="add episode to library" + className=" w-[225px] h-[50px] bg-NAVY-5 text-white rounded-[32px] h4 leading-4"> + Add To Library + <img + className="inline ml-[45px]" + width={16} + height={16} + src={PlusIcon} + alt="plus icon" /> + </button> + + <button + data-te-toggle="tooltip" + title="play episode" + className=" w-[48px] h-[48px] bg-black text-white rounded-[32px] h4 leading-4 ml-[30px] hover:bg-gray-600"> + <img + className="inline ml-[5px]" + width={18} + height={18} + src={PlayIcon} + alt="play podcast" /> + </button> + + </div> + </div> + ); + } \ No newline at end of file diff --git a/src/pages/episode/index.tsx b/src/pages/episode/index.tsx new file mode 100644 index 0000000000000000000000000000000000000000..8b24cf92c703e4abff3945f4044f9b834b247ead --- /dev/null +++ b/src/pages/episode/index.tsx @@ -0,0 +1,27 @@ +import EpisodeHeader, { headerProps } from "../../components/EpisodeHeader"; +import { useEffect, useState } from 'react' +import axios from 'axios' +import { useParams } from 'react-router-dom' + +export default function PodcastPage() : JSX.Element{ + const { episodeId } = useParams(); + + const [episodeHeader, setEpisodeHeader] = useState<headerProps>(); + + useEffect(() => { + (async () => { + const resEpisodeHeader = await axios.get( + `http://localhost:3000/episode/${episodeId}` + ); + setEpisodeHeader(resEpisodeHeader.data.episode[0]); + })(); + }, [episodeId]) + + return( + <div className="ml-[100px]"> + {episodeHeader? (<EpisodeHeader {...episodeHeader}/>) : <h1 className="h1"></h1>} + </div> + + ); + +} \ No newline at end of file diff --git a/src/pages/library/index.tsx b/src/pages/library/index.tsx new file mode 100644 index 0000000000000000000000000000000000000000..a6e02c3d5071403b0f06a5c5d2eb7e255397a407 --- /dev/null +++ b/src/pages/library/index.tsx @@ -0,0 +1,42 @@ +import LibraryCard from "../../components/LibraryCard"; +import PlusIcon from "../../assets/plus-icon.svg"; +export default function LibraryPage() : JSX.Element{ + + return( + <section className="px-6 pb-10 xl:px-8 xl:pb-20 mt-8"> + <div className="flex justify-between"> + <h2 className="h1 md:max-lg:text-xl lg:max-xl:text-2xl">Your Library</h2> + <button + data-te-toggle="tooltip" + title="add episode to library" + className=" w-[225px] h-[50px] bg-NAVY-5 text-white rounded-[32px] h4 leading-4"> + New Library + <img + className="inline ml-[45px]" + width={16} + height={16} + src={PlusIcon} + alt="plus icon" /> + </button> + + </div> + <div className="mt-4 -ml-1.5 px-1.5 py-2 overflow-x-auto xl:mt-4 grid xl:grid-cols-5 lg:grid-cols-4 gap-12"> + <LibraryCard title="Test Library" imageurl="boong"/> + <LibraryCard title="Test Library" imageurl="boong"/> + <LibraryCard title="Test Library" imageurl="boong"/> + <LibraryCard title="Test Library" imageurl="boong"/> + <LibraryCard title="Test Library" imageurl="boong"/> + <LibraryCard title="Test Library" imageurl="boong"/> + <LibraryCard title="Test Library" imageurl="boong"/> + <LibraryCard title="Test Library" imageurl="boong"/> + <LibraryCard title="Test Library" imageurl="boong"/> + <LibraryCard title="Test Library" imageurl="boong"/> + <LibraryCard title="Test Library" imageurl="boong"/> + <LibraryCard title="Test Library" imageurl="boong"/> + <LibraryCard title="Test Library" imageurl="boong"/> + <LibraryCard title="Test Library" imageurl="boong"/> + </div> + + </section> + ); +} \ No newline at end of file diff --git a/src/pages/podcast/index.tsx b/src/pages/podcast/index.tsx new file mode 100644 index 0000000000000000000000000000000000000000..e0d2e3444f449d3cc1b933eb272126ac42540461 --- /dev/null +++ b/src/pages/podcast/index.tsx @@ -0,0 +1,42 @@ +import { useEffect, useState } from "react"; +import Episode, { episodeProps} from "../../components/EpisodeList" +import PodcastHeader, { headerProps} from "../../components/PodcastHeader"; +import axios from "axios" +import { useParams } from 'react-router-dom' + + +export default function PodcastPage() : JSX.Element{ + const { podcastId } = useParams(); + // component states + const [episodes, setEpisodes] = useState<episodeProps[]>([]); + const [podcastHeader, setPodcastHeader] = useState<headerProps>(); + + useEffect(() => { + (async () => { + const resPodcastHeader = await axios.get( + `http://localhost:3000/podcast/${podcastId}` + ); + setPodcastHeader(resPodcastHeader.data.podcast[0]); + + const resEpisodes = await axios.get( + `http://localhost:3000/podcast/episode/${podcastId}` + ); + setEpisodes(resEpisodes.data.episodes); + })(); + }, [podcastId]) + + return( + <div className="ml-[100px]"> + {podcastHeader? (<PodcastHeader {...podcastHeader}/>) : <h1 className="h1"></h1>} + <div> + <hr className="h-px mt-[30px] ml-[-100px] bg-black bg-opacity-40"/> + </div> + + <section className="mt-[20px] overflow-y-scroll h-[60vh] mb-[60px]"> + {episodes?.map((episode: episodeProps, idx: number) => ( + <Episode key={idx} title={episode.title} description={episode.description} imageurl={episode.imageurl} order={idx+1}/> + ))} + </section> + </div> + ); +} \ No newline at end of file