Bläddra i källkod

Module near finish

Gennadysht 2 år sedan
förälder
incheckning
8a8396b771

+ 170 - 100
js/lesson21_Module/Gql_promis copy.html

@@ -9,39 +9,54 @@
         crossorigin="anonymous"></script>
     <link rel="stylesheet" href="https://cdn.reflowhq.com/v2/toolkit.min.css">
     <link rel="stylesheet" href="index.css">
+    <style>
+        .alert-fixed {
+            position: fixed;
+            top: 0px;
+            left: 0px;
+            width: 100%;
+            z-index: 9999;
+            border-radius: 0px;
+        }
+    </style>
+    <div class="alert-fixed" id="alertsZone"></div>
 </head>
 
 <body>
-    <nav class="navbar navbar-expand-md">
-        <a class="navbar-brand" href="#">Logo</a>
-        <button class="navbar-toggler navbar-dark" type="button" data-toggle="collapse" data-target="#main-navigation">
-            <span class="navbar-toggler-icon"></span>
-        </button>
-        <div class="collapse navbar-collapse" id="main-navigation">
-            <ul class="navbar-nav">
-                <li id="loginLink" class="nav-item  d-none">
-                    <a class="nav-link" href="#/login/">Login</a>
-                </li>
-                <li id="regLink" class="nav-item d-none">
-                    <a class="nav-link" href="#/register/">Register</a>
-                </li>
-                <li id="logoutLink" class="nav-item d-none">
-                    <a class="nav-link" href="#/logout/">Logout</a>
-                </li>
-                <li class="nav-item">
-                    <a class="nav-link" href="#/cart/">Cart</a>
-                </li>
-                <li id="historyLink" class="nav-item  d-none">
-                    <a class="nav-link" href="#/orders/">History</a>
-                </li>
-            </ul>
-        </div>
-    </nav>
+    <div class="container-fluid">
+        <nav class="navbar navbar-expand-md">
+            <a class="navbar-brand" href="#"><img src="./Img/Logo.png" width="60px"></a>
+            <div class="collapse navbar-collapse" id="main-navigation">
+                <ul class="navbar-nav  align-items-center">
+                    <li id="loginLink" class="nav-item  d-none">
+                        <a class="nav-link" href="#/login/">Login</a>
+                    </li>
+                    <li id="regLink" class="nav-item d-none">
+                        <a class="nav-link" href="#/register/">Register</a>
+                    </li>
+                    <li id="logoutLink" class="nav-item d-none">
+                        <a class="nav-link" href="#/logout/">Logout</a>
+                    </li>
+                    <li id="cartLink" class="nav-item">
+                        <a class="nav-link" href="#/cart/">
+                            <i class="fa badge fa-lg" id="cartCountBadge" value=0>
+                                <img src="./Img/корзина.png" width="60px">
+                                <!--<span class="badge badge-light" id="cartCountBadge">0</span>-->
+                            </i>
+                        </a>
+                    </li>
+                    <li id="historyLink" class="nav-item  d-none">
+                        <a class="nav-link" href="#/orders/">History</a>
+                    </li>
+                </ul>
+            </div>
+        </nav>
+    </div>
 
-    <div class="container">
+    <div class="container-fluid">
         <div class="row">
-            <div class="col-2">
-                <div class="d-flex flex-column flex-shrink-0 p-3 text-white bg-dark">
+            <div class="col-2 left">
+                <div style="background-color: wheat;" class="d-flex flex-column flex-shrink-0 p-3 text-dark">
                     <ul id="categoriesList" class="nav nav-pills flex-column mb-auto">
                     </ul>
                 </div>
@@ -76,7 +91,12 @@
         </main>
     </div>-->
     <script type="text/javascript" src="./login.js"></script>
+    <script type="text/javascript" src="./alerts.js"></script>
+    <script type="text/javascript" src="./pagination.js"></script>
     <script>
