├── .gitignore
├── Desktop-Version.PNG
├── mobile version.PNG
├── modules
├── mydate.js
├── variables.js
├── menu.js
└── bookClass.js
├── .hintrc
├── .eslintrc.json
├── .stylelintrc.json
├── js
└── index.js
├── package.json
├── MIT.md
├── .github
└── workflows
│ └── linters.yml
├── index.html
├── README.md
└── css
└── index.css
/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules/
2 |
--------------------------------------------------------------------------------
/Desktop-Version.PNG:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Chrissiku/Awesome-Books/HEAD/Desktop-Version.PNG
--------------------------------------------------------------------------------
/mobile version.PNG:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Chrissiku/Awesome-Books/HEAD/mobile version.PNG
--------------------------------------------------------------------------------
/modules/mydate.js:
--------------------------------------------------------------------------------
1 | import { DateTime } from '../luxon.js';
2 |
3 | const dateTime = DateTime.now().toFormat('LLL dd yyyy, hh:mm:ss a');
4 | document.getElementById('date').innerHTML = Date();
5 |
6 | export default dateTime;
7 |
--------------------------------------------------------------------------------
/.hintrc:
--------------------------------------------------------------------------------
1 | {
2 | "connector": {
3 | "name": "local",
4 | "options": {
5 | "pattern": ["**", "!.git/**", "!node_modules/**"]
6 | }
7 | },
8 | "extends": ["development"],
9 | "formatters": ["stylish"],
10 | "hints": [
11 | "button-type",
12 | "disown-opener",
13 | "html-checker",
14 | "meta-charset-utf-8",
15 | "meta-viewport",
16 | "no-inline-styles:error"
17 | ]
18 | }
--------------------------------------------------------------------------------
/modules/variables.js:
--------------------------------------------------------------------------------
1 | const bookTitle = document.querySelector('#book-title');
2 | const bookAuthor = document.querySelector('#book-author');
3 | const addBookBtn = document.querySelector('#add');
4 | const bookList = document.querySelector('#bookList');
5 |
6 | // Initialise the collection of books
7 |
8 | const collectBooks = JSON.parse(localStorage.getItem('books')) || [];
9 |
10 | export {
11 | bookTitle, bookAuthor, addBookBtn, bookList, collectBooks,
12 | };
13 |
--------------------------------------------------------------------------------
/.eslintrc.json:
--------------------------------------------------------------------------------
1 | {
2 | "env": {
3 | "browser": true,
4 | "es6": true,
5 | "jest": true
6 | },
7 | "parser": "babel-eslint",
8 | "parserOptions": {
9 | "ecmaVersion": 2018,
10 | "sourceType": "module"
11 | },
12 | "extends": ["airbnb-base"],
13 | "rules": {
14 | "no-shadow": "off",
15 | "no-param-reassign": "off",
16 | "eol-last": "off",
17 | "import/extensions": [ 1, {
18 | "js": "always", "json": "always"
19 | }]
20 | },
21 | "ignorePatterns": [
22 | "dist/",
23 | "build/"
24 | ]
25 | }
26 |
--------------------------------------------------------------------------------
/.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 |
--------------------------------------------------------------------------------
/modules/menu.js:
--------------------------------------------------------------------------------
1 | const myMenu = () => {
2 | const menu = document.querySelectorAll('.menu-item ');
3 | const contents = document.querySelectorAll('.page_content');
4 | // Display tabulation
5 | menu.forEach((menuItem, index) => {
6 | menuItem.addEventListener('click', () => {
7 | contents.forEach((content) => {
8 | content.classList.remove('active');
9 | });
10 | menu.forEach((menuItem) => {
11 | menuItem.classList.remove('active');
12 | });
13 | contents[index].classList.add('active');
14 | menu[index].classList.add('active');
15 | });
16 | });
17 | };
18 |
19 | export default myMenu;
20 |
--------------------------------------------------------------------------------
/js/index.js:
--------------------------------------------------------------------------------
1 | import myMenu from '../modules/menu.js';
2 | import {
3 | BookClass,
4 | bookTitle,
5 | bookAuthor,
6 | addBookBtn,
7 | // collectBooks,
8 | } from '../modules/bookClass.js';
9 | import myDateTime from '../modules/mydate.js';
10 |
11 | const collectionBooks = JSON.parse(localStorage.getItem('books')) || [];
12 |
13 | const myBookList = new BookClass();
14 |
15 | // Button to add new book to the collection
16 |
17 | addBookBtn.addEventListener('click', () => {
18 | myBookList.addBook();
19 | bookTitle.value = '';
20 | bookAuthor.value = '';
21 | localStorage.setItem('books', JSON.stringify(collectionBooks));
22 | myBookList.displayBooks();
23 | });
24 |
25 | window.addEventListener('DOMContentLoaded', () => {
26 | myBookList.displayBooks();
27 | });
28 |
29 | myMenu();
30 |
31 | document.getElementById('date').innerHTML = myDateTime;
32 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "devDependencies": {
3 | "babel-eslint": "^10.1.0",
4 | "eslint": "^7.32.0",
5 | "eslint-config-airbnb-base": "^14.2.1",
6 | "eslint-plugin-import": "^2.26.0",
7 | "hint": "^6.2.0",
8 | "stylelint": "^13.13.1",
9 | "stylelint-config-standard": "^21.0.0",
10 | "stylelint-csstree-validator": "^1.9.0",
11 | "stylelint-scss": "^3.21.0"
12 | },
13 | "name": "awesome-books",
14 | "description": "",
15 | "version": "1.0.0",
16 | "main": "index.js",
17 | "scripts": {
18 | "test": "echo \"Error: no test specified\" && exit 1"
19 | },
20 | "repository": {
21 | "type": "git",
22 | "url": "git+https://github.com/Chrissiku/Awesome-Books.git"
23 | },
24 | "keywords": [],
25 | "author": "",
26 | "license": "ISC",
27 | "bugs": {
28 | "url": "https://github.com/Chrissiku/Awesome-Books/issues"
29 | },
30 | "homepage": "https://github.com/Chrissiku/Awesome-Books#readme"
31 | }
32 |
--------------------------------------------------------------------------------
/MIT.md:
--------------------------------------------------------------------------------
1 | ## Copyright 2021, [YOUR NAME]
2 |
3 | ###### Please delete this line and the next one
4 | ###### APP TYPE can be a webpage/website, a web app, a software and so on
5 |
6 | Permission is hereby granted, free of charge, to any person obtaining a copy of this [APP TYPE] and associated documentation files, to deal in the [APP TYPE] without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the [APP TYPE], and to permit persons to whom the [APP TYPE] is furnished to do so, subject to the following conditions:
7 |
8 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the [APP TYPE].
9 |
10 | THE [APP TYPE] IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE [APP TYPE] OR THE USE OR OTHER DEALINGS IN THE [APP TYPE].
11 |
--------------------------------------------------------------------------------
/.github/workflows/linters.yml:
--------------------------------------------------------------------------------
1 | name: Linters
2 |
3 | on: pull_request
4 |
5 | env:
6 | FORCE_COLOR: 1
7 |
8 | jobs:
9 | lighthouse:
10 | name: Lighthouse
11 | runs-on: ubuntu-18.04
12 | steps:
13 | - uses: actions/checkout@v2
14 | - uses: actions/setup-node@v1
15 | with:
16 | node-version: "12.x"
17 | - name: Setup Lighthouse
18 | run: npm install -g @lhci/cli@0.7.x
19 | - name: Lighthouse Report
20 | run: lhci autorun --upload.target=temporary-public-storage --collect.staticDistDir=.
21 | webhint:
22 | name: Webhint
23 | runs-on: ubuntu-18.04
24 | steps:
25 | - uses: actions/checkout@v2
26 | - uses: actions/setup-node@v1
27 | with:
28 | node-version: "12.x"
29 | - name: Setup Webhint
30 | run: |
31 | npm install --save-dev hint@6.x
32 | [ -f .hintrc ] || wget https://raw.githubusercontent.com/microverseinc/linters-config/master/html-css-js/.hintrc
33 | - name: Webhint Report
34 | run: npx hint .
35 | stylelint:
36 | name: Stylelint
37 | runs-on: ubuntu-18.04
38 | steps:
39 | - uses: actions/checkout@v2
40 | - uses: actions/setup-node@v1
41 | with:
42 | node-version: "12.x"
43 | - name: Setup Stylelint
44 | run: |
45 | npm install --save-dev stylelint@13.x stylelint-scss@3.x stylelint-config-standard@21.x stylelint-csstree-validator@1.x
46 | [ -f .stylelintrc.json ] || wget https://raw.githubusercontent.com/microverseinc/linters-config/master/html-css-js/.stylelintrc.json
47 | - name: Stylelint Report
48 | run: npx stylelint "**/*.{css,scss}" --fix
49 | eslint:
50 | name: ESLint
51 | runs-on: ubuntu-18.04
52 | steps:
53 | - uses: actions/checkout@v2
54 | - uses: actions/setup-node@v1
55 | with:
56 | node-version: "12.x"
57 | - name: Setup ESLint
58 | run: |
59 | npm install --save-dev eslint@7.x eslint-config-airbnb-base@14.x eslint-plugin-import@2.x babel-eslint@10.x
60 | [ -f .eslintrc.json ] || wget https://raw.githubusercontent.com/microverseinc/linters-config/master/html-css-js/.eslintrc.json
61 | - name: ESLint Report
62 | run: npx eslint . --fix
63 |
--------------------------------------------------------------------------------
/modules/bookClass.js:
--------------------------------------------------------------------------------
1 | // Declaration of my items in he DOM
2 | const bookTitle = document.querySelector('#book-title');
3 | const bookAuthor = document.querySelector('#book-author');
4 | const addBookBtn = document.querySelector('#add');
5 | const bookList = document.querySelector('#bookList');
6 |
7 | // // Initialise the collection of books
8 |
9 | let collectBooks = JSON.parse(localStorage.getItem('books')) || [];
10 |
11 | // Display the current date
12 |
13 | // Create a class to store books
14 | class BookClass {
15 | // Create class constructor
16 |
17 | Constructor(title, author) {
18 | this.bookTitle = title;
19 | this.bookAuthor = author;
20 | }
21 |
22 | // Add the method to add new book to the localStorage
23 | addBook() {
24 | this.book = {};
25 | this.book.title = bookTitle.value;
26 | this.book.author = bookAuthor.value;
27 | collectBooks.push(this.book);
28 | }
29 |
30 | // Add the method to remove a given book to the localStorage
31 |
32 | remove(element) {
33 | this.bookId = element.target.id;
34 | this.bookToDelete = collectBooks[this.bookId - 1];
35 | this.freshCollection = collectBooks.filter(
36 | (book) => book !== this.bookToDelete,
37 | );
38 | collectBooks = this.freshCollection;
39 | localStorage.setItem('books', JSON.stringify(this.freshCollection));
40 | element.target.parentElement.remove();
41 | }
42 |
43 | // Add the method to display the whole localStorage
44 |
45 | displayBooks() {
46 | bookList.innerHTML = '';
47 | collectBooks.forEach((element, index) => {
48 | // Define all neccessary items
49 |
50 | const parentContainer = document.createElement('div');
51 | parentContainer.classList.add('book-card');
52 | const titleContainer = document.createElement('span');
53 | const authorContainer = document.createElement('span');
54 | const removeButton = document.createElement('button');
55 | removeButton.classList.add('btn');
56 | const bookInfos = document.createElement('p');
57 | bookInfos.classList.add('book-infos');
58 | removeButton.innerText = 'Remove';
59 |
60 | // Remove book button on Click
61 |
62 | removeButton.addEventListener('click', (e) => {
63 | this.remove(e);
64 | });
65 |
66 | removeButton.setAttribute('id', index + 1);
67 | titleContainer.innerText = `'' ${element.title} '' by `;
68 | authorContainer.innerText = element.author;
69 | bookInfos.appendChild(titleContainer);
70 | bookInfos.appendChild(authorContainer);
71 | parentContainer.appendChild(bookInfos);
72 | parentContainer.appendChild(removeButton);
73 |
74 | bookList.append(parentContainer);
75 | });
76 | }
77 | }
78 |
79 | export {
80 | BookClass, bookTitle, bookAuthor, addBookBtn, bookList,
81 | };
82 |
--------------------------------------------------------------------------------
/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | Awesome Books
8 |
9 |
16 |
17 |
18 |
19 |
20 |
36 |
37 |
38 | The Date Here
39 |
40 |
41 |
All awesome Books
42 |
43 |
44 |
45 |
46 |
Add a new Book
47 |
48 |
67 |
68 |
69 |
Contact information
70 |
87 |
88 |
89 |
90 |
93 |
94 |
95 |
96 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | 
2 |
3 | # Project Name : Awesome Books
4 |
5 | > Awesome Books is a basic website that allows users to add/remove books from a list. This project is achieved by JavaScript objects and arrays. But also I dynamically modify the DOM and add basic events.
6 | >
7 | >
8 | > In order to achieve this project I used some resources and technologies listed below :
9 |
10 |
11 |
12 |
13 |
14 | | On Desktop | On Mobile |
15 | | --------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
16 | |
|
|
17 |
18 |
19 |
20 | ## Built With
21 |
22 | - Major languages : JavaScript Object & HTLM
23 | - Frameworks : none
24 | - Technologies used : Github, Git and Linters
25 |
26 | ## Live Demo
27 |
28 | > Click here for the [demo](https://chrissiku.github.io/Awesome-Books/)
29 |
30 | ## Getting Started
31 |
32 | To get a local copy up and running follow these simple example steps.
33 |
34 | ### Prerequisites ♻️
35 |
36 | - A personal computer connected to the internet;
37 | - Have a working and updated browser.
38 | - Have a local version control like git installed on your computer
39 | - Have an account on Github, as an online/remote Version Control System
40 | - Code editor (Visual studio code, sublime text, notepad++, ...) installed on your computer;
41 | - At least basic of HTML and CSS.
42 |
43 | ### Setup 🎰
44 |
45 | - Click the [me](https://github.com/Chrissiku/Awesome-Books) and you'll land on the pure repository;
46 | - click on the green (🟢) button with `Code` Inscription;
47 | - You can choose to download the project or just clone it if you are familiar with Git and Github;
48 | - Follow normal step once download in your computer and use it as it's yours.
49 |
50 | ### Install
51 |
52 | - If you are familiar with git, Run `npm install` to get all dependecies required to run the linters checks, otherwise use the downloaded project on your computer without testing files
53 |
54 | ### Run test 🧪
55 |
56 | > Run thes command bellow inside your `git bash` or command line interface.
57 |
58 | - `npx hint .` : for the `html` codebase and use of best practices.
59 | - `npx stylelint "**/*.{css,scss}"` : for the `css` codebase and use of best practices. you can add
60 | - `npx eslint .` : for the `JavaScript` codebase and use of best practices.
61 | - > Note that you can add `--fix` keyword to the command for `Css` and/or `JavaScript` to make sure that you avoid all errors comming from your `code editor`
62 |
63 | ## Author
64 |
65 | 👤 **Chris Siku (Main Author)**
66 |
67 | - GitHub: [@githubhandle](https://github.com/Chrissiku)
68 | - Twitter: [@twitterhandle](https://twitter.com/christian_siku)
69 | - LinkedIn: [LinkedIn](https://www.linkedin.com/in/chris-siku-4bb53b232/)
70 |
71 | 
72 |
73 |
74 | 👤 **Shaquille Ndunda (Coding partner)**
75 |
76 | - GitHub: [@githubhandle](https://github.com/shaqdeff)
77 | - Twitter: [@twitterhandle](https://twitter.com/shaquillendunda)
78 | - LinkedIn: [LinkedIn](https://www.linkedin.com/in/shaquille-ndunda-b13a95107/)
79 |
80 | 
81 |
82 | ## 🤝 Contributing
83 |
84 | Contributions, issues, and feature requests are welcome!
85 |
86 | Feel free to check the [issues page](../../issues/).
87 |
88 | ## Show your support
89 |
90 | Give a ⭐️ if you like this project!
91 |
92 | ## Acknowledgments
93 |
94 | - Microverse
95 | - Hat tip to anyone whose code was used.
96 |
97 | ## 📝 License
98 |
99 | This project is [MIT](./MIT.md) licensed.
100 |
--------------------------------------------------------------------------------
/css/index.css:
--------------------------------------------------------------------------------
1 | @import url("https://fonts.googleapis.com/css2?family=Montserrat+Alternates:ital,wght@0,400;0,500;0,700;1,800&display=swap");
2 |
3 | * {
4 | margin: 0;
5 | padding: 0;
6 | text-decoration: none;
7 | list-style: none;
8 | box-sizing: border-box;
9 | font-family: "Montserrat Alternates", sans-serif;
10 | color: #1d1c1c;
11 | }
12 |
13 | body {
14 | display: flex;
15 | flex-direction: column;
16 | align-items: center;
17 |
18 | /* align-items: center; */
19 | }
20 |
21 | header {
22 | position: fixed;
23 | width: 100%;
24 | display: flex;
25 | flex-direction: column;
26 | justify-content: space-between;
27 | align-items: center;
28 | gap: 10px;
29 | padding: 10px 25px;
30 | background: #dad5d5;
31 | box-shadow: 0 2px 10px rgba(82, 80, 80, 0.384);
32 | }
33 |
34 | header a.logo {
35 | background: rgba(133, 127, 167, 0.5);
36 | display: flex;
37 | justify-content: space-around;
38 | align-items: center;
39 | gap: 10px;
40 | padding: 18px;
41 | border-radius: 50px 0 50px 0;
42 | }
43 |
44 | header a.logo i {
45 | font-size: 35px;
46 | color: rgb(219, 101, 16);
47 | }
48 |
49 | header a.logo span {
50 | font-size: 15px;
51 | font-weight: 800;
52 | text-transform: uppercase;
53 | }
54 |
55 | header nav ul.menu {
56 | display: flex;
57 | justify-content: space-between;
58 | align-items: center;
59 | padding: 10px 0;
60 | cursor: pointer;
61 | }
62 |
63 | header nav ul.menu li {
64 | text-align: center;
65 | width: 100px;
66 | padding: 5px 5px;
67 | border-radius: 20px;
68 | }
69 |
70 | header nav ul.menu li a {
71 | font-weight: 500;
72 | font-size: 13px;
73 | }
74 |
75 | header nav ul.menu li.active {
76 | background: rgba(133, 127, 167, 0.5);
77 | }
78 |
79 | main .page_content .contact ul li {
80 | color: #000;
81 | font-weight: 500;
82 | }
83 |
84 | header nav ul.menu li:hover {
85 | background: rgba(155, 141, 236, 0.5);
86 | }
87 |
88 | main .page_content .contact ul li a {
89 | color: rgb(16, 30, 219);
90 | font-weight: 500;
91 | }
92 |
93 | header nav ul.menu li.active > a {
94 | color: #000;
95 | font-weight: 600;
96 | }
97 |
98 | main {
99 | display: flex;
100 | flex-direction: column;
101 | justify-content: center;
102 | align-items: stretch;
103 | gap: 20px;
104 | margin-top: 155px;
105 | padding: 10px;
106 | }
107 |
108 | main .date {
109 | text-align: center;
110 | }
111 |
112 | main .page h3 {
113 | text-align: center;
114 | font-size: 20px;
115 | }
116 |
117 | main .page_content {
118 | display: none;
119 | }
120 |
121 | .page_content.active {
122 | display: block;
123 | }
124 |
125 | main .page_content .book-list {
126 | margin: 10px;
127 | max-height: 50vh;
128 | padding: 20px 10px;
129 | overflow: hidden;
130 | overflow-y: scroll;
131 | box-shadow: 0 5px 6px rgba(0, 0, 0, 0.329);
132 | }
133 |
134 | main .page_content .book-list .book-card {
135 | display: flex;
136 | justify-content: space-between;
137 | padding: 10px;
138 | font-size: 15px;
139 | font-weight: 600;
140 | gap: 10px;
141 | }
142 |
143 | main .page_content .book-list .book-card:nth-child(odd) {
144 | background: #dad5d5;
145 | }
146 |
147 | main .page_content form button {
148 | width: 100px;
149 | border: 1px solid green;
150 | color: #000;
151 | background: rgba(9, 255, 0, 0.199);
152 | padding: 5px;
153 | }
154 |
155 | main .page_content form button:hover {
156 | background: rgba(0, 128, 0, 0.699);
157 | }
158 |
159 | main .page_content .book-list .book-card button {
160 | padding: 0 5px;
161 | color: #000;
162 | border: none;
163 | background: rgba(255, 0, 0, 0.199);
164 | height: 20px;
165 | align-self: end;
166 | }
167 |
168 | main .page_content form {
169 | display: flex;
170 | flex-direction: column;
171 | justify-content: center;
172 | align-items: center;
173 | gap: 20px;
174 | margin-top: 20px;
175 | }
176 |
177 | main .page_content form input {
178 | padding: 5px;
179 | width: 280px;
180 | }
181 |
182 | main .page_content .contact {
183 | display: flex;
184 | flex-direction: column;
185 | justify-content: center;
186 | align-items: center;
187 | gap: 20px;
188 | margin-top: 20px;
189 | }
190 |
191 | main .page_content .contact ul {
192 | align-self: center;
193 | max-width: 300px;
194 | }
195 |
196 | main .page_content .contact .user {
197 | margin-top: 10px;
198 | font-weight: 600;
199 | }
200 |
201 | footer {
202 | position: fixed;
203 | width: 100%;
204 | height: 40px;
205 | display: flex;
206 | padding-left: 30px;
207 | align-items: center;
208 | background: #dad5d5;
209 | box-shadow: 0 2px 10px rgba(82, 80, 80, 0.384);
210 | bottom: 0;
211 | }
212 |
213 | @media only screen and (min-width: 768px) {
214 | header {
215 | flex-direction: row;
216 | justify-content: space-around;
217 | }
218 |
219 | main {
220 | margin-top: 95px;
221 | width: 80%;
222 | }
223 | }
224 |
--------------------------------------------------------------------------------