Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

implemented bookList modal dialog with book details #32

Merged
merged 1 commit into from
Dec 24, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion src/App.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import Login from './components/user/login/Login'
import Signup from './components/user/signup/Signup'
import Error from './components/error/Error'
import { syncLoadBook } from './redux/slices/booksSlice'
import { auth } from './services/firebaseConfig'
import { auth } from './api/services/firebaseConfig'
import './App.css'

function App() {
Expand Down
File renamed without changes.
39 changes: 38 additions & 1 deletion src/components/BookListSection/BookListSection.jsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { useEffect } from 'react'
import { useEffect, useState } from 'react'
import { useDispatch, useSelector } from 'react-redux'
import { setError } from '../../redux/slices/errorSlice'
import Book from './book/Book'
Expand All @@ -13,24 +13,38 @@ import {
selectTitleFilter,
selectAuthorsFilter,
} from '../../redux/slices/filterSlice'
import BookListModal from './bookListModal/BookListModal'

const BookListSection = ({ 'data-testid': testId }) => {
const dispatch = useDispatch()
const books = useSelector(selectBook)
const titleFilter = useSelector(selectTitleFilter)
const authorsFilter = useSelector(selectAuthorsFilter)
const [isModalOpen, setIsModalOpen] = useState(false)
const [modalBook, setModalBook] = useState(null)

useEffect(() => {
if (process.env.NODE_ENV !== 'test') {
dispatch(syncLoadBook())
}
}, [dispatch])

useEffect(() => {
if (isModalOpen && modalBook) {
const updatedBookState = books.find((book) => book.id === modalBook.id)
if (updatedBookState) {
setModalBook(updatedBookState)
}
}
}, [books, isModalOpen, modalBook])

const handleDeleteBook = (id) => {
const book = books.find((book) => book.id === id)
if (book) {
if (!book.isFavorite) {
dispatch(syncDeleteBook(id))
setIsModalOpen(false)
setModalBook(null)
} else {
dispatch(setError("Can't Delete Favorite Book!"))
}
Expand All @@ -41,6 +55,19 @@ const BookListSection = ({ 'data-testid': testId }) => {
dispatch(syncToggleFavorite(id))
}

const handleOpenBookModal = (id) => {
const book = books.find((book) => book.id === id)
if (book) {
setIsModalOpen(true)
setModalBook(book)
}
}

const handleCloseBookModal = () => {
setIsModalOpen(false)
setModalBook(null)
}

const filteredBooksArr = books.filter((book) => {
return (
book.title.toLowerCase().includes(titleFilter.toLowerCase()) &&
Expand Down Expand Up @@ -81,11 +108,21 @@ const BookListSection = ({ 'data-testid': testId }) => {
bookAuthor={highlightFilterMatch(book.authors, authorsFilter)}
onHandleDeleteBook={handleDeleteBook}
onToggleFavoriteBook={toggleFavoriteBook}
onClick={() => handleOpenBookModal(book.id)}
data-testid={`bookList_item id=${book.id}`}
/>
))
)}
</ul>
{isModalOpen && (
<BookListModal
isOpen={isModalOpen}
onClose={handleCloseBookModal}
modalBook={modalBook}
onHandleDeleteBook={handleDeleteBook}
onToggleFavoriteBook={toggleFavoriteBook}
></BookListModal>
)}
</section>
)
}
Expand Down
40 changes: 29 additions & 11 deletions src/components/BookListSection/book/Book.css
Original file line number Diff line number Diff line change
Expand Up @@ -5,20 +5,22 @@ li[data-testid^='bookList_item'] {
gap: 1rem;
align-items: center;
padding: 0.5rem 1rem;
cursor: pointer;
background-color: #fff;
border-bottom: 1px solid #ccc;
transition: transform 0.3s, background-color 0.3s ease;
}

li[data-testid*='bookList_item']:nth-child(even) {
background-color: #f2f2f2;
}

li[data-testid*='bookList_item']:hover {
background-color: #bcd0fcfe;
li[data-testid^='bookList_item']:hover {
background-color: #eaf3ff;
transform: scale(1.005);
}

/* ----- */

span[data-testid='book_index'] {
text-align: center;
min-width: 2rem;
Expand All @@ -35,8 +37,7 @@ div[data-testid='book_title_and_author_wrapper'] {
overflow: hidden;
}

/* -----title */

/* Title */
div[data-testid='book_title_wrapper'] {
white-space: nowrap;
overflow: hidden;
Expand All @@ -54,8 +55,7 @@ span[data-testid='book_title']::after {
content: '"';
}

/* -----author */

/* Author */
div[data-testid='book_author_wrapper'] {
white-space: nowrap;
overflow: hidden;
Expand All @@ -65,31 +65,49 @@ div[data-testid='book_author_wrapper'] {
span[data-testid='book_author'] {
padding-left: 0.5rem;
font-weight: bold;
transition: color 0.2s ease;
}

/* -----action buttons */
span[data-testid='book_author']:hover {
color: #007bff;
}

/* Action Buttons */
div[data-testid='book_actions'] {
display: flex;
justify-content: center;
align-items: center;
}

button[data-testid^='book_favorite_toggle_'] {
min-width: 0;
padding: 0.5rem;
border: 1px solid #fca510;
background-color: #fff;
transition: background-color 0.3s ease;
}

button[data-testid='book_favorite_toggle_false']:hover,
button[data-testid='book_favorite_toggle_true']:hover {
background-color: #f6dbabfd;
}

[data-testid='book_favorite_icon'],
[data-testid='book_nonFavorite_icon'] {
width: 2.5rem;
height: 2.5rem;
margin: 0rem 1rem;
cursor: pointer;
transition: color 0.3s, transform 0.3s ease-in-out;
color: #fca510;
transition: transform 0.2s, color 0.2s ease;
}

[data-testid='book_favorite_icon']:hover,
[data-testid='book_nonFavorite_icon']:hover {
transform: scale(1.3);
transform: scale(1.1);
color: #e69303;
}

/* Delete Button */
button[data-testid='delete_book_btn'] {
margin: 0 0.5rem;
background-color: #fff;
Expand Down
19 changes: 13 additions & 6 deletions src/components/BookListSection/book/Book.jsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { TbStar } from 'react-icons/tb'
import { TbStarFilled } from 'react-icons/tb'
import './Book.css'
import Button from '../../common/button/Button'
import './Book.css'

const Book = ({
index,
Expand All @@ -10,10 +10,11 @@ const Book = ({
bookAuthor,
onHandleDeleteBook,
onToggleFavoriteBook,
onClick,
'data-testid': testId,
}) => {
return (
<li data-testid={testId}>
<li onClick={onClick} data-testid={testId}>
<span data-testid="book_index">{++index}.</span>
<div data-testid="book_title_and_author_wrapper">
<div data-testid="book_title_wrapper">
Expand All @@ -25,19 +26,25 @@ const Book = ({
</div>
</div>
<div data-testid="book_actions">
<span
onClick={() => onToggleFavoriteBook(book.id)}
<Button
onClick={(e) => {
e.stopPropagation()
onToggleFavoriteBook(book.id)
}}
data-testid={`book_favorite_toggle_${book.isFavorite}`}
>
{book.isFavorite ? (
<TbStarFilled data-testid="book_favorite_icon" />
) : (
<TbStar data-testid="book_nonFavorite_icon" />
)}
</span>
</Button>
<Button
text="Delete"
onClick={() => onHandleDeleteBook(book.id)}
onClick={(e) => {
e.stopPropagation()
onHandleDeleteBook(book.id)
}}
data-testid="delete_book_btn"
></Button>
</div>
Expand Down
107 changes: 107 additions & 0 deletions src/components/BookListSection/bookListModal/BookListModal.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
div[data-testid='bookListModal_body'] {
display: flex;
flex-direction: column;
padding: 1rem;
padding-bottom: 5rem;
overflow-y: auto;
gap: 1rem;
}

/* Book Item */
div[data-testid^='bookListModal_book_item'] {
display: flex;
flex-direction: column;
align-items: stretch;
border-bottom: 1px solid #ddd;
gap: 1rem;
padding: 1rem;
}

div[data-testid='bookListModal_book_content'] {
width: 100%;
display: flex;
gap: 1rem;
}

div[data-testid='bookListModal_left_content'] {
display: flex;
align-items: flex-start;
justify-content: center;
width: 30%;
}

img[data-testid='bookListModal_book_img'] {
min-width: 70%;
max-height: 100%;
padding-top: 3rem;
}

div[data-testid='bookListModal_right_content'] {
width: 70%;
display: flex;
flex-direction: column;
justify-content: space-between;
gap: 2rem;
padding: 2rem;
}

div[data-testid='bookListModal_add_book_wrapper'] {
flex-grow: 1;
display: flex;
align-items: flex-end;
justify-content: flex-start;
}

/* Actions Wrapper */
div[data-testid='bookListModal_actions_wrapper'] {
display: flex;
align-items: center;
column-gap: 1rem;
}

/* Favorite Toggle Button */
button[data-testid^='bookListModal_favorite_toggle_'] {
min-width: 0;
padding: 0.5rem;
border: 1px solid #fca510;
background-color: #fff;
/* transition: background-color 0.2s, transform 0.3s ease; */
}

button[data-testid^='bookListModal_favorite_toggle_false']:hover,
button[data-testid^='bookListModal_favorite_toggle_true']:hover {
background-color: #f6dbab;
}

/* Favorite Icon */
[data-testid='bookListModal_favorite_icon'],
[data-testid='bookListModal_nonFavorite_icon'] {
width: 2.5rem;
height: 2.5rem;
cursor: pointer;
transition: transform 0.2s, color 0.3s, ease;
color: #fca510;
}

[data-testid='bookListModal_favorite_icon']:hover,
[data-testid='bookListModal_nonFavorite_icon']:hover {
transform: scale(1.1);
color: #e69303;
}

button[data-testid='bookListModal_delete_btn'] {
margin: 0;
background-color: #fff;
color: red;
border: 1px solid red;
}

button[data-testid='bookListModal_delete_btn']:hover {
background-color: rgb(255, 204, 204);
}

button[data-testid='bookListModal_delete_btn']:disabled {
border: 1px solid darkgray;
color: fff;
background-color: darkgray;
}
Loading
Loading