module.js 28 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915
  1. const API_URL = "http://shop-roles.node.ed.asmer.org.ua/graphql";
  2. function getGql(url) {
  3. return (query, variables = {}) => {
  4. const token = store.getState().auth.token;
  5. return fetch(url, {
  6. method: 'POST',
  7. headers: {
  8. 'Content-Type': 'application/json',
  9. 'Accept': 'application/json',
  10. 'Authorization': token ? 'Bearer ' + token : ''
  11. },
  12. body: JSON.stringify({
  13. query,
  14. variables
  15. }),
  16. }).then(response => response.json()).then(response => {
  17. if (response.data) {
  18. return Object.values(response.data)[0];
  19. } else if (response.errors) {
  20. throw new Error(JSON.stringify(response.errors));
  21. }
  22. });
  23. }
  24. }
  25. const gql = getGql(API_URL);
  26. function createStore(reducer) {
  27. let state = reducer(undefined, {}); //стартовая инициализация состояния, запуск редьюсера со state === undefined
  28. let cbs = []; //массив подписчиков
  29. const getState = () => state; //функция, возвращающая переменную из замыкания
  30. const subscribe = cb => (cbs.push(cb), //запоминаем подписчиков в массиве
  31. () => cbs = cbs.filter(c => c !== cb)); //возвращаем функцию unsubscribe, которая удаляет подписчика из списка
  32. const dispatch = action => {
  33. if (typeof action === 'function') { //если action - не объект, а функция
  34. return action(dispatch, getState); //запускаем эту функцию и даем ей dispatch и getState для работы
  35. }
  36. const newState = reducer(state, action); //пробуем запустить редьюсер
  37. if (newState !== state) { //проверяем, смог ли редьюсер обработать action
  38. state = newState; //если смог, то обновляем state
  39. for (let cb of cbs) cb(state); //и запускаем подписчиков
  40. }
  41. }
  42. return {
  43. getState, //добавление функции getState в результирующий объект
  44. dispatch,
  45. subscribe //добавление subscribe в объект
  46. }
  47. }
  48. function combineReducers(reducers) {
  49. function totalReducer(state = {}, action) {
  50. const newTotalState = {};
  51. for (const [reducerName, reducer] of Object.entries(reducers)) {
  52. const newSubState = reducer(state[reducerName], action);
  53. if (newSubState !== state[reducerName]) {
  54. newTotalState[reducerName] = newSubState;
  55. }
  56. }
  57. if (Object.keys(newTotalState).length) {
  58. return { ...state, ...newTotalState };
  59. }
  60. return state;
  61. }
  62. return totalReducer;
  63. }
  64. const reducers = {
  65. promise: promiseReducer, //допилить много имен для многих промисо
  66. auth: authReducer, //часть предыдущего ДЗ
  67. cart: cartReducer, //часть предыдущего ДЗ
  68. }
  69. const totalReducer = combineReducers(reducers);
  70. function promiseReducer(state = {}, { key, type, status, payload, error }) {
  71. if (type === 'PROMISE') {
  72. return { ...state, [key]: { status, payload, error } };
  73. }
  74. return state;
  75. }
  76. const actionPending = (key) => ({ key, type: 'PROMISE', status: 'PENDING' });
  77. const actionFulfilled = (key, payload) => ({ key, type: 'PROMISE', status: 'FULFILLED', payload });
  78. const actionRejected = (key, error) => ({ key, type: 'PROMISE', status: 'REJECTED', error });
  79. const actionPromise = (key, promise) =>
  80. async dispatch => {
  81. dispatch(actionPending(key)); //сигнализируем redux, что промис начался
  82. try {
  83. const payload = await promise; //ожидаем промиса
  84. dispatch(actionFulfilled(key, payload)); //сигнализируем redux, что промис успешно выполнен
  85. return payload; //в месте запуска store.dispatch с этим thunk можно так же получить результат промиса
  86. }
  87. catch (error) {
  88. dispatch(actionRejected(key, error)); //в случае ошибки - сигнализируем redux, что промис несложился
  89. main.innerHTML = '<div style="background-color:red; width:500px; height:500px"></div>';
  90. }
  91. }
  92. function authReducer(state = {}, { type, token }) {
  93. if (type === 'AUTH_LOGIN') {
  94. try {
  95. let mediumStr = token.split('.')[1];
  96. let result = JSON.parse(atob(mediumStr));
  97. return { ...state, 'token': token, 'payload': result };
  98. } catch (e) {
  99. return {};
  100. }
  101. }
  102. if (type === 'AUTH_LOGOUT') {
  103. return {};
  104. }
  105. return state;
  106. }
  107. const actionAuthLogin = token => ({ type: 'AUTH_LOGIN', token });
  108. const actionAuthLogout = () => ({ type: 'AUTH_LOGOUT' });
  109. function cartReducer(state = {}, { type, good, count }) {
  110. let goodKey, oldCount, goodValue;
  111. if (good) {
  112. goodKey = good['_id'];
  113. oldCount = state[goodKey]?.count || 0;
  114. goodValue = { good, count: oldCount };
  115. }
  116. if (type === 'CART_ADD') {
  117. goodValue.count += +count;
  118. return { ...state, [goodKey]: goodValue };
  119. } else if (type === 'CART_SUB') {
  120. goodValue.count -= +count;
  121. if (goodValue.count <= 0) {
  122. delete state[goodKey];
  123. return { ...state };
  124. }
  125. return { ...state, [goodKey]: goodValue };
  126. } else if (type === 'CART_DEL') {
  127. delete state[goodKey];
  128. return { ...state };
  129. } else if (type === 'CART_SET') {
  130. goodValue.count = +count;
  131. if (goodValue.count <= 0) {
  132. delete state[goodKey];
  133. return { ...state };
  134. }
  135. return { ...state, [goodKey]: goodValue };
  136. } else if (type === 'CART_CLEAR') {
  137. return {};
  138. }
  139. return state;
  140. }
  141. const actionCartAdd = (good, count = 1) => ({ type: 'CART_ADD', count, good });
  142. const actionCartSub = (good, count = 1) => ({ type: 'CART_SUB', count, good });
  143. const actionCartDel = (good) => ({ type: 'CART_DEL', good });
  144. const actionCartSet = (good, count = 1) => ({ type: 'CART_SET', count, good });
  145. const actionCartClear = () => ({ type: 'CART_CLEAR' });
  146. function localStoredReducer(originalReducer, localStorageKey) {
  147. function wrapper(state, action) {
  148. if (state === undefined) {
  149. try {
  150. let savedState = JSON.parse(localStorage[localStorageKey]);
  151. return savedState;
  152. } catch (e) {
  153. }
  154. }
  155. let newState = originalReducer(state, action);
  156. localStorage.setItem(localStorageKey, JSON.stringify(newState));
  157. return newState;
  158. }
  159. return wrapper;
  160. }
  161. const store = createStore(localStoredReducer(totalReducer, 'total'));
  162. store.subscribe(() => console.log(store.getState()));
  163. function drawTitle(name) {
  164. let nameEl = document.createElement('div');
  165. main.append(nameEl);
  166. nameEl.innerText = name;
  167. nameEl.classList.add('title');
  168. }
  169. function drawCategoriesSection(categories, categoryEl) {
  170. let containerEl = document.createElement('section');
  171. main.append(containerEl);
  172. containerEl.innerText = categoryEl;
  173. containerEl.classList.add('info-about-category');
  174. for (const category of categories) {
  175. let categoryName = document.createElement('a');
  176. categoryName.classList.add('link-style');
  177. containerEl.append(categoryName);
  178. categoryName.href = `#/category/${category._id}`;
  179. categoryName.innerText = category.name;
  180. }
  181. }
  182. function drawImage(parent, url, route, id) {
  183. let imageContainer = document.createElement('div');
  184. parent.append(imageContainer);
  185. if (route == 'category') {
  186. imageContainer.addEventListener('click', (e) => location.href = `#/good/${id}`);
  187. }
  188. imageContainer.classList.add('image-container');
  189. let goodImage = document.createElement('img');
  190. imageContainer.append(goodImage);
  191. goodImage.classList.add('good-image');
  192. goodImage.src = 'http://shop-roles.node.ed.asmer.org.ua/' + url;
  193. }
  194. function drawSum(parent, priceText) {
  195. let sumEl = document.createElement('div');
  196. parent.append(sumEl);
  197. sumEl.classList.add('price');
  198. sumEl.innerText = priceText;
  199. }
  200. function drawCounter(parent, value) {
  201. let counterEl = document.createElement('input');
  202. parent.append(counterEl);
  203. counterEl.type = 'number';
  204. counterEl.min = 1;
  205. counterEl.value = value;
  206. return counterEl;
  207. }
  208. function drawAddButton(parent) {
  209. let cartAddButton = document.createElement('button');
  210. parent.append(cartAddButton);
  211. cartAddButton.innerText = 'Add to cart';
  212. cartAddButton.classList.add('button');
  213. return cartAddButton;
  214. }
  215. function drawDelButton(parent) {
  216. let cartDelButton = document.createElement('button');
  217. parent.append(cartDelButton);
  218. cartDelButton.innerText = 'Delete from cart';
  219. cartDelButton.classList.add('button');
  220. return cartDelButton;
  221. }
  222. const drawCategory = () => {
  223. const [, route] = location.hash.split('/');
  224. if (route !== 'category') return;
  225. let status, payload, error;
  226. if (store.getState().promise.category) {
  227. ({ status, payload, error } = store.getState().promise.category);
  228. }
  229. if (status === 'PENDING') {
  230. main.innerHTML = `<img src='images/pending.jpeg'>`;
  231. }
  232. if (status === 'FULFILLED') {
  233. main.innerHTML = '';
  234. const { name, subCategories, parent, goods } = payload;
  235. drawTitle(name);
  236. if (subCategories?.length > 0) {
  237. drawCategoriesSection(subCategories, 'Subcategories: ');
  238. }
  239. if (parent) {
  240. drawCategoriesSection([parent], 'Parent category: ');
  241. }
  242. if (goods?.length > 0) {
  243. let goodsContainer = document.createElement('section');
  244. main.append(goodsContainer);
  245. goodsContainer.classList.add('goods-container');
  246. for (const good of goods) {
  247. let goodCart = document.createElement('div');
  248. goodsContainer.append(goodCart);
  249. goodCart.classList.add('good-cart');
  250. let goodName = document.createElement('a');
  251. goodCart.append(goodName);
  252. goodName.href = `#/good/${good._id}`;
  253. goodName.innerText = good.name;
  254. if (good.images?.length > 0) {
  255. drawImage(goodCart, good.images[0].url, route, good._id);
  256. }
  257. if (good.price) {
  258. drawSum(goodCart, `${good.price} UAH`);
  259. }
  260. let goodCountEl = drawCounter(goodCart, 1);
  261. let cartAddButton = drawAddButton(goodCart);
  262. let cartDelButton = drawDelButton(goodCart);
  263. cartAddButton.addEventListener('click', event => {
  264. store.dispatch(actionCartAdd(good, goodCountEl.value));
  265. });
  266. cartDelButton.addEventListener('click', event => {
  267. store.dispatch(actionCartDel(good));
  268. });
  269. }
  270. } else {
  271. main.innerHTML += `<div class="goods-out-stock">Goods out of stock</div>`;
  272. }
  273. }
  274. }
  275. store.subscribe(() => drawCategory());
  276. function LoginForm(parent) {
  277. parent.innerHTML = '';
  278. if (location.hash == '#/login') {
  279. let loginTitle = document.createElement('div');
  280. parent.append(loginTitle);
  281. loginTitle.classList.add('title');
  282. loginTitle.innerText = 'Account login';
  283. } else if (location.hash == '#/register') {
  284. let registerTitle = document.createElement('div');
  285. parent.append(registerTitle);
  286. registerTitle.classList.add('title');
  287. registerTitle.innerText = 'Registration';
  288. }
  289. let form = document.createElement('form');
  290. parent.append(form);
  291. form.classList.add('form');
  292. let loginLabel = document.createElement('label');
  293. loginLabel.innerText = 'Login: ';
  294. form.append(loginLabel);
  295. let login = new Login(form);
  296. let passwordLabel = document.createElement('label');
  297. passwordLabel.innerText = 'Password: ';
  298. form.append(passwordLabel);
  299. let password = new Password(form, false);
  300. let submit = document.createElement('button');
  301. submit.classList.add('button');
  302. submit.innerText = 'Submit';
  303. form.append(submit);
  304. this.validateForm = () => {
  305. if (login.getValue() == '' || password.getValue() == '') {
  306. submit.disabled = true;
  307. } else {
  308. submit.disabled = false;
  309. if (this.onValidForm) {
  310. this.onValidForm();
  311. }
  312. }
  313. }
  314. submit.addEventListener('click', (e) => {
  315. e.preventDefault();
  316. if (this.onSubmitForm) {
  317. this.onSubmitForm();
  318. }
  319. });
  320. this.getLoginValue = () => {
  321. return login.getValue();
  322. }
  323. this.setLoginValue = (value) => {
  324. login.setValue(value);
  325. }
  326. this.getPasswordValue = () => {
  327. return password.getValue();
  328. }
  329. this.setPasswordValue = (value) => {
  330. password.setValue(value);
  331. }
  332. this.validateForm();
  333. login.onChange = this.validateForm;
  334. password.onChange = this.validateForm;
  335. }
  336. function Login(parent) {
  337. let loginInputEl = document.createElement('input');
  338. loginInputEl.type = 'text';
  339. parent.append(loginInputEl);
  340. loginInputEl.addEventListener('input', (event) => {
  341. if (this.onChange) {
  342. this.onChange();
  343. }
  344. });
  345. this.setValue = (value) => {
  346. loginInputEl.value = value;
  347. }
  348. this.getValue = () => {
  349. return loginInputEl.value;
  350. }
  351. }
  352. function Password(parent, open) {
  353. let passInputEl = document.createElement('input');
  354. parent.append(passInputEl);
  355. let passVisibilityCheckboxEl = document.createElement('input');
  356. passVisibilityCheckboxEl.type = 'checkbox';
  357. passVisibilityCheckboxEl.checked = open;
  358. parent.append(passVisibilityCheckboxEl);
  359. if (open) {
  360. passInputEl.type = 'text';
  361. } else {
  362. passInputEl.type = 'password';
  363. }
  364. passVisibilityCheckboxEl.addEventListener('change', (event) => {
  365. if (event.currentTarget.checked) {
  366. passInputEl.type = 'text';
  367. } else {
  368. passInputEl.type = 'password';
  369. }
  370. if (this.onOpenChange) {
  371. this.onOpenChange(event.currentTarget.checked);
  372. }
  373. });
  374. passInputEl.addEventListener('input', (event) => {
  375. if (this.onChange) {
  376. this.onChange();
  377. }
  378. });
  379. this.setValue = (value) => {
  380. passInputEl.value = value;
  381. }
  382. this.getValue = () => {
  383. return passInputEl.value;
  384. }
  385. this.setOpen = (value) => {
  386. passVisibilityCheckboxEl.checked = value;
  387. }
  388. this.getOpen = () => {
  389. return passVisibilityCheckboxEl.checked;
  390. }
  391. }
  392. function drawLoginForm() {
  393. const form = new LoginForm(main);
  394. form.onSubmitForm = () =>
  395. store.dispatch(actionFullLogin(form.getLoginValue(), form.getPasswordValue()));
  396. }
  397. store.subscribe(() => {
  398. const [, route] = location.hash.split('/');
  399. if (route !== 'login') return;
  400. let status, payload, error;
  401. if (store.getState().promise?.login) {
  402. ({ status, payload, error } = store.getState().promise?.login);
  403. }
  404. if (status === 'FULFILLED') {
  405. if (payload) {
  406. location.hash = '';
  407. main.innerHTML = '';
  408. } else {
  409. let errorMessageEl = document.createElement('div');
  410. main.append(errorMessageEl);
  411. errorMessageEl.innerText = 'You entered wrong login or password';
  412. }
  413. }
  414. })
  415. store.subscribe(() => {
  416. // header update
  417. userName.innerText = 'Hello, ' + (store.getState().auth.payload?.sub?.login || "anon");
  418. login.hidden = store.getState().auth.token;
  419. registration.hidden = store.getState().auth.token;
  420. logout.hidden = !(store.getState().auth.token);
  421. historyPage.hidden = !(store.getState().auth.token);
  422. });
  423. function drawRegisterForm() {
  424. const form = new LoginForm(main);
  425. form.onSubmitForm = () =>
  426. store.dispatch(actionFullRegister(form.getLoginValue(), form.getPasswordValue()));
  427. }
  428. const drawGood = () => {
  429. const [, route] = location.hash.split('/');
  430. if (route !== 'good') return;
  431. let status, payload, error;
  432. if (store.getState().promise.good) {
  433. ({ status, payload, error } = store.getState().promise.good);
  434. }
  435. if (status === 'PENDING') {
  436. main.innerHTML = `<img src='images/pending.jpeg'>`;
  437. }
  438. if (status === "FULFILLED") {
  439. main.innerHTML = '';
  440. const { name, images, categories, price, description, _id } = payload;
  441. drawTitle(name);
  442. if (categories?.length > 0) {
  443. drawCategoriesSection(categories, 'Category: ');
  444. }
  445. let goodSection = document.createElement('section');
  446. main.append(goodSection);
  447. goodSection.classList.add('good-section');
  448. let imagesContainer = document.createElement('div');
  449. goodSection.append(imagesContainer);
  450. if (images.length > 0) {
  451. for (const image of images) {
  452. drawImage(imagesContainer, image.url);
  453. }
  454. }
  455. let goodInfo = document.createElement('div');
  456. goodSection.append(goodInfo);
  457. goodInfo.classList.add('good-info');
  458. if (description) {
  459. let descriptionEl = document.createElement('div');
  460. goodInfo.append(descriptionEl);
  461. descriptionEl.innerText = description;
  462. descriptionEl.classList.add('description');
  463. }
  464. if (price) {
  465. drawSum(goodInfo, `${price} UAH`);
  466. }
  467. let goodCountEl = drawCounter(goodInfo, 1);
  468. let buttonsContainer = document.createElement('div');
  469. goodInfo.append(buttonsContainer);
  470. buttonsContainer.classList.add('buttons-container');
  471. let cartAddButton = drawAddButton(buttonsContainer);
  472. let cartDelButton = drawDelButton(buttonsContainer);
  473. cartAddButton.addEventListener('click', event => {
  474. store.dispatch(actionCartAdd(payload, goodCountEl.value));
  475. });
  476. cartDelButton.addEventListener('click', event => {
  477. store.dispatch(actionCartDel(payload));
  478. });
  479. }
  480. }
  481. store.subscribe(() => drawGood());
  482. function drawGoodsOrderContainer(parent, orderNameValue) {
  483. let goodsOrderContainer = document.createElement('div');
  484. parent.append(goodsOrderContainer);
  485. goodsOrderContainer.classList.add('order-container');
  486. let orderName = document.createElement('div');
  487. goodsOrderContainer.append(orderName);
  488. orderName.innerText = orderNameValue;
  489. return goodsOrderContainer;
  490. }
  491. function drawTotalAmount(parent) {
  492. let totalAmountEl = document.createElement('div');
  493. parent.append(totalAmountEl);
  494. totalAmountEl.classList.add('price');
  495. totalAmountEl.classList.add('right-aligment');
  496. return totalAmountEl;
  497. }
  498. const drawOrderHistory = () => {
  499. const [, route] = location.hash.split('/');
  500. if (route !== 'history') return;
  501. let status, payload, error;
  502. if (store.getState().promise.history) {
  503. ({ status, payload, error } = store.getState().promise.history);
  504. }
  505. if (status === 'PENDING') {
  506. main.innerHTML = `<img src='images/pending.jpeg'>`;
  507. }
  508. if (status === 'FULFILLED') {
  509. main.innerHTML = '';
  510. drawTitle('My orders');
  511. payload.sort((a, b) => b.createdAt - a.createdAt);
  512. payload.forEach(order => {
  513. let finalSum = 0;
  514. const { orderGoods, _id, total, createdAt } = order;
  515. let orderCreatedAt = new Date(+createdAt);
  516. let year = orderCreatedAt.getFullYear();
  517. let month = orderCreatedAt.getMonth() < 9 ? '0' + (orderCreatedAt.getMonth() + 1) : orderCreatedAt.getMonth() + 1;
  518. let day = orderCreatedAt.getDate() < 9 ? '0' + (orderCreatedAt.getMonth() + 1) : orderCreatedAt.getMonth() + 1;
  519. let hours = orderCreatedAt.getHours() < 10 ? '0' + orderCreatedAt.getHours() : orderCreatedAt.getHours();
  520. let minutes = orderCreatedAt.getMinutes() < 10 ? '0' + orderCreatedAt.getMinutes() : orderCreatedAt.getMinutes();
  521. if (orderGoods?.length > 0) {
  522. let orderContainer = drawGoodsOrderContainer(main, `Order: ${_id}`);
  523. for (orderGood of orderGoods) {
  524. if (orderGood?.good?.name && orderGood?.count && orderGood?.price) {
  525. let orderGoodName = document.createElement('div');
  526. orderContainer.append(orderGoodName);
  527. orderGoodName.innerText = `Good: ${orderGood?.good?.name}`;
  528. let goodCount = document.createElement('div');
  529. orderContainer.append(goodCount);
  530. goodCount.innerText = `Count: ${orderGood?.count}`;
  531. drawSum(orderContainer, `${orderGood?.price} UAH`);
  532. }
  533. if (orderGood?.count && orderGood?.price) {
  534. finalSum += orderGood?.count * orderGood?.price;
  535. }
  536. }
  537. let totalAmountEl = drawTotalAmount(orderContainer);
  538. if (total > 0) {
  539. totalAmountEl.innerText += `Total amount: ${total} UAH`;
  540. } else {
  541. totalAmountEl.innerText += `Total amount: ${finalSum} UAH`;
  542. }
  543. let orderCreatedAtEl = document.createElement('div');
  544. orderContainer.append(orderCreatedAtEl);
  545. orderCreatedAtEl.style.fontSize = '14px';
  546. orderCreatedAtEl.classList.add('right-aligment');
  547. orderCreatedAtEl.innerText = `Order created at: ${hours}:${minutes} ${day}.${month}.${year}`;
  548. }
  549. });
  550. }
  551. }
  552. store.subscribe(() => drawOrderHistory());
  553. const drawCartPage = () => {
  554. const [, route] = location.hash.split('/');
  555. if (route !== 'cart') return;
  556. if (!(Object.values(store.getState().cart).length > 0)) {
  557. main.innerHTML = '<div class="cart-is-empty">Cart is empty</div>';
  558. } else {
  559. main.innerHTML = '';
  560. let totalAmount = 0;
  561. let orderArray = [];
  562. drawTitle('Cart');
  563. for (const goodOnCart of Object.values(store.getState().cart)) {
  564. const { good, count } = goodOnCart;
  565. let goodContainer = drawGoodsOrderContainer(main, `${good.name} (${good._id})`);
  566. drawSum(goodContainer, `${good.price} UAH`);
  567. let countContainer = document.createElement('div');
  568. goodContainer.append(countContainer);
  569. countContainer.classList.add('count-container');
  570. let countText = document.createElement('div');
  571. countContainer.append(countText);
  572. countText.innerText = 'Count: ';
  573. let decreaseCountEl = document.createElement('button');
  574. countContainer.append(decreaseCountEl);
  575. decreaseCountEl.classList.add('icon-container');
  576. let decreaseImg = document.createElement('img');
  577. decreaseCountEl.append(decreaseImg);
  578. decreaseImg.src = 'images/minus.png';
  579. let goodCountEl = drawCounter(countContainer, count);
  580. let increaseCountEl = document.createElement('button');
  581. countContainer.append(increaseCountEl);
  582. increaseCountEl.classList.add('icon-container');
  583. let increaseImg = document.createElement('img');
  584. increaseCountEl.append(increaseImg);
  585. increaseImg.src = 'images/plus.png';
  586. //total good sum
  587. drawSum(goodContainer, `Total: ${count * good.price} UAH`);
  588. totalAmount += count * good.price;
  589. goodCountEl.addEventListener('change', e => {
  590. store.dispatch(actionCartSet(good, goodCountEl.value));
  591. })
  592. decreaseCountEl.addEventListener('click', e => {
  593. store.dispatch(actionCartSub(good));
  594. });
  595. increaseCountEl.addEventListener('click', e => {
  596. store.dispatch(actionCartAdd(good));
  597. })
  598. }
  599. orderArray = Object.values(store.getState().cart).map(value => {
  600. return {
  601. good: { _id: value.good._id },
  602. count: value.count
  603. }
  604. });
  605. let totalAmountEl = drawTotalAmount(main);
  606. totalAmountEl.innerText = `Total sum: ${totalAmount} UAH`;
  607. let createOrderButton = document.createElement('button');
  608. main.append(createOrderButton);
  609. createOrderButton.classList.add('button');
  610. createOrderButton.classList.add('order-button');
  611. createOrderButton.innerText = 'Checkout';
  612. let warningMessage = document.createElement('div');
  613. main.append(warningMessage);
  614. warningMessage.classList.add('warning-message');
  615. warningMessage.innerText = 'You need to login or register to place an order';
  616. if (store.getState().auth.token) {
  617. createOrderButton.addEventListener('click', e => {
  618. store.dispatch(actionOrder(orderArray));
  619. });
  620. } else {
  621. createOrderButton.addEventListener('click', e => {
  622. warningMessage.style.display = 'block';
  623. });
  624. }
  625. }
  626. }
  627. let cartIcon = document.querySelector("img.cart-icon");
  628. cartIcon.addEventListener('click', () => location.href = `#/cart`);
  629. store.subscribe(() => drawCartPage());
  630. store.subscribe(() => {
  631. let sumTotal = 0;
  632. for (let { count } of Object.values(store.getState().cart)) {
  633. sumTotal += count;
  634. }
  635. cartIconEl.innerText = sumTotal;
  636. });
  637. const gqlGetCategories = () => {
  638. const categoriesQuery = `query categories($q: String){
  639. CategoryFind(query: $q){
  640. _id
  641. name,
  642. goods{
  643. name
  644. },
  645. parent{
  646. name
  647. },
  648. image{
  649. url
  650. },
  651. subCategories{
  652. name,
  653. subCategories{
  654. name
  655. }
  656. }
  657. }
  658. }`;
  659. return gql(categoriesQuery, { q: "[{\"parent\": null}]" });
  660. }
  661. const gqlGetCategory = (id) => {
  662. const categoryQuery = `query category($q: String) {
  663. CategoryFindOne(query: $q) {
  664. _id
  665. name,
  666. goods{
  667. name,
  668. _id,
  669. images{
  670. _id,
  671. url
  672. },
  673. price
  674. },
  675. parent {
  676. _id,
  677. name
  678. },
  679. subCategories{
  680. name,
  681. _id
  682. subCategories{
  683. name,
  684. _id
  685. }
  686. }
  687. }
  688. }`;
  689. return gql(categoryQuery, { q: `[{"_id": "${id}"}]` });
  690. }
  691. const gqlGetGood = (id) => {
  692. const goodQuery = `query good($q: String) {
  693. GoodFindOne(query: $q) {
  694. _id,
  695. name,
  696. categories{
  697. _id,
  698. name
  699. },
  700. description,
  701. price,
  702. images{
  703. _id,
  704. url
  705. }
  706. }
  707. }`;
  708. return gql(goodQuery, { q: `[{"_id": "${id}"}]` });
  709. }
  710. const gqlLogin = (login, password) => {
  711. const loginQuery = `query login($login:String, $password:String){
  712. login(login:$login, password:$password)
  713. }`;
  714. return gql(loginQuery, { login, password });
  715. }
  716. const gqlCreateUser = (login, password) => {
  717. const registrationQuery = `mutation registration($login:String, $password: String){
  718. UserUpsert(user: {login:$login, password: $password}){
  719. _id login createdAt
  720. }
  721. }`;
  722. return gql(registrationQuery, { login, password });
  723. }
  724. const gqlGetOwnerOrders = () => {
  725. const ordersQuery = `query orders($q: String) {
  726. OrderFind(query: $q) {
  727. _id, total, createdAt, owner{
  728. _id, login
  729. }, orderGoods{
  730. price, count, good{
  731. name
  732. }
  733. }
  734. }
  735. }`;
  736. return gql(ordersQuery, { q: `[{}]` });
  737. }
  738. const gqlCreateOrder = (orderGoods) => {
  739. const orderQuery = `mutation ordering($orderGoods: OrderInput) {
  740. OrderUpsert(order: $orderGoods) {
  741. _id, total, orderGoods {
  742. good {
  743. name
  744. }
  745. }
  746. }
  747. }`;
  748. return gql(orderQuery, { orderGoods: { orderGoods } });
  749. }
  750. const actionCategories = () =>
  751. actionPromise('categories', gqlGetCategories());
  752. const actionCategoryById = (id) =>
  753. actionPromise('category', gqlGetCategory(id));
  754. const actionGoodById = (id) =>
  755. actionPromise('good', gqlGetGood(id));
  756. const actionLogin = (login, password) =>
  757. actionPromise('login', gqlLogin(login, password));
  758. const actionCreateUser = (login, password) =>
  759. actionPromise('register', gqlCreateUser(login, password));
  760. const actionOwnerOrders = () =>
  761. actionPromise('history', gqlGetOwnerOrders());
  762. const actionCreateOrder = (orderGoods) =>
  763. actionPromise('cart', gqlCreateOrder(orderGoods));
  764. const actionFullLogin = (login, password) =>
  765. async dispatch => {
  766. //dispatch возвращает то, что вернул thunk, возвращаемый actionLogin, а там промис,
  767. //так как actionPromise возвращает асинхронную функцию
  768. const token = await dispatch(actionLogin(login, password))
  769. //проверьте что token - строка и отдайте его в actionAuthLogin
  770. console.log(token);
  771. if (typeof token === 'string') {
  772. dispatch(actionAuthLogin(token));
  773. }
  774. }
  775. const actionFullRegister = (login, password) =>
  776. async dispatch => {
  777. await dispatch(actionCreateUser(login, password));
  778. dispatch(actionFullLogin(login, password));
  779. }
  780. const actionOrder = (orderGoods) =>
  781. async dispatch => {
  782. await dispatch(actionCreateOrder(orderGoods));
  783. console.log('order was created');
  784. dispatch(actionCartClear());
  785. }
  786. store.dispatch(actionCategories());
  787. store.subscribe(() => {
  788. const { status, payload, error } = store.getState().promise.categories;
  789. if (status === 'FULFILLED' && payload) {
  790. aside.innerHTML = '';
  791. for (const { _id, name } of payload) {
  792. aside.innerHTML += `<a href="#/category/${_id}">${name}</a>`;
  793. }
  794. }
  795. });
  796. store.subscribe(() => {
  797. const [, route] = location.hash.split('/');
  798. if (!route) {
  799. main.innerHTML = `<img class='banner-image' src='images/welcome.png'>`;
  800. }
  801. })
  802. logout.addEventListener('click', event => {
  803. store.dispatch(actionAuthLogout());
  804. store.dispatch(actionCartClear());
  805. });
  806. window.onhashchange = () => {
  807. const [, route, _id] = location.hash.split('/');
  808. const routes = {
  809. category() {
  810. store.dispatch(actionCategoryById(_id));
  811. },
  812. good() {
  813. store.dispatch(actionGoodById(_id));
  814. console.log('good', _id);
  815. },
  816. login() {
  817. console.log('А ТУТ ЩА ДОЛЖНА БЫТЬ ФОРМА ЛОГИНА');
  818. drawLoginForm();
  819. },
  820. register() {
  821. drawRegisterForm();
  822. },
  823. history() {
  824. store.dispatch(actionOwnerOrders());
  825. console.log('history page');
  826. },
  827. cart() {
  828. console.log('cart page');
  829. drawCartPage();
  830. }
  831. }
  832. if (route in routes) {
  833. routes[route]();
  834. }
  835. }
  836. window.onhashchange();