├── public ├── favicon.ico ├── logo192.png ├── logo512.png ├── robots.txt ├── manifest.json └── index.html ├── .babelrc ├── src ├── components │ ├── CategoriesPage.jsx │ ├── HomePage.jsx │ ├── Navigation.jsx │ ├── BookForm.jsx │ └── BookList.jsx ├── redux │ ├── Store.js │ ├── Categories │ │ └── categoriesSlice.js │ └── Books │ │ └── bookSlice.js ├── App.js ├── index.js ├── logo.svg └── index.css ├── .gitignore ├── .stylelintrc.json ├── .eslintrc.json ├── LICENSE.md ├── package.json ├── .github └── workflows │ └── linters.yml └── README.md /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Naledi-Dikgale/bookstore/HEAD/public/favicon.ico -------------------------------------------------------------------------------- /public/logo192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Naledi-Dikgale/bookstore/HEAD/public/logo192.png -------------------------------------------------------------------------------- /public/logo512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Naledi-Dikgale/bookstore/HEAD/public/logo512.png -------------------------------------------------------------------------------- /public/robots.txt: -------------------------------------------------------------------------------- 1 | # https://www.robotstxt.org/robotstxt.html 2 | User-agent: * 3 | Disallow: 4 | -------------------------------------------------------------------------------- /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | "@babel/preset-react" 4 | ], 5 | "plugins": ["@babel/plugin-syntax-jsx"] 6 | } -------------------------------------------------------------------------------- /src/components/CategoriesPage.jsx: -------------------------------------------------------------------------------- 1 | function CategoriesPage() { 2 | return ( 3 |
4 |

Categories Page

5 |

Page under construction

