Skip to content
Open
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
24 changes: 24 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,12 +31,36 @@

1. How long did you spend on the coding?

2.5 hours

2. What would you add to your solution if you had more time?

- Implement navigation to allow users to browse all the books in the JSON file, with controls to navigate
between pages.
- Add an option for the user to select how many books to dislay per page and display full JSON file.
- Add a search feature to filter books by author or title.
- Make the "READ MORE" button functional by displaying additional details from the JSON file, such as language, country, and year of publication.

3. Share a code snippet that you are proud of and explain what it does

```js
// Load initial state from localStorage or set to an empty array
const [favourites, setFavourites] = useState(() => {
const stored = localStorage.getItem("favourites");
return stored ? JSON.parse(stored) : [];
});

// Sync favourites with localStorage whenever it changes
useEffect(() => {
localStorage.setItem("favourites", JSON.stringify(favourites));
}, [favourites]);
```
After implementing the core functionality, I noticed that the favourites list was reset on page reload. To address this, I used localStorage to persist the favourites state and ensure data remains available across sessions.


4. How would you track down a performance issue in production? Have you ever had to do this?

I have not but I'm eager to learn more about it!
---

# Comments
Expand Down
15 changes: 15 additions & 0 deletions app/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,26 @@
<head>
<meta charset="UTF-8" />
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
<link
href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.5/dist/css/bootstrap.min.css"
rel="stylesheet"
integrity="sha384-SgOJa3DmI69IUzQ2PVdRZhwQ+dy64/BUtbMJw1MZ8t5HZApcHrRKUc4W0kG879m7"
crossorigin="anonymous"
/>
<link
rel="stylesheet"
href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.10.5/font/bootstrap-icons.css"
/>
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Vite + React</title>
</head>
<body>
<div id="root"></div>
<script type="module" src="/src/main.jsx"></script>
<script
src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.5/dist/js/bootstrap.bundle.min.js"
integrity="sha384-k6d4wzSIapyDyv1kpU366/PK5hCdSbCRGRCMv+eplOQJWyd1fbcAu9OCUj5zNLiq"
crossorigin="anonymous"
></script>
</body>
</html>
8 changes: 8 additions & 0 deletions app/src/App.css
Original file line number Diff line number Diff line change
@@ -1,3 +1,11 @@
.App {
text-align: center;
}

.custom-background {
background-color: #efeeeb;
}

.custom-shadow {
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
}
35 changes: 23 additions & 12 deletions app/src/App.jsx
Original file line number Diff line number Diff line change
@@ -1,17 +1,28 @@
import './App.css'
import "./App.css";
import Books from "./components/Books";
import Favourites from "./components/Favourites";

function App() {

return (
<div className="App">
<h1>Hello CodeOper!</h1>

<h2>
Welcome to your technical assigment. Please, read carefully the
README.md file and follow the instructions.
</h2>

<div className="custom-background min-vh-100 py-4">
<div className="container">
<div className="row gx-4">
<div className="col-md-8">
<h4>Books</h4>
<div className="bg-light p-4 rounded h-100">
<Books />
</div>
</div>
<div className="col-md-4">
<h4>Favourites</h4>
<div className="bg-light p-4 rounded h-100">
<Favourites />
</div>
</div>
</div>
</div>
</div>
)
);
}

export default App
export default App;
39 changes: 39 additions & 0 deletions app/src/components/BookCard.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import React from "react";

// Reusable card component for both Books and Favourites
const BookCard = ({ book, onAction, iconClass }) => {
return (
<div className="card h-100 custom-shadow mb-4">
<div className="card-body d-flex flex-column h-100">

{/* Top section */}
<div>
<h6 className="text-muted">{book.author}</h6>
<h5>{book.title}</h5>
<p className="text-muted">{book.pages}</p>
<div style={{ height: "150px" }}>
<img
src={book.imageLink}
alt={book.title}
className="img-fluid"
style={{ maxHeight: "100%", objectFit: "contain" }}
/>
</div>
</div>

{/* Bottom row with buttons */}
<div className="d-flex align-items-center gap-4 mt-auto pt-3">
<button className="btn p-0">READ MORE</button>
<button
className="btn p-0 border-0 bg-transparent"
onClick={() => onAction(book)}
>
<i className={iconClass}></i>
</button>
</div>
</div>
</div>
);
};

