index.html 40 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843
  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="60px"></a>
  28. <div class="collapse navbar-collapse" id="main-navigation">
  29. <ul class="navbar-nav align-items-center">
  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>
  93. const addErrorAlert = (error) => {
  94. addAlert(alertsZone, error, 'warning');
  95. }
  96. function jwtDecode(token) { // расщифровки токена авторизации
  97. if (!token || typeof token != "string")
  98. return undefined;
  99. let tokenArr = token.split(".");
  100. if (tokenArr.length != 3)
  101. return undefined;
  102. try {
  103. let tokenJsonStr = atob(tokenArr[1]);
  104. let tokenJson = JSON.parse(tokenJsonStr);
  105. return tokenJson;
  106. }
  107. catch (error) {
  108. addErrorAlert(error.message);
  109. return undefined;
  110. }
  111. }
  112. function combineReducers(reducers) {
  113. function totalReducer(totalState = {}, action) {
  114. const newTotalState = {} //объект, который будет хранить только новые состояния дочерних редьюсеров
  115. //цикл + квадратные скобочки позволяют написать код, который будет работать с любыми количеством дочерных редьюсеров
  116. for (const [reducerName, childReducer] of Object.entries(reducers)) {
  117. const newState = childReducer(totalState[reducerName], action) //запуск дочернего редьюсера
  118. if (newState !== totalState[reducerName]) { //если он отреагировал на action
  119. newTotalState[reducerName] = newState //добавляем его в newTotalState
  120. }
  121. }
  122. //Универсальная проверка на то, что хотя бы один дочерний редьюсер создал новый стейт:
  123. if (Object.values(newTotalState).length) {
  124. return { ...totalState, ...newTotalState } //создаем новый общий стейт, накладывая новый стейты дочерних редьюсеров на старые
  125. }
  126. return totalState //если экшен не был понят ни одним из дочерних редьюсеров, возвращаем общий стейт как был.
  127. }
  128. return totalReducer
  129. }
  130. function cartReducer(state = {}, action) { // диспетчер обработки
  131. switch (action.type) {
  132. case 'CART_ADD':
  133. if (action.count >= 0) {
  134. let newState = { ...state };
  135. let { count } = state[action.good._id] ?? { count: 0 };
  136. newState[action.good._id] = { count: action.count + count, good: { ...action.good } }
  137. return newState;
  138. }
  139. case 'CART_SUB':
  140. if (action.count >= 0) {
  141. let newState = { ...state };
  142. let { count } = state[action.good._id] ?? { count: 0 };
  143. if (count >= action.count) {
  144. newState[action.good._id] = { count: action.count - count, good: { ...action.good } }
  145. return newState;
  146. }
  147. }
  148. break;
  149. case 'CART_DEL':
  150. {
  151. let newState = { ...state };
  152. delete newState[action.good._id];
  153. return newState;
  154. }
  155. case 'CART_SET':
  156. {
  157. let newState = { ...state };
  158. newState[action.good._id] = { count: action.count, good: { ...action.good } };
  159. return newState;
  160. }
  161. case 'CART_SHOW':
  162. {
  163. return newState = { ...state };
  164. }
  165. case 'CART_CLEAR':
  166. return {};
  167. }
  168. return state;
  169. }
  170. function localStoredReducer(originalReducer, localStorageKey) {
  171. function wrapper(state, action) {
  172. if (!state) { /////проверка на первичность запуска !state
  173. try {
  174. return JSON.parse(localStorage[localStorageKey]);
  175. }
  176. catch { }
  177. }
  178. let res = originalReducer(state, action);
  179. localStorage[localStorageKey] = JSON.stringify(res);
  180. return res;
  181. }
  182. return wrapper
  183. }
  184. function promiseReducer(state = {}, action) { // диспетчер обработки
  185. if (action) {
  186. if (action.type === 'PROMISE') {
  187. let newState = { ...state };
  188. newState[action.name] = { status: action.status, payload: action.payload, error: action.error };
  189. return newState;
  190. }
  191. }
  192. return state;
  193. }
  194. function authReducer(state = {}, action) { // диспетчер обработки login
  195. if (action) {
  196. if (action.type === 'AUTH_LOGIN') {
  197. let newState = { ...state };
  198. newState.token = action.token;
  199. newState.payload = jwtDecode(action.token);
  200. if (!newState.payload) {
  201. newState.token = undefined;
  202. }
  203. if (newState.token)
  204. localStorage.authToken = newState.token;
  205. else
  206. delete localStorage.authToken;
  207. window.onhashchange();
  208. return newState;
  209. }
  210. else if (action.type === 'AUTH_LOGOUT') {
  211. let newState = { ...state };
  212. newState.token = undefined;
  213. newState.payload = undefined;
  214. delete localStorage.authToken;
  215. window.onhashchange();
  216. return newState;
  217. }
  218. }
  219. return state;
  220. }
  221. function createStore(reducer) {
  222. let state = reducer(undefined, {}) //стартовая инициализация состояния, запуск редьюсера со state === undefined
  223. let cbs = [] //массив подписчиков
  224. const getState = () => { return state; } //функция, возвращающая переменную из замыкания
  225. const subscribe = cb => (cbs.push(cb), //запоминаем подписчиков в массиве
  226. () => cbs = cbs.filter(c => c !== cb)) //возвращаем функцию unsubscribe, которая удаляет подписчика из списка
  227. function dispatch(action) {
  228. if (typeof action === 'function') { //если action - не объект, а функция
  229. return action(dispatch, getState) //запускаем эту функцию и даем ей dispatch и getState для работы
  230. }
  231. const newState = reducer(state, action) //пробуем запустить редьюсер
  232. if (newState !== state) { //проверяем, смог ли редьюсер обработать action
  233. state = newState //если смог, то обновляем state
  234. for (let cb of cbs) cb() //и запускаем подписчиков
  235. }
  236. }
  237. return {
  238. getState, //добавление функции getState в результирующий объект
  239. dispatch,
  240. subscribe //добавление subscribe в объект
  241. }
  242. }
  243. function getGql(url) {
  244. return function gql(query, vars = undefined) {
  245. try {
  246. let fetchSettings =
  247. {
  248. method: "POST",
  249. headers:
  250. {
  251. "Content-Type": "application/json",
  252. "Accept": "application/json"
  253. },
  254. body: JSON.stringify(
  255. {
  256. query: query,
  257. variables: vars
  258. })
  259. };
  260. let authToken = window.localStorage.authToken;
  261. if (authToken) {
  262. fetchSettings.headers["Authorization"] = `Bearer ${authToken}`;
  263. }
  264. return fetch(url, fetchSettings)
  265. .then(res => {
  266. try {
  267. if (!res.ok) {
  268. addErrorAlert(res.statusText);
  269. throw Error(res.statusText);
  270. }
  271. return res.json();
  272. }
  273. catch (error) {
  274. addErrorAlert(error.message);
  275. throw error;
  276. }
  277. });
  278. }
  279. catch (error) {
  280. addErrorAlert(error.message);
  281. throw error;
  282. }
  283. }
  284. }
  285. const gql = getGql("http://shop-roles.node.ed.asmer.org.ua/graphql");
  286. const actionPromise = (name, promise) => {
  287. return actionPromiseInt = async (dispatch) => {
  288. dispatch(actionPending(name)) //сигнализируем redux, что промис начался
  289. try {
  290. let payload = await promise //ожидаем промиса
  291. if (payload && payload.data)
  292. payload = Object.values(payload.data)[0];
  293. dispatch(actionFulfilled(name, payload)) //сигнализируем redux, что промис успешно выполнен
  294. return payload //в месте запуска store.dispatch с этим thunk можно так же получить результат промиса
  295. }
  296. catch (error) {
  297. addErrorAlert(error.message);
  298. dispatch(actionRejected(name, error)) //в случае ошибки - сигнализируем redux, что промис несложился
  299. }
  300. }
  301. }
  302. const actionPending = (name) => ({ type: 'PROMISE', name: name, status: 'PENDING' });
  303. const actionFulfilled = (name, payload) => ({ type: 'PROMISE', name: name, payload: payload, status: 'FULFILLED' });
  304. const actionRejected = (name, error) => ({ type: 'PROMISE', name: name, error: error, status: 'REJECTED' });
  305. const actionAuthLogin = token => ({ type: 'AUTH_LOGIN', token });
  306. const actionAuthLogout = () => ({ type: 'AUTH_LOGOUT' });
  307. const actionCartAdd = (good, count = 1) => ({ type: 'CART_ADD', count: count, good: good });
  308. const actionCartSub = (good, count = 1) => ({ type: 'CART_SUB', count, good }); //Уменьшение количества товара. Должен уменьшать количество товара в state, или удалять его если количество будет 0 или отрицательным
  309. const actionCartDel = (good) => ({ type: 'CART_DEL', good }); //Удаление товара. Должен удалять ключ из state
  310. const actionCartSet = (good, count = 1) => ({ type: 'CART_SET', count, good }); //Задание количества товара. В отличие от добавления и уменьшения, не учитывает того количества, которое уже было в корзине, а тупо назначает количество поверху (или создает новый ключ, если в корзине товара не было). Если count 0 или отрицательное число - удаляем ключ из корзины;
  311. const actionCartShow = (good, count = 1) => ({ type: 'CART_SHOW', count, good }); //Задание количества товара. В отличие от добавления и уменьшения, не учитывает того количества, которое уже было в корзине, а тупо назначает количество поверху (или создает новый ключ, если в корзине товара не было). Если count 0 или отрицательное число - удаляем ключ из корзины;
  312. const actionCartClear = () => ({ type: 'CART_CLEAR' }); //Очистка корзины. state должен стать пустым объектом {}
  313. ///////////////////////////////////////
  314. const gqlRootCats = () => {
  315. const catQuery = `query roots {
  316. CategoryFind(query: "[{\\"parent\\": null }]") {
  317. _id name
  318. }}`;
  319. return gql(catQuery);
  320. }
  321. const actionRootCats = () =>
  322. actionPromise('rootCats', gqlRootCats());
  323. const gqlCategoryFindOne = (id) => {
  324. const catQuery = `query CategoryFindOne($q: String) {
  325. CategoryFindOne(query: $q) {
  326. _id name
  327. parent { _id name }
  328. subCategories { _id name }
  329. goods { _id name price description
  330. images { url }
  331. }
  332. }
  333. }`;
  334. return gql(catQuery, { q: `[{\"_id\": \"${id}\"}]` });
  335. }
  336. const actionCategoryFindOne = (id) =>
  337. actionPromise('catFindOne', gqlCategoryFindOne(id));
  338. const gqlGoodFindOne = (id) => {
  339. const catQuery = `
  340. query GoodFindOne($q: String) {
  341. GoodFindOne(query: $q) {
  342. _id name price description
  343. images { url }
  344. }
  345. }
  346. `;
  347. return gql(catQuery, { q: `[{\"_id\": \"${id}\"}]` });
  348. }
  349. const actionGoodFindOne = (id) =>
  350. actionPromise('goodFindOne', gqlGoodFindOne(id));
  351. //////////////////////////////////
  352. const actionLogin = (login, password) => {
  353. const upsertQuery = `query login($login:String, $password:String){
  354. login(login:$login, password:$password)
  355. }`;
  356. return gql(upsertQuery, { login: login, password: password });
  357. }
  358. const actionFullLogin = (login, password) => {
  359. return gqlFullLogin = async (dispatch) => {
  360. try {
  361. delete localStorage.authToken;
  362. //dispatch возвращает то, что вернул thunk, возвращаемый actionLogin, а там промис,
  363. //так как actionPromise возвращает асинхронную функцию
  364. let promiseResult = actionLogin(login, password);
  365. let res = await promiseResult;
  366. if (res && res.data) {
  367. let token = Object.values(res.data)[0];
  368. if (token && typeof token == 'string')
  369. return dispatch(actionAuthLogin(token));
  370. else
  371. addErrorAlert("User not found");
  372. }
  373. }
  374. catch (error) {
  375. addErrorAlert(error.message);
  376. throw error;
  377. }
  378. //проверьте что token - строка и отдайте его в actionAuthLogin
  379. }
  380. }
  381. ////////////////////////////////////////
  382. const actionAuthUpsert = (login, password) => {
  383. const loginQuery = `mutation UserRegistration($login: String, $password: String) {
  384. UserUpsert(user: {login: $login, password: $password}) {
  385. _id createdAt
  386. }
  387. }`;
  388. return gql(loginQuery, { login: login, password: password });////////
  389. }
  390. const actionFullAuthUpsert = (login, password) => {
  391. return gqlFullAuthUpsert = async (dispatch) => {
  392. //dispatch возвращает то, что вернул thunk, возвращаемый actionLogin, а там промис,
  393. //так как actionPromise возвращает асинхронную функцию
  394. delete localStorage.authToken;
  395. let promiseResult = actionAuthUpsert(login, password);
  396. let res = await promiseResult;
  397. dispatch(actionFullLogin(login, password));
  398. console.log(res)
  399. //проверьте что token - строка и отдайте его в actionAuthLogin
  400. }
  401. }
  402. ////////////////////////////////////////
  403. const orderUpsert = (order, id = null) => {
  404. const orderUpsertQuery = `mutation OrderUpsert($order: OrderInput) {
  405. OrderUpsert(order: $order) {
  406. _id
  407. }
  408. }`;
  409. return gql(orderUpsertQuery, { order: { "_id": id, "orderGoods": order } });
  410. }
  411. const orderFullUpsert = (then) => {
  412. return gqlFullOrderUpsert = async (dispatch, getState) => {
  413. let state = getState();
  414. let order = [];
  415. for (cartItem of Object.values(state.cartReducer)) {
  416. //{count: 3, good: {_id: "xxxx" }}
  417. order.push({ good: { _id: cartItem.good._id }, count: cartItem.count });
  418. }
  419. if (order.length > 0) {
  420. //dispatch возвращает то, что вернул thunk, возвращаемый actionLogin, а там промис,
  421. //так как actionPromise возвращает асинхронную функцию
  422. let promiseResult = orderUpsert(order);
  423. let res = await promiseResult;
  424. if (res && res.errors && res.errors.length > 0) {
  425. addErrorAlert(res.errors[0].message);
  426. throw res.errors[0];
  427. }
  428. dispatch(actionCartClear());
  429. }
  430. if (then)
  431. then();
  432. //проверьте что token - строка и отдайте его в actionAuthLogin
  433. }
  434. }
  435. const gqlFindOrders = () => {
  436. const findOrdersQuery = `query OrderFind {
  437. OrderFind(query: "[{}]") {
  438. _id total
  439. orderGoods {
  440. _id price count total createdAt
  441. good {
  442. name
  443. images { url }
  444. }
  445. }
  446. }
  447. }`;
  448. return gql(findOrdersQuery);
  449. }
  450. const actionFindOrders = () =>
  451. actionPromise('orders', gqlFindOrders());
  452. ///////////////////////////////////////////////////
  453. const store = createStore(combineReducers({ promiseReducer, authReducer, cartReducer: localStoredReducer(cartReducer, 'cart') }));
  454. const delay = (ms, action) => new Promise(ok => setTimeout(() => {
  455. action();
  456. ok(ms);
  457. }, ms));
  458. store.subscribe(() => {
  459. let state = store.getState();
  460. let cartItemsCount = 0;
  461. if (state.cartReducer) {
  462. let cartItems = Object.values(state.cartReducer);
  463. for (item of Object.values(state.cartReducer))
  464. cartItemsCount += item.count; //
  465. }
  466. cartCountBadge.setAttribute("value", cartItemsCount);
  467. console.log({ state: store.getState() });
  468. });
  469. //////////////////////////////////////////////
  470. const addToCartBtn = (htmlEl, good) => {
  471. let btn = document.createElement("button");
  472. btn.innerText = "Add to Cart";
  473. btn.classList.add("ref-button");
  474. btn.classList.add("preview-toggle");
  475. btn.onclick = () => {
  476. store.dispatch(actionCartAdd(good));
  477. };
  478. htmlEl.appendChild(btn);
  479. return btn;
  480. }
  481. const fillRootCategories = (categories, htmlEl) => {
  482. htmlEl.innerText = '';
  483. if (!categories)
  484. return;
  485. /*
  486. <li class="nav-item">
  487. <a href="#" class="nav-link active" aria-current="page">
  488. Home
  489. </a>
  490. </li>
  491. <li>
  492. <a href="#" class="nav-link text-white">
  493. Dashboard
  494. </a>
  495. </li>
  496. */
  497. let innerHtml = '';
  498. for (category of categories) {
  499. innerHtml += `<li><a href="#/categories/${category._id}" class="nav-link text-dark">${category.name}</a></li>`;
  500. }
  501. htmlEl.innerHTML = innerHtml;
  502. }
  503. store.subscribe(() =>
  504. subscribePromiseItem("rootCats", categoriesList, [], fillRootCategories));
  505. const fillCurrentCategoryContent = (category, htmlEl) => {
  506. htmlEl.innerHTML = '';
  507. const { name, parent, subCategories, goods } = category;
  508. htmlEl.innerHTML = `<h1>${name}</h1>
  509. <section>Parent: <a href="#/subCategories/${parent?._id}">${parent?.name ?? 'Empty'}</a></section>
  510. `
  511. if (subCategories?.length > 0) {
  512. htmlEl.innerHTML += `<section>Sub Categories:</section><br>`
  513. for (const subCategory of subCategories) {
  514. htmlEl.innerHTML += `<a href="#/subCategories/${subCategory._id}">${subCategory.name}</a><br>`
  515. }
  516. }
  517. htmlEl.innerHTML += `<section>Products:</section><br>`;
  518. addProductsList(goods, htmlEl)
  519. }
  520. store.subscribe(() =>
  521. subscribePromiseItem(
  522. "catFindOne", main, ["categories", "subCategories"], fillCurrentCategoryContent));
  523. const addProductsList = (goods, htmlEl) => {
  524. let outerDiv = document.createElement("div");
  525. outerDiv.innerHTML =
  526. `
  527. <div class="reflow-product-list">
  528. <div id="productsList" class="ref-products">
  529. </div>
  530. <div id="productsListPagination">
  531. </div>
  532. </div>
  533. `;
  534. htmlEl.appendChild(outerDiv);
  535. for (good of goods) {
  536. addProductToList(good, productsList);
  537. }
  538. new Pagination(productsList, productsListPagination, 5, listItemTag = 'div.ref-product')
  539. }
  540. const addProductToList = (good, htmlEl) => {
  541. let outerDiv = document.createElement('div');
  542. const { name, _id, price, description, images } = good;
  543. outerDiv.innerHTML =
  544. `
  545. <div id="good_${_id}" class="ref-product d-none">
  546. <div class="ref-media"><img class="ref-image"
  547. src="${getFullImageUrl(good.images[0])}" loading="lazy" /></div>
  548. <div class="ref-product-data">
  549. <div class="ref-product-info">
  550. <h5 class="ref-name"><a href="#/goods/${_id}">${name}</a></h5>
  551. <p class="ref-excerpt">${description}</p>
  552. </div><strong class="ref-price">$${price}</strong>
  553. </div>
  554. <div id="addCartDiv_${_id}" class="ref-addons"></div>
  555. </div>
  556. `;
  557. htmlEl.appendChild(outerDiv);
  558. let addCartDiv = document.getElementById(`addCartDiv_${_id}`);
  559. addToCartBtn(addCartDiv, good);
  560. }
  561. const fillCurrentGoodContent = (good, htmlEl) => {
  562. htmlEl.innerHTML = '';
  563. const { name, _id, price, description, images } = good;
  564. htmlEl.innerHTML = `<h1>${name}</h1>
  565. <section>Description: ${description}</section>
  566. <section>Price: ${price}</section>
  567. `;
  568. htmlEl.innerHTML += `<section>Images:</section><br>` //вставить css display block
  569. for (const image of images) {
  570. htmlEl.innerHTML += `<img width="170px" src="${"http://shop-roles.node.ed.asmer.org.ua/"}${image.url}"</img><br>`//вставить css display block
  571. }
  572. addToCartBtn(htmlEl, good);
  573. }
  574. store.subscribe(() =>
  575. subscribePromiseItem(
  576. "goodFindOne", main, ["goods"], fillCurrentGoodContent));
  577. const subscribePromiseItem = (promiseName, htmlEl, subscrNames, execFunc) => {
  578. const [, route, _id] = location.hash.split('/');
  579. if ((subscrNames.length > 0 && (!route || !subscrNames.some(v => v == route)))/* || !_id*/)
  580. return;
  581. let reducerData = store.getState().promiseReducer[promiseName];
  582. if (!reducerData)
  583. return;
  584. const { status, payload, error } = reducerData;
  585. if (status === 'PENDING') {
  586. htmlEl.innerHTML = `<img src='https://cdn.dribbble.com/users/63485/screenshots/1309731/infinite-gif-preloader.gif' />`
  587. }
  588. else if (status == "FULFILLED") {
  589. execFunc(payload, htmlEl);
  590. }
  591. else if (status == "FULFILLED") {
  592. addErrorAlert(error.message);
  593. }
  594. }
  595. const getFullImageUrl = (image) =>
  596. `http://shop-roles.node.ed.asmer.org.ua/${image?.url}`;
  597. const showCartContent = (cart, htmlEl) => {
  598. htmlEl.innerHTML = '';
  599. htmlEl.innerHTML = `<h1>Cart</h1>
  600. `;
  601. htmlEl.innerHTML += `<section>Items:</section><br>` //вставить css display block
  602. let allCount = 0;
  603. let htmlContent = '';
  604. for (const item of Object.values(cart)) {
  605. let { count, good } = item;
  606. let inpId = `inp_${good._id}`;
  607. let delBtnId = `delBtn_${good._id}`;
  608. htmlContent += `
  609. <div>
  610. <img width="170px" src="${getFullImageUrl(good.images[0])}"</img>
  611. <a href="#/goods/${good._id}">${good.name}</a>
  612. <input type="number" min="1" max="999" id="${inpId}" value="${count}">
  613. <button id="${delBtnId}">Remove</button>
  614. </div>
  615. <br>`//вставить css display block
  616. allCount += count;
  617. }
  618. htmlContent += `<div>Count ${allCount}</div><br>`;
  619. htmlContent += `<button id="btnCheckout">Checkout</button><br>`;
  620. htmlEl.innerHTML += htmlContent;
  621. for (const item of Object.values(cart)) {
  622. let { good } = item;
  623. let inpId = `inp_${good._id}`;
  624. let delBtnId = `delBtn_${good._id}`;
  625. let inp = document.getElementById(inpId);
  626. inp.addEventListener("change", function (e) { store.dispatch(actionCartSet(good, +inp.value)); });
  627. let delBtn = document.getElementById(delBtnId);
  628. delBtn.addEventListener("click", function (e) { store.dispatch(actionCartDel(good)); });
  629. }
  630. let btnCheckout = document.getElementById("btnCheckout");
  631. btnCheckout.addEventListener("click", function (e) {
  632. window.location = "#/checkout/";
  633. });
  634. }
  635. store.subscribe(() =>
  636. subscribeSimple(
  637. "cartReducer", main, ["cart"], showCartContent));
  638. const showOrder = (order, num, htmlEl) => {
  639. let htmlContent = `<div class="order d-none"><h2>Order: #${num}</h2>
  640. <!--<div>Created on: ${order.createdAt}</div>-->
  641. `;
  642. htmlContent += `<h3>Items:</h3>` //вставить css display block
  643. let orderGoods = Object.values(order.orderGoods);
  644. for (let i = 0; i < orderGoods.length; i++) {
  645. let { order, count, price, total, good } = orderGoods[i];
  646. htmlContent +=
  647. `
  648. <div class="ref-product align-items-center">
  649. <strong class="ref-count">${i}. </strong>
  650. <div class="ref-media"><img class="ref-image" width="170px" src="${getFullImageUrl(good.images[0])}"</img></div>
  651. <div class="ref-product-data">
  652. <div class="ref-product-info">
  653. <h5 class="ref-name"><a href="#/goods/${good._id}">${good.name}</a></h5>
  654. </div>
  655. <strong class="ref-price">Price: ${price}</strong>
  656. <strong class="ref-count">Count: ${count}</strong>
  657. <strong class="ref-price">Total: ${total}</strong>
  658. </div>
  659. </div>
  660. `//вставить css display block
  661. }
  662. htmlContent += `<h3 class="ref-count">Total ${order.total}</h3><hr/></div>`;
  663. htmlEl.innerHTML += htmlContent;
  664. }
  665. const showOrders = (orders, htmlEl) => {
  666. htmlEl.innerHTML = "<header>Orders:</header>";
  667. if (!localStorage?.authToken)
  668. return;
  669. if (orders) {
  670. let ordersContainerDiv = document.createElement("div");
  671. ordersContainerDiv.classList.add("container", "reflow-product-list");
  672. let ordersDiv = document.createElement("div");
  673. ordersContainerDiv.appendChild(ordersDiv);
  674. ordersDiv.classList.add("ref-products");
  675. for (let i = orders.length; i > 0; i--) {
  676. let order = orders[i - 1];
  677. showOrder(order, i, ordersDiv);
  678. }
  679. let paginationDiv = document.createElement("div");
  680. htmlEl.append(ordersContainerDiv, paginationDiv);
  681. new Pagination(ordersDiv, paginationDiv, 5, listItemTag = 'div.order')
  682. }
  683. }
  684. store.subscribe(() =>
  685. subscribePromiseItem(
  686. "orders", main, ["orders"], showOrders));
  687. const subscribeSimple = (reducerName, htmlEl, subscrNames, execFunc) => {
  688. const [, route, _id] = location.hash.split('/');
  689. if (!subscrNames || !subscrNames.some(v => v == route))
  690. return;
  691. let reducerData = store.getState()[reducerName];
  692. execFunc(reducerData, htmlEl);
  693. }
  694. window.onhashchange = () => {
  695. const [, route, _id] = location.hash.split('/')
  696. const routes = {
  697. categories() {
  698. console.log('Category', _id)
  699. store.dispatch(actionCategoryFindOne(_id))
  700. },
  701. subCategories() {
  702. console.log('subCategory', _id)
  703. store.dispatch(actionCategoryFindOne(_id))
  704. },
  705. goods() {
  706. console.log('good', _id)
  707. store.dispatch(actionGoodFindOne(_id))
  708. },
  709. cart() {
  710. console.log('cart')
  711. store.dispatch(actionCartShow())
  712. },
  713. checkout() {
  714. console.log('checkout');
  715. let state = store.getState();
  716. if (routes.login())
  717. store.dispatch(orderFullUpsert(() => window.location = "#/orders/"));
  718. },
  719. orders() {
  720. if (!localStorage.authToken) {
  721. showOrders([], main);
  722. return;
  723. }
  724. console.log('order');
  725. store.dispatch(actionFindOrders());
  726. },
  727. login() {
  728. if (!localStorage.authToken) {
  729. main.innerText = '';
  730. const form = new LoginForm(main);
  731. form.onLogin = (login, password) => {
  732. store.dispatch(actionFullLogin(login, password));
  733. }
  734. return false;
  735. }
  736. else if (route == "login") {
  737. window.location = "#/";
  738. window.location.reload();
  739. }
  740. return true;
  741. },
  742. register() {
  743. if (!localStorage.authToken) {
  744. main.innerText = '';
  745. const form = new LoginForm(main);
  746. form.onLogin = (login, password) => {
  747. store.dispatch(actionFullAuthUpsert(login, password));
  748. }
  749. return false;
  750. }
  751. else if (route == "register") {
  752. window.location = "#/";
  753. window.location.reload();
  754. }
  755. return true;
  756. },
  757. logout() {
  758. if (localStorage.authToken) {
  759. store.dispatch(actionAuthLogout());
  760. window.location = "#/login/";
  761. window.location.reload();
  762. }
  763. },
  764. //register(){
  765. ////нарисовать форму регистрации, которая по нажатию кнопки Login делает store.dispatch(actionFullRegister(login, password))
  766. //},
  767. }
  768. if (localStorage.authToken) {
  769. loginLink.classList.add('d-none');
  770. regLink.classList.add('d-none');
  771. historyLink.classList.remove('d-none');
  772. logoutLink.classList.remove('d-none');
  773. }
  774. else {
  775. loginLink.classList.remove('d-none');
  776. regLink.classList.remove('d-none');
  777. historyLink.classList.add('d-none');
  778. logoutLink.classList.add('d-none');
  779. }
  780. if (route in routes) {
  781. routes[route]()
  782. }
  783. }
  784. window.onhashchange()
  785. store.dispatch(actionRootCats());
  786. /*store.dispatch(actionCategoryFindOne("6262ca7dbf8b206433f5b3d1"));
  787. store.dispatch(actionGoodFindOne("62d3099ab74e1f5f2ec1a125"));
  788. store.dispatch(actionFullLogin("Berg", "123456789"));
  789. //store.dispatch(actionFullAuthUpsert("Berg1", "12345678911"));
  790. store.dispatch(actionCartAdd({ _id: '62d30938b74e1f5f2ec1a124', price: 50 }));
  791. delay(3000, () => {
  792. store.dispatch(orderFullUpsert());
  793. store.dispatch(actionFindOrders());
  794. });*/
  795. //delay(500, () => store.dispatch(actionFindOrders()));
  796. </script>
  797. </body>