From c2f1e40f3172fcda9908be5d72b1111d314348b6 Mon Sep 17 00:00:00 2001 From: Animesh Dhara Date: Thu, 11 Jul 2024 14:41:17 +0530 Subject: [PATCH 01/13] Added wishlist page --- src/common_components/cards/Card.jsx | 2 +- .../user/bookDetails/RelatedBookSection.jsx | 2 +- .../user/bookDetails/UserButtonSection.jsx | 6 ++-- src/pages/user/wishlist/WishList.jsx | 28 +++++++++++++++++++ src/routes/Routers.jsx | 9 ++++-- 5 files changed, 40 insertions(+), 7 deletions(-) create mode 100644 src/pages/user/wishlist/WishList.jsx diff --git a/src/common_components/cards/Card.jsx b/src/common_components/cards/Card.jsx index db77b99c2..54333c665 100644 --- a/src/common_components/cards/Card.jsx +++ b/src/common_components/cards/Card.jsx @@ -62,7 +62,7 @@ const Card = ({ Object }) => { } console.log("Object at card: ", Object) // console.log(Object) - const path = `/user/book/viewdetails/${Object.book.isbn}` + const path = `/user/books/viewdetails/${Object.book.isbn}` return (
diff --git a/src/pages/user/bookDetails/RelatedBookSection.jsx b/src/pages/user/bookDetails/RelatedBookSection.jsx index a31e01f1f..8579e27e6 100644 --- a/src/pages/user/bookDetails/RelatedBookSection.jsx +++ b/src/pages/user/bookDetails/RelatedBookSection.jsx @@ -22,7 +22,7 @@ export default function RelatedBookSection() { } { - Object.keys(relatedBookList).length === 0 && No books found! + loading === false && Object.keys(relatedBookList).length === 0 && No books found! } diff --git a/src/pages/user/bookDetails/UserButtonSection.jsx b/src/pages/user/bookDetails/UserButtonSection.jsx index 53a4b0e42..bf22bc838 100644 --- a/src/pages/user/bookDetails/UserButtonSection.jsx +++ b/src/pages/user/bookDetails/UserButtonSection.jsx @@ -76,7 +76,7 @@ export default function UserButtonSection(props) { const handleAddtoWishlist = async () => { if (isAdded == false) { const apiURL = import.meta.env.VITE_APP_API_URL - const memberId = 28;//TODO + const memberId = 'm_11201';//TODO take member id from user slice. toast.info("Request sent to the server."); @@ -104,7 +104,7 @@ export default function UserButtonSection(props) { } } else { - navigate("/") + navigate("/user/books/goto-wishlist") } } const handleShare = () => { @@ -113,7 +113,7 @@ export default function UserButtonSection(props) { const handleReserveBook = async () => { console.log("called handle reserve book"); const apiURL = import.meta.env.VITE_APP_API_URL - const memberId = 28;//TODO + const memberId = 'm_11201';//TODO take member id from user state. toast.info("Request sent to the server."); diff --git a/src/pages/user/wishlist/WishList.jsx b/src/pages/user/wishlist/WishList.jsx new file mode 100644 index 000000000..cd4a6af3e --- /dev/null +++ b/src/pages/user/wishlist/WishList.jsx @@ -0,0 +1,28 @@ +import React from 'react' +import { useSelector } from 'react-redux'; +import Box from '@mui/material/Box'; +import CircularProgress from '@mui/material/CircularProgress'; +import Card from '../../../common_components/cards/Card'; + +export default function WishList() { + const wishList = useSelector((state) => state.relatedBookList.books); + const loading = useSelector((state)=> state.relatedBookList.loading); + const noOfBooks = Object.keys(wishList).length; + return ( + <> + My Wishlist {noOfBooks} books +
+ {loading === false ? Object.entries(wishList).map(([isbn, book]) => ( + + )) : + + } + { + loading === false && Object.keys(wishList).length === 0 && No books found! + + } + +
+ + ) +} diff --git a/src/routes/Routers.jsx b/src/routes/Routers.jsx index ca1d88e36..a042847f3 100644 --- a/src/routes/Routers.jsx +++ b/src/routes/Routers.jsx @@ -4,7 +4,9 @@ import { AdminHome, Transaction, Settings, AdminBook} from '../pages/admin'; import Layout from './Layout'; import { userNavItems, adminNavItems } from './NavigationPaths'; import ShowBookDetails from '../common_components/ViewBookDetails/ShowBookDetails' +import WishList from '../pages/user/wishlist/WishList'; import Footer from '../common_components/footer/Footer'; + const Routers = () => { return ( @@ -13,8 +15,11 @@ const Routers = () => { }> } /> } /> - } /> - } /> + + } /> + } /> + } /> + } /> } /> From 60394476b64a2dea095af5417f67788072bb44f5 Mon Sep 17 00:00:00 2001 From: Animesh Dhara Date: Thu, 11 Jul 2024 17:51:40 +0530 Subject: [PATCH 02/13] Finished the wishlist fetch and showing into wishlist page --- public/Book_logo.png | Bin 0 -> 1168 bytes src/api/fetchWishListBook.js | 16 +++++ src/api/index.js | 3 +- .../ViewBookDetails/ShowBookDetails.jsx | 56 +++++------------ src/common_components/cards/cardStyle.css | 13 ++++ src/common_components/footer/Footer.jsx | 2 +- .../wishlist_card/WishListCard.jsx | 57 ++++++++++++++++++ src/features/userSlice.js | 24 +++++++- src/features/userThunks.js | 15 ++++- src/pages/user/Profile/Profile.jsx | 7 ++- .../user/bookDetails/UserButtonSection.jsx | 13 ++-- src/pages/user/wishlist/WishList.jsx | 10 +-- 12 files changed, 159 insertions(+), 57 deletions(-) create mode 100644 public/Book_logo.png create mode 100644 src/api/fetchWishListBook.js create mode 100644 src/common_components/wishlist_card/WishListCard.jsx diff --git a/public/Book_logo.png b/public/Book_logo.png new file mode 100644 index 0000000000000000000000000000000000000000..167bc615d780401b8aa4d67583ced7dc78077cde GIT binary patch literal 1168 zcmeAS@N?(olHy`uVBq!ia0vp^(LkKQ!3HFYy7|5XDb50q$YKTtZeb8+WSBKa0w~B> z9OUlAuhv%ZJgKI1(?wp?B)=Vdp6y7UaZ)-M)Rh{`d1u1&=OD?BLk9 zZ=arPfgH=Z$B!4kJzvX`DlaR0lX*>7;qUzsaq;nI&z|k=eKJK)NmRyuR{w%GKY#x8 z;y3D_+?N|Dus1zDeX09Ljb+Q0o%#KH_jl8Km1cZXRByfIkG*#3(xn^WlYjG=Ov`9k zsklx_KJI4f_sbg-_P^M7?b@|P{R{O@RvLtUx#)3|PsV1E%-u%*KlO7bC1)O8|9am( zaYn&kMzfrGL8`SNdS4jCV1 zLQ0Jn@rv~2tg(Op{=IuA+YZNq>T2n~zvjCn-jtS>RukojkBl^oUA!UZ&O(WHv$dI1 zToZ4eQ1m?dG_9of(58(?I=?=-VyzXsWXesBlt!r+(Jan^_D=*}iFFmhkmQhfS zp+gz#+FpT+HW>Z@CmD3lK?K9g#O05!5L*9c-4cl@t|z94n-y7!+%0^ttbE0U8|yEy zzj^mgN>EKLQq#ggPF`Mn7xRMO99O?EJKpSgx6W?61oNH+a@l8oh?#zMo*T$cpt-*w(Qw8bGo~ua!$)F_d5CYG*Ilj z>W<}=fqs=Cx%+k%9N)EbXJ+)($qkoM`I{V7?tis-xw19)^eN`{fR3XdLj?EQX-&87 z=vK06pB!W(Q=4|`JbU5Mi;{viU7c!wY(2vLOGA=u*72QbX={AN_xkl~=lkp{+1c5* znLhg7vL^9M)!v-aQq$S<=AC=??AfNnhY!DrUh&=bZ{7WUeD57^-@0{b-}+f=GEI~= l?QY7G6Tr- { + const rootUrl = import.meta.env.VITE_APP_API_URL; + console.log("app url at api: ",rootUrl); + + try { + const response = await axios.get( `${rootUrl}/api/user/myBooks/wishlist/memberId/${memberId}` ); + console.log("response at api call of fetch wishlisted books: ", response ) + return response.data; + } catch ( error ) { + console.log( error.message ); + } +} + +export default fetchWishListBook; \ No newline at end of file diff --git a/src/api/index.js b/src/api/index.js index 9cef99f9c..42f83e060 100644 --- a/src/api/index.js +++ b/src/api/index.js @@ -2,5 +2,6 @@ import fetchUserDetails from "./fetchUserDetails"; import updateUserDetails from "./updateUserDetails"; import updateFavSubject from "./updateFavSubject"; import getLibraryCards from "./getLibraryCards"; +import fetchWishListBook from "./fetchWishListBook"; -export { fetchUserDetails, updateUserDetails, updateFavSubject, getLibraryCards }; \ No newline at end of file +export { fetchUserDetails, updateUserDetails, updateFavSubject, getLibraryCards, fetchWishListBook }; \ No newline at end of file diff --git a/src/common_components/ViewBookDetails/ShowBookDetails.jsx b/src/common_components/ViewBookDetails/ShowBookDetails.jsx index 5224695bc..9068a1566 100644 --- a/src/common_components/ViewBookDetails/ShowBookDetails.jsx +++ b/src/common_components/ViewBookDetails/ShowBookDetails.jsx @@ -3,7 +3,6 @@ import '../../pages/user/bookDetails/BookDetailsDesign.css' import { useNavigate, useParams } from "react-router-dom"; import { useDispatch } from 'react-redux'; import { fetchRelatedBookList } from '../../features/relatedBoolReducer/RelatedBookReducer'; -import { fetchSearchQueryResult } from '../../features/searchBookReducer/SearchBookReducer'; //Material ui icons import ArrowBackOutlinedIcon from '@mui/icons-material/ArrowBackOutlined'; @@ -13,7 +12,6 @@ import ExpandMoreOutlinedIcon from '@mui/icons-material/ExpandMoreOutlined'; // import Box from '@mui/material/Box'; import Backdrop from '@mui/material/Backdrop'; import CircularProgress from '@mui/material/CircularProgress'; -import Button from '@mui/material/Button'; //Common components @@ -25,22 +23,17 @@ import Footer from '../footer/Footer'; //This component accepts the book as props. export default function ShowBookDetails(props) { - //TODO props.book - const dummy_book = { shelving_no: "sh-2-4", isbn: '978-0-07-140194-4', author: "Sanjay Saha", title: "Data base management system", description: "Animesh ipsum dolor sit amet consectetur adipisicing elit. Maiores, modi veniam nostrum repudiandae officia dolorum sit aliquid asperiores nemo necessitatibus nam eaque voluptatibus blanditiis voluptatem vero eius accusamus velit vitae quos tempore! Autem temporibus dolor expedita earum enim, ullam suscipit voluptate hic aliquid vitae dignissimos officiis accusamus quas, velit veritatis.", dateOfPublication: "2020", publisher: "Mc Graw Hill", no_of_copies: 1 }; const dispatch = useDispatch(); const {isbn} = useParams(); - console.log("header isbn: ",isbn); const apiURL = import.meta.env.VITE_APP_API_URL; - // console.log("Api url: ",apiURL); - const [book, setBook] = useState(props.book === undefined ? props.book : null); + const [book, setBook] = useState(undefined); //Initially book will be undefined, if changed to null then gives error as we are reading book.description const [loading, setLoading] = useState(true); + const description_length = 50;//Describe description length to be shown in frontend. - const isbn_no = "978-0-07-140194-4";//TODO pops.book.isbn - // const isbn_no = props.book.isbn; useEffect(() => { console.log("isbn changed: ",isbn); const fetchBook = async (isbn) => { @@ -49,10 +42,9 @@ export default function ShowBookDetails(props) { ); if (response.status === 200) { const data = await response.json(); - console.log("data at fetch by isbn: ",data); data.book.author = data.author_name; setBook(data.book); - setDetails(data.book.description.substring(0, 150)); + setDetails(data.book.description.substring(0, description_length)); setLoading(false); } } @@ -61,10 +53,6 @@ export default function ShowBookDetails(props) { } } - - // if (isbn !== undefined && loading === true && props.book === undefined) { - // fetchBook(isbn); - // } setLoading(true); fetchBook(isbn); if (props.type === 'user' ) { @@ -74,38 +62,24 @@ export default function ShowBookDetails(props) { if (isbn === undefined) { setBook(props.book); - // console.log("Entered"); } - console.log("book: ", book); - - //Book details - // const shelVingNo = book.shelving_no; - // const author = book.author; - // const book_title = book.title; - // const date_publication = book.date_of_publication; - const des = book !== undefined ? book.description : "dummy description"; - - // const publisher = book.publisher; - // const noOfCopies = book.no_of_copies; - - // console.log("des: ",des); + const book_description = book!== undefined?book.description:""; // temporarily storing book description. const navigate = useNavigate(); //Used for showing up/down arrow at read more section const upRef = useRef(); const downRef = useRef(); - const description_length = 150; - const [details, setDetails] = useState(des.substring(0, description_length)); + const [details, setDetails] = useState(book_description.substring(0, description_length)); const [read, setRead] = useState("More"); const handleMoreDetails = () => { - details == des ? setDetails(des.substring(0, 150)) : setDetails(des); - read == "More" ? setRead("Less") : setRead("More"); + details === book_description ? setDetails(book_description.substring(0, description_length)) : setDetails(book_description); + read === "More" ? setRead("Less") : setRead("More"); - upRef.current.style.display == "none" ? upRef.current.style.display = "inline" : upRef.current.style.display = "none"; - downRef.current.style.display == "none" ? downRef.current.style.display = "inline" : downRef.current.style.display = "none"; + upRef.current.style.display === "none" ? upRef.current.style.display = "inline" : upRef.current.style.display = "none"; + downRef.current.style.display === "none" ? downRef.current.style.display = "inline" : downRef.current.style.display = "none"; } const handleBackButton = () => { @@ -129,7 +103,7 @@ export default function ShowBookDetails(props) { Loading image!
{ - props.type === 'user' ? : + props.type === 'user' ? : } @@ -149,18 +123,16 @@ export default function ShowBookDetails(props) { {book.publisher && Publisher: {book.publisher}} - {/* No of copies: {book.no_of_copies} */} - Description: - {details.length === description_length ? details + "...." : details} + {details !== book_description ? details + "...." : details} - {des.length > description_length ?
Read {read}
: null} + {book_description.length > description_length ?
Read {read}
: null} {/* Related Books Section */} {props.type === 'user' ? : null} -
- +
+
: null} diff --git a/src/common_components/cards/cardStyle.css b/src/common_components/cards/cardStyle.css index 7a7b8410a..e2ae6bf5e 100644 --- a/src/common_components/cards/cardStyle.css +++ b/src/common_components/cards/cardStyle.css @@ -92,3 +92,16 @@ color: aliceblue; background-color: rgb(46, 47, 84); } + +.custom-stylebtn-wishlist{ + margin: 0px -4px; + display: flex; + align-items: center; + font-weight: 600; + place-content: space-evenly; + background: aliceblue; + width: 45%; + height: 1.5vw; + border-radius: 0.8vw; + font-size: var(--font1); +} diff --git a/src/common_components/footer/Footer.jsx b/src/common_components/footer/Footer.jsx index 5c6af6422..7f7a26cb8 100644 --- a/src/common_components/footer/Footer.jsx +++ b/src/common_components/footer/Footer.jsx @@ -8,7 +8,7 @@ export default function Footer() {
- Image loading.... + Image loading.... LitLib
Contact Support diff --git a/src/common_components/wishlist_card/WishListCard.jsx b/src/common_components/wishlist_card/WishListCard.jsx new file mode 100644 index 000000000..83c34743d --- /dev/null +++ b/src/common_components/wishlist_card/WishListCard.jsx @@ -0,0 +1,57 @@ +import React, { useState, useEffect, useRef } from 'react' +import { NavLink } from 'react-router-dom'; +import '../cards/cardStyle.css'; +import { useSelector, useDispatch } from 'react-redux' +import FavoriteIcon from '@mui/icons-material/Favorite'; +import TravelExploreTwoToneIcon from '@mui/icons-material/TravelExploreTwoTone' +import Tooltip from '@mui/material/Tooltip' +import "/src/common_components/ViewBookDetails/ShowBookDetails" + +const WishListCard = ({ Object }) => { + const path = `/user/books/viewdetails/${Object.book.isbn}` + + return ( +
+
0 ? '#3fd43f':'#d32e0d'}}/> +
+
+ + +

{Object.book.title}

+
+ +

{Object.author_name}

+
+
+
+ + + +

Details

+
+
+
+ ) +} + +export default WishListCard \ No newline at end of file diff --git a/src/features/userSlice.js b/src/features/userSlice.js index 95628b28c..7e61322bc 100644 --- a/src/features/userSlice.js +++ b/src/features/userSlice.js @@ -1,5 +1,5 @@ import { createSlice } from '@reduxjs/toolkit'; -import { fetchUserData, updateUserData, updateFavSubjectData } from './userThunks.js'; +import { fetchUserData, updateUserData, updateFavSubjectData, fetchWishList } from './userThunks.js'; const initialState = { details: { @@ -16,6 +16,7 @@ const initialState = { }, loading: false, error: null, + wishList:{}, }; const userSlice = createSlice({ @@ -96,6 +97,27 @@ const userSlice = createSlice({ state.loading = false; state.error = action.payload || 'Subjects couldnot be updated' }) + + .addCase(fetchWishList.pending, (state)=>{ + state.loading = true; + }) + .addCase(fetchWishList.fulfilled, (state,action)=>{ + state.loading = false; + state.error = null; + + const wishListBooks = action.payload; + console.log("wishListBooks in fetchWishlist: ",wishListBooks); + const size = Object.keys(wishListBooks).length; + state.wishList =size>=1? wishListBooks.reduce((acc, item) => { + acc[item.book.isbn] = item; + return acc; + }, {}):{}; + console.log("wish list state: ",state.wishList); + }) + .addCase(fetchWishList.rejected, (state)=>{ + state.loading = false; + state.error = action.payload || 'Wishlist couldnot be fetched' + }) }); export const { diff --git a/src/features/userThunks.js b/src/features/userThunks.js index 2ded1490e..200ca5ad4 100644 --- a/src/features/userThunks.js +++ b/src/features/userThunks.js @@ -1,5 +1,7 @@ import { createAsyncThunk } from '@reduxjs/toolkit'; -import { updateFavSubject, fetchUserDetails, updateUserDetails, getLibraryCards } from '../api'; +import { updateFavSubject, fetchUserDetails, updateUserDetails, getLibraryCards, fetchWishListBook } from '../api'; + + export const fetchUserData = createAsyncThunk( 'user/fetchData', @@ -39,4 +41,15 @@ export const updateFavSubjectData = createAsyncThunk( return thunkAPI.rejectWithValue( error.message ); } } +); + +export const fetchWishList = createAsyncThunk('user/fetchWishlist', + async (memberId, thunkAPI)=>{ + try{ + const wishList = await fetchWishListBook(memberId); + return wishList; + }catch(error){ + return thunkAPI.rejectWithValue(error.message); + } + } ); \ No newline at end of file diff --git a/src/pages/user/Profile/Profile.jsx b/src/pages/user/Profile/Profile.jsx index 9235f3e82..43acadf00 100644 --- a/src/pages/user/Profile/Profile.jsx +++ b/src/pages/user/Profile/Profile.jsx @@ -3,7 +3,7 @@ import { useSelector, useDispatch } from 'react-redux'; import ProfileCard from './ProfileCard'; import LibraryCards from './LibraryCards'; import Loading from './Loading'; -import { fetchUserData, updateUserData } from '../../../features/userThunks'; +import { fetchUserData, updateUserData, fetchWishList } from '../../../features/userThunks'; import { setEmail, setPhoneNumber, setAddress } from '../../../features/userSlice'; import ChangeConfirmationModal from '../../../common_components/modals/ChangeConfirmationModal'; import { EditSubjects } from '../../../components'; @@ -12,6 +12,7 @@ import toast from 'react-hot-toast'; import './Profile.css'; const Profile = () => { + console.log("profile rendered"); const dispatch = useDispatch(); const user = useSelector((state) => state.user); const { name, department, studentID, joiningDate, rollNo, email, phoneNumber, address, subjectsOfInterest, libraryCardDetails } = user.details; @@ -30,6 +31,10 @@ const Profile = () => { setUserCopy({ email, phoneNumber, address }); }, [user.details]); + useEffect(()=>{ + dispatch(fetchWishList('m_11201'));// TODO member id. + }, [dispatch]) + const handleChange = (event) => { const { name, value } = event.target; setUserCopy((prevUserCopy) => ({ diff --git a/src/pages/user/bookDetails/UserButtonSection.jsx b/src/pages/user/bookDetails/UserButtonSection.jsx index bf22bc838..97d2a414a 100644 --- a/src/pages/user/bookDetails/UserButtonSection.jsx +++ b/src/pages/user/bookDetails/UserButtonSection.jsx @@ -1,6 +1,7 @@ import { React, useState, useRef, useEffect } from 'react' import './BookDetailsDesign.css' import { useNavigate } from 'react-router-dom'; +import { useSelector } from 'react-redux'; //Material ui icons import FavoriteIcon from '@mui/icons-material/Favorite'; @@ -50,14 +51,16 @@ const style = { //Expect isbn no of the book as props. export default function UserButtonSection(props) { // const isbn_no = "978-0-07-140194-4";//TODO pops.isbn + const noOfCopies = props.no_of_copies; const isbn_no = props.isbn; const navigate = useNavigate(); + const wishListBook = useSelector((state)=> state.user.wishList); const heartRef = useRef(); const shareRef = useRef(); const [heart_class, setHeartClass] = useState(''); - const [isAdded, setIsAdded] = useState(false); + const [isAdded, setIsAdded] = useState(isbn_no in wishListBook?true:false); const [open, setOpen] = useState(false); const handleOpen = () => setOpen(true); @@ -183,10 +186,10 @@ export default function UserButtonSection(props) {
- - -
- + + +
+
diff --git a/src/pages/user/wishlist/WishList.jsx b/src/pages/user/wishlist/WishList.jsx index cd4a6af3e..81bafa1cb 100644 --- a/src/pages/user/wishlist/WishList.jsx +++ b/src/pages/user/wishlist/WishList.jsx @@ -2,18 +2,18 @@ import React from 'react' import { useSelector } from 'react-redux'; import Box from '@mui/material/Box'; import CircularProgress from '@mui/material/CircularProgress'; -import Card from '../../../common_components/cards/Card'; +import WishListCard from '../../../common_components/wishlist_card/WishListCard'; export default function WishList() { - const wishList = useSelector((state) => state.relatedBookList.books); - const loading = useSelector((state)=> state.relatedBookList.loading); + const wishList = useSelector((state) => state.user.wishList); + const loading = useSelector((state)=> state.user.loading); const noOfBooks = Object.keys(wishList).length; return ( <> - My Wishlist {noOfBooks} books + My Wishlist {noOfBooks} {noOfBooks>0?'books':'book'}
{loading === false ? Object.entries(wishList).map(([isbn, book]) => ( - + )) : } From 5f3a61d5ffa9b78f0e4e27df8485ba58dde8b8d5 Mon Sep 17 00:00:00 2001 From: Animesh Dhara Date: Thu, 11 Jul 2024 18:15:23 +0530 Subject: [PATCH 03/13] Add go to wish list property in book card section --- src/common_components/cards/Card.jsx | 26 ++++++++++++++++++-------- 1 file changed, 18 insertions(+), 8 deletions(-) diff --git a/src/common_components/cards/Card.jsx b/src/common_components/cards/Card.jsx index 54333c665..8fd2db7b0 100644 --- a/src/common_components/cards/Card.jsx +++ b/src/common_components/cards/Card.jsx @@ -9,16 +9,19 @@ import image from '/src/common_components/cards/image.jpeg' import { useNavigate } from 'react-router-dom'; import "/src/common_components/ViewBookDetails/ShowBookDetails" +import ShortcutIcon from '@mui/icons-material/Shortcut'; + import { toast } from 'react-toastify'; const Card = ({ Object }) => { - + const wishList = useSelector((state)=>state.user.wishList); const isbn_no = Object.book.isbn; const navigate = useNavigate(); const heartRef = useRef(); - const [isAdded, setIsAdded] = useState(false); + const [isAdded, setIsAdded] = useState(isbn_no in wishList?true:false); + const handleAddtoWishlist = async () => { if (isAdded == false) { const apiURL = import.meta.env.VITE_APP_API_URL @@ -55,10 +58,15 @@ const Card = ({ Object }) => { } } - const [isFavorite, setIsFavorite] = useState(false) + const [isFavorite, setIsFavorite] = useState(isbn_no in wishList?true:false) const toggleFavorite = () => { - handleAddtoWishlist(); + if(isFavorite === false){ + handleAddtoWishlist(); + } + else { + navigate("/user/books/goto-wishlist") + } } console.log("Object at card: ", Object) // console.log(Object) @@ -87,15 +95,17 @@ const Card = ({ Object }) => {
- Date: Thu, 11 Jul 2024 18:23:27 +0530 Subject: [PATCH 04/13] Change member id --- src/common_components/cards/Card.jsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/common_components/cards/Card.jsx b/src/common_components/cards/Card.jsx index 8fd2db7b0..e86db4351 100644 --- a/src/common_components/cards/Card.jsx +++ b/src/common_components/cards/Card.jsx @@ -25,7 +25,7 @@ const Card = ({ Object }) => { const handleAddtoWishlist = async () => { if (isAdded == false) { const apiURL = import.meta.env.VITE_APP_API_URL - const memberId = 'm_11111';//TODO + const memberId = 'm_11201';//TODO toast.info("Request sent to the server."); From 00cbfd409e4a66d4c81ca3ad3d9b7e3cfd4d7201 Mon Sep 17 00:00:00 2001 From: Animesh Dhara Date: Fri, 12 Jul 2024 18:26:14 +0530 Subject: [PATCH 05/13] Added nested routes --- src/routes/Routers.jsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/routes/Routers.jsx b/src/routes/Routers.jsx index a042847f3..99ea57aa3 100644 --- a/src/routes/Routers.jsx +++ b/src/routes/Routers.jsx @@ -15,13 +15,13 @@ const Routers = () => { }> } /> } /> + } /> + } /> } /> } /> } /> - } /> - } /> }> } /> From 3ad7a367a31ee68fb343eac8af4e1cc6d2f1d7ae Mon Sep 17 00:00:00 2001 From: Animesh Dhara Date: Sat, 13 Jul 2024 19:28:51 +0530 Subject: [PATCH 06/13] Finish the wishlist page design and modify the card design --- .../ViewBookDetails/ShowBookDetails.jsx | 21 ++- src/common_components/cards/Card.jsx | 161 ++++++++++++------ src/common_components/cards/cardStyle.css | 57 ++----- src/common_components/footer/FooterDesign.css | 2 +- .../wishlist_card/WishListCard.jsx | 57 ------- src/features/userSlice.js | 9 + .../user/bookDetails/BookDetailsDesign.css | 2 +- .../user/bookDetails/UserButtonSection.jsx | 20 ++- src/pages/user/wishlist/WishList.jsx | 38 +++-- 9 files changed, 179 insertions(+), 188 deletions(-) delete mode 100644 src/common_components/wishlist_card/WishListCard.jsx diff --git a/src/common_components/ViewBookDetails/ShowBookDetails.jsx b/src/common_components/ViewBookDetails/ShowBookDetails.jsx index 9068a1566..4a9f45568 100644 --- a/src/common_components/ViewBookDetails/ShowBookDetails.jsx +++ b/src/common_components/ViewBookDetails/ShowBookDetails.jsx @@ -29,7 +29,7 @@ export default function ShowBookDetails(props) { const apiURL = import.meta.env.VITE_APP_API_URL; - const [book, setBook] = useState(undefined); //Initially book will be undefined, if changed to null then gives error as we are reading book.description + const [book, setBook] = useState(undefined); //Initially book will be undefined, if changed to null then gives error as we are reading book.book.description const [loading, setLoading] = useState(true); const description_length = 50;//Describe description length to be shown in frontend. @@ -42,8 +42,7 @@ export default function ShowBookDetails(props) { ); if (response.status === 200) { const data = await response.json(); - data.book.author = data.author_name; - setBook(data.book); + setBook(data); setDetails(data.book.description.substring(0, description_length)); setLoading(false); } @@ -64,7 +63,7 @@ export default function ShowBookDetails(props) { setBook(props.book); } - const book_description = book!== undefined?book.description:""; // temporarily storing book description. + const book_description = book!== undefined?book.book.description:""; // temporarily storing book description. const navigate = useNavigate(); //Used for showing up/down arrow at read more section @@ -103,25 +102,25 @@ export default function ShowBookDetails(props) { Loading image!
{ - props.type === 'user' ? : + props.type === 'user' ? : }
- {book.title} 0 ? 'in_stock' : 'out_stock'}`}>{book.no_of_copies > 0 ? "In Stock" : "Out of Stock"} + {book.book.title} 0 ? 'in_stock' : 'out_stock'}`}>{book.book.no_of_copies > 0 ? "In Stock" : "Out of Stock"} - by {book.author} + by {book.author_name} - Shelving No: {book.shelving_no} + Shelving No: {book.book.shelving_no} - ISBN No: {book.isbn} + ISBN No: {book.book.isbn} - Date of publication: {book.date_of_publication} + Date of publication: {book.book.date_of_publication} - {book.publisher && Publisher: {book.publisher}} + {book.book.publisher && Publisher: {book.book.publisher}} Description: {details !== book_description ? details + "...." : details} diff --git a/src/common_components/cards/Card.jsx b/src/common_components/cards/Card.jsx index e86db4351..a28e4943e 100644 --- a/src/common_components/cards/Card.jsx +++ b/src/common_components/cards/Card.jsx @@ -4,23 +4,33 @@ import './cardStyle.css' import { useSelector, useDispatch } from 'react-redux' import FavoriteIcon from '@mui/icons-material/Favorite'; import TravelExploreTwoToneIcon from '@mui/icons-material/TravelExploreTwoTone' -import Tooltip from '@mui/material/Tooltip' -import image from '/src/common_components/cards/image.jpeg' +import Tooltip from '@mui/material/Tooltip'; import { useNavigate } from 'react-router-dom'; import "/src/common_components/ViewBookDetails/ShowBookDetails" +import { toast } from 'react-toastify'; -import ShortcutIcon from '@mui/icons-material/Shortcut'; +import { addBookToWishList, removeBookFromWishList } from '../../features/userSlice'; -import { toast } from 'react-toastify'; +//Boot Strap modal +import Button from 'react-bootstrap/Button'; +import Modal from 'react-bootstrap/Modal'; const Card = ({ Object }) => { - const wishList = useSelector((state)=>state.user.wishList); + const dispatch = useDispatch(); + + // Used for modal control. + const [show, setShow] = useState(false); + const handleClose = () => setShow(false); + const handleShow = () => setShow(true); + + const wishList = useSelector((state) => state.user.wishList); const isbn_no = Object.book.isbn; const navigate = useNavigate(); - const heartRef = useRef(); + const modalRef = useRef(); - const [isAdded, setIsAdded] = useState(isbn_no in wishList?true:false); + const [isAdded, setIsAdded] = useState(isbn_no in wishList ? true : false); + const [isFavorite, setIsFavorite] = useState(isbn_no in wishList ? true : false) const handleAddtoWishlist = async () => { if (isAdded == false) { @@ -28,9 +38,10 @@ const Card = ({ Object }) => { const memberId = 'm_11201';//TODO toast.info("Request sent to the server."); + setIsFavorite(true); try { - const response = await fetch(`${apiURL}/api/user/books/wishlist/isbn/${isbn_no}/memberId/${memberId}`, { + const response = await fetch(`${apiURL}/api/user/books/add-wishlist/isbn/${isbn_no}/memberId/${memberId}`, { method: 'POST', headers: { 'Content-Type': 'application/json', @@ -38,30 +49,63 @@ const Card = ({ Object }) => { }); const json = await response.json() if (response.status === 200 && json.message != "Member does not exist") { + dispatch(addBookToWishList(Object)); setIsAdded(true); - // setIsFavorite((prevState) => !prevState) - setIsFavorite(true) + setIsFavorite(true); toast.success("Book added to your wishlist."); } else { toast.error(json.message); + setIsFavorite(false); } console.log(`Response for add to favorite books isbn - ${isbn_no} : ${json.message}`); } catch (error) { + setIsFavorite(false); toast.error("Something went wrong. Please try again later.") console.log('Error while requesing for add to favorite books: ', error) } } else { - navigate("/") + modalRef.current.click(); } } - const [isFavorite, setIsFavorite] = useState(isbn_no in wishList?true:false) + const handleRemoveWishList = async ()=>{ + setShow(false) + const apiURL = import.meta.env.VITE_APP_API_URL + const memberId = 'm_11201';//TODO + + toast.info("Request sent to the server."); + + try { + const response = await fetch(`${apiURL}/api/user/books/remove-wishlist/isbn/${isbn_no}/memberId/${memberId}`, { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + }); + const json = await response.json() + if (response.status === 200 && json.message != "Member does not exist") { + dispatch(removeBookFromWishList(Object)); + setIsAdded(false); + setIsFavorite(false) + + toast.success("Book removed from your wishlist successfully."); + } + else { + toast.error(json.message); + } + console.log(`Response for add to favorite books isbn - ${isbn_no} : ${json.message}`); + } catch (error) { + toast.error("Something went wrong. Please try again later.") + console.log('Error while requesing for add to favorite books: ', error) + } + } + const toggleFavorite = () => { - if(isFavorite === false){ + if (isFavorite === false) { handleAddtoWishlist(); } else { @@ -73,48 +117,57 @@ const Card = ({ Object }) => { const path = `/user/books/viewdetails/${Object.book.isbn}` return ( -
-
0 ? '#3fd43f':'#d32e0d'}}/> -
-
- - -

{Object.book.title}

-
- -

{Object.author_name}

-
-
-
- - - -

Details

-
+ <> + + + + + Remove Book from Wish List + + Are you sure you want to remove "{Object.book.title}" from wish list? + + + + + + +
+
+ Image loading.... +
+ {/* {Object.book.no_of_copies === 0 && ( +
Out of Stock
+ )} */} +
+ + +

{Object.book.title}

+
+ +

{Object.author_name}

+
+
+
+
+ + +
+ + +

Details

+
+
-
+ ) } diff --git a/src/common_components/cards/cardStyle.css b/src/common_components/cards/cardStyle.css index e2ae6bf5e..577b92178 100644 --- a/src/common_components/cards/cardStyle.css +++ b/src/common_components/cards/cardStyle.css @@ -11,32 +11,14 @@ margin: 10px 5px; width: 14vw; height: auto; - border: 2px solid darkgray; - background: rgba(128, 128, 128, 0.3); - border-radius: 1vw; -} - -/* Availability section */ -.custom-availability { - margin: 0px 0px; - padding: 0px 0px; - width: 20%; - height: 1.5vw; - position: relative; - left: 10.5vw; - z-index: 100; - top: 0.5vw; border-radius: 1vw; } /* Image section */ .custom-image { - margin: -1.3vw auto 3px; - border-radius: 1vw; aspect-ratio: 2/2.5; - width: 97%; - background-size: cover; - background-repeat: no-repeat; + width: inherit; + height: inherit; } .custom-name_section { @@ -66,12 +48,7 @@ /* buttons */ .custom-buttons { margin: 3px 0px; - place-content: space-around; - /* position: relative; */ - /* top: 15%; */ justify-content: space-around; - /* margin: 3px 0px; */ - /* position: relative; */ } .custom-stylebtn { @@ -87,21 +64,23 @@ font-size: var(--font1); } -.custom-stylebtn:hover { - /* font-size: 1.1vw; */ - color: aliceblue; - background-color: rgb(46, 47, 84); +.out-of-stock{ + opacity: 0.5; + z-index: -1; + background: rgba(0, 0, 0, 0.5); } -.custom-stylebtn-wishlist{ - margin: 0px -4px; +.out-of-stock-overlay{ + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 100%; + background: rgba(0, 0, 0, 0.5); + color: white; display: flex; + justify-content: center; align-items: center; - font-weight: 600; - place-content: space-evenly; - background: aliceblue; - width: 45%; - height: 1.5vw; - border-radius: 0.8vw; - font-size: var(--font1); -} + font-size: 1.5em; + opacity: 1; +} \ No newline at end of file diff --git a/src/common_components/footer/FooterDesign.css b/src/common_components/footer/FooterDesign.css index 59cab92cc..1d448713e 100644 --- a/src/common_components/footer/FooterDesign.css +++ b/src/common_components/footer/FooterDesign.css @@ -5,6 +5,7 @@ height: 20%; display: grid; grid-template-columns: repeat(10, 1fr); + padding: 5px; } .logo{ @@ -53,6 +54,5 @@ } .right_text{ font-size: medium; - font-family: 'Courier New', Courier, monospace; font-weight: bold; } \ No newline at end of file diff --git a/src/common_components/wishlist_card/WishListCard.jsx b/src/common_components/wishlist_card/WishListCard.jsx deleted file mode 100644 index 83c34743d..000000000 --- a/src/common_components/wishlist_card/WishListCard.jsx +++ /dev/null @@ -1,57 +0,0 @@ -import React, { useState, useEffect, useRef } from 'react' -import { NavLink } from 'react-router-dom'; -import '../cards/cardStyle.css'; -import { useSelector, useDispatch } from 'react-redux' -import FavoriteIcon from '@mui/icons-material/Favorite'; -import TravelExploreTwoToneIcon from '@mui/icons-material/TravelExploreTwoTone' -import Tooltip from '@mui/material/Tooltip' -import "/src/common_components/ViewBookDetails/ShowBookDetails" - -const WishListCard = ({ Object }) => { - const path = `/user/books/viewdetails/${Object.book.isbn}` - - return ( -
-
0 ? '#3fd43f':'#d32e0d'}}/> -
-
- - -

{Object.book.title}

-
- -

{Object.author_name}

-
-
-
- - - -

Details

-
-
-
- ) -} - -export default WishListCard \ No newline at end of file diff --git a/src/features/userSlice.js b/src/features/userSlice.js index 7e61322bc..0adff88cb 100644 --- a/src/features/userSlice.js +++ b/src/features/userSlice.js @@ -35,6 +35,13 @@ const userSlice = createSlice({ updateSubjectOfInterest: (state, action) => { state.details.subjectsOfInterest = [...action.payload]; }, + addBookToWishList: (state, action)=>{ + state.wishList = {...state.wishList, [action.payload.book.isbn]: action.payload + }; + }, + removeBookFromWishList: (state, action)=>{ + delete state.wishList[action.payload.book.isbn]; + }, }, extraReducers: builder => builder @@ -125,6 +132,8 @@ export const { setPhoneNumber, setAddress, updateSubjectOfInterest, + addBookToWishList, + removeBookFromWishList, } = userSlice.actions; export default userSlice.reducer; \ No newline at end of file diff --git a/src/pages/user/bookDetails/BookDetailsDesign.css b/src/pages/user/bookDetails/BookDetailsDesign.css index fdc79663b..a7c8c50f4 100644 --- a/src/pages/user/bookDetails/BookDetailsDesign.css +++ b/src/pages/user/bookDetails/BookDetailsDesign.css @@ -78,7 +78,7 @@ } .heart_icon{ - color: rgb(241, 134, 134); + color: rgb(224, 81, 112); font-size: 2rem; } diff --git a/src/pages/user/bookDetails/UserButtonSection.jsx b/src/pages/user/bookDetails/UserButtonSection.jsx index 97d2a414a..7f0db7924 100644 --- a/src/pages/user/bookDetails/UserButtonSection.jsx +++ b/src/pages/user/bookDetails/UserButtonSection.jsx @@ -1,7 +1,8 @@ import { React, useState, useRef, useEffect } from 'react' import './BookDetailsDesign.css' import { useNavigate } from 'react-router-dom'; -import { useSelector } from 'react-redux'; +import { useSelector, useDispatch } from 'react-redux'; +import { addBookToWishList } from '../../../features/userSlice'; //Material ui icons import FavoriteIcon from '@mui/icons-material/Favorite'; @@ -44,15 +45,17 @@ const style = { width: 400, bgcolor: 'background.paper', border: '2px solid #000', + borderRadius: '12px', boxShadow: 24, p: 4, }; //Expect isbn no of the book as props. export default function UserButtonSection(props) { - // const isbn_no = "978-0-07-140194-4";//TODO pops.isbn + const dispatch = useDispatch(); + const noOfCopies = props.no_of_copies; - const isbn_no = props.isbn; + const isbn_no = props.book.book.isbn; const navigate = useNavigate(); const wishListBook = useSelector((state)=> state.user.wishList); @@ -80,11 +83,11 @@ export default function UserButtonSection(props) { if (isAdded == false) { const apiURL = import.meta.env.VITE_APP_API_URL const memberId = 'm_11201';//TODO take member id from user slice. - toast.info("Request sent to the server."); - + setHeartClass('heart_icon'); + try { - const response = await fetch(`${apiURL}/api/user/books/wishlist/isbn/${isbn_no}/memberId/${memberId}`, { + const response = await fetch(`${apiURL}/api/user/books/add-wishlist/isbn/${isbn_no}/memberId/${memberId}`, { method: 'POST', headers: { 'Content-Type': 'application/json', @@ -92,16 +95,17 @@ export default function UserButtonSection(props) { }); const json = await response.json() if (response.status === 200 && json.message != "Member does not exist") { + dispatch(addBookToWishList(props.book)) setIsAdded(true); - setHeartClass('heart_icon'); - toast.success("Book added to your wishlist."); } else { + setHeartClass(''); toast.error(json.message); } console.log(`Response for add to favorite books isbn - ${isbn_no} : ${json.message}`); } catch (error) { + setHeartClass(''); toast.error("Something went wrong. Please try again later.") console.log('Error while requesing for add to favorite books: ', error) } diff --git a/src/pages/user/wishlist/WishList.jsx b/src/pages/user/wishlist/WishList.jsx index 81bafa1cb..428d78281 100644 --- a/src/pages/user/wishlist/WishList.jsx +++ b/src/pages/user/wishlist/WishList.jsx @@ -2,27 +2,31 @@ import React from 'react' import { useSelector } from 'react-redux'; import Box from '@mui/material/Box'; import CircularProgress from '@mui/material/CircularProgress'; -import WishListCard from '../../../common_components/wishlist_card/WishListCard'; +import Footer from '../../../common_components/footer/Footer'; +import Card from '../../../common_components/cards/Card'; export default function WishList() { - const wishList = useSelector((state) => state.user.wishList); - const loading = useSelector((state)=> state.user.loading); + const wishList = useSelector((state) => state.user.wishList); + const loading = useSelector((state) => state.user.loading); const noOfBooks = Object.keys(wishList).length; - return ( - <> - My Wishlist {noOfBooks} {noOfBooks>0?'books':'book'} -
- {loading === false ? Object.entries(wishList).map(([isbn, book]) => ( - - )) : - - } - { - loading === false && Object.keys(wishList).length === 0 && No books found! - - } + return ( + <> +
+ My Wishlist {noOfBooks} {noOfBooks > 0 ? 'books' : 'book'} +
+ {loading === false ? Object.entries(wishList).map(([isbn, book]) => ( + + )) : + + } + { + loading === false && Object.keys(wishList).length === 0 && No books found! + + } +
+