diff options
| author | real-zephex <[email protected]> | 2024-05-24 22:51:36 +0530 |
|---|---|---|
| committer | real-zephex <[email protected]> | 2024-05-24 22:51:36 +0530 |
| commit | 180c9577f8337991ca71470816333fe8430cd3ca (patch) | |
| tree | 82caa5a920443bcf0db3746c7ecacd968d4fc148 /src/app/anime/components | |
| parent | style: minor improvements to the anime cards (diff) | |
| download | dramalama-180c9577f8337991ca71470816333fe8430cd3ca.tar.xz dramalama-180c9577f8337991ca71470816333fe8430cd3ca.zip | |
✨ feat(ui): 🎨 migrate from vanilla css to tailwind css, adopted next ui and restructured
Diffstat (limited to 'src/app/anime/components')
| -rw-r--r-- | src/app/anime/components/cacher.js | 4 | ||||
| -rw-r--r-- | src/app/anime/components/episode_buttons.jsx | 2 | ||||
| -rw-r--r-- | src/app/anime/components/infoTabs.jsx | 46 | ||||
| -rw-r--r-- | src/app/anime/components/popularAnimes.jsx | 53 | ||||
| -rw-r--r-- | src/app/anime/components/recentEpisodes.jsx | 54 | ||||
| -rw-r--r-- | src/app/anime/components/saveToLocalStorage.js | 29 | ||||
| -rw-r--r-- | src/app/anime/components/search.jsx | 55 | ||||
| -rw-r--r-- | src/app/anime/components/search_results.jsx | 51 | ||||
| -rw-r--r-- | src/app/anime/components/topAiring.jsx | 56 | ||||
| -rw-r--r-- | src/app/anime/components/vidButtonContainer.jsx | 133 |
10 files changed, 270 insertions, 213 deletions
diff --git a/src/app/anime/components/cacher.js b/src/app/anime/components/cacher.js index d3008fa..164dafd 100644 --- a/src/app/anime/components/cacher.js +++ b/src/app/anime/components/cacher.js @@ -1,11 +1,11 @@ "use server"; -import { info_url, watch_url } from "../../../../utils/anime_urls"; +import { anime_info } from "../data-fetch/request"; export async function preFetchAnimeInfo(data) { try { const fetchPromises = data.results.map(async (element) => { - await fetch(info_url(element.id), { next: { revalidate: 21600 } }); + await anime_info(element.id); }); await Promise.all(fetchPromises); diff --git a/src/app/anime/components/episode_buttons.jsx b/src/app/anime/components/episode_buttons.jsx index 013dee1..71f338f 100644 --- a/src/app/anime/components/episode_buttons.jsx +++ b/src/app/anime/components/episode_buttons.jsx @@ -202,4 +202,4 @@ function store_to_local(name, image, episode, id) { } } -export default EpisodesButtons;
\ No newline at end of file +export default EpisodesButtons; diff --git a/src/app/anime/components/infoTabs.jsx b/src/app/anime/components/infoTabs.jsx new file mode 100644 index 0000000..68a1da1 --- /dev/null +++ b/src/app/anime/components/infoTabs.jsx @@ -0,0 +1,46 @@ +"use client"; + +import { Tabs, Tab, Card, CardBody } from "@nextui-org/react"; + +import { lexend, atkinson } from "../../../../config/fonts"; + +export default function DescriptionTabs({ data: data }) { + return ( + <div className="flex w-full flex-col"> + <Tabs aria-label="Options" className={lexend.className}> + <Tab key="description" title="Description"> + <Card> + <CardBody className={atkinson.className}> + {data.description || "No description found"} + </CardBody> + </Card> + </Tab> + <Tab key="episodes" title="Details"> + <Card> + <CardBody className={atkinson.className}> + <h4> + <strong>Episodes</strong>:{" "} + <span>{data.totalEpisodes}</span> + </h4> + <h4> + <strong>Type</strong>: <span>{data.type}</span> + </h4> + <h4> + <strong>SUB/DUB</strong>:{" "} + <span>{data.subOrDub.toUpperCase()}</span> + </h4> + <h4> + <strong>Status</strong>:{" "} + <span>{data.status}</span> + </h4> + <h4> + <strong>Release Year</strong>:{" "} + <span>{data.releaseDate}</span> + </h4> + </CardBody> + </Card> + </Tab> + </Tabs> + </div> + ); +} diff --git a/src/app/anime/components/popularAnimes.jsx b/src/app/anime/components/popularAnimes.jsx deleted file mode 100644 index e62f70f..0000000 --- a/src/app/anime/components/popularAnimes.jsx +++ /dev/null @@ -1,53 +0,0 @@ -import Link from "next/link"; -import Image from "next/image"; -import { Lexend_Deca } from "next/font/google"; - -import styles from "../styles/pop_recent_top.module.css"; -import { popular } from "../data-fetch/request"; -import { preFetchAnimeInfo } from "./cacher"; - -const lexend = Lexend_Deca({ subsets: ["latin"], weight: "400" }); - -const PopularAnimes = async () => { - const data = await popular(); - - preFetchAnimeInfo(data); - - return ( - <main className={styles.Main}> - <section> - <h2 className={styles.AnimeHeaderText}>Popular Animes</h2> - <div className={styles.AnimeContainer}> - {data && - data.results.map((item, index) => ( - <Link - key={index} - href={`/anime/${item.id}`} - shallow - style={{ - color: "white", - textDecoration: "none", - }} - className={lexend.className} - title={item.title} - > - <section className={styles.AnimeEntry}> - <Image - src={item.image} - width={180} - height={300} - alt="Anime Poster Image" - /> - <p className={styles.AnimeTitle}> - {item.title} - </p> - </section> - </Link> - ))} - </div> - </section> - </main> - ); -}; - -export default PopularAnimes; diff --git a/src/app/anime/components/recentEpisodes.jsx b/src/app/anime/components/recentEpisodes.jsx deleted file mode 100644 index 9e7899f..0000000 --- a/src/app/anime/components/recentEpisodes.jsx +++ /dev/null @@ -1,54 +0,0 @@ -import Link from "next/link"; -import Image from "next/image"; -import { Lexend_Deca } from "next/font/google"; - -import styles from "../styles/pop_recent_top.module.css"; -import { recent } from "../data-fetch/request"; -import { preFetchAnimeInfo } from "./cacher"; - -const lexend = Lexend_Deca({ subsets: ["latin"], weight: "400" }); - -const RecentAnimes = async () => { - const data = await recent(); - - preFetchAnimeInfo(data); - - return ( - <main className={styles.Main}> - <section> - <h2 className={styles.AnimeHeaderText}>Recent Releases</h2> - <div className={styles.AnimeContainer}> - {data && - data.results.map((item, index) => ( - <Link - key={index} - href={`/anime/${item.id}`} - shallow - style={{ - color: "white", - textDecoration: "none", - }} - className={lexend.className} - title={item.title} - > - <section className={styles.AnimeEntry}> - <Image - src={item.image} - width={180} - height={300} - alt="Anime Poster Image" - /> - <p className={styles.AnimeTitle}> - {item.title} - </p> - <p className={styles.AnimeReleasedEpisode}>{item.episodeNumber}</p> - </section> - </Link> - ))} - </div> - </section> - </main> - ); -}; - -export default RecentAnimes; diff --git a/src/app/anime/components/saveToLocalStorage.js b/src/app/anime/components/saveToLocalStorage.js new file mode 100644 index 0000000..313b157 --- /dev/null +++ b/src/app/anime/components/saveToLocalStorage.js @@ -0,0 +1,29 @@ +"use client"; + +import { storeLocal } from "./storeHistory"; + +export default function store_to_local(name, image, episode, id) { + const currentDate = new Date(); + + try { + let newData = { + name: name, + image: image, + episode: episode, + id: id, + type: "anime", + date: `${currentDate.getDate()}-${String( + currentDate.getMonth() + 1 + ).padStart(2, "0")}`, + time: `${currentDate.getHours()}:${String( + currentDate.getMinutes() + ).padStart(2, "0")}`, + }; + storeLocal(newData); + } catch (error) { + console.error( + "Some error occurred during the process of saving your watch history to local storage. Please try again or contact us on GitHub if this issue persists.", + error.message + ); + } +} diff --git a/src/app/anime/components/search.jsx b/src/app/anime/components/search.jsx index 0a780f8..0fe883b 100644 --- a/src/app/anime/components/search.jsx +++ b/src/app/anime/components/search.jsx @@ -2,37 +2,37 @@ import { FaSearch } from "react-icons/fa"; import { useState } from "react"; -import Link from "next/link"; +import { Input, Link, Button, Progress } from "@nextui-org/react"; -import styles from "../styles/search.module.css"; import SearchResults from "./search_results"; -const SearcBar = () => { +const SearchBar = () => { const [title, setTitle] = useState(""); const [searchResults, setSearchResults] = useState(null); const [loading, setLoading] = useState(false); const handleSearchInput = async (title) => { setSearchResults(null); - setLoading(true); + setLoading( + <Progress + size="sm" + isIndeterminate + aria-label="Loading..." + className="w-full mt-2 lg:w-1/2" + /> + ); setSearchResults(await SearchResults(title)); setLoading(false); }; return ( <main> - <section className={styles.SearchBarContainer}> - <div className={styles.SearchInputContainer}> - <FaSearch color="white" size={22} /> - <input - placeholder="Enter anime title" - name="Anime Title" + <section> + <div className="flex w-full md:flex-nowrap gap-2 lg:w-1/2"> + <Input type="text" - onChange={(event) => { - if (event.target.value.trim() != "") { - setTitle(event.target.value); - } - }} + label="Search for anime" + placeholder="Enter anime title here" autoComplete="off" onKeyDown={async (event) => { if ( @@ -43,22 +43,23 @@ const SearcBar = () => { await handleSearchInput(title); } }} - ></input> - <Link shallow href={"/anime/continueWatching"}> - <button className={styles.animeHistoryButton}> - History - </button> + onChange={(event) => { + if (event.target.value.trim() != "") { + setTitle(event.target.value); + } + }} + startContent={<FaSearch />} + /> + + <Link href={"/anime/continueWatching"}> + <Button color="primary">History</Button> </Link> </div> + {loading} </section> - {loading && ( - <p className={styles.SearchLoading}> - Please wait while we crunch up all the data..... - </p> - )} - {searchResults} + <div className="mt-2">{searchResults}</div> </main> ); }; -export default SearcBar; +export default SearchBar; diff --git a/src/app/anime/components/search_results.jsx b/src/app/anime/components/search_results.jsx index 051124d..3097a96 100644 --- a/src/app/anime/components/search_results.jsx +++ b/src/app/anime/components/search_results.jsx @@ -1,12 +1,9 @@ -import { Lexend_Deca } from "next/font/google"; -import Link from "next/link"; -import Image from "next/image"; - -import styles from "../styles/search.module.css"; import { search_results } from "../data-fetch/request"; import { preFetchAnimeInfo } from "./cacher"; +import styles from "../../page.module.css"; -const lexend = Lexend_Deca({ subsets: ["latin"], weight: "400" }); +import { Card, CardHeader, CardBody, Image, Link } from "@nextui-org/react"; +import NextImage from "next/image"; const SearchResults = async (title) => { const data = await search_results(title); @@ -14,25 +11,39 @@ const SearchResults = async (title) => { preFetchAnimeInfo(data); return ( - <section className={styles.SearchResultsContainer}> + <section + className={`flex items-center overflow-auto pb-2 ${styles.ScrollBarAdjuster}`} + > {data && data.results.map((item, index) => ( <Link - shallow - href={`/anime/${item.id}`} key={index} - className={lexend.className} - style={{ color: "white", textDecoration: "none" }} + href={`/anime/${item.id}`} + aria-label="anime redirection links" + className="flex flex-col items-center mx-1 " > - <div className={styles.AnimeEntry}> - <Image - src={item.image} - width={180} - height={300} - alt="Anime Poster" - /> - <p>{item.title}</p> - </div> + <Card className="overflow-hidden" isPressable> + <CardBody> + <Image + as={NextImage} + isBlurred + alt="Anime Poster" + src={item.image} + width={190} + height={120} + shadow="lg" + className="h-64" + priority + /> + </CardBody> + <CardHeader> + <h4 + className={`antialiased text-small text-center uppercase w-44 overflow-hidden whitespace-nowrap text-ellipsis `} + > + {item.title} + </h4> + </CardHeader> + </Card> </Link> ))} </section> diff --git a/src/app/anime/components/topAiring.jsx b/src/app/anime/components/topAiring.jsx deleted file mode 100644 index 22e8c3b..0000000 --- a/src/app/anime/components/topAiring.jsx +++ /dev/null @@ -1,56 +0,0 @@ -import Link from "next/link"; -import Image from "next/image"; -import { Lexend_Deca } from "next/font/google"; - -import styles from "../styles/pop_recent_top.module.css"; -import { top_airing } from "../data-fetch/request"; -import { preFetchAnimeInfo } from "./cacher"; - -const lexend = Lexend_Deca({ subsets: ["latin"], weight: "400" }); - -const TopAiringAnimes = async () => { - const data = await top_airing(); - - preFetchAnimeInfo(data); - - return ( - <main className={styles.Main}> - <section> - <h2 className={styles.AnimeHeaderText}>Top Airing Animes</h2> - <div className={styles.AnimeContainer}> - {data && - data.results.map((item, index) => ( - <Link - key={index} - href={`/anime/${item.id}`} - shallow - style={{ - color: "white", - textDecoration: "none", - }} - className={lexend.className} - title={item.title} - > - <section className={styles.AnimeEntry}> - <Image - src={item.image} - width={180} - height={300} - alt="Anime Poster Image" - /> - <p className={styles.AnimeTitle}> - {item.title} - </p> - <p className={styles.AnimeReleasedEpisode}> - {item.episodeNumber} - </p> - </section> - </Link> - ))} - </div> - </section> - </main> - ); -}; - -export default TopAiringAnimes; diff --git a/src/app/anime/components/vidButtonContainer.jsx b/src/app/anime/components/vidButtonContainer.jsx new file mode 100644 index 0000000..6b872d8 --- /dev/null +++ b/src/app/anime/components/vidButtonContainer.jsx @@ -0,0 +1,133 @@ +"use client"; + +import { MediaPlayer, MediaProvider } from "@vidstack/react"; +import "@vidstack/react/player/styles/default/theme.css"; +import "@vidstack/react/player/styles/default/layouts/video.css"; +import { + defaultLayoutIcons, + DefaultVideoLayout, +} from "@vidstack/react/player/layouts/default"; +import { Select, SelectItem, Button, Skeleton } from "@nextui-org/react"; +import { useState, useEffect } from "react"; + +import { lexend } from "../../../../config/fonts"; +import { video_url } from "../data-fetch/request"; +import store_to_local from "./saveToLocalStorage"; + +const EpisodesContainer = ({ data: data }) => { + const [videoLink, setVideoLink] = useState(""); + const [buttonGroups, setButtonGroups] = useState(<></>); + const [videoLoading, setVideoLoading] = useState(<></>); + + useEffect(() => { + setButtonGroups(createButtonGroups(0, 50)); + }, []); + + const groups = createGroups(data.episodes, 50); + + function createButtonGroups(start, end) { + setButtonGroups(<></>); + return ( + <div className={`${lexend.className} text-center`}> + {data.episodes && + data.episodes.slice(start, end).map((item, index) => ( + <Button + radius="sm" + color="default" + key={index} + className="mr-2 mt-2" + size="sm" + onClick={async () => { + await changeVideoLink(item.id); + store_to_local( + data.title, + data.image, + item.number, + data.id + ); + }} + > + {item.number} + </Button> + ))} + </div> + ); + } + + function handleSelectChange(item) { + // console.log(item[item.length - 1].number); + const start_index = item[0].number; + const end_index = item[item.length - 1].number; + setButtonGroups(createButtonGroups(start_index - 1, end_index)); + } + + async function changeVideoLink(id) { + setVideoLink(""); + setVideoLoading( + <div className="w-full flex items-center gap-3"> + <div className="w-full flex flex-col gap-2"> + <Skeleton className="h-44 rounded-lg lg:h-96" /> + </div> + </div> + ); + const videoURL = await video_url(id); + setVideoLoading(<></>); + setVideoLink(videoURL.sources[videoURL.sources.length - 2].url); + } + + return ( + <main> + {videoLoading} + {videoLink && ( + <div> + <MediaPlayer + title={data.title} + src={videoLink} + aspectRatio="16/9" + load="eager" + playsInline + volume={0.8} + autoPlay + > + <MediaProvider /> + <DefaultVideoLayout icons={defaultLayoutIcons} /> + </MediaPlayer> + </div> + )} + {data.episodes && ( + <div className="flex w-full flex-wrap md:flex-nowrap gap-4 my-2"> + <Select + label="Select Episode Group" + className={`${lexend.className} max-w-xs`} + > + {groups && + groups.map((item, index) => ( + <SelectItem + key={index} + textValue={`${item[0].number} - ${ + item[item.length - 1].number + }`} + onClick={() => handleSelectChange(item)} + className={lexend.className} + > + {item[0].number} -{" "} + {item[item.length - 1].number} + </SelectItem> + ))} + </Select> + </div> + )} + {buttonGroups} + </main> + ); +}; + +function createGroups(array, size) { + const groups = []; + for (let i = 0; i < array.length; i += size) { + groups.push(array.slice(i, i + size)); + } + return groups; +} + +export default EpisodesContainer; |