script.js 26 KB

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