├── src
├── App.css
├── index.css
├── components
│ ├── ui
│ │ ├── footer.jsx
│ │ ├── select.jsx
│ │ ├── navbar.jsx
│ │ ├── header.jsx
│ │ └── searchInput.jsx
│ └── book
│ │ ├── bookGrid.jsx
│ │ └── bookGridItem.jsx
├── main.jsx
├── App.jsx
└── constants
│ └── data.js
├── README.md
├── public
├── assets
│ ├── book.png
│ ├── logo.png
│ ├── star.svg
│ └── lws-logo-en.svg
└── vite.svg
├── postcss.config.js
├── tailwind.config.js
├── vite.config.js
├── .gitignore
├── .eslintrc.cjs
├── index.html
└── package.json
/src/App.css:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # 🤒 Hazrat Ali
2 |
3 | # 🤠 Software Engineering
4 |
5 |
--------------------------------------------------------------------------------
/src/index.css:
--------------------------------------------------------------------------------
1 | @tailwind base;
2 | @tailwind components;
3 | @tailwind utilities;
--------------------------------------------------------------------------------
/public/assets/book.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Hazrat-Ali9/Book-Filtering/HEAD/public/assets/book.png
--------------------------------------------------------------------------------
/public/assets/logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Hazrat-Ali9/Book-Filtering/HEAD/public/assets/logo.png
--------------------------------------------------------------------------------
/postcss.config.js:
--------------------------------------------------------------------------------
1 | export default {
2 | plugins: {
3 | tailwindcss: {},
4 | autoprefixer: {},
5 | },
6 | }
7 | // postcss config
--------------------------------------------------------------------------------
/tailwind.config.js:
--------------------------------------------------------------------------------
1 | /** @type {import('tailwindcss').Config} */
2 | export default {
3 | content: [
4 | "./index.html",
5 | "./src/**/*.{js,ts,jsx,tsx}",
6 | ],
7 | theme: {
8 | extend: {},
9 | },
10 | plugins: [],
11 | }
12 | // Tailwind Css
13 |
--------------------------------------------------------------------------------
/vite.config.js:
--------------------------------------------------------------------------------
1 | import { defineConfig } from 'vite'
2 | import react from '@vitejs/plugin-react'
3 | import tailwindcss from 'tailwindcss';
4 |
5 | // https://vitejs.dev/config/
6 | export default defineConfig({
7 | plugins: [react(),tailwindcss()],
8 | })
9 | // Vite Config
--------------------------------------------------------------------------------
/src/components/ui/footer.jsx:
--------------------------------------------------------------------------------
1 | const Footer = () => {
2 | return (
3 |
10 | )
11 | }
12 |
13 | export default Footer
14 |
15 | // footer
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Logs
2 | logs
3 | *.log
4 | npm-debug.log*
5 | yarn-debug.log*
6 | yarn-error.log*
7 | pnpm-debug.log*
8 | lerna-debug.log*
9 | # gitignore
10 | node_modules
11 | dist
12 | dist-ssr
13 | *.local
14 |
15 | # Editor directories and files
16 | .vscode/*
17 | !.vscode/extensions.json
18 | .idea
19 | .DS_Store
20 | *.suo
21 | *.ntvs*
22 | *.njsproj
23 | *.sln
24 | *.sw?
25 |
--------------------------------------------------------------------------------
/src/main.jsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import ReactDOM from "react-dom/client";
3 | import App from "./App.jsx";
4 | import { createBrowserRouter, RouterProvider } from "react-router-dom";
5 | import "./index.css";
6 | // Main js
7 | const router = createBrowserRouter([
8 | {
9 | path: "/",
10 | element: ,
11 | },
12 | ]);
13 |
14 | ReactDOM.createRoot(document.getElementById("root")).render(
15 |
16 |
17 |
18 | );
19 |
20 |
--------------------------------------------------------------------------------
/.eslintrc.cjs:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | root: true,
3 | env: { browser: true, es2020: true },
4 | extends: [
5 | 'eslint:recommended',
6 | 'plugin:react/recommended',
7 | 'plugin:react/jsx-runtime',
8 | 'plugin:react-hooks/recommended',
9 | ],
10 | ignorePatterns: ['dist', '.eslintrc.cjs'],
11 | parserOptions: { ecmaVersion: 'latest', sourceType: 'module' },
12 | settings: { react: { version: '18.2' } },
13 | plugins: ['react-refresh'],
14 | rules: {
15 | 'react-refresh/only-export-components': [
16 | 'warn',
17 | { allowConstantExport: true },
18 | ],
19 | },
20 | }
21 |
22 | // Eslintrc
--------------------------------------------------------------------------------
/src/App.jsx:
--------------------------------------------------------------------------------
1 | import "./App.css";
2 | import BookGrid from "./components/book/bookGrid";
3 | import Footer from "./components/ui/footer";
4 | import Header from "./components/ui/header";
5 | import Navbar from "./components/ui/navbar";
6 | // Apps js
7 | function App() {
8 | return (
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 | );
18 | }
19 |
20 | export default App;
21 |
22 | // Apps js
23 |
--------------------------------------------------------------------------------
/src/constants/data.js:
--------------------------------------------------------------------------------
1 | export const bookGridItems = [
2 | // Data JS
3 | {
4 | id: 1,
5 | date: "2012-04-23",
6 | title: "The Hobbit",
7 | image: "./assets/book.png",
8 | author: "J.R.R. Tolkien",
9 | price: 9.99,
10 | rating: 4,
11 | isFavourite: false,
12 | },
13 | {
14 | id: 2,
15 | date: "2016-04-23",
16 | title: "The Alchemist",
17 | image: "./assets/book.png",
18 | author: "Paulo Coelho",
19 | price: 5.99,
20 | rating: 5,
21 | isFavourite: false,
22 | },
23 | {
24 | id: 3,
25 | date: "2018-04-23",
26 | title: "The Secret Garden",
27 | image: "./assets/book.png",
28 | author: "Frances Hodgson Burnett",
29 | price: 7.99,
30 | rating: 3,
31 | isFavourite: false,
32 | },
33 | ];
34 |
35 |
--------------------------------------------------------------------------------
/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
14 | Book Finder
15 |
16 |
17 |
18 |
19 |
20 |
21 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "react",
3 | "private": true,
4 | "version": "0.0.0",
5 | "type": "module",
6 | "scripts": {
7 | "dev": "vite",
8 | "build": "vite build",
9 | "lint": "eslint . --ext js,jsx --report-unused-disable-directives --max-warnings 0",
10 | "preview": "vite preview"
11 | },
12 | "dependencies": {
13 | "react": "^18.2.0",
14 | "react-dom": "^18.2.0",
15 | "react-router-dom": "^6.21.3"
16 | },
17 | "devDependencies": {
18 | "@types/react": "^18.2.43",
19 | "@types/react-dom": "^18.2.17",
20 | "@vitejs/plugin-react": "^4.2.1",
21 | "autoprefixer": "^10.4.17",
22 | "eslint": "^8.55.0",
23 | "eslint-plugin-react": "^7.33.2",
24 | "eslint-plugin-react-hooks": "^4.6.0",
25 | "eslint-plugin-react-refresh": "^0.4.5",
26 | "postcss": "^8.4.33",
27 | "tailwindcss": "^3.4.1",
28 | "vite": "^5.0.8"
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/src/components/ui/select.jsx:
--------------------------------------------------------------------------------
1 | import { useSearchParams } from "react-router-dom";
2 | // Select
3 | const Select = () => {
4 |
5 | const [searchParams, setSearchParams] = useSearchParams();
6 | const sort = searchParams.get("sort");
7 | const search = searchParams.get("search");
8 |
9 | return (
10 |
23 | );
24 | };
25 |
26 | export default Select;
27 |
28 | // Select jsx
--------------------------------------------------------------------------------
/public/vite.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/components/book/bookGrid.jsx:
--------------------------------------------------------------------------------
1 | import { useEffect, useState } from "react";
2 | import BookGridItem from "./bookGridItem";
3 | import { bookGridItems } from "../../constants/data";
4 | import { useSearchParams } from "react-router-dom";
5 | // Books Grid
6 | const BookGrid = () => {
7 | const [books, setBooks] = useState(bookGridItems);
8 | const [searchParams] = useSearchParams();
9 | const sort = searchParams.get("sort") || "";
10 | const search = searchParams.get("search") || "";
11 |
12 | useEffect(() => {
13 | const filteredBooks = bookGridItems.filter((book) =>
14 | book.title.toLowerCase().includes(search)
15 | );
16 |
17 | // year_asc, year_desc, name_asc, name_desc
18 |
19 | if (sort === "year_asc") {
20 | filteredBooks.sort(
21 | (a, b) => new Date(a.date).getTime() - new Date(b.date).getTime()
22 | );
23 | }
24 |
25 | if (sort === "year_desc") {
26 | filteredBooks.sort(
27 | (a, b) => new Date(b.date).getTime() - new Date(a.date).getTime()
28 | );
29 | }
30 |
31 | if (sort === "name_asc") {
32 | filteredBooks.sort((a, b) => a.title.localeCompare(b.title));
33 | }
34 |
35 | if (sort === "name_desc") {
36 | filteredBooks.sort((a, b) => b.title.localeCompare(a.title));
37 | }
38 |
39 | setBooks(filteredBooks);
40 | }, [sort, search]);
41 |
42 | // decide what to render
43 | let content;
44 |
45 | if (books.length === 0) {
46 | content = No books found
;
47 | } else {
48 | content = books.map((book) => (
49 |
50 | ));
51 | }
52 |
53 | return (
54 |
55 | {content}
56 |
57 | );
58 | };
59 |
60 | export default BookGrid;
61 | // Book Grid
--------------------------------------------------------------------------------
/public/assets/star.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/components/ui/navbar.jsx:
--------------------------------------------------------------------------------
1 | // Navbar
2 | const Navbar = () => {
3 | return (
4 |
44 | );
45 | };
46 |
47 | export default Navbar;
48 | // Nabbar js
49 |
50 |
51 |
--------------------------------------------------------------------------------
/src/components/ui/header.jsx:
--------------------------------------------------------------------------------
1 | import { useSearchParams } from "react-router-dom";
2 | import SearchInput from "./searchInput";
3 | import Select from "./select";
4 | // Header file
5 | const Header = () => {
6 | const [searchParams, setSearchParams] = useSearchParams();
7 | const sort = searchParams.get("sort");
8 | const search = searchParams.get("search");
9 |
10 | const handleClear = () => {
11 | setSearchParams({ sort: "", search: "" });
12 | };
13 |
14 | return (
15 |
53 | );
54 | };
55 |
56 | export default Header;
57 | // Header js
--------------------------------------------------------------------------------
/src/components/ui/searchInput.jsx:
--------------------------------------------------------------------------------
1 | import { useState } from "react";
2 | import { useSearchParams } from "react-router-dom";
3 | // Search Item
4 | const SearchInput = () => {
5 | const [searchParams, setSearchParams] = useSearchParams();
6 | const [searchInput, setSearchInput] = useState("");
7 | const sort = searchParams.get("sort");
8 |
9 | const handleSubmit = (e) => {
10 | e.preventDefault();
11 | setSearchParams({ sort, search: searchInput.toLocaleLowerCase() });
12 | };
13 |
14 | const handleSearch = (e) => setSearchInput(e.target.value);
15 |
16 | return (
17 |
55 | );
56 | };
57 |
58 | export default SearchInput;
59 | // Search Input
--------------------------------------------------------------------------------
/src/components/book/bookGridItem.jsx:
--------------------------------------------------------------------------------
1 | /* eslint-disable react/prop-types */
2 | // Book Grid Item
3 | const BookGridItem = ({ setBooks, book }) => {
4 | const { id, title, author, price, rating, image, isFavourite } = book;
5 |
6 | const handleSetFavourite = () => {
7 | setBooks((prevBooks) =>
8 | prevBooks.map((prevBook) =>
9 | prevBook.id === id
10 | ? { ...prevBook, isFavourite: !prevBook.isFavourite }
11 | : prevBook
12 | )
13 | );
14 | };
15 |
16 | const favouriteBtnCls = isFavourite
17 | ? "bg-[#DC2954]/[14%] text-[#DC2954] hover:bg-[#DC2954]/[24%]"
18 | : "bg-[#1C4336]/[14%] text-[#1C4336] hover:bg-[#1C4336]/[24%]";
19 |
20 | return (
21 |
22 |
23 |

24 |
25 |
26 |
{title}
27 |
28 | By : {author}
29 |
30 |
31 |
${price}
32 |
33 |

34 |

35 |

36 |

37 |
({rating} Star)
38 |
39 |
40 |
41 |
42 |
59 |
79 |
80 |
81 |
82 | );
83 | };
84 |
85 | export default BookGridItem;
86 |
--------------------------------------------------------------------------------
/public/assets/lws-logo-en.svg:
--------------------------------------------------------------------------------
1 |
32 |
--------------------------------------------------------------------------------