├── 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 |
10 |
11 |
12 | Bookstore CMS
13 | BOOKS
14 | CATEGORIES
15 |
16 |
17 |
18 |
19 |
20 |
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 | You need to enable JavaScript to run this app.
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 |
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 |
16 | {books.map((book) => (
17 |
18 |
19 |
20 |
{book.category}
21 | {book.title}
22 | {book.author}
23 |
24 |
25 | Comments
26 | {
30 | dispatch(removeBook(book.item_id));
31 | dispatch(removeBookFromApi(book.item_id));
32 | }}
33 | >
34 | Remove Book
35 |
36 | Edit
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 | {Math.floor(Math.random() * 100)}
46 | %
47 |
48 | Completed
49 |
50 |
51 |
52 |
CURRENT CHAPTER
53 | Chapter 17
54 | UPDATE PROGRESS
55 |
56 |
57 |
58 | ))}
59 |
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 |
--------------------------------------------------------------------------------