export default BookCard;
41 changes: 41 additions & 0 deletions app/src/components/Books.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import React, { useEffect, useState } from "react";
import BookCard from "./BookCard";
import { useFavourites } from "./FavouritesContext";

// Books component to fetch and display the first 20 books
const Books = () => {
const [books, setBooks] = useState([]);
const { addToFavourites } = useFavourites();

// Fetch books from the JSON file
useEffect(() => {
const fetchBooks = async () => {
try {
const response = await fetch("/books.json");
const data = await response.json();
setBooks(data.slice(0, 20));
} catch (error) {
console.error("Error fetching books:", error);
setBooks([]);
}
};

fetchBooks();
}, []);

return (
<div className="row">
{books.map((book, index) => (
<div className="col-md-4 mb-4" key={index}>
<BookCard
book={book}
onAction = {addToFavourites}
iconClass="bi bi-plus-circle-fill text-success fs-5"
/>
</div>
))}
</div>
);
};

export default Books;
27 changes: 27 additions & 0 deletions app/src/components/Favourites.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import React from "react";
import BookCard from "./BookCard";
import { useFavourites } from "../components/FavouritesContext.jsx";

// Favourites component to display the list of favourite books
const Favourites = () => {
const { favourites, removeFromFavourites } = useFavourites();
return (
<div className="row">
{favourites.length === 0 ? (
<p className="text-muted">No favourites yet.</p>
) : (
favourites.map((book, index) => (
<div className="col-12 mb-3" key={index}>
<BookCard
book={book}
onAction={removeFromFavourites}
iconClass="bi bi-dash-circle-fill text-danger fs-5"
/>
</div>
))
)}
</div>
);
};

export default Favourites;
41 changes: 41 additions & 0 deletions app/src/components/FavouritesContext.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import { createContext, useContext, useState, useEffect} from "react";

// Create the context
const FavouritesContext = createContext();

// Custom hook to access the context in components
export const useFavourites = () => useContext(FavouritesContext);

// Provider component
export const FavouritesProvider = ({ children }) => {
// Load initial state from localStorage or set to an empty array
const [favourites, setFavourites] = useState(() => {
const stored = localStorage.getItem("favourites");
return stored ? JSON.parse(stored) : [];
});

// Synch favourites with localStorage whenever it changes
useEffect(() => {
localStorage.setItem("favourites", JSON.stringify(favourites));
}, [favourites]);

// Add a book to favourites, if it's not already there
const addToFavourites = (book) => {
if (!favourites.some((fav) => fav.title === book.title)) {
setFavourites([...favourites, book]);
}
};

// Remove a book from favourites, checking if it exists
const removeFromFavourites = (bookToRemove) => {
setFavourites(favourites.filter((book) => book.title !== bookToRemove.title));
};

return (
<FavouritesContext.Provider
value={{ favourites, addToFavourites, removeFromFavourites }}
>
{children}
</FavouritesContext.Provider>
);
};
19 changes: 11 additions & 8 deletions app/src/main.jsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,13 @@
import React from 'react'
import ReactDOM from 'react-dom/client'
import App from './App.jsx'
import './index.css'
import React from "react";
import ReactDOM from "react-dom/client";
import App from "./App.jsx";
import "./index.css";
import { FavouritesProvider } from "./components/FavouritesContext.jsx";

ReactDOM.createRoot(document.getElementById('root')).render(
ReactDOM.createRoot(document.getElementById("root")).render(
<React.StrictMode>
<App />
</React.StrictMode>,
)
<FavouritesProvider>
<App />
</FavouritesProvider>
</React.StrictMode>
);