From 5dd2fd7c5aa1d493b9ff6562b7be875104793b7e Mon Sep 17 00:00:00 2001 From: Valik Date: Sat, 6 Jan 2024 13:25:30 +0100 Subject: [PATCH 1/2] refactor(api): separate fetch functions for each endpoint --- src/js/bookAPI.js | 152 ++++++++++++++++++++++++------- src/js/fetchBookById.js | 4 +- src/js/fetchBooks.js | 4 +- src/js/fetchBooksByCategories.js | 4 +- src/js/fetchCategories.js | 4 +- src/js/localStorage.js | 4 +- 6 files changed, 129 insertions(+), 43 deletions(-) diff --git a/src/js/bookAPI.js b/src/js/bookAPI.js index e428753..127bbe0 100644 --- a/src/js/bookAPI.js +++ b/src/js/bookAPI.js @@ -3,55 +3,141 @@ import axios from 'axios'; import { Notify, Loading } from './notiflixConfig.js'; /** - * The base URL for the books API. + * Base URL for the Books API. * @type {string} */ -export const API_URL = 'https://books-backend.p.goit.global/books/'; +const API_URL = 'https://books-backend.p.goit.global/books/'; /** - * Fetches books from the specified API endpoint. + * Builds the complete URL for an API endpoint with optional query parameters. * - * @param {string} endpoint - The endpoint to fetch books from. - * @param {string} [selectedCategory=''] - Optional parameter for filtering books by category. - * @returns {Promise} A promise that resolves to an array of book objects or null if an error occurs. + * @param {string} endpoint - The API endpoint. + * @param {Object} params - Optional query parameters. + * @returns {string} The complete URL. */ -export const fetchBooks = async (endpoint, selectedCategory = '') => { +const buildURL = (endpoint, params = {}) => { + const queryString = Object.keys(params) + .map(key => `${key}=${params[key]}`) + .join('&'); + + return queryString ? `${API_URL}${endpoint}?${queryString}` : `${API_URL}${endpoint}`; +}; + +/** + * Fetches the list of categories from the Books API. + * + * @returns {Promise} A promise that resolves to an array of categories. + */ +export const fetchCategories = async () => { try { - // Display loading dots while fetching data. Loading.dots(); + const URL = buildURL('category-list'); + const response = await axios.get(URL); - // Construct the complete URL based on the endpoint and optional category. - const URL = selectedCategory - ? `${API_URL}${endpoint}?category=${selectedCategory}` - : `${API_URL}${endpoint}`; + // Check the HTTP status code and handle accordingly. + handleResponse(response); + + return response.data; + } catch (error) { + console.error('Error fetching categories:', error.message); + Notify.failure('Failed to fetch categories. Please try again later.'); + return null; + } finally { + Loading.remove(500); + } +}; - // Make a GET request using axios. +/** + * Fetches the list of top books from the Books API. + * + * @returns {Promise} A promise that resolves to an array of top books. + */ +export const fetchTopBooks = async () => { + try { + Loading.dots(); + const URL = buildURL('top-books'); const response = await axios.get(URL); // Check the HTTP status code and handle accordingly. - if (response.status === 200) { - // Return the data received in the response. - return response.data; - } else if (response.status === 404) { - // Handle 404 Not Found - console.error('Error fetching: Not Found'); - Notify.failure('Books not found. Please check your search criteria.'); - } else if (response.status === 500) { - // Handle 500 Internal Server Error - console.error('Error fetching: Internal Server Error'); - Notify.failure('Internal server error. Please try again later.'); - } else { - // Handle other status codes if needed - console.error('Error fetching: Unexpected status', response.status); - Notify.failure('An unexpected error occurred. Please try again later.'); - } + handleResponse(response); + + return response.data; } catch (error) { - // Log an error message if fetching fails and return null. - console.error('Error fetching:', error.message); - Notify.failure('Failed to fetch books. Please try again later.'); + console.error('Error fetching top books:', error.message); + Notify.failure('Failed to fetch top books. Please try again later.'); return null; } finally { - // Remove loading indicators Loading.remove(500); } }; + +/** + * Fetches books belonging to a specific category from the Books API. + * + * @param {string} category - The category to filter books. + * @returns {Promise} A promise that resolves to an array of books. + */ +export const fetchBooksByCategory = async category => { + try { + Loading.dots(); + const URL = buildURL('category', { category }); + const response = await axios.get(URL); + + // Check the HTTP status code and handle accordingly. + handleResponse(response); + return response.data; + } catch (error) { + console.error('Error fetching books by category:', error.message); + Notify.failure('Failed to fetch books by category. Please try again later.'); + return null; + } finally { + Loading.remove(500); + } +}; + +/** + * Fetches a specific book by its ID from the Books API. + * + * @param {string} id - The ID of the book. + * @returns {Promise} A promise that resolves to the book object. + */ +export const fetchBookById = async id => { + try { + Loading.dots(); + const URL = buildURL(id); + const response = await axios.get(URL); + + // Check the HTTP status code and handle accordingly. + handleResponse(response); + return response.data; + } catch (error) { + console.error('Error fetching book by ID:', error.message); + Notify.failure('Failed to fetch book by ID. Please try again later.'); + return null; + } finally { + Loading.remove(500); + } +}; + +/** + * Handles the API response based on HTTP status codes. + * + * @param {Object} response - The Axios response object. + */ +const handleResponse = response => { + if (response.status === 200) { + // Do nothing for successful response + } else if (response.status === 404) { + // Handle 404 Not Found + console.error('Error fetching: Not Found'); + Notify.failure('Books not found. Please check your search criteria.'); + } else if (response.status === 500) { + // Handle 500 Internal Server Error + console.error('Error fetching: Internal Server Error'); + Notify.failure('Internal server error. Please try again later.'); + } else { + // Handle other status codes if needed + console.error('Error fetching: Unexpected status', response.status); + Notify.failure('An unexpected error occurred. Please try again later.'); + } +}; diff --git a/src/js/fetchBookById.js b/src/js/fetchBookById.js index 84db613..0932713 100644 --- a/src/js/fetchBookById.js +++ b/src/js/fetchBookById.js @@ -1,4 +1,4 @@ -import { fetchBooks } from './bookAPI.js'; +import { fetchBookById } from './bookAPI.js'; import { modalContainer, toggleModal } from './modalHandler.js'; import { bookAddButtonHandler } from './handleBookAddition.js'; @@ -12,7 +12,7 @@ export const displayBookById = async bookId => { toggleModal(true); // Fetch book details using the provided bookId - const bookById = await fetchBooks(`${bookId}`); + const bookById = await fetchBookById(bookId); // Destructure book details const { _id, title, author, book_image, description, buy_links } = bookById; diff --git a/src/js/fetchBooks.js b/src/js/fetchBooks.js index 56ccb90..89d21b7 100644 --- a/src/js/fetchBooks.js +++ b/src/js/fetchBooks.js @@ -1,4 +1,4 @@ -import { fetchBooks } from './bookAPI.js'; +import { fetchTopBooks } from './bookAPI.js'; import { displayBookById } from './fetchBookById.js'; import { topBooksContainer, switchView } from './viewSwitcher.js'; @@ -12,7 +12,7 @@ export const displayTopBooks = async () => { switchView('topBooks'); // Fetch top books from the API. - const topBooks = await fetchBooks('top-books'); + const topBooks = await fetchTopBooks(); const placeholderImageURL = new URL('/src/images/placeholder.jpg', import.meta.url).href; diff --git a/src/js/fetchBooksByCategories.js b/src/js/fetchBooksByCategories.js index 3414c9d..4eaeb92 100644 --- a/src/js/fetchBooksByCategories.js +++ b/src/js/fetchBooksByCategories.js @@ -1,10 +1,10 @@ -import { fetchBooks } from './bookAPI.js'; +import { fetchBooksByCategory } from './bookAPI.js'; import { displayTopBooks } from './fetchBooks.js'; import { displayBookById } from './fetchBookById.js'; import { topBooksContainer, booksContainer, switchView } from './viewSwitcher.js'; const displayBooksByCategory = async categoryName => { - const booksByCategory = await fetchBooks('category', categoryName); + const booksByCategory = await fetchBooksByCategory(categoryName); if (!booksByCategory || !Array.isArray(booksByCategory)) { console.error('Invalid response from fetchBooks:', booksByCategory); diff --git a/src/js/fetchCategories.js b/src/js/fetchCategories.js index a3699cb..d3de3fa 100644 --- a/src/js/fetchCategories.js +++ b/src/js/fetchCategories.js @@ -1,4 +1,4 @@ -import { fetchBooks } from './bookAPI.js'; +import { fetchCategories } from './bookAPI.js'; /** * Displays book categories in the specified HTML container. @@ -11,7 +11,7 @@ const displayCategories = async () => { const categoriesContainer = document.querySelector('.category-list'); // Fetch book categories from the API. - const bookCategories = await fetchBooks('category-list'); + const bookCategories = await fetchCategories(); // Sorting categories alphabetically. bookCategories.sort((a, b) => a.list_name.localeCompare(b.list_name)); diff --git a/src/js/localStorage.js b/src/js/localStorage.js index 25c15e4..67f4310 100644 --- a/src/js/localStorage.js +++ b/src/js/localStorage.js @@ -1,9 +1,9 @@ -import { fetchBooks } from './bookAPI.js'; +import { fetchBookById } from './bookAPI.js'; //Funkcja dodająca książkę do localStorage export const addToLocalStorage = async bookId => { //wykonujemy zapytanie do serwera w celu pobrania danych o książce - const bookById = await fetchBooks(bookId); + const bookById = await fetchBookById(bookId); //sprawdzamy czy lacalStorage jest obsługiwane w bieżącej przeglądarce if (typeof Storage != 'undefined') { From bd6340ff94bc544c836c749703526a9d99e9d308 Mon Sep 17 00:00:00 2001 From: Valik Date: Sat, 6 Jan 2024 13:59:12 +0100 Subject: [PATCH 2/2] feat(ui): add function to generate book markup --- src/js/bookMarkup.js | 19 +++++++++++++++++++ src/js/fetchBooks.js | 28 +++------------------------- src/js/fetchBooksByCategories.js | 22 ++-------------------- 3 files changed, 24 insertions(+), 45 deletions(-) create mode 100644 src/js/bookMarkup.js diff --git a/src/js/bookMarkup.js b/src/js/bookMarkup.js new file mode 100644 index 0000000..9d37b4a --- /dev/null +++ b/src/js/bookMarkup.js @@ -0,0 +1,19 @@ +export const createBookMarkup = ({ _id, title, author, book_image }) => { + const placeholderImageURL = new URL('/src/images/placeholder.jpg', import.meta.url).href; + + return ` +
+
+ ${title} +
+
Quick View
+
+
+
${title}
+
${author}
+
+ `; +}; diff --git a/src/js/fetchBooks.js b/src/js/fetchBooks.js index 89d21b7..1eda954 100644 --- a/src/js/fetchBooks.js +++ b/src/js/fetchBooks.js @@ -1,6 +1,7 @@ import { fetchTopBooks } from './bookAPI.js'; import { displayBookById } from './fetchBookById.js'; import { topBooksContainer, switchView } from './viewSwitcher.js'; +import { createBookMarkup } from './bookMarkup.js'; /** * Displays top books in the specified HTML container. @@ -14,34 +15,11 @@ export const displayTopBooks = async () => { // Fetch top books from the API. const topBooks = await fetchTopBooks(); - const placeholderImageURL = new URL('/src/images/placeholder.jpg', import.meta.url).href; - // Generate markup for each top book category. const markup = topBooks .map(category => { - // Generate markup for each book within the category. - const booksMarkup = category.books - .map(({ title, author, book_image, _id }) => { - return ` -
-
- ${title} -
-
Quick View
-
-
-
${title}
-
${author}
-
- `; - }) - .join(''); - - // Combine category title, book markup, and "See more" button into a single category markup. + const booksMarkup = category.books.map(book => createBookMarkup(book)).join(''); + return `

${category.list_name}

diff --git a/src/js/fetchBooksByCategories.js b/src/js/fetchBooksByCategories.js index 4eaeb92..9b4f681 100644 --- a/src/js/fetchBooksByCategories.js +++ b/src/js/fetchBooksByCategories.js @@ -2,6 +2,7 @@ import { fetchBooksByCategory } from './bookAPI.js'; import { displayTopBooks } from './fetchBooks.js'; import { displayBookById } from './fetchBookById.js'; import { topBooksContainer, booksContainer, switchView } from './viewSwitcher.js'; +import { createBookMarkup } from './bookMarkup.js'; const displayBooksByCategory = async categoryName => { const booksByCategory = await fetchBooksByCategory(categoryName); @@ -13,27 +14,9 @@ const displayBooksByCategory = async categoryName => { switchView('category'); - const placeholderImageURL = new URL('/src/images/placeholder.jpg', import.meta.url).href; - const booksMarkup = booksByCategory .flat() - .map(book => { - return ` -
-
- ${book.title} -
-
Quick View
-
-
-
${book.title}
-
${book.author}
-
- `; - }) + .map(book => createBookMarkup(book)) .join(''); const categoryTitleMarkup = ` @@ -52,7 +35,6 @@ const displayBooksByCategory = async categoryName => { books.forEach(book => { book.addEventListener('click', () => { const bookId = book.dataset.bookId; - console.log('Displaying book with ID:', bookId); displayBookById(bookId); }); });