+        const addErrorAlert = (error) => {
+            addAlert(alertsZone, error, 'warning');
+        }
         function jwtDecode(token) {                         // расщифровки токена авторизации
             if (!token || typeof token != "string")
                 return undefined;
@@ -88,7 +108,8 @@
                 let tokenJson = JSON.parse(tokenJsonStr);
                 return tokenJson;
             }
-            catch {
+            catch (error) {
+                addErrorAlert(error.message);
                 return undefined;
             }
         }
@@ -236,31 +257,45 @@
 
         function getGql(url) {
             return function gql(query, vars = undefined) {
-                let fetchSettings =
-                {
-                    method: "POST",
-                    headers:
+                try {
+                    let fetchSettings =
                     {
-                        "Content-Type": "application/json",
-                        "Accept": "application/json"
-                    },
-                    body: JSON.stringify(
+                        method: "POST",
+                        headers:
                         {
-                            query: query,
-                            variables: vars
-                        })
-                };
-                let authToken = window.localStorage.authToken;
-                if (authToken) {
-                    fetchSettings.headers["Authorization"] = `Bearer ${authToken}`;
+                            "Content-Type": "application/json",
+                            "Accept": "application/json"
+                        },
+                        body: JSON.stringify(
+                            {
+                                query: query,
+                                variables: vars
+                            })
+                    };
+                    let authToken = window.localStorage.authToken;
+                    if (authToken) {
+                        fetchSettings.headers["Authorization"] = `Bearer ${authToken}`;
+                    }
+                    return fetch(url, fetchSettings)
+                        .then(res => {
+                            try {
+                                if (!res.ok) {
+                                    addErrorAlert(res.statusText);
+                                    throw Error(res.statusText);
+                                }
+                                return res.json();
+                            }
+                            catch (error) {
+                                addErrorAlert(error.message);
+                                throw error;
+                            }
+
+                        });
+                }
+                catch (error) {
+                    addErrorAlert(error.message);
+                    throw error;
                 }
-                return fetch(url, fetchSettings)
-                    .then(res => {
-                        if (!res.ok) {
-                            throw Error(res.statusText);
-                        }
-                        return res.json();
-                    });
             }
         }
         const gql = getGql("http://shop-roles.node.ed.asmer.org.ua/graphql");
@@ -275,7 +310,7 @@
                     return payload //в месте запуска store.dispatch с этим thunk можно так же получить результат промиса
                 }
                 catch (error) {
-                    console.log(error);
+                    addErrorAlert(error.message);
                     dispatch(actionRejected(name, error)) //в случае ошибки - сигнализируем redux, что промис несложился
                 }
             }
@@ -341,15 +376,23 @@
         }
         const actionFullLogin = (login, password) => {
             return gqlFullLogin = async (dispatch) => {
-                delete localStorage.authToken;
-                //dispatch возвращает то, что вернул thunk, возвращаемый actionLogin, а там промис, 
-                //так как actionPromise возвращает асинхронную функцию
-                let promiseResult = dispatch((dispatch) => actionLogin(login, password));
-                let res = await promiseResult;
-                if (res && res.data) {
-                    let token = Object.values(res.data)[0];
-                    if (token && typeof token == 'string')
-                        return dispatch(actionAuthLogin(token));
+                try {
+                    delete localStorage.authToken;
+                    //dispatch возвращает то, что вернул thunk, возвращаемый actionLogin, а там промис, 
+                    //так как actionPromise возвращает асинхронную функцию
+                    let promiseResult = actionLogin(login, password);
+                    let res = await promiseResult;
+                    if (res && res.data) {
+                        let token = Object.values(res.data)[0];
+                        if (token && typeof token == 'string')
+                            return dispatch(actionAuthLogin(token));
+                        else
+                            addErrorAlert("User not found");
+                    }
+                }
+                catch (error) {
+                    addErrorAlert(error.message);
+                    throw error;
                 }
                 //проверьте что token - строка и отдайте его в actionAuthLogin
             }
@@ -369,11 +412,10 @@
                 //dispatch возвращает то, что вернул thunk, возвращаемый actionLogin, а там промис, 
                 //так как actionPromise возвращает асинхронную функцию
                 delete localStorage.authToken;
-                let promiseResult = dispatch(() => actionAuthUpsert(login, password));
+                let promiseResult = actionAuthUpsert(login, password);
                 let res = await promiseResult;
                 dispatch(actionFullLogin(login, password));
                 console.log(res)
-
                 //проверьте что token - строка и отдайте его в actionAuthLogin
             }
         }