6 |
7 | ); 8 | } 9 | 10 | export default CategoriesPage; 11 | -------------------------------------------------------------------------------- /src/components/HomePage.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import BookList from './BookList'; 3 | import BookForm from './BookForm'; 4 | 5 | function HomePage() { 6 | return ( 7 |
8 | 9 | 10 |
11 | ); 12 | } 13 | 14 | export default HomePage; 15 | -------------------------------------------------------------------------------- /src/redux/Store.js: -------------------------------------------------------------------------------- 1 | import { configureStore } from '@reduxjs/toolkit'; 2 | import bookReducer from './Books/bookSlice'; 3 | import categoriesReducer from './Categories/categoriesSlice'; 4 | 5 | const store = configureStore({ 6 | reducer: { 7 | books: bookReducer, 8 | categories: categoriesReducer, 9 | }, 10 | }); 11 | 12 | export default store; 13 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | /.pnp 6 | .pnp.js 7 | 8 | # testing 9 | /coverage 10 | 11 | # production 12 | /build 13 | 14 | # misc 15 | .DS_Store 16 | .env.local 17 | .env.development.local 18 | .env.test.local 19 | .env.production.local 20 | 21 | npm-debug.log* 22 | yarn-debug.log* 23 | yarn-error.log* 24 | -------------------------------------------------------------------------------- /src/redux/Categories/categoriesSlice.js: -------------------------------------------------------------------------------- 1 | import { createSlice } from '@reduxjs/toolkit'; 2 | 3 | const initialState = { 4 | categories: [], 5 | }; 6 | const categoriesSlice = createSlice({ 7 | name: 'categories', 8 | initialState, 9 | reducers: { 10 | setStatus: (state) => { 11 | state.status = 'under construction'; 12 | }, 13 | }, 14 | }); 15 | export const { setStatus } = categoriesSlice.actions; 16 | export default categoriesSlice.reducer; 17 | -------------------------------------------------------------------------------- /public/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "short_name": "React App", 3 | "name": "Create React App Sample", 4 | "icons": [ 5 | { 6 | "src": "favicon.ico", 7 | "sizes": "64x64 32x32 24x24 16x16", 8 | "type": "image/x-icon" 9 | }, 10 | { 11 | "src": "logo192.png", 12 | "type": "image/png", 13 | "sizes": "192x192" 14 | }, 15 | { 16 | "src": "logo512.png", 17 | "type": "image/png", 18 | "sizes": "512x512" 19 | } 20 | ], 21 | "start_url": ".", 22 | "display": "standalone", 23 | "theme_color": "#000000", 24 | "background_color": "#ffffff" 25 | } 26 | -------------------------------------------------------------------------------- /src/App.js: -------------------------------------------------------------------------------- 1 | import { BrowserRouter as Router, Routes, Route } from 'react-router-dom'; 2 | import HomePage from './components/HomePage'; 3 | import CategoriesPage from './components/CategoriesPage'; 4 | import Navigation from './components/Navigation'; 5 | 6 | function App() { 7 | return ( 8 | 9 |
10 | 11 | 12 | } /> 13 | } /> 14 | 15 |
16 |
17 | ); 18 | } 19 | 20 | export default App; 21 | -------------------------------------------------------------------------------- /.stylelintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": ["stylelint-config-standard"], 3 | "plugins": ["stylelint-scss", "stylelint-csstree-validator"], 4 | "rules": { 5 | "at-rule-no-unknown": [ 6 | true, 7 | { 8 | "ignoreAtRules": ["tailwind", "apply", "variants", "responsive", "screen"] 9 | } 10 | ], 11 | "scss/at-rule-no-unknown": [ 12 | true, 13 | { 14 | "ignoreAtRules": ["tailwind", "apply", "variants", "responsive", "screen"] 15 | } 16 | ], 17 | "csstree/validator": true 18 | }, 19 | "ignoreFiles": ["build/**", "dist/**", "**/reset*.css", "**/bootstrap*.css", "**/*.js", "**/*.jsx"] 20 | } 21 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDOM from 'react-dom/client'; 3 | import { Provider } from 'react-redux'; 4 | import store from './redux/Store'; 5 | import './index.css'; 6 | import App from './App'; 7 | 8 | const root = ReactDOM.createRoot(document.getElementById('root')); 9 | root.render( 10 | 11 | 12 | 13 | 14 | , 15 | ); 16 | 17 | // If you want to start measuring performance in your app, pass a function 18 | // to log results (for example: reportWebVitals(console.log)) 19 | // or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals 20 | -------------------------------------------------------------------------------- /src/components/Navigation.jsx: -------------------------------------------------------------------------------- 1 | import { MdPerson } from 'react-icons/md'; 2 | 3 | function Navigation() { 4 | const links = document.querySelectorAll('a'); 5 | links.forEach((link) => { 6 | link.addEventListener('click', () => link.classList.add('active-link')); 7 | }); 8 | return ( 9 | 21 | ); 22 | } 23 | 24 | export default Navigation; 25 | -------------------------------------------------------------------------------- /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "env": { 3 | "browser": true, 4 | "es6": true, 5 | "jest": true 6 | }, 7 | "parser": "@babel/eslint-parser", 8 | "parserOptions": { 9 | "ecmaFeatures": { 10 | "jsx": true 11 | }, 12 | "ecmaVersion": 2018, 13 | "sourceType": "module" 14 | }, 15 | "extends": ["airbnb", "plugin:react/recommended", "plugin:react-hooks/recommended"], 16 | "plugins": ["react"], 17 | "rules": { 18 | "react/jsx-filename-extension": ["warn", { "extensions": [".js", ".jsx"] }], 19 | "react/react-in-jsx-scope": "off", 20 | "import/no-unresolved": "off", 21 | "no-shadow": "off" 22 | }, 23 | "overrides": [ 24 | { 25 | // feel free to replace with your preferred file pattern - eg. 'src/**/*Slice.js' or 'redux/**/*Slice.js' 26 | "files": ["src/**/*Slice.js"], 27 | // avoid state param assignment 28 | "rules": { "no-param-reassign": ["error", { "props": false }] } 29 | } 30 | ], 31 | "ignorePatterns": [ 32 | "dist/", 33 | "build/" 34 | ] 35 | } 36 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 Naledi 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 12 | 13 | 17 | 18 | 27 | React App 28 | 29 | 30 | 31 |
32 | 42 | 43 | 44 | -------------------------------------------------------------------------------- /src/redux/Books/bookSlice.js: -------------------------------------------------------------------------------- 1 | import { createSlice, createAsyncThunk } from '@reduxjs/toolkit'; 2 | import axios from 'axios'; 3 | 4 | const BaseUrl = 'https://us-central1-bookstore-api-e63c8.cloudfunctions.net/bookstoreApi/apps/uMfxud1McryQAfRNygOp/books'; 5 | 6 | export const addBookToApi = createAsyncThunk('books/addBookToApi', async (book) => { 7 | const result = await axios.post(BaseUrl, book); 8 | return result.data; 9 | }); 10 | 11 | export const fetchBooks = createAsyncThunk('books/fetchBooks', async () => { 12 | const response = await axios.get(BaseUrl); 13 | const booksArray = Object.keys(response.data).map((key) => ({ 14 | item_id: key, 15 | ...response.data[key][0], 16 | })); 17 | return booksArray; 18 | }); 19 | 20 | export const removeBookFromApi = createAsyncThunk('books/removeBookFromApi', async (bookId) => { 21 | await axios.delete(`${BaseUrl}/${bookId}`); 22 | return bookId; 23 | }); 24 | 25 | const initialState = { 26 | books: [], 27 | loading: false, 28 | error: null, 29 | }; 30 | 31 | const bookSlice = createSlice({ 32 | name: 'books', 33 | initialState, 34 | reducers: { 35 | addBook: (state, action) => { 36 | state.books.push(action.payload); 37 | }, 38 | removeBook: (state, action) => { 39 | const newState = { ...state }; 40 | newState.books = state.books.filter((book) => book.item_id !== action.payload); 41 | return newState; 42 | }, 43 | }, 44 | extraReducers: { 45 | [fetchBooks.pending]: (state) => { 46 | state.loading = true; 47 | state.error = null; 48 | }, 49 | [fetchBooks.fulfilled]: (state, action) => { 50 | state.loading = false; 51 | state.books = action.payload; 52 | }, 53 | [fetchBooks.rejected]: (state, action) => { 54 | state.loading = false; 55 | state.error = action.error.message; 56 | }, 57 | }, 58 | }); 59 | 60 | export const { addBook, removeBook } = bookSlice.actions; 61 | export default bookSlice.reducer; 62 | -------------------------------------------------------------------------------- /src/components/BookForm.jsx: -------------------------------------------------------------------------------- 1 | import { useState } from 'react'; 2 | import { useDispatch, useSelector } from 'react-redux'; 3 | import { addBook, addBookToApi } from '../redux/Books/bookSlice'; 4 | 5 | const BookForm = () => { 6 | const [formState, setFormState] = useState({ title: '', author: '' }); 7 | const { books } = useSelector((state) => state.books); 8 | 9 | const handleChange = (event) => { 10 | const { name, value } = event.target; 11 | setFormState((prevState) => ({ 12 | ...prevState, 13 | [name]: value, 14 | item_id: `item${books.length + 1}`, 15 | })); 16 | }; 17 | 18 | const dispatch = useDispatch(); 19 | 20 | const handleSubmit = (event) => { 21 | event.preventDefault(); 22 | const { title, author } = formState; 23 | if (title.trim() !== '' && author.trim() !== '') { 24 | const formInput = { 25 | item_id: `item${books.length + 1}`, 26 | title: formState.title, 27 | author: formState.author, 28 | category: 'Action', 29 | }; 30 | dispatch(addBookToApi(formInput)); 31 | dispatch(addBook(formInput)); 32 | setFormState({ title: '', author: '' }); 33 | } 34 | }; 35 | 36 | return ( 37 |
38 |

ADD NEW BOOK

39 |
40 | 48 | 56 | 62 |
63 |
64 | ); 65 | }; 66 | 67 | export default BookForm; 68 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "bookstore", 3 | "version": "0.1.0", 4 | "private": true, 5 | "dependencies": { 6 | "@fortawesome/fontawesome-svg-core": "^6.4.0", 7 | "@fortawesome/free-regular-svg-icons": "^6.4.0", 8 | "@fortawesome/free-solid-svg-icons": "^6.4.0", 9 | "@fortawesome/react-fontawesome": "^0.2.0", 10 | "@reduxjs/toolkit": "^1.9.5", 11 | "@testing-library/jest-dom": "^5.16.5", 12 | "@testing-library/react": "^13.4.0", 13 | "@testing-library/user-event": "^13.5.0", 14 | "axios": "^1.4.0", 15 | "react": "^18.2.0", 16 | "react-circular-progressbar": "^2.1.0", 17 | "react-dom": "^18.2.0", 18 | "react-icons": "^4.8.0", 19 | "react-redux": "^8.0.5", 20 | "react-router-dom": "^6.11.0", 21 | "react-scripts": "5.0.1", 22 | "web-vitals": "^2.1.4" 23 | }, 24 | "scripts": { 25 | "start": "react-scripts start", 26 | "build": "react-scripts build", 27 | "test": "react-scripts test", 28 | "eject": "react-scripts eject" 29 | }, 30 | "eslintConfig": { 31 | "extends": [ 32 | "react-app", 33 | "react-app/jest" 34 | ] 35 | }, 36 | "browserslist": { 37 | "production": [ 38 | ">0.2%", 39 | "not dead", 40 | "not op_mini all" 41 | ], 42 | "development": [ 43 | "last 1 chrome version", 44 | "last 1 firefox version", 45 | "last 1 safari version" 46 | ] 47 | }, 48 | "devDependencies": { 49 | "@babel/core": "^7.21.8", 50 | "@babel/eslint-parser": "^7.21.8", 51 | "@babel/plugin-syntax-jsx": "^7.21.4", 52 | "@babel/preset-env": "^7.21.5", 53 | "@babel/preset-react": "^7.18.6", 54 | "eslint": "^7.32.0", 55 | "eslint-config-airbnb": "^18.2.1", 56 | "eslint-plugin-import": "^2.27.5", 57 | "eslint-plugin-jsx-a11y": "^6.7.1", 58 | "eslint-plugin-react": "^7.32.2", 59 | "eslint-plugin-react-hooks": "^4.6.0", 60 | "stylelint": "^13.13.1", 61 | "stylelint-config-standard": "^21.0.0", 62 | "stylelint-csstree-validator": "^1.9.0", 63 | "stylelint-scss": "^3.21.0" 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /.github/workflows/linters.yml: -------------------------------------------------------------------------------- 1 | name: Linters 2 | 3 | on: pull_request 4 | 5 | env: 6 | FORCE_COLOR: 1 7 | 8 | jobs: 9 | eslint: 10 | name: ESLint 11 | runs-on: ubuntu-22.04 12 | steps: 13 | - uses: actions/checkout@v3 14 | - uses: actions/setup-node@v3 15 | with: 16 | node-version: "18.x" 17 | - name: Setup ESLint 18 | run: | 19 | npm install --save-dev eslint@7.x eslint-config-airbnb@18.x eslint-plugin-import@2.x eslint-plugin-jsx-a11y@6.x eslint-plugin-react@7.x eslint-plugin-react-hooks@4.x @babel/eslint-parser@7.x @babel/core@7.x @babel/plugin-syntax-jsx@7.x @babel/preset-env@7.x @babel/preset-react@7.x 20 | [ -f .eslintrc.json ] || wget https://raw.githubusercontent.com/microverseinc/linters-config/master/react-redux/.eslintrc.json 21 | [ -f .babelrc ] || wget https://raw.githubusercontent.com/microverseinc/linters-config/master/react-redux/.babelrc 22 | - name: ESLint Report 23 | run: npx eslint "**/*.{js,jsx}" 24 | stylelint: 25 | name: Stylelint 26 | runs-on: ubuntu-22.04 27 | steps: 28 | - uses: actions/checkout@v3 29 | - uses: actions/setup-node@v3 30 | with: 31 | node-version: "18.x" 32 | - name: Setup Stylelint 33 | run: | 34 | npm install --save-dev stylelint@13.x stylelint-scss@3.x stylelint-config-standard@21.x stylelint-csstree-validator@1.x 35 | [ -f .stylelintrc.json ] || wget https://raw.githubusercontent.com/microverseinc/linters-config/master/react-redux/.stylelintrc.json 36 | - name: Stylelint Report 37 | run: npx stylelint "**/*.{css,scss}" 38 | nodechecker: 39 | name: node_modules checker 40 | runs-on: ubuntu-22.04 41 | steps: 42 | - uses: actions/checkout@v3 43 | - name: Check node_modules existence 44 | run: | 45 | if [ -d "node_modules/" ]; then echo -e "\e[1;31mThe node_modules/ folder was pushed to the repo. Please remove it from the GitHub repository and try again."; echo -e "\e[1;32mYou can set up a .gitignore file with this folder included on it to prevent this from happening in the future." && exit 1; fi 46 | -------------------------------------------------------------------------------- /src/logo.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/components/BookList.jsx: -------------------------------------------------------------------------------- 1 | import { useEffect } from 'react'; 2 | import { CircularProgressbar } from 'react-circular-progressbar'; 3 | import { useDispatch, useSelector } from 'react-redux'; 4 | import { removeBook, fetchBooks, removeBookFromApi } from '../redux/Books/bookSlice'; 5 | import 'react-circular-progressbar/dist/styles.css'; 6 | 7 | function BookList() { 8 | const dispatch = useDispatch(); 9 | const { books } = useSelector((store) => store.books); 10 | useEffect(() => { 11 | dispatch(fetchBooks()); 12 | }, [dispatch]); 13 | return ( 14 |
15 | 60 |
61 | ); 62 | } 63 | export default BookList; 64 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | # 📗 Table of Contents 4 | 5 | - [📖 About the Project](#about-project) 6 | - [🛠 Built With](#built-with) 7 | - [Video Walkthrough](#video-walkthrough) 8 | - [Tech Stack](#tech-stack) 9 | - [Key Features](#key-features) 10 | - [💻 Getting Started](#getting-started) 11 | - [Setup](#setup) 12 | - [Prerequisites](#prerequisites) 13 | - [Install](#install) 14 | - [Usage](#usage) 15 | - [Run tests](#run-tests) 16 | - [Deployment](#triangular_flag_on_post-deployment) 17 | - [👥 Authors](#authors) 18 | - [🔭 Future Features](#future-features) 19 | - [🤝 Contributing](#contributing) 20 | - [⭐️ Show your support](#support) 21 | - [🙏 Acknowledgements](#acknowledgements) 22 | - [❓ FAQ (OPTIONAL)](#faq) 23 | - [📝 License](#license) 24 | 25 | 26 | 27 | # 📖 Bookstore Project 28 | 29 | **Bookstore Project** Bookstore Project is an application that displays a list of books, allows the user to add and delete books. 30 | 31 | ## 🛠 Built With Create React App 32 | 33 | 34 | ### Key Features 35 | 36 | - **Project setup with Create React App** 37 | 38 |

