test.html 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458
  1. <head>
  2. <Header>MODULE MARKET</Header>
  3. <meta charset="utf-8" />
  4. <meta name="viewport" content="width=device-width, initial-scale=1" />
  5. <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.2.3/dist/css/bootstrap.min.css" rel="stylesheet"
  6. integrity="sha384-rbsA2VBKQhggwzxH7pPCaAqO46MgnOM80zW1RWuH61DGLwZJEdK2Kadq2F9CUG65" crossorigin="anonymous" />
  7. <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.2.3/dist/js/bootstrap.bundle.min.js"
  8. integrity="sha384-kenU1KFdBIe4zVF0s0G1M5b4hcpxyD9F7jL+jjXkk+Q2h455rYXK/7HAuoJl+0I4"
  9. crossorigin="anonymous"></script>
  10. <link rel="stylesheet" href="https://cdn.reflowhq.com/v2/toolkit.min.css">
  11. <link rel="stylesheet" href="index.css">
  12. <style>
  13. .alert-fixed {
  14. position: fixed;
  15. top: 0px;
  16. left: 0px;
  17. width: 100%;
  18. z-index: 9999;
  19. border-radius: 0px;
  20. }
  21. </style>
  22. <div class="alert-fixed" id="alertsZone"></div>
  23. </head>
  24. <body>
  25. <div class="container-fluid">
  26. <nav class="navbar navbar-expand-md">
  27. <a class="navbar-brand" href="?#"><img src="./Img/Logo.png" width="140px"></a>
  28. <div class="collapse navbar-collapse" id="main-navigation">
  29. <ul class="navbar-nav align-items-center h3">
  30. <li id="loginLink" class="nav-item d-none">
  31. <a class="nav-link" href="?#/login/">Login</a>
  32. </li>
  33. <li id="regLink" class="nav-item d-none">
  34. <a class="nav-link" href="?#/register/">Register</a>
  35. </li>
  36. <li id="logoutLink" class="nav-item d-none">
  37. <a class="nav-link" href="?#/logout/">Logout</a>
  38. </li>
  39. <li id="cartLink" class="nav-item">
  40. <a class="nav-link" href="?#/cart/">
  41. <i class="fa badge fa-lg" id="cartCountBadge" value=0>
  42. <img src="./Img/корзина.png" width="60px">
  43. <!--<span class="badge badge-light" id="cartCountBadge">0</span>-->
  44. </i>
  45. </a>
  46. </li>
  47. <li id="historyLink" class="nav-item d-none">
  48. <a class="nav-link" href="#/orders/">History</a>
  49. </li>
  50. </ul>
  51. </div>
  52. </nav>
  53. </div>
  54. <div class="container-fluid">
  55. <div class="row">
  56. <div class="col-2 left">
  57. <div style="background-color: wheat;" class="d-flex flex-column flex-shrink-0 p-3 text-dark">
  58. <ul id="categoriesList" class="nav nav-pills flex-column mb-auto">
  59. </ul>
  60. </div>
  61. </div>
  62. <div class="col-8">
  63. <div id="main">
  64. </div>
  65. </div>
  66. </div>
  67. </div>
  68. <!--<header>
  69. <div>
  70. <a href="#/login/">Login</a>
  71. </div>
  72. <div>
  73. <a href="#/logout/">Logout</a>
  74. </div>
  75. <div>
  76. <a href="#/orders/">Orders History</a>
  77. </div>
  78. <div id='cartIcon'></div>
  79. <div>
  80. <a href="#/cart">Cart</a>
  81. </div>
  82. </header>
  83. <div id='mainContainer'>
  84. <aside id='aside'>
  85. </aside>
  86. <main id='main'>
  87. </main>
  88. </div>-->
  89. <script type="text/javascript" src="./login.js"></script>
  90. <script type="text/javascript" src="./alerts.js"></script>
  91. <script type="text/javascript" src="./pagination.js"></script>
  92. <script type="module" src="./src/store.js"></script>
  93. <script type="module" src="./src/reducers/localStoredReducer.js"></script>
  94. <script type="module" src="./src/utills/gql.js"></script>
  95. <script type="module" >
  96. import {createStore} from "./src/store.js";
  97. const addErrorAlert = (error) => {
  98. alert(error);
  99. }
  100. const store = createStore(combineReducers({ promiseReducer, authReducer, cartReducer: localStoredReducer(cartReducer, 'cart') }));
  101. const delay = (ms, action) => new Promise(ok => setTimeout(() => {
  102. action();
  103. ok(ms);
  104. }, ms));
  105. store.subscribe(() => {
  106. let state = store.getState();
  107. let cartItemsCount = 0;
  108. if (state.cartReducer) {
  109. let cartItems = Object.values(state.cartReducer);
  110. for (item of Object.values(state.cartReducer))
  111. cartItemsCount += item.count;
  112. }
  113. cartCountBadge.setAttribute("value", cartItemsCount);
  114. console.log({ state: store.getState() });
  115. });
  116. //////////////////////////////////////////////
  117. const addToCartBtn = (htmlEl, good) => {
  118. let btn = document.createElement("button");
  119. btn.innerText = "Add to Cart";
  120. btn.classList.add("ref-button");
  121. btn.classList.add("preview-toggle");
  122. btn.onclick = () => {
  123. store.dispatch(actionCartAdd(good));
  124. };
  125. htmlEl.appendChild(btn);
  126. return btn;
  127. }
  128. const fillRootCategories = (categories, htmlEl) => {
  129. htmlEl.innerText = '';
  130. if (!categories)
  131. return;
  132. let innerHtml = '';
  133. for (category of categories) {
  134. innerHtml += `<li class="h3"><a href="#/categories/${category._id}" class="nav-link text-dark">${category.name}</a></li>`;
  135. }
  136. htmlEl.innerHTML = innerHtml;
  137. }
  138. store.subscribe(() =>
  139. subscribePromiseItem("rootCats", categoriesList, [], fillRootCategories));
  140. const fillCurrentCategoryContent = (category, htmlEl) => {
  141. htmlEl.innerHTML = '';
  142. const { name, parent, subCategories, goods } = category;
  143. htmlEl.innerHTML = `<h1>${name}</h1>
  144. ${parent?.name ? `<section>Parent: <a href="#/subCategories/${parent?._id}">${parent.name}</a></section>` : ''}
  145. `
  146. if (subCategories?.length > 0) {
  147. htmlEl.innerHTML += '';//`<h3>Sub Categories:</h3><br>`
  148. for (const subCategory of subCategories) {
  149. htmlEl.innerHTML += `<h4><a href="#/subCategories/${subCategory._id}">${subCategory.name}</a><h4><hr/>`
  150. }
  151. }
  152. htmlEl.innerHTML += '';//`<section>Products:</section><br>`;
  153. addProductsList(goods, htmlEl)
  154. }
  155. store.subscribe(() =>
  156. subscribePromiseItem(
  157. "catFindOne", main, ["categories", "subCategories"], fillCurrentCategoryContent));
  158. const addProductsList = (goods, htmlEl) => {
  159. let outerDiv = document.createElement("div");
  160. outerDiv.innerHTML =
  161. `
  162. <div class="reflow-product-list">
  163. <div id="productsList" class="ref-products">
  164. </div>
  165. <div id="productsListPagination">
  166. </div>
  167. </div>
  168. `;
  169. htmlEl.appendChild(outerDiv);
  170. for (good of goods) {
  171. addProductToList(good, productsList);
  172. }
  173. new Pagination(productsList, productsListPagination, 5, listItemTag = 'div.ref-product')
  174. }
  175. const addProductToList = (good, htmlEl) => {
  176. let outerDiv = document.createElement('div');
  177. const { name, _id, price, description, images } = good;
  178. outerDiv.innerHTML =
  179. `
  180. <div id="good_${_id}" class="ref-product d-none">
  181. <div class="ref-media"><img class="ref-image"
  182. src="${getFullImageUrl(good.images[0])}" loading="lazy" /></div>
  183. <div class="ref-product-data">
  184. <div class="ref-product-info">
  185. <h5 class="ref-name"><a href="#/goods/${_id}">${name}</a></h5>
  186. <p class="ref-excerpt">${description}</p>
  187. </div><strong class="ref-price">$${price}</strong>
  188. </div>
  189. <div id="addCartDiv_${_id}" class="ref-addons"></div>
  190. </div>
  191. `;
  192. htmlEl.appendChild(outerDiv);
  193. let addCartDiv = document.getElementById(`addCartDiv_${_id}`);
  194. addToCartBtn(addCartDiv, good);
  195. }
  196. const fillCurrentGoodContent = (good, htmlEl) => {
  197. htmlEl.innerHTML = '';
  198. const { name, _id, price, description, images } = good;
  199. htmlEl.innerHTML = `<h1>${name}</h1>
  200. <section>Description: ${description}</section>
  201. <section>Price: ${price}</section>
  202. `;
  203. htmlEl.innerHTML += `<section>Images:</section><br>` //вставить css display block
  204. for (const image of images) {
  205. htmlEl.innerHTML += `<img width="170px" src="${"http://shop-roles.node.ed.asmer.org.ua/"}${image.url}"</img><br>`//вставить css display block
  206. }
  207. addToCartBtn(htmlEl, good);
  208. }
  209. store.subscribe(() =>
  210. subscribePromiseItem(
  211. "goodFindOne", main, ["goods"], fillCurrentGoodContent));
  212. const subscribePromiseItem = (promiseName, htmlEl, subscrNames, execFunc) => {
  213. const [, route, _id] = location.hash.split('/');
  214. if ((subscrNames.length > 0 && (!route || !subscrNames.some(v => v == route)))/* || !_id*/)
  215. return;
  216. let reducerData = store.getState().promiseReducer[promiseName];
  217. if (!reducerData)
  218. return;
  219. const { status, payload, error } = reducerData;
  220. if (status === 'PENDING') {
  221. htmlEl.innerHTML = `<img src='https://cdn.dribbble.com/users/63485/screenshots/1309731/infinite-gif-preloader.gif' />`
  222. }
  223. else if (status == "FULFILLED") {
  224. execFunc(payload, htmlEl);
  225. }
  226. else if (status == "FULFILLED") {
  227. addErrorAlert(error.message);
  228. }
  229. }
  230. const getFullImageUrl = (image) =>
  231. `http://shop-roles.node.ed.asmer.org.ua/${image?.url}`;
  232. const showCartContent = (cart, htmlEl) => {
  233. htmlEl.innerHTML = '';
  234. htmlEl.innerHTML = `<h1>Cart</h1>
  235. `;
  236. htmlEl.innerHTML += '';//`<section>Items:</section><br>` //вставить css display block
  237. let allCount = 0;
  238. let htmlContent = '';
  239. for (const item of Object.values(cart)) {
  240. let { count, good } = item;
  241. let inpId = `inp_${good._id}`;
  242. let delBtnId = `delBtn_${good._id}`;
  243. htmlContent += `
  244. <div>
  245. <img width="170px" src="${getFullImageUrl(good.images[0])}"</img>
  246. <a href="#/goods/${good._id}">${good.name}</a>
  247. <input type="number" min="1" max="999" id="${inpId}" value="${count}">
  248. <button class="ref-button preview-toggle" id="${delBtnId}">Remove</button>
  249. </div>
  250. <br>`//вставить css display block
  251. allCount += count;
  252. }
  253. htmlContent += `<div>Count ${allCount}</div><br>`;
  254. htmlContent += `<button class="ref-button preview-toggle" id="btnCheckout">Checkout</button><br>`;
  255. htmlEl.innerHTML += htmlContent;
  256. for (const item of Object.values(cart)) {
  257. let { good } = item;
  258. let inpId = `inp_${good._id}`;
  259. let delBtnId = `delBtn_${good._id}`;
  260. let inp = document.getElementById(inpId);
  261. inp.addEventListener("change", function (e) { store.dispatch(actionCartSet(good, +inp.value)); });
  262. let delBtn = document.getElementById(delBtnId);
  263. delBtn.addEventListener("click", function (e) { store.dispatch(actionCartDel(good)); });
  264. }
  265. let btnCheckout = document.getElementById("btnCheckout");
  266. btnCheckout.addEventListener("click", function (e) {
  267. window.location = "#/checkout/";
  268. });
  269. }
  270. store.subscribe(() =>
  271. subscribeSimple(
  272. "cartReducer", main, ["cart"], showCartContent));
  273. const showOrder = (order, num, htmlEl) => {
  274. let htmlContent = `<div class="order d-none"><h2>Order: #${num}</h2>
  275. <!--<div>Created on: ${order.createdAt}</div>-->
  276. `;
  277. htmlContent += `<h3>Items:</h3>` //вставить css display block
  278. let orderGoods = Object.values(order.orderGoods);
  279. for (let i = 0; i < orderGoods.length; i++) {
  280. let { order, count, price, total, good } = orderGoods[i];
  281. htmlContent +=
  282. `
  283. <div class="ref-product align-items-center">
  284. <strong class="ref-count">${i}. </strong>
  285. <div class="ref-media"><img class="ref-image" width="170px" src="${getFullImageUrl(good.images[0])}"</img></div>
  286. <div class="ref-product-data">
  287. <div class="ref-product-info">
  288. <h5 class="ref-name"><a href="#/goods/${good._id}">${good.name}</a></h5>
  289. </div>
  290. <strong class="ref-price">Price: ${price}</strong>
  291. <strong class="ref-count">Count: ${count}</strong>
  292. <strong class="ref-price">Total: ${total}</strong>
  293. </div>
  294. </div>
  295. `//вставить css display block
  296. }
  297. htmlContent += `<h3 class="ref-count">Total ${order.total}</h3><hr/></div>`;
  298. htmlEl.innerHTML += htmlContent;
  299. }
  300. const showOrders = (orders, htmlEl) => {
  301. htmlEl.innerHTML = "<header>Orders:</header>";
  302. if (!localStorage?.authToken)
  303. return;
  304. if (orders) {
  305. let ordersContainerDiv = document.createElement("div");
  306. ordersContainerDiv.classList.add("container", "reflow-product-list");
  307. let ordersDiv = document.createElement("div");
  308. ordersContainerDiv.appendChild(ordersDiv);
  309. ordersDiv.classList.add("ref-products");
  310. for (let i = orders.length; i > 0; i--) {
  311. let order = orders[i - 1];
  312. showOrder(order, i, ordersDiv);
  313. }
  314. let paginationDiv = document.createElement("div");
  315. htmlEl.append(ordersContainerDiv, paginationDiv);
  316. new Pagination(ordersDiv, paginationDiv, 5, listItemTag = 'div.order')
  317. }
  318. }
  319. store.subscribe(() =>
  320. subscribePromiseItem(
  321. "orders", main, ["orders"], showOrders));
  322. const subscribeSimple = (reducerName, htmlEl, subscrNames, execFunc) => {
  323. const [, route, _id] = location.hash.split('/');
  324. if (!subscrNames || !subscrNames.some(v => v == route))
  325. return;
  326. let reducerData = store.getState()[reducerName];
  327. execFunc(reducerData, htmlEl);
  328. }
  329. window.onhashchange = () => {
  330. const [, route, _id] = location.hash.split('/')
  331. const routes = {
  332. categories() {
  333. console.log('Category', _id)
  334. store.dispatch(actionCategoryFindOne(_id))
  335. },
  336. subCategories() {
  337. console.log('subCategory', _id)
  338. store.dispatch(actionCategoryFindOne(_id))
  339. },
  340. goods() {
  341. console.log('good', _id)
  342. store.dispatch(actionGoodFindOne(_id))
  343. },
  344. cart() {
  345. console.log('cart')
  346. store.dispatch(actionCartShow())
  347. },
  348. checkout() {
  349. console.log('checkout');
  350. let state = store.getState();
  351. if (routes.login())
  352. store.dispatch(orderFullUpsert(() => window.location = "#/orders/"));
  353. },
  354. orders() {
  355. if (!localStorage.authToken) {
  356. showOrders([], main);
  357. return;
  358. }
  359. console.log('order');
  360. store.dispatch(actionFindOrders());
  361. },
  362. login() {
  363. if (!localStorage.authToken) {
  364. main.innerText = '';
  365. const form = new LoginForm(main);
  366. form.onLogin = (login, password) => {
  367. store.dispatch(actionFullLogin(login, password));
  368. }
  369. return false;
  370. }
  371. else if (route == "login") {
  372. window.location = "#/";
  373. window.location.reload();
  374. }
  375. return true;
  376. },
  377. register() {
  378. if (!localStorage.authToken) {
  379. main.innerText = '';
  380. const form = new LoginForm(main);
  381. form.onLogin = (login, password) => {
  382. store.dispatch(actionFullAuthUpsert(login, password));
  383. }
  384. return false;
  385. }
  386. else if (route == "register") {
  387. window.location = "#/";
  388. window.location.reload();
  389. }
  390. return true;
  391. },
  392. logout() {
  393. if (localStorage.authToken) {
  394. store.dispatch(actionAuthLogout());
  395. window.location = "#/login/";
  396. window.location.reload();
  397. }
  398. },
  399. //register(){
  400. ////нарисовать форму регистрации, которая по нажатию кнопки Login делает store.dispatch(actionFullRegister(login, password))
  401. //},
  402. }
  403. if (localStorage.authToken) {
  404. loginLink.classList.add('d-none');
  405. regLink.classList.add('d-none');
  406. historyLink.classList.remove('d-none');
  407. logoutLink.classList.remove('d-none');
  408. }
  409. else {
  410. loginLink.classList.remove('d-none');
  411. regLink.classList.remove('d-none');
  412. historyLink.classList.add('d-none');
  413. logoutLink.classList.add('d-none');
  414. }
  415. if (route in routes) {
  416. routes[route]()
  417. }
  418. }
  419. window.onhashchange()
  420. store.dispatch(actionRootCats());
  421. /*store.dispatch(actionCategoryFindOne("6262ca7dbf8b206433f5b3d1"));
  422. store.dispatch(actionGoodFindOne("62d3099ab74e1f5f2ec1a125"));
  423. store.dispatch(actionFullLogin("Berg", "123456789"));
  424. //store.dispatch(actionFullAuthUpsert("Berg1", "12345678911"));
  425. store.dispatch(actionCartAdd({ _id: '62d30938b74e1f5f2ec1a124', price: 50 }));
  426. delay(3000, () => {
  427. store.dispatch(orderFullUpsert());
  428. store.dispatch(actionFindOrders());
  429. });*/
  430. //delay(500, () => store.dispatch(actionFindOrders()));
  431. </script>
  432. </body>