@@ -400,6 +442,7 @@
                     let promiseResult = orderUpsert(order);
                     let res = await promiseResult;
                     if (res && res.errors && res.errors.length > 0) {
+                        addErrorAlert(res.errors[0].message);
                         throw res.errors[0];
                     }
                     dispatch(actionCartClear());
@@ -435,6 +478,14 @@
             ok(ms);
         }, ms));
         store.subscribe(() => {
+            let state = store.getState();
+            let cartItemsCount = 0;
+            if (state.cartReducer) {
+                let cartItems = Object.values(state.cartReducer);
+                for (item of  Object.values(state.cartReducer))
+                     cartItemsCount = item.count;
+            }
+            cartCountBadge.setAttribute("value", cartItemsCount);
             console.log({ state: store.getState() });
         });
         //////////////////////////////////////////////
@@ -470,7 +521,7 @@
             */
             let innerHtml = '';
             for (category of categories) {
-                innerHtml += `<li><a href="#/categories/${category._id}" class="nav-link text-white">${category.name}</a></li>`;
+                innerHtml += `<li><a href="#/categories/${category._id}" class="nav-link text-dark">${category.name}</a></li>`;
             }
             htmlEl.innerHTML = innerHtml;
         }
@@ -504,20 +555,8 @@
                     <div class="reflow-product-list">
                         <div id="productsList" class="ref-products">
                         </div>
-                        <ul class="pagination">
-                            <li class="page-item disabled"><a class="page-link" href="#"
-                                    tabindex="-1">Previous</a></li>
-                            <li class="page-item"><a class="page-link" href="#/prodpage/1">1</a></li>
-                            <li class="page-item active"><span class="page-link">2</span></li>
-                            <li class="page-item"><a class="page-link" href="#/prodpage/3">3</a></li>
-                            <li class="page-item"><a class="page-link" href="#/prodpage/4">4</a></li>
-                            <li class="page-item"><a class="page-link" href="#/prodpage/5">5</a></li>
-                            <li class="page-item"><a class="page-link" href="#/prodpage/6">6</a></li>
-                            <li class="page-item"><a class="page-link" href="#/prodpage/7">7</a></li>
-                            <li class="page-item"><a class="page-link" href="#/prodpage/8">8</a></li>
-                            <li class="page-item"><a class="page-link" href="#/prodpage/9">9</a></li>
-                            <li class="page-item"><a class="page-link" href="#">Next</a></li>
-                        </ul>
+                        <div id="productsListPagination">
+                        </div>
                     </div>
                 `;
 
@@ -525,13 +564,14 @@
             for (good of goods) {
                 addProductToList(good, productsList);
             }
+            new Pagination(productsList, productsListPagination, 5, listItemTag = 'div.ref-product')
         }
         const addProductToList = (good, htmlEl) => {
-            let outerDiv = document.createElement("div");
+            let outerDiv = document.createElement('div');
             const { name, _id, price, description, images } = good;
             outerDiv.innerHTML =
                 `
-                    <div id="good_${_id}" class="ref-product">
+                    <div id="good_${_id}" class="ref-product d-none">
                         <div class="ref-media"><img class="ref-image"
                                 src="${getFullImageUrl(good.images[0])}" loading="lazy" /></div>
                         <div class="ref-product-data">
@@ -576,9 +616,12 @@
             if (status === 'PENDING') {
                 htmlEl.innerHTML = `<img src='https://cdn.dribbble.com/users/63485/screenshots/1309731/infinite-gif-preloader.gif' />`
             }
-            if (status == "FULFILLED") {
+            else if (status == "FULFILLED") {
                 execFunc(payload, htmlEl);
             }
+            else if (status == "FULFILLED") {
+                addErrorAlert(error.message);
+            }
         }
 
         const getFullImageUrl = (image) =>
@@ -628,36 +671,49 @@
 
 
         const showOrder = (order, num, htmlEl) => {
-            let htmlContent = `<h1>Order: #${num}</h1>
+            let htmlContent = `<div class="order d-none"><h2>Order: #${num}</h2>
                                 <!--<div>Created on: ${order.createdAt}</div>-->
                                `;
-            htmlContent += `<section>Items:</section><br>`  //вставить css display block
+            htmlContent += `<h3>Items:</h3>`  //вставить css display block
             let orderGoods = Object.values(order.orderGoods);
             for (let i = 0; i < orderGoods.length; i++) {
                 let { order, count, price, total, good } = orderGoods[i];
-                htmlContent += `
-                    <div>
-                        <div>${i}.</div>
-                        <a href="#/goods/${good._id}">${good.name}</a>
-                        <div>Price: ${price}</div>
-                        <div>Count: ${count}</div>
-                        <div>Total: ${total}</div>
-                        <img width="170px" src="${getFullImageUrl(good.images[0])}"</img>
-                    </div>
-                    <br>`//вставить css display block
+                htmlContent +=
+                    `
+                        <div class="ref-product align-items-center">
+                            <strong class="ref-count">${i}.   </strong>
+                            <div class="ref-media"><img class="ref-image" width="170px" src="${getFullImageUrl(good.images[0])}"</img></div>
+                            <div class="ref-product-data">
+                                <div class="ref-product-info">
+                                    <h5 class="ref-name"><a href="#/goods/${good._id}">${good.name}</a></h5>
+                                </div>
+                                <strong class="ref-price">Price: ${price}</strong>
+                                <strong class="ref-count">Count: ${count}</strong>
+                                <strong class="ref-price">Total: ${total}</strong>
+                            </div>
+                        </div>
+                    `//вставить css display block
             }
-            htmlContent += `<div>Total ${order.total}</div><br>`;
+            htmlContent += `<h3 class="ref-count">Total ${order.total}</h3><hr/></div>`;
             htmlEl.innerHTML += htmlContent;
         }
         const showOrders = (orders, htmlEl) => {
-            main.innerHTML = "<header>Orders:</header>";
+            htmlEl.innerHTML = "<header>Orders:</header>";
             if (!localStorage?.authToken)
                 return;
             if (orders) {
-                for (let i = 0; i < orders.length; i++) {
-                    let order = orders[i];
-                    showOrder(order, i, htmlEl);
+                let ordersContainerDiv = document.createElement("div");
+                ordersContainerDiv.classList.add("container", "reflow-product-list");
+                let ordersDiv = document.createElement("div");
+                ordersContainerDiv.appendChild(ordersDiv);
+                ordersDiv.classList.add("ref-products");
+                for (let i = orders.length; i > 0; i--) {
+                    let order = orders[i - 1];
+                    showOrder(order, i, ordersDiv);
                 }
+                let paginationDiv = document.createElement("div");
+                htmlEl.append(ordersContainerDiv, paginationDiv);
+                new Pagination(ordersDiv, paginationDiv, 5, listItemTag = 'div.order')
             }
         }
         store.subscribe(() =>
@@ -711,7 +767,7 @@
                         main.innerText = '';
                         const form = new LoginForm(main);
                         form.onLogin = (login, password) => {
-                            store.dispatch(actionFullAuthUpsert(login, password));
+                            store.dispatch(actionFullLogin(login, password));
                         }
                         return false;
                     }
@@ -721,6 +777,21 @@
                     }
                     return true;
                 },
+                register() {
+                    if (!localStorage.authToken) {
+                        main.innerText = '';
+                        const form = new LoginForm(main);
+                        form.onLogin = (login, password) => {
+                            store.dispatch(actionFullAuthUpsert(login, password));
+                        }
+                        return false;
+                    }
+                    else if (route == "register") {
+                        window.location = "#/";
+                        window.location.reload();
+                    }
+                    return true;
+                },
                 logout() {
                     if (localStorage.authToken) {
                         store.dispatch(actionAuthLogout());
@@ -755,7 +826,6 @@
 
         store.dispatch(actionRootCats());
 
-
         /*store.dispatch(actionCategoryFindOne("6262ca7dbf8b206433f5b3d1"));
         store.dispatch(actionGoodFindOne("62d3099ab74e1f5f2ec1a125"));
         store.dispatch(actionFullLogin("Berg", "123456789"));

BIN
js/lesson21_Module/Img/Logo.png


BIN
js/lesson21_Module/Img/корзина.png


+ 60 - 0
js/lesson21_Module/alerts.js

@@ -0,0 +1,60 @@
+function addAlert(
+    addToEl, alertMessage,
+    type = "primary",
+    timeout = 5,
+    info = undefined,
+    onAdd, onClose
+  ) {
+    function setAttributes(el, attrs) {
+      if (!attrs)
+        return;
+      for (var key in attrs) 
+        el.setAttribute(key, attrs[key]);
+    }
+    function addElement({
+      tag = "div", parent = null,
+      classes = [], attrs = {},
+      innerText = null,
+      children = []
+    } = {}) {
+      let result = document.createElement(tag);
+      result.innerText = innerText;
+      result.classList.add(...classes);
+      setAttributes(result, attrs)
+      parent?.appendChild(result);
+      result.append(...(children?.filter(c => c) ?? []));
+      return result;
+    }
+
+    if (!info && ["success", "danger", "warning", "info"].includes(type.toLowerCase()))
+      info = type.charAt(0).toUpperCase() + type.slice(1);
+
+    let alertDiv = null;
+    addElement(
+    { 
+      parent: addToEl, classes: ["container"],
+      children: 
+      [
+        alertDiv = addElement(
+        {
+          classes: ["alert", "alert-" + type, "alert-dismissible", "fade", "show"], role: "alert",
+          children: 
+          [
+            info ? addElement({tag: "strong", innerText: info + " "}) : null,
+            addElement({tag: "a", innerText: alertMessage}),
+            addElement({ tag: "button", classes: ["btn-close"], attrs: {"type": "button", "data-bs-dismiss": "alert", "aria-label": "Close" }})
+          ]
+        })
+      ]
+    });
+
+    if (onClose)
+      alertDiv.addEventListener("closed.bs.alert", onClose);
+    if (onAdd)
+      onAdd();
+    if (timeout > 0)
+    {
+      var bootstrapAlert = new bootstrap.Alert(alertDiv);
+      setTimeout(() => bootstrapAlert.close(), timeout * 1000);
+    }
+  }

+ 116 - 75
js/lesson21_Module/index.css

@@ -1,147 +1,188 @@
+.badge:after {
+    content: attr(value);
+    font-size: 12px;
+    color: #fff;
+    background: red;
+    border-radius: 50%;
+    padding: 0 5px;
+    position: relative;
+    left: -8px;
+    top: -10px;
+    opacity: 0.9;
+}
+
 body {
     padding: 0;
     margin: 0;
     background: #f2f6e9;
-    }
-    .navbar {
-    background: #6ab446;
-    }
-    .nav-link,
-    .navbar-brand {
-    color: #fff;
+}
+
+.navbar {
+    background: rgb(63, 239, 192);
+}
+
+.nav-link,
+.navbar-brand {
+    color: rgb(39, 34, 34);
     cursor: pointer;
-    }
-    .nav-link {
+}
+
+.nav-link {
     margin-right: 1em !important;
-    }
-    .nav-link:hover {
+}
+
+/*.nav-link:hover {
     color: #000;
-    }
-    .navbar-collapse {
+    }*/
+.navbar-collapse {
     justify-content: flex-end;
-    }
-    .header {
+}
+
+.categories-list {
+    background-color: #333;
+    color: #dedec8;
+}
+
+.header {
     background-image: url("images/header-background.jpg");
     background-size: cover;
     background-position: center;
     position: relative;
-    }
-    .overlay {
+}
+
+/*.overlay {
     position: absolute;
     min-height: 100%;
     min-width: 100%;
     left: 0;
     top: 0;
     background: rgba(0, 0, 0, 0.6);
-    }
-    .description {
+    }*/
+.description {
     left: 50%;
     position: absolute;
     top: 45%;
     transform: translate(-50%, -55%);
     text-align: center;
-    }
-    .description h1 {
+}
+
+.description h1 {
     color: #6ab446;
-    }
-    .description p {
+}
+
+.description p {
     color: #fff;
     font-size: 1.3rem;
     line-height: 1.5;
-    }
-    .description button {
+}
+
+.description button {
     border: 1px solid #6ab446;
     background: #6ab446;
     border-radius: 0;
     color: #fff;
-    }
-    .description button:hover {
+}
+
+/*.description button:hover {
     border: 1px solid #fff;
     background: #fff;
     color: #000;
-    }
-    .features {
+    }*/
+.features {
     margin: 4em auto;
     padding: 1em;
     position: relative;
-    }
-    .feature-title {
+}
+
+.feature-title {
     color: #333;
     font-size: 1.3rem;
     font-weight: 700;
     margin-bottom: 20px;
     text-transform: uppercase;
-    }
-    .features img {
+}
+
+.features img {
     -webkit-box-shadow: 1px 1px 4px rgba(0, 0, 0, 0.4);
     -moz-box-shadow: 1px 1px 4px rgba(0, 0, 0, 0.4);
     box-shadow: 1px 1px 4px rgba(0, 0, 0, 0.4);
     margin-bottom: 16px;
-    }
-    .features .form-control,
-    .features input {
+}
+
+.features .form-control,
+.features input {
     border-radius: 0;
-    }
-    .features .btn {
+}
+
+.features .btn {
     background-color: #589b37;
     border: 1px solid #589b37;
     color: #fff;
     margin-top: 20px;
-    }
-    .features .btn:hover {
+}
+
+.features .btn:hover {
     background-color: #333;
     border: 1px solid #333;
-    }
-    .background {
+}
+
+.background {
     background: #dedec8;
     padding: 4em 0;
-    }
-    .team {
+}
+
+.team {
     color: #5e5e55;
     padding: 0 180px;
-    }
-    .team .card-columns {
+}
+
+.team .card-columns {
     -webkit-column-count: 4;
     -moz-column-count: 4;
     column-count: 4;
-    }
-    .team .card {
+}
+
+.team .card {
     background: none;
     border: none;
-    }
-    .team .card-title {
+}
+
+.team .card-title {
     font-size: 1.3rem;
     margin-bottom: 0;
     text-transform: uppercase;
-    }
-    .page-footer {
+}
+
+.page-footer {
     background-color: #222;
     color: #ccc;
     padding: 60px 0 30px;
-    }
-    .footer-copyright {
+}
+
+.footer-copyright {
     color: #666;
     padding: 40px 0;
+}
+
+@media (max-width: 575.98px) {
+    .description {
+        left: 0;
+        padding: 0 15px;
+        position: absolute;
+        top: 10%;
+        transform: none;
+        text-align: center;
+    }
+
+    .description h1 {
+        font-size: 2em;
+    }
+
+    .description p {
+        font-size: 1.2rem;
     }
-    @media (max-width: 575.98px) {
-        .description {
-            left: 0;
-            padding: 0 15px;
-            position: absolute;
-            top: 10%;
-            transform: none;
-            text-align: center;
-        }
-     
-        .description h1 {
-            font-size: 2em;
-        }
-     
-        .description p {
-            font-size: 1.2rem;
-        }
-     
-        .features {
-            margin: 0;
-        }
+
+    .features {
+        margin: 0;
     }
+}

+ 121 - 0
js/lesson21_Module/pagination.js

@@ -0,0 +1,121 @@
+function Pagination(paginatedList, paginationContainer, paginationLimit = 5, listItemTag = "li"){
+  const createMainPagination = ()=>{
+    paginationContainer.innerHTML =
+      `
+      <nav aria-label="Page navigation example">
+        <ul class="pagination justify-content-center">
+          <li class="page-item disabled" id="prev-button" tabindex="-1">
+            <a class="page-link" href="#${location.hash}" tabindex="-1">Previous</a>
+          </li>
+          <!--<li class="page-item"><a class="page-link" href="#${location.hash}">1</a></li>
+          <li class="page-item"><a class="page-link" href="#${location.hash}">2</a></li>
+          <li class="page-item"><a class="page-link" href="#${location.hash}">3</a></li>-->
+          <li class="page-item" id="next-button">
+            <a class="page-link" href="#${location.hash}">Next</a>
+          </li>
+        </ul>
+      </nav>
+      `;
+  }
+  createMainPagination();
+  const listItems = paginatedList.querySelectorAll(listItemTag);
+  const nextButton = paginationContainer.querySelectorAll("#next-button")[0];
+  const prevButton = paginationContainer.querySelectorAll("#prev-button")[0];
+  
+  const pageCount = Math.ceil(listItems.length / paginationLimit);
+  let currentPage = 1;
+  
+  const disableButton = (button) => {
+    button.classList.add("disabled");
+    button.setAttribute("disabled", true);
+  };
+  
+  const enableButton = (button) => {
+    button.classList.remove("disabled");
+    button.removeAttribute("disabled");
+  };
+  
+  const handlePageButtonsStatus = () => {
+    if (currentPage === 1) {
+      disableButton(prevButton);
+    } else {
+      enableButton(prevButton);
+    }
+  
+    if (pageCount === currentPage) {
+      disableButton(nextButton);
+    } else {
+      enableButton(nextButton);
+    }
+  };
+  
+  const handleActivePageNumber = () => {
+    document.querySelectorAll(".page-item").forEach((button) => {
+      button.classList.remove("active");
+      const pageIndex = Number(button.getAttribute("page-index"));
+      if (pageIndex == currentPage) {
+        button.classList.add("active");
+      }
+    });
+  };
+  
+  const appendPageNumber = (index) => {
+    const pageNumber = document.createElement("li");
+    pageNumber.classList.add("page-item");
+    pageNumber.innerHTML = `<a class="page-link" href="#${location.hash}">${index}</a>`;
+    pageNumber.setAttribute("page-index", index);
+    pageNumber.setAttribute("aria-label", "Page " + index);
+    nextButton.parentNode.insertBefore(pageNumber, nextButton);
+  };
+  
+  const getPaginationNumbers = () => {
+    for (let i = 1; i <= pageCount; i++) {
+      appendPageNumber(i);
+    }
+  };
+  
+  const setCurrentPage = (pageNum) => {
+    currentPage = pageNum;
+    if (currentPage < 1 || currentPage > pageCount)
+      return;
+  
+    handleActivePageNumber();
+    handlePageButtonsStatus();
+    
+    const prevRange = (pageNum - 1) * paginationLimit;
+    const currRange = pageNum * paginationLimit;
+  
+    listItems.forEach((item, index) => {
+      item.classList.add("d-none");
+      if (index >= prevRange && index < currRange) {
+        item.classList.remove("d-none");
+      }
+    });
+  };
+
+  function init() {
+    getPaginationNumbers();
+    setCurrentPage(1);
+  
+    prevButton.addEventListener("click", () => {
+      setCurrentPage(currentPage - 1);
+    });
+  
+    nextButton.addEventListener("click", () => {
+      setCurrentPage(currentPage + 1);
+    });
+  
+    document.querySelectorAll(".page-item").forEach((button) => {
+      const pageIndex = Number(button.getAttribute("page-index"));
+  
+      if (pageIndex) {
+        button.childNodes[0].addEventListener("click", (event) => {
+          event.preventDefault();
+          if (!button.classList.contains('disabled'))
+            setCurrentPage(pageIndex);
+        });
+      }
+    });
+  }
+  init();
+}