diff --git a/next.config.js b/next.config.js index aadb4d1..881082a 100644 --- a/next.config.js +++ b/next.config.js @@ -3,11 +3,17 @@ module.exports = { images: { remotePatterns: [ { - protocol: 'https', - hostname: 'res.cloudinary.com', - port: '', + protocol: "https", + hostname: "res.cloudinary.com", + port: "", // pathname: 'dhqqzaaid/image/upload/v1706643301/**', }, + { + protocol: "https", + hostname: "covers.openlibrary.org", + port: "", + pathname: "/b/**", // Matches the book cover path structure + }, ], - } -}; \ No newline at end of file + }, +} diff --git a/src/app/(home)/books/library/page.tsx b/src/app/(home)/books/library/page.tsx index 20aa0e4..f8995f9 100644 --- a/src/app/(home)/books/library/page.tsx +++ b/src/app/(home)/books/library/page.tsx @@ -25,7 +25,7 @@ const Booklibrary: React.FC = () => { const { bookData, loadingBooks, error } = useBookFetch( `${config.API_URL}/books`, - limit + limit, ) const readBooks = bookData?.filter((book) => book.read === true) @@ -34,7 +34,7 @@ const Booklibrary: React.FC = () => { const filteredResults = Array.isArray(readBooks) ? readBooks?.filter((book) => - book.title.toLowerCase().includes(searchBar.toLowerCase()) + book.title.toLowerCase().includes(searchBar.toLowerCase()), ) : ["No results"] @@ -100,8 +100,9 @@ const Booklibrary: React.FC = () => { totalScore={book?.totalScore} ratingArr={book?.scoreRatings?.rating} raterArr={book?.scoreRatings?.raterId} + imageURL={book?.imageURL} hideScores={handleHideScores_NoSetter( - book?.actualDateOfMeeting + book?.actualDateOfMeeting, )} /> diff --git a/src/app/(home)/books/randomiser/page.tsx b/src/app/(home)/books/randomiser/page.tsx index 3dd41f2..6087898 100644 --- a/src/app/(home)/books/randomiser/page.tsx +++ b/src/app/(home)/books/randomiser/page.tsx @@ -17,16 +17,16 @@ const RandomiserHomepage: React.FC = () => { const { bookData, loadingBooks, error } = useBookFetch( `${config.API_URL}/books/unread/all`, - null + null, ) const { userData, loadingUsers } = useUserFetch( `${config.API_URL}/users`, - null + null, ) useEffect(() => { setRandomiserBooks(bookData) - }, [loadingBooks, isRefresh]) + }, [bookData, loadingBooks, isRefresh]) return (
diff --git a/src/components/books/library/BookCover.tsx b/src/components/books/library/BookCover.tsx index f8697e2..b8bea65 100644 --- a/src/components/books/library/BookCover.tsx +++ b/src/components/books/library/BookCover.tsx @@ -4,6 +4,7 @@ import { useJwt } from "react-jwt" import useUserFetch from "@/hooks/fetch-hooks/useUserFetch" import { useAppSelector } from "@/store/lib/hooks" import { config } from "@/configs/config" +import Image from "next/image" type Props = { title: string @@ -12,6 +13,7 @@ type Props = { raterArr: string[] hideScores: boolean isSingleBook?: boolean + imageURL: string } const BookCover: React.FC = ({ @@ -21,6 +23,7 @@ const BookCover: React.FC = ({ raterArr, hideScores, isSingleBook, + imageURL, }) => { const token = useAppSelector((state) => state.token.tokenState) const { decodedToken }: { decodedToken?: { username: string; _id: string } } = @@ -29,7 +32,7 @@ const BookCover: React.FC = ({ const { userData, loadingUsers, error } = useUserFetch( `${config.API_URL}/users`, - null + null, ) const findUser = (id) => { @@ -51,7 +54,6 @@ const BookCover: React.FC = ({ } } findBookScore() - return ( <>
= ({ } >
-
-

{title}

-

- (Image pending) -

-
+ {imageURL.length ? ( +
+ {title} +
+ ) : ( +
+

{title}

+

+ (Image pending) +

+
+ )}

Book Club Brothers

diff --git a/src/components/books/library/single-book/AdminViewLeftSide.tsx b/src/components/books/library/single-book/AdminViewLeftSide.tsx index f05df25..3e9af31 100644 --- a/src/components/books/library/single-book/AdminViewLeftSide.tsx +++ b/src/components/books/library/single-book/AdminViewLeftSide.tsx @@ -1,14 +1,13 @@ import DeleteBook from "@/components/forms/bookform-delete/DeleteBook" import EditTitleButton from "@/components/forms/editbookform-single-book/title/EditTitleButton" import { Book } from "@/types/BookInterface" -import React, { useState } from "react" +import React from "react" import BookCover from "../BookCover" import { handleHideScores_NoSetter } from "@/utils/time-functions/hideScores" import EditImageButton from "@/components/forms/editbookform-single-book/image/EditImageButton" import EditTitle from "@/components/forms/editbookform-single-book/title/EditTitle" import EditImage from "@/components/forms/editbookform-single-book/image/EditImage" import { useAppSelector } from "@/store/lib/hooks" -import NavigateBook from "./NavigateBook" import Profile from "@/components/misc/profile/Profile" import useSingleUserFetch from "@/hooks/fetch-hooks/useSingleUserFetch" import Image from "next/image" @@ -21,11 +20,11 @@ type Props = { const AdminViewSingleBook: React.FC = ({ bookData, bookId }) => { const { showTitle, showBookImage } = useAppSelector( - (state) => state.editBookButtons + (state) => state.editBookButtons, ) const { singleUserData } = useSingleUserFetch( `${config.API_URL}/users/id/${bookData?.suggestedBy}`, - bookData?.suggestedBy + bookData?.suggestedBy, ) return ( <> @@ -63,8 +62,9 @@ const AdminViewSingleBook: React.FC = ({ bookData, bookId }) => { totalScore={bookData?.totalScore} ratingArr={bookData?.scoreRatings?.rating} raterArr={bookData?.scoreRatings?.raterId} + imageURL={bookData?.imageURL} hideScores={handleHideScores_NoSetter( - bookData?.actualDateOfMeeting + bookData?.actualDateOfMeeting, )} isSingleBook={true} /> diff --git a/src/components/books/library/single-book/UserViewLeftSide.tsx b/src/components/books/library/single-book/UserViewLeftSide.tsx index 77b4e96..7e3430d 100644 --- a/src/components/books/library/single-book/UserViewLeftSide.tsx +++ b/src/components/books/library/single-book/UserViewLeftSide.tsx @@ -18,7 +18,7 @@ type Props = { const UserViewLeftSide = ({ bookData }: Props) => { const { singleUserData, loadingUser } = useSingleUserFetch( `${config.API_URL}/users/id/${bookData?.suggestedBy}`, - bookData?.suggestedBy + bookData?.suggestedBy, ) const isDarkMode = useAppSelector((state) => state.darkMode.darkMode) @@ -50,8 +50,9 @@ const UserViewLeftSide = ({ bookData }: Props) => { totalScore={bookData?.totalScore} ratingArr={bookData?.scoreRatings?.rating} raterArr={bookData?.scoreRatings?.raterId} + imageURL={bookData?.imageURL} hideScores={handleHideScores_NoSetter( - bookData?.actualDateOfMeeting + bookData?.actualDateOfMeeting, )} isSingleBook={true} /> diff --git a/src/components/books/randomiser/RandomSectionLeft.tsx b/src/components/books/randomiser/RandomSectionLeft.tsx index 6f1c1f6..3a1afa8 100644 --- a/src/components/books/randomiser/RandomSectionLeft.tsx +++ b/src/components/books/randomiser/RandomSectionLeft.tsx @@ -23,7 +23,6 @@ const RandomSectionLeft: React.FC = ({ }) => { const { decodedToken } = useAuth() const dispatch = useAppDispatch() - const isDarkMode = useAppSelector((state) => state.darkMode.darkMode) const findUser = (id) => { const user = userData?.find((user) => user._id === id) diff --git a/src/components/books/randomiser/RandomSectionRight.tsx b/src/components/books/randomiser/RandomSectionRight.tsx index df1835d..6c72101 100644 --- a/src/components/books/randomiser/RandomSectionRight.tsx +++ b/src/components/books/randomiser/RandomSectionRight.tsx @@ -6,7 +6,7 @@ import { Book } from "@/types/BookInterface" import { useAppSelector } from "@/store/lib/hooks" import { User } from "@/types/UserInterface" import { useAuth } from "@/hooks/auth-hooks/useAuth" -import { useMediaQuery } from "react-responsive" +import Image from "next/image" import { UiSkeletonTitle } from "@/components/ui/skeleton/UiSkeletonTitle" type Props = { @@ -27,17 +27,17 @@ const RandomSectionRight: React.FC = ({ bookData, error, userData }) => { } return ( -
+
+ {bookData && bookData[index] ? ( + {bookData[index]?.title} + ) : null} {!bookData ? ( <>
@@ -63,7 +63,7 @@ const RandomSectionRight: React.FC = ({ bookData, error, userData }) => {
) : ( -
+
{error ? (

{error.message}

) : !bookData[index] ? ( diff --git a/src/components/brothers/dashboard/BrotherBanner.tsx b/src/components/brothers/dashboard/BrotherBanner.tsx index ca36ff5..08cb19e 100644 --- a/src/components/brothers/dashboard/BrotherBanner.tsx +++ b/src/components/brothers/dashboard/BrotherBanner.tsx @@ -85,8 +85,9 @@ const BrotherBanner: React.FC = ({ user, readBooks }) => { totalScore={findMinBook?.totalScore} ratingArr={findMinBook?.scoreRatings?.rating} raterArr={findMinBook?.scoreRatings?.raterId} + imageURL={findMinBook?.imageURL} hideScores={handleHideScores_NoSetter( - findMinBook?.actualDateOfMeeting + findMinBook?.actualDateOfMeeting, )} /> )} @@ -106,8 +107,9 @@ const BrotherBanner: React.FC = ({ user, readBooks }) => { totalScore={findMaxBook?.totalScore} ratingArr={findMaxBook?.scoreRatings?.rating} raterArr={findMaxBook?.scoreRatings?.raterId} + imageURL={findMaxBook?.imageURL} hideScores={handleHideScores_NoSetter( - findMaxBook?.actualDateOfMeeting + findMaxBook?.actualDateOfMeeting, )} /> )} diff --git a/src/components/forms/bookform-randomise/CreateUnreadBookForm.tsx b/src/components/forms/bookform-randomise/CreateUnreadBookForm.tsx index 5fb27d3..8577e41 100644 --- a/src/components/forms/bookform-randomise/CreateUnreadBookForm.tsx +++ b/src/components/forms/bookform-randomise/CreateUnreadBookForm.tsx @@ -1,14 +1,13 @@ -import { Button, Form, Input, Space, Select } from "antd" +import { Form, Input, Space, Select } from "antd" import useForm from "@/hooks/crud-hooks/useForm" import { useAppDispatch, useAppSelector } from "@/store/lib/hooks" import { setFormData } from "@/store/lib/features/books/bookFormDataSlice" import { useEffect, useState } from "react" -import { editBookButtonSlice } from "@/store/lib/features/books/editBookButtonsSlice" import { setShowCreate } from "@/store/lib/features/auth/editButtonsSlice" import { config } from "@/configs/config" -import { UiInput } from "@/components/ui/input/UiInput" import { InputConfigWrapper } from "../InputConfigWrapper" import { UiButton } from "@/components/ui/button/UiButton" +import useBookImage from "@/hooks/book-hooks/useBookImage" const { Option } = Select @@ -18,21 +17,28 @@ const CreateBook: React.FC = () => { author: false, yearPublished: false, pages: false, - imageURL: false, }) - const [noImageMessage, setNoImageMessage] = useState() const { handleSubmit, error, enterLoading, loadings, setError } = useForm( `${config.API_URL}/books/unread/create`, - "POST" + "POST", ) const formData = useAppSelector((state) => state.bookFormData.formData) const dispatch = useAppDispatch() + const fetchCoverId = useBookImage() const handleLoading = () => { enterLoading() setTimeout(() => dispatch(setShowCreate()), 1250) } + const handleSubmitSuggestion = async () => { + if (!formData.title) return + const coverUrl = await fetchCoverId(formData.title) + if (!coverUrl) return + const finalSubmissionData = { ...formData, imageURL: coverUrl } + dispatch(setFormData(finalSubmissionData)) + } + useEffect(() => { if (Object.values(errorObject).some((value) => value === false)) { setError("Please check all required fields are correct") @@ -82,6 +88,7 @@ const CreateBook: React.FC = () => { > handleSubmitSuggestion()} onChange={(e) => dispatch(setFormData({ ...formData, title: e.target.value })) } @@ -146,7 +153,7 @@ const CreateBook: React.FC = () => { type="number" onChange={(e) => dispatch( - setFormData({ ...formData, pages: Number(e.target.value) }) + setFormData({ ...formData, pages: Number(e.target.value) }), ) } value={formData["pages"]} @@ -180,7 +187,7 @@ const CreateBook: React.FC = () => { setFormData({ ...formData, yearPublished: Number(e.target.value), - }) + }), ) } value={formData["yearPublished"]} @@ -318,56 +325,6 @@ const CreateBook: React.FC = () => { - - {/* ImageURL */} - - No image URL?? Let me help with that. Click{" "} - - here - {" "} - you lazy bastard, find one you like, right-click and - copy the image URL and paste it in the above field - - ) - return Promise.reject() - } - if (imageRegex.test(value)) { - setErrorObject({ ...errorObject, imageURL: true }) - setNoImageMessage("") - return Promise.resolve() - } else if (value && !imageRegex.test(value)) { - setErrorObject({ ...errorObject, imageURL: false }) - return Promise.reject() - } - }, - message: - "URLs must end in either .jpg, .jpeg, .png, .svg, or .webp", - }, - ]} - > - - dispatch(setFormData({ ...formData, imageURL: e.target.value })) - } - value={formData["imageURL"]} - /> - {/* Submission */} @@ -391,11 +348,6 @@ const CreateBook: React.FC = () => { {error} ) : null} - {noImageMessage ? ( -

- {noImageMessage} -

- ) : null}
diff --git a/src/configs/config.ts b/src/configs/config.ts index 41146bc..a5064da 100644 --- a/src/configs/config.ts +++ b/src/configs/config.ts @@ -1,3 +1,5 @@ export const config = { API_URL: process.env.NEXT_PUBLIC_API_URL, + IA_API_URL: "https://openlibrary.org/search.json?title=", + OL_BOOK_COVER_URL: "https://covers.openlibrary.org/b/id/", } diff --git a/src/hooks/book-hooks/useBookImage.ts b/src/hooks/book-hooks/useBookImage.ts new file mode 100644 index 0000000..d69e255 --- /dev/null +++ b/src/hooks/book-hooks/useBookImage.ts @@ -0,0 +1,21 @@ +import { config } from "@/configs/config" + +const useBookImage = () => { + const fetchCoverId = async (title: string) => { + try { + const response = await fetch(`${config.IA_API_URL}${title}`) + if (!response.ok) return + const data = await response.json() + const filteredData = data?.docs.filter( + (book) => book.cover_i !== undefined, + ) + const bookCoverUrl = `${config.OL_BOOK_COVER_URL}${filteredData[0].cover_i}-L.jpg` + return bookCoverUrl + } catch (err) { + console.error(err) + } + } + return fetchCoverId +} + +export default useBookImage