(back to top)

39 | 40 | ## 💻 Getting Started 41 | 42 | Try to have these in your local machine 43 | 44 | - Git and GitHub 45 | - VS Code or any code editor 46 | 47 | ### Prerequisites 48 | 49 | In order to run this project you need 50 | - A web browser 51 | - Code editor 52 | 53 | ### Setup 54 | 55 | Clone this repository to your desired folder: 56 | 57 | git clone https://github.com/Naledi-Dikgale/bookstore-setup.git 58 | 59 | ### Install 60 | 61 | Install this project using the cloned repo or download and use the files to your local machine. 62 | 63 | ### Usage 64 | 65 | - Clone the repository first 66 | - Commnads to Run: 67 | - npm install 68 | - npm run build 69 | - npm start 70 | 71 | ### Run tests 72 | 73 | - tests are included using Jest framework 74 | - commands to run: npm run test 75 | 76 | ### Deployment 77 | 78 | You can deploy this project through Github Pages 79 | 80 |

(back to top)

81 | 82 | 83 | 84 | ## 👥 Authors 85 | 86 | 👤 **Naledi Dikgale** 87 | 88 | - GitHub: [@githubhandle](https://github.com/Naledi-Dikgale) 89 | - LinkedIn: [LinkedIn](https://www.linkedin.com/in/naledi-dikgale-068423159/) 90 | - Website: [@naledi_dikgale](https://naledi.netlify.app/) 91 | 92 | 93 | 94 |

(back to top)

95 | 96 | ## 🔭 Future Features 97 | 98 | - [ ] **Changes will be made as comming project requirement.** 99 | 100 | - [ ] **Full functionality and responsiveness.** 101 | 102 |

(back to top)

103 | 104 | 105 | 106 | ## 🤝 Contributing 107 | 108 | Contributions, issues, and feature requests are welcome! 109 | 110 | Feel free to check the [issues page](../../issues/). 111 | 112 |

(back to top)

113 | 114 | ## ⭐️ Show your support 115 | 116 | If you like this project give a star to this repositiory. 117 | 118 |

(back to top)

119 | 120 | ## 🙏 Acknowledgments 121 | 122 | - I would also like to thank Microverse for the guidance they made all the way up to this point. 123 | 124 |

(back to top)

125 | 126 | ## ❓ FAQ 127 | 128 | - **Is it allowed to copy the contents of this project and use it for personal use?** 129 | 130 | - This project is free for copying and reusing in any way you like. 131 | 132 | ## 📝 License 133 | 134 | This project is [MIT](./LICENSE.md) licensed. 135 | 136 |

(back to top)

137 | -------------------------------------------------------------------------------- /src/index.css: -------------------------------------------------------------------------------- 1 | :root { 2 | --azure: #07f; 3 | --lightgrey: #e8e8e8; 4 | --fontmont: "montserrat", sans-serif; 5 | --fontroboto: "robotoslab", sans-serif; 6 | --black: #121212; 7 | --dark: #777474; 8 | --white: #fff; 9 | --pale-grey: #f5f6fa; 10 | --lightblue: #4386bf; 11 | } 12 | 13 | * { 14 | margin: 0; 15 | padding: 0; 16 | box-sizing: border-box; 17 | } 18 | 19 | body { 20 | background-color: var(--pale-grey); 21 | } 22 | 23 | .addBook { 24 | padding: 0.801rem 10px; 25 | border-radius: 3px; 26 | background-color: #0290ff; 27 | text-transform: uppercase; 28 | color: var(--white); 29 | font-family: var(--fontroboto); 30 | font-size: 0.813rem; 31 | font-weight: bold; 32 | white-space: nowrap; 33 | } 34 | 35 | .buttons { 36 | display: flex; 37 | gap: 10px; 38 | } 39 | 40 | button { 41 | border: none; 42 | cursor: pointer; 43 | } 44 | 45 | .buttons button { 46 | font-family: var(--fontroboto); 47 | font-size: 0.875rem; 48 | font-weight: 300; 49 | background-color: var(--white); 50 | color: var(--lightblue); 51 | } 52 | 53 | .remove-book { 54 | border-right: 1px solid var(--azure); 55 | border-left: 1px solid var(--azure); 56 | padding: 0 10px; 57 | } 58 | 59 | nav { 60 | background-color: var(--white); 61 | width: 100vw; 62 | } 63 | 64 | .nav ul { 65 | margin: 0 auto; 66 | display: flex; 67 | justify-content: space-between; 68 | align-items: center; 69 | width: 80%; 70 | } 71 | 72 | .bookDisplay li { 73 | list-style-type: none; 74 | } 75 | 76 | .icon-container { 77 | border: 1px solid var(--lightgrey); 78 | border-radius: 50%; 79 | padding: 5px; 80 | width: 2.8rem; 81 | height: 2.8rem; 82 | display: flex; 83 | justify-content: center; 84 | align-items: center; 85 | } 86 | 87 | .icon { 88 | color: var(--azure); 89 | font-size: 1.8rem; 90 | } 91 | 92 | .nav ul li { 93 | list-style: none; 94 | padding: 20px 0; 95 | } 96 | 97 | .nav .logo { 98 | display: flex; 99 | align-items: center; 100 | gap: 10px; 101 | } 102 | 103 | .logo h1 { 104 | font-family: var(--fontmont); 105 | color: var(--azure); 106 | font-size: 1.875rem; 107 | font-weight: bold; 108 | } 109 | 110 | .nav ul li a { 111 | text-decoration: none; 112 | color: var(--dark); 113 | font-size: 0.813rem; 114 | font-weight: normal; 115 | } 116 | 117 | .nav ul li a:hover, 118 | .nav ul li a:active, 119 | .nav ul li a:focus { 120 | color: var(--black); 121 | } 122 | 123 | .book-title { 124 | text-transform: capitalize; 125 | font-family: var(--fontroboto); 126 | font-size: 1.375rem; 127 | font-weight: bold; 128 | font-stretch: normal; 129 | color: var(--black); 130 | } 131 | 132 | .bookDisplay { 133 | margin: 20px 0; 134 | } 135 | 136 | .book-wrapper { 137 | margin: 2rem 0; 138 | padding: 22px 15px; 139 | display: flex; 140 | justify-content: space-between; 141 | align-items: center; 142 | background-color: var(--white); 143 | } 144 | 145 | .category { 146 | opacity: 0.5; 147 | font-family: var(--fontmont); 148 | font-size: 0.875rem; 149 | font-weight: bold; 150 | text-transform: capitalize; 151 | margin-bottom: 7px; 152 | } 153 | 154 | .author { 155 | font-family: var(--fontroboto); 156 | font-size: 0.875rem; 157 | font-weight: 300; 158 | color: var(--lightblue); 159 | text-transform: capitalize; 160 | margin-bottom: 5px; 161 | height: 30px; 162 | } 163 | 164 | .book-container { 165 | width: 80%; 166 | margin: 0 auto; 167 | list-style: none; 168 | } 169 | 170 | .book-container form { 171 | display: flex; 172 | justify-content: space-between; 173 | gap: 10px; 174 | align-items: center; 175 | margin: 20px 0; 176 | padding: 20px 5px; 177 | } 178 | 179 | .book-container form input { 180 | padding: 0.813rem 10px; 181 | width: 50%; 182 | border-radius: 4px; 183 | border: solid 1px #e8e8e8; 184 | background-color: #fff; 185 | } 186 | 187 | .title { 188 | width: 70%; 189 | height: 30px; 190 | } 191 | 192 | .progress { 193 | display: flex; 194 | gap: 1em; 195 | justify-content: center; 196 | border-right: 1px solid var(--lightgrey); 197 | padding-right: 8em; 198 | } 199 | 200 | .circular { 201 | border-radius: 5%; 202 | width: 70px; 203 | height: 70px; 204 | } 205 | 206 | .percentage { 207 | font-family: var(--fontmont); 208 | font-size: 2rem; 209 | font-weight: lighter; 210 | color: var(--black); 211 | } 212 | 213 | .completed { 214 | opacity: 0.5; 215 | font-family: var(--fontmont); 216 | font-size: 0.875rem; 217 | font-weight: normal; 218 | } 219 | 220 | .progress-info { 221 | display: flex; 222 | flex-direction: column; 223 | justify-content: center; 224 | align-items: flex-start; 225 | } 226 | 227 | .update { 228 | padding-right: 3em; 229 | } 230 | 231 | .chapter { 232 | font-family: var(--fontroboto); 233 | font-weight: 300; 234 | color: var(--black); 235 | margin-bottom: 5px; 236 | opacity: 0.5; 237 | font-size: 0.813rem; 238 | } 239 | 240 | .chapter-title { 241 | font-family: var(--fontroboto); 242 | font-size: 1rem; 243 | font-weight: 300; 244 | font-stretch: normal; 245 | color: var(--black); 246 | margin-bottom: 15px; 247 | } 248 | 249 | .updateProgress { 250 | max-width: 11.5rem; 251 | height: 2.063rem; 252 | padding: 0.438rem 1.188rem 0.5rem 1.375rem; 253 | border-radius: 3px; 254 | background-color: var(--azure); 255 | color: var(--white); 256 | font-size: 12px; 257 | } 258 | 259 | .right-display { 260 | display: flex; 261 | } 262 | 263 | @media (max-width: 500px) { 264 | .right-display { 265 | display: flex; 266 | flex-direction: column; 267 | } 268 | 269 | .updateProgress { 270 | width: 100px; 271 | padding: 3px 0; 272 | } 273 | 274 | .chapter { 275 | font-size: 12px; 276 | 277 | /* font-weight: 200; */ 278 | } 279 | } 280 | --------------------------------------------------------------------------------