|
@@ -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"));
|