+
{/* SEARCH MOBILE AND DESKTOP, SEARCH RESULTS CONTAINER INSIDE*/}
+ {/* NOTIFICATIONS */}
+
+
{/* USER MENU -- RIGHT SIDE OF SCREEN */}
diff --git a/app/lib/fetchAnimeOnApi.ts b/app/lib/fetchAnimeOnApi.ts
index b7fc4ee7..5f53f442 100644
--- a/app/lib/fetchAnimeOnApi.ts
+++ b/app/lib/fetchAnimeOnApi.ts
@@ -10,13 +10,15 @@ export async function fetchWithGoGoAnime(textSearch: string, only?: "episodes")
const regexMediaTitle = regexOnlyAlphabetic(checkApiMisspellingMedias(textSearch)).toLowerCase()
- let mediaSearched = await gogoanime.getInfoFromThisMedia(textSearch, "anime") as MediaInfo
+ let mediaSearched = await gogoanime.getInfoFromThisMedia(regexMediaTitle, "anime") as MediaInfo
let searchResultsForMedia
let filterBestResult
+ // if no results were found by matching the id, search the title
if (!mediaSearched) {
searchResultsForMedia = await gogoanime.searchMedia(regexMediaTitle, "anime") as MediaSearchResult[]
+ // filter to closest title name to the query
filterBestResult = searchResultsForMedia.filter(item =>
regexOnlyAlphabetic(item.title).toLowerCase().indexOf(regexMediaTitle) !== -1
)
@@ -26,6 +28,7 @@ export async function fetchWithGoGoAnime(textSearch: string, only?: "episodes")
if (!mediaSearched) return null
+ // if only EPISODES is requested
if (only == "episodes") {
let episodes: any[] = []
@@ -46,35 +49,63 @@ export async function fetchWithGoGoAnime(textSearch: string, only?: "episodes")
}
-export async function fetchWithAniWatch(textSearch: string, only?: "episodes", format?: string, mediaTotalEpisodes?: number) {
+export async function fetchWithAniWatch(textSearch: string, only?: "episodes" | "search_list", format?: string, mediaTotalEpisodes?: number) {
const regexMediaTitle = regexOnlyAlphabetic(checkApiMisspellingMedias(textSearch)).toLowerCase()
let searchResultsForMedia = await aniwatch.searchMedia(regexMediaTitle).then((res) => res!.animes) as MediaInfoAniwatch[]
+ // filter the same format
if (format) {
searchResultsForMedia = searchResultsForMedia.filter(item => item.type.toLowerCase() == format.toLowerCase())
}
- let closestResult: MediaInfoAniwatch | undefined
+ let closestResult: MediaInfoAniwatch[] | undefined
+ // filter to item which has the same media name
+ closestResult = searchResultsForMedia.filter(
+ (item) => regexOnlyAlphabetic(item.name).toLowerCase() == regexMediaTitle
+ )
+
+ // if is only SEARCH LIST is requested
+ if (only == "search_list") {
+ if (closestResult.length != 0) {
+ return closestResult
+ }
+ }
+ else {
+ closestResult = closestResult.length == 0 ? searchResultsForMedia : closestResult
+ }
+
+ // filter which has the same ammount of episodes
if (mediaTotalEpisodes) {
- closestResult = searchResultsForMedia.find(
+ const filter = closestResult.filter(
(item) => item.episodes.sub == mediaTotalEpisodes
)
+
+ if (filter.length != 0) closestResult = filter
+ }
+
+ // if is only SEARCH LIST is requested
+ if (only == "search_list") {
+
+ return closestResult.length > 0 ? closestResult : searchResultsForMedia
+
}
+ // filter closest title result
if (!closestResult) {
- closestResult = searchResultsForMedia.find((item) =>
+ closestResult = searchResultsForMedia.filter((item) =>
regexOnlyAlphabetic(item.name).toLowerCase().includes(regexMediaTitle) || searchResultsForMedia[0]
)
}
+ // if only EPISODES is requested
if (only == "episodes") {
if (!closestResult) return null
- const res = await aniwatch.getEpisodes(closestResult.id) as EpisodesFetchedAnimeWatch
+ const res = await aniwatch.getEpisodes(closestResult[0].id) as EpisodesFetchedAnimeWatch
return res?.episodes?.length == 0 ? null : res.episodes
}
diff --git a/app/media/[id]/components/AddToNotifications/component.module.css b/app/media/[id]/components/AddToNotifications/component.module.css
new file mode 100644
index 00000000..e6035281
--- /dev/null
+++ b/app/media/[id]/components/AddToNotifications/component.module.css
@@ -0,0 +1,43 @@
+#container {
+
+ transition: all ease-in-out 200ms;
+
+ height: inherit;
+
+ text-align: center;
+
+ font-weight: 600;
+
+ border: 1px solid var(--white-100) !important;
+
+ background-color: transparent !important;
+
+}
+
+#container svg {
+
+ transform: scale(1.4);
+
+}
+
+#container[data-added=false] svg {
+
+ fill: var(--white-100);
+
+}
+
+#container[disabled] {
+
+ background-color: var(--white-100);
+
+}
+
+#container[data-added=true] {
+
+ gap: 0 8px;
+
+ background-color: var(--white-100) !important;
+ color: var(--black-100) !important;
+
+ border-color: var(--black-100) !important;
+}
\ No newline at end of file
diff --git a/app/media/[id]/components/AddToNotifications/index.tsx b/app/media/[id]/components/AddToNotifications/index.tsx
new file mode 100644
index 00000000..5ebb7fdd
--- /dev/null
+++ b/app/media/[id]/components/AddToNotifications/index.tsx
@@ -0,0 +1,150 @@
+"use client"
+import React, { useEffect, useState } from 'react'
+import styles from "./component.module.css"
+import LoadingSvg from "@/public/assets/ripple-1s-200px.svg"
+import BellFillSvg from "@/public/assets/bell-fill.svg"
+import BellSvg from "@/public/assets/bell-slash.svg"
+import {
+ getFirestore, doc,
+ updateDoc, arrayUnion,
+ arrayRemove, getDoc,
+ FieldPath, setDoc,
+ DocumentSnapshot, DocumentData
+} from 'firebase/firestore';
+import { initFirebase } from '@/app/firebaseApp'
+import { getAuth } from 'firebase/auth'
+import { ApiDefaultResult } from '@/app/ts/interfaces/apiAnilistDataInterface'
+import { useAuthState } from 'react-firebase-hooks/auth'
+import UserModal from '@/app/components/UserLoginModal';
+import { AnimatePresence, motion } from 'framer-motion';
+
+function AddToNotificationsList({ data }: { data: ApiDefaultResult }) {
+
+ const [isLoading, setIsLoading] = useState
(false)
+ const [wasAddedToNotifications, setWasAddedToNotifications] = useState(false)
+
+ const [isUserModalOpen, setIsUserModalOpen] = useState(false)
+
+ const auth = getAuth()
+
+ const [user, loading] = useAuthState(auth)
+
+ const db = getFirestore(initFirebase());
+
+ // WHEN BUTTON IS CLICKED, RUN FUNCTION TO ADD OR REMOVE MEDIA FROM FIRESTORE
+ async function addThisMedia() {
+
+ if (!user) {
+
+ // opens user login modal
+ setIsUserModalOpen(true)
+ return
+ }
+
+ setIsLoading(true)
+
+ const notificationData = {
+ mediaId: data.id,
+ title: {
+ romaji: data.title.romaji
+ },
+ isComplete: data.status,
+ nextReleaseDate: data.nextAiringEpisode?.airingAt,
+ episodeNumber: data.nextAiringEpisode?.episode,
+ lastEpisode: data.nextAiringEpisode?.episode == data.episodes,
+ coverImage: {
+ extraLarge: data.coverImage.extraLarge,
+ large: data.coverImage.large
+ }
+ }
+
+ await updateDoc(doc(db, 'users', user.uid),
+ {
+ notifications: !wasAddedToNotifications ? arrayUnion(...[notificationData]) : arrayRemove(...[notificationData])
+
+ } as unknown as FieldPath,
+ { merge: true }
+ )
+
+ !wasAddedToNotifications ? setWasAddedToNotifications(true) : setWasAddedToNotifications(false)
+
+ setIsLoading(false)
+ }
+
+ // IF MEDIA ID MATCHS ANY RESULT ON DB, IT SETS THIS COMPONENT BUTTON AS ACTIVE
+ async function isMediaOnDB() {
+
+ if (!user) return setWasAddedToNotifications(false)
+
+ let userDoc: DocumentSnapshot = await getDoc(doc(db, 'users', user.uid))
+
+ // IF USER HAS NO DOC ON FIRESTORE, IT CREATES ONE
+ if (userDoc.exists() == false) {
+
+ userDoc = await setDoc(doc(db, 'users', user.uid), {}) as unknown as DocumentSnapshot
+
+ return
+ }
+
+ const isMediaIdOnDoc = userDoc.get("notifications")?.find((item: NotificationFirebase) => item.mediaId == data.id)
+
+ if (isMediaIdOnDoc) {
+ setWasAddedToNotifications(true)
+ }
+ }
+
+ useEffect(() => {
+
+ if (!user || loading) {
+ return
+ } else {
+ setIsUserModalOpen(false)
+ isMediaOnDB()
+ }
+
+ }, [user])
+
+ if (data.nextAiringEpisode?.airingAt) {
+ return (
+ <>
+
+ {isUserModalOpen && (
+ setIsUserModalOpen(false)}
+ auth={auth}
+ />
+ )}
+
+
+ addThisMedia()}
+ disabled={isLoading}
+ data-added={wasAddedToNotifications}
+ title={wasAddedToNotifications ?
+ `Remove ${data.title && data.title?.romaji} From Notifications`
+ :
+ `Get Notified When ${data.title && data.title?.romaji} Get a New Episode`
+ }
+ >
+ {isLoading ?
+
+ :
+ (wasAddedToNotifications ?
+
+ :
+
+ )
+ }
+
+
+ >
+ )
+ }
+}
+
+export default AddToNotificationsList
\ No newline at end of file
diff --git a/app/media/[id]/components/AnimeEpisodesContainer/index.tsx b/app/media/[id]/components/AnimeEpisodesContainer/index.tsx
index 54b4e4ed..1b6eb29e 100644
--- a/app/media/[id]/components/AnimeEpisodesContainer/index.tsx
+++ b/app/media/[id]/components/AnimeEpisodesContainer/index.tsx
@@ -120,10 +120,10 @@ function EpisodesContainer(props: EpisodesContainerTypes) {
break
- // get data from GOGOANIME as default
- case "gogoanime":
+ case "gogoanime": // get data from GOGOANIME as default
setEpisodeSource(chooseSource)
+
mediaEpisodes = await fetchWithGoGoAnime(query, "episodes") as MediaEpisodes[]
if (mediaEpisodes == null) {
@@ -142,16 +142,13 @@ function EpisodesContainer(props: EpisodesContainerTypes) {
break
- // get data from ANIWATCH
- case "aniwatch":
+ case "aniwatch": // get data from ANIWATCH
setEpisodeSource(chooseSource)
- const searchResultsForMedia = await aniwatch.searchMedia(regexOnlyAlphabetic(query)) as MediaInfoFetchedAnimeWatch
+ const searchResultsForMedia = await fetchWithAniWatch(query, "search_list", props.mediaFormat, dataImdbMapped.length) as MediaInfoAniwatch[]
- setMediaResultsInfoArray(searchResultsForMedia.animes.filter(item => item.type.toLowerCase() == props.mediaFormat.toLowerCase()))
-
- setEpisodeSource(chooseSource)
+ setMediaResultsInfoArray(searchResultsForMedia)
mediaEpisodes = await fetchWithAniWatch(query, "episodes", props.mediaFormat, dataImdbMapped.length) as EpisodesFetchedAnimeWatch["episodes"]
@@ -164,8 +161,7 @@ function EpisodesContainer(props: EpisodesContainerTypes) {
break
- // get data from VIDSRC
- default:
+ case "vidsrc": // get data from VIDSRC
// VIDSRC has no episodes INFO
// so it will use IMDB info data, and redirect it to a url with episode and season number.
@@ -190,6 +186,12 @@ function EpisodesContainer(props: EpisodesContainerTypes) {
break
+ default:
+
+ console.log("need to work on it")
+
+ break
+
}
}
diff --git a/app/media/[id]/page.module.css b/app/media/[id]/page.module.css
index caa001ec..dbab27f8 100644
--- a/app/media/[id]/page.module.css
+++ b/app/media/[id]/page.module.css
@@ -110,7 +110,15 @@ h2.heading_style {
}
-#media_title_container #add_playlist_container>button {
+#media_title_container #btns_actions_container {
+
+ display: flex;
+ flex-direction: row;
+ gap: 8px;
+
+}
+
+#media_title_container #btns_actions_container>button {
box-shadow: 3px 3px 4px var(--black-75);
@@ -127,7 +135,7 @@ h2.heading_style {
}
-#media_title_container #add_playlist_container>button[data-added=true] {
+#media_title_container #btns_actions_container>button[data-added=true] {
background-color: var(--brand-color);
@@ -162,12 +170,11 @@ h2.heading_style {
#media_title_container #genres_and_type_container {
gap: 16px;
- /* flex-wrap: wrap; */
justify-content: space-between;
}
-#media_title_container #genres_and_type_container>div:not(#add_playlist_container) {
+#media_title_container #genres_and_type_container>div:not(#btns_actions_container) {
margin: auto 0;
diff --git a/app/media/[id]/page.tsx b/app/media/[id]/page.tsx
index 91c17e81..688737a1 100644
--- a/app/media/[id]/page.tsx
+++ b/app/media/[id]/page.tsx
@@ -25,6 +25,7 @@ import { convertFromUnix } from '@/app/lib/formatDateUnix'
import CommentSectionContainer from '../../components/CommentSectionContainer'
import { getMediaInfo } from '@/api/imdb'
import { ImdbEpisode, ImdbMediaInfo } from '@/app/ts/interfaces/apiImdbInterface'
+import AddToNotificationsList from './components/AddToNotifications'
export const revalidate = 43200 // revalidate cached data every 12 hours
@@ -133,13 +134,17 @@ async function MediaPage({ params }: { params: { id: number } }) {
)}
-
@@ -175,7 +180,15 @@ async function MediaPage({ params }: { params: { id: number } }) {
STATUS
-
{mediaData.status == "NOT_YET_RELEASED" ? "TO BE RELEASED" : mediaData.status || "Not Available"}
+
+ {
+ mediaData.status == "NOT_YET_RELEASED" ? "TO BE RELEASED"
+ :
+ mediaData.status == "FINISHED" ? "COMPLETE"
+ :
+ mediaData.status || "Not Available"
+ }
+
)}
diff --git a/app/ts/interfaces/apiAnilistDataInterface.d.ts b/app/ts/interfaces/apiAnilistDataInterface.d.ts
index 191e6a39..f619cae4 100644
--- a/app/ts/interfaces/apiAnilistDataInterface.d.ts
+++ b/app/ts/interfaces/apiAnilistDataInterface.d.ts
@@ -13,6 +13,11 @@ export interface ApiDefaultResult {
month: number,
day: number,
},
+ nextAiringEpisode: {
+ airingAt: number,
+ episode: number
+ },
+ status: string | null,
description: string,
episodes: number,
duration: number,
@@ -73,14 +78,9 @@ export interface EpisodesType {
export interface ApiMediaResults extends ApiDefaultResult {
- nextAiringEpisode: {
- airingAt: number,
- episode: number
- },
hashtag: string,
favourites: number,
source: string,
- status: string | null,
duration: number | null,
volumes: number | null,
chapters: number | null,
diff --git a/app/ts/interfaces/firestoreDataInterface.d.ts b/app/ts/interfaces/firestoreDataInterface.d.ts
index 724b2c67..936c49af 100644
--- a/app/ts/interfaces/firestoreDataInterface.d.ts
+++ b/app/ts/interfaces/firestoreDataInterface.d.ts
@@ -49,4 +49,21 @@ interface KeepWatchingItem {
},
id: number
+}
+
+interface NotificationFirebase {
+
+ mediaId: number,
+ title: {
+ romaji: string
+ },
+ isComplete: boolean,
+ nextReleaseDate: number,
+ lastEpisode: boolean,
+ episodeNumber: number,
+ coverImage: {
+ extraLarge: string,
+ large: string
+ }
+
}
\ No newline at end of file
diff --git a/package-lock.json b/package-lock.json
index 48a6bac4..ed8d6a3f 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -1,12 +1,12 @@
{
"name": "aniproject",
- "version": "2.5.7",
+ "version": "2.5.8",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "aniproject",
- "version": "2.5.7",
+ "version": "2.5.8",
"dependencies": {
"@vidstack/react": "^1.10.9",
"axios": "^1.6.6",
diff --git a/package.json b/package.json
index 94b0c258..af066a44 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
{
"name": "aniproject",
- "version": "2.5.7",
+ "version": "2.5.8",
"private": true,
"scripts": {
"dev": "next dev",
diff --git a/public/assets/bell-fill.svg b/public/assets/bell-fill.svg
new file mode 100644
index 00000000..a537c3a0
--- /dev/null
+++ b/public/assets/bell-fill.svg
@@ -0,0 +1,3 @@
+
\ No newline at end of file
diff --git a/public/assets/bell-slash.svg b/public/assets/bell-slash.svg
new file mode 100644
index 00000000..7817e2b4
--- /dev/null
+++ b/public/assets/bell-slash.svg
@@ -0,0 +1,3 @@
+
\ No newline at end of file
diff --git a/public/assets/bell.svg b/public/assets/bell.svg
new file mode 100644
index 00000000..a71eba30
--- /dev/null
+++ b/public/assets/bell.svg
@@ -0,0 +1,3 @@
+
\ No newline at end of file