diff --git a/src/App.tsx b/src/App.tsx index 6458afffa55e98c76d49b2a99a1e3017039cb8ba..fb42b17c0c141dc08acd8495a52497ebbe7f26cd 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -10,6 +10,7 @@ import QueuePage from "./pages/queue"; import NotFoundPage from "./pages/not-found"; import axios from "axios"; import LibraryPage from "./pages/library"; +import PlaylistPage from "./pages/playlist" export default function App(): JSX.Element { const userQueueLoader = async () => { @@ -46,7 +47,7 @@ export default function App(): JSX.Element { element: <h1>Hello search</h1>, }, { - path: "/library", + path: "/library/:userId", element: <LibraryPage/>, }, { @@ -61,6 +62,10 @@ export default function App(): JSX.Element { element: <QueuePage />, loader: userQueueLoader, }, + { + path: "/playlist/:playlistId", + element: <PlaylistPage/>, + } ], }, ], diff --git a/src/components/EpisodeList.tsx b/src/components/EpisodeList.tsx index 500091a71b9611c0b299064b2be558dfe01baac8..7303a76ccd66ce3b2452a207e4efe8812a512e64 100644 --- a/src/components/EpisodeList.tsx +++ b/src/components/EpisodeList.tsx @@ -20,7 +20,7 @@ export default function EpisodeList({ url_thumbnail, id_episode, }: episodeProps): JSX.Element { - const urlPrefix = "http://localhost:3000/images/"; + const urlPrefix = `${import.meta.env.VITE_REST_URL}/images/`; const dispatchQueue = useQueueDispatch(); const queue = useQueue(); diff --git a/src/components/LibraryCard.tsx b/src/components/LibraryCard.tsx index e1381e305ac742ddfa19dd068b031a7cfc4535c7..bc799e4e18270e026ba431e2cfb609c8472a9a6c 100644 --- a/src/components/LibraryCard.tsx +++ b/src/components/LibraryCard.tsx @@ -4,16 +4,25 @@ 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"; +import { useNavigate } from "react-router-dom"; export type cardProps = { title: string; + id_playlist: number; }; export default function LibraryCard({ - title, + title, + id_playlist }: cardProps): JSX.Element { + const navigate = useNavigate(); + const handleNavigate = () => { + navigate(`/podcast/${id_playlist}`); + }; + + 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 onClick={handleNavigate} 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 diff --git a/src/components/PodcastHeader.tsx b/src/components/PodcastHeader.tsx index bd571a3c8a1b51f91b9a6bb2272325493ccaa118..cd7f763785fc661f49ce0015540d18791c13cf88 100644 --- a/src/components/PodcastHeader.tsx +++ b/src/components/PodcastHeader.tsx @@ -3,6 +3,8 @@ import Placeholder from "../assets/placeholder_image.jpg"; import PlayIcon from "../assets/play-icon.svg"; import PlusIcon from "../assets/plus-icon.svg"; import { Queue, useQueue, useQueueDispatch } from "../contexts/QueueContext"; +// import { useState, useEffect } from "react"; +// import { cardProps } from "./LibraryCard"; export type headerProps = { category: string; @@ -21,10 +23,39 @@ export default function PodcastHeader({ url_thumbnail, id_podcast, }: headerProps): JSX.Element { - const urlPrefix = "http://localhost:3000/images/"; + const urlPrefix = `${import.meta.env.VITE_REST_URL}/images/`; const queue = useQueue(); const dispatchQueue = useQueueDispatch(); + // const [libraryData, setLibraryData] = useState<cardProps[]>([]); + + // useEffect(() => { + // (async () => { + // const resLibaryData = await axios.get( + // `${import.meta.env.VITE_REST_URL}/library/${userId}`, + // { + // headers: { + // Authorization: `Bearer ${localStorage.getItem("token")}`, + // }, + // } + // ); + // setLibraryData(resLibaryData.data.playlists); + // })(); + // }, [userId]); + + // const handleAddPodcastToLibrary = async () => { + // const axiosInstance = axios.create({ + // headers: { + // Authorization: `Bearer ${localStorage.getItem("token")}`, + // }, + // }); + + // await axiosInstance.post(`${import.meta.env.VITE_REST_URL}/playlist/podcast/${id_podcast}`, { + // idPlaylist: , + // idPodcast: , + // }) + + // } const handleAddToQueue = async (e: React.MouseEvent<HTMLElement>) => { e.stopPropagation(); diff --git a/src/pages/library/index.tsx b/src/pages/library/index.tsx index 571d352f4feea3e3df99f81c79fc18852755c2c5..1843002cf1fcff42e587d398859dd3155e9870ce 100644 --- a/src/pages/library/index.tsx +++ b/src/pages/library/index.tsx @@ -3,11 +3,41 @@ import PlusIcon from "../../assets/plus-icon.svg"; import { useParams } from "react-router-dom"; import { useState, useEffect } from "react"; import axios from "axios" +import toast, {Toaster} from 'react-hot-toast'; export default function LibraryPage() : JSX.Element{ const { userId } = useParams(); - + const [isVisible, setIsVisible] = useState(false); const [libraryData, setLibraryData] = useState<cardProps[]>([]); + const [newPlaylistTitle, setNewPlaylistTitle] = useState<string>(""); + + + const handleNewPlaylistSubmit = () => { + toast.promise(( + (async () => { + await axios.post( + `${import.meta.env.VITE_REST_URL}/library/${userId}`, + { + title: newPlaylistTitle, + idUser: userId, + }, + { + headers: { + Authorization: `Bearer ${localStorage.getItem("token")}`, + }, + } + ); + })() + ),{ + loading: 'loading...', + success: "New Playlist Created!", + error: "Fail To Create Playlist!", + }); + }; + + const toggleVisibility = () => { + setIsVisible(!isVisible); + } useEffect(() => { (async () => { @@ -25,13 +55,15 @@ 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"> + <Toaster position="top-center"/> + <div className="flex justify-between mr-12"> <h2 className="h1 md:max-lg:text-xl lg:max-xl:text-2xl">Your Library</h2> <button + onClick={toggleVisibility} 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 + className=" xl:w-[200px] h-[50px] bg-NAVY-5 text-white rounded-[32px] h4 leading-4"> + New Playlist <img className="inline ml-[45px]" width={16} @@ -40,11 +72,26 @@ export default function LibraryPage() : JSX.Element{ 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"> - { libraryData?.map((data: cardProps) => ( - < LibraryCard {...data}/> + + { isVisible && + <div className="mt-5 w-[300px] h-[150px] bg-white shadow-lg rounded-md hover:shadow-xl px-2 border-NAVY-5 border-2 border-opacity-70 absolute right-10 z-10"> + <h2 className="h3 text-center mt-2">New Playlist</h2> + <form className="flex flex-col mt-2 justify-center"> + <input className="rounded-xl placeholder:opacity-80" type="text" required placeholder="New Playlist Title" onChange={(e) => setNewPlaylistTitle(e.target.value)}/> + <button onClick={handleNewPlaylistSubmit} className="bg-NAVY-5 rounded-lg w-[100px] h-[30px] text-white mt-5 self-center" type="submit" >Submit</button> + </form> + </div> + } + + <div className= {`${ + isVisible ? 'blur-[3px]' : ''} + mt-4 -ml-1.5 px-1.5 py-2 xl:mt-4 flex flex-wrap align-start gap-20`} + onClick={isVisible? toggleVisibility: undefined} + > + { libraryData?.map((data: cardProps, idx: number) => ( + < LibraryCard {...data} key={idx}/> )) } + </div> </section> diff --git a/src/pages/playlist/index.tsx b/src/pages/playlist/index.tsx new file mode 100644 index 0000000000000000000000000000000000000000..0447b2652953babd333c24a806d56983d2ec58c4 --- /dev/null +++ b/src/pages/playlist/index.tsx @@ -0,0 +1,48 @@ +import { useEffect, useState } from "react"; +import PodcastCard, { cardProps } from "../../components/PodcastCard"; +import axios from "axios"; +import { useParams } from "react-router-dom"; + +export default function PlaylistPage(): JSX.Element { + const { playlistId } = useParams(); + // component states + const [playlistTitle, setPlaylistTitle] = useState<string>(""); + const [playlistPodcast, setPlaylistPodcast] = useState<cardProps[]>([]); + + useEffect(() => { + (async () => { + const axiosInstance = axios.create({ + headers: { + Authorization: `Bearer ${localStorage.getItem("token")}`, + }, + }); + + const [resPlaylistTitle, resPlaylistPodcast] = await Promise.all([ + axiosInstance.get( + `${import.meta.env.VITE_REST_URL}/playlist/title/${playlistId}` + ), + axiosInstance.get( + `${import.meta.env.VITE_REST_URL}/playlist/${playlistId}` + ), + ]); + + setPlaylistTitle(resPlaylistTitle.data.playlist); + setPlaylistPodcast(resPlaylistPodcast.data.podcasts); + })(); + }, [playlistId]); + + 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">{playlistTitle? playlistTitle : "Playlist Title"}</h2> + </div> + + <div className="mt-4 -ml-1.5 px-1.5 py-2 xl:mt-4 flex flex-wrap align-start gap-20"> + { playlistPodcast?.map((data: cardProps, idx: number ) => ( + < PodcastCard {...data} key={idx}/> + )) } + </div> + + </section> + ); +}