index.js 23 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741
  1. function createStore(reducer){
  2. let state = reducer(undefined, {}) //стартовая инициализация состояния, запуск редьюсера со state === undefined
  3. let cbs = [] //массив подписчиков
  4. const getState = () => state //функция, возвращающая переменную из замыкания
  5. const subscribe = cb => (cbs.push(cb), //запоминаем подписчиков в массиве
  6. () => cbs = cbs.filter(c => c !== cb)) //возвращаем функцию unsubscribe, которая удаляет подписчика из списка
  7. const dispatch = action => {
  8. if (typeof action === 'function'){ //если action - не объект, а функция
  9. return action(dispatch, getState) //запускаем эту функцию и даем ей dispatch и getState для работы
  10. }
  11. const newState = reducer(state, action) //пробуем запустить редьюсер
  12. if (newState !== state){ //проверяем, смог ли редьюсер обработать action
  13. state = newState //если смог, то обновляем state
  14. for (let cb of cbs) cb() //и запускаем подписчиков
  15. }
  16. }
  17. return {
  18. getState, //добавление функции getState в результирующий объект
  19. dispatch,
  20. subscribe //добавление subscribe в объект
  21. }
  22. }
  23. function promiseReducer(state={}, {type, name, status, payload, error}){
  24. ////?????
  25. //ОДИН ПРОМИС:
  26. //состояние: PENDING/FULFILLED/REJECTED
  27. //результат
  28. //ошибка:
  29. //{status, payload, error}
  30. //{
  31. // name1:{status, payload, error}
  32. // name2:{status, payload, error}
  33. // name3:{status, payload, error}
  34. //}
  35. if (type === 'PROMISE'){
  36. return {
  37. ...state,
  38. [name]:{status, payload, error}
  39. }
  40. }
  41. return state
  42. }
  43. const actionPending = (name) => ({type: 'PROMISE', status: 'PENDING', name})
  44. const actionFulfilled = (name, payload) => ({type: 'PROMISE', status: 'FULFILLED', name, payload})
  45. const actionRejected = (name, error) => ({type: 'PROMISE', status: 'REJECTED', name, error})
  46. const actionPromise = (name, promise) =>
  47. async dispatch => {
  48. try {
  49. dispatch(actionPending(name))
  50. let payload = await promise
  51. dispatch(actionFulfilled(name, payload))
  52. return payload
  53. }
  54. catch(e){
  55. dispatch(actionRejected(name, e))
  56. }
  57. }
  58. const goToMainPage = () => location.href = location.href.split("#")[0];
  59. const checkAuthToken = () => {
  60. const headers = {
  61. "Content-Type": "application/json",
  62. "Accept": "application/json",
  63. }
  64. if(localStorage.getItem('authToken')) {
  65. return {
  66. ...headers,
  67. "Authorization": `Bearer ${localStorage.getItem('authToken')}`
  68. }
  69. } else {
  70. return headers;
  71. }
  72. }
  73. const getGQL = url =>
  74. (query, variables= {}) =>
  75. fetch(url, {
  76. method: 'POST',
  77. headers: checkAuthToken(),
  78. body:JSON.stringify({query, variables})
  79. }).then(res => res.json())
  80. .then(data => {
  81. try {
  82. if(!data.data && data.errors) {
  83. throw new SyntaxError(`SyntaxError - ${JSON.stringify(Object.values(data.errors)[0])}`);
  84. }
  85. return Object.values(data.data)[0];
  86. } catch (e) {
  87. console.error(e);
  88. }
  89. });
  90. function jwtDecode(token){
  91. try {
  92. return JSON.parse(atob(token.split('.')[1]))
  93. }
  94. catch(e){
  95. }
  96. }
  97. function authReducer(state={}, {type, token}){
  98. //{
  99. // token,payload (раскодированный токен)
  100. //} или, если не залогинены
  101. //{
  102. // нихрена, т. .е. пустой объект
  103. //}
  104. if (type === 'AUTH_LOGIN'){ //то мы логинимся
  105. const payload = jwtDecode(token)
  106. if (payload){
  107. return {
  108. token,
  109. payload
  110. }
  111. }
  112. }
  113. if (type === 'AUTH_LOGOUT'){ //мы разлогиниваемся
  114. return {}
  115. }
  116. return state
  117. }
  118. const actionAuthLogout = () =>
  119. dispatch => {
  120. dispatch({type: 'AUTH_LOGOUT'});
  121. localStorage.removeItem('authToken');
  122. goToMainPage()
  123. }
  124. const actionAuthLogin = (token) =>
  125. (dispatch, getState) => {
  126. const oldState = getState()
  127. dispatch({type: 'AUTH_LOGIN', token})
  128. const newState = getState()
  129. if (oldState !== newState)
  130. localStorage.setItem('authToken', token)
  131. }
  132. function cartReducer(state={}, {type, amount=1, good}){
  133. /*
  134. {
  135. id1: {amount, good: {объект с бэка с _id, description, name, price}},
  136. id2: {amount, good: {объект с бэка с _id, description, name, price}},
  137. id3: {amount, good: {объект с бэка с _id, description, name, price}}
  138. }
  139. */
  140. if (type === 'CART_ADD'){
  141. return {
  142. ...state,
  143. [good._id]: {good, amount: (state[good._id]?.amount || 0) + amount }
  144. }
  145. }
  146. if (type === 'CART_SET'){
  147. return {
  148. ...state,
  149. [good._id]: {good, amount}
  150. }
  151. }
  152. if (type === 'CART_DELETE'){
  153. const {[good._id]: skip,...newState} = state
  154. //const newState = { ...state }
  155. //delete newState[good._id]
  156. return newState
  157. }
  158. if (type === 'CART_CLEAR'){
  159. return {}
  160. }
  161. //if (type === ''){
  162. //}
  163. return state
  164. }
  165. const actionCartAdd = (good, amount=1) => ({type: 'CART_ADD', good, amount})
  166. const actionCartSet = (good, amount=1) => ({type: 'CART_SET', good, amount})
  167. const actionCartClear = () => ({type: 'CART_CLEAR'})
  168. const actionCartDelete = (good) => ({type: 'CART_DELETE', good})
  169. function combineReducers(reducers){
  170. function totalReducer(state={}, action){
  171. //{
  172. //promise:{
  173. //name1:{status, payload, error},
  174. //name2:{status, payload, error}
  175. //},
  176. //auth: {
  177. //token, payload
  178. //}
  179. //}
  180. const newTotalState = {}
  181. for (const [reducerName, reducer] of Object.entries(reducers)){
  182. const newSubState = reducer(state[reducerName], action)
  183. if (newSubState !== state[reducerName]){
  184. newTotalState[reducerName] = newSubState
  185. }
  186. }
  187. if (Object.keys(newTotalState).length){
  188. return {...state, ...newTotalState}
  189. }
  190. return state
  191. }
  192. return totalReducer
  193. }
  194. function localStoredReducer(reducer, localStorageKey){
  195. function wrapperReducer(state, action){
  196. if (state === undefined){ //если загрузка сайта
  197. try {
  198. return JSON.parse(localStorage[localStorageKey]) //пытаемся распарсить сохраненный
  199. //в localStorage state и подсунуть его вместо результата редьюсера
  200. }
  201. catch(e){ } //если распарсить не выйдет, то код пойдет как обычно:
  202. }
  203. const newState = reducer(state, action)
  204. localStorage.setItem(localStorageKey, JSON.stringify(newState)) //сохраняем состояние в localStorage
  205. return newState
  206. }
  207. return wrapperReducer
  208. }
  209. const reducers = {
  210. auth: authReducer,
  211. cart: localStoredReducer(cartReducer, 'cart'), //в localStorage должен появится ключ cart с JSON стейта корзины
  212. promise: localStoredReducer(promiseReducer, 'promise'),
  213. }
  214. const totalReducer = combineReducers(reducers)
  215. const store = createStore(totalReducer) //не забудьте combineReducers если он у вас уже есть
  216. store.subscribe(() => console.log(store.getState()))
  217. const backendURL = 'http://shop-roles.node.ed.asmer.org.ua/'
  218. const gql = getGQL(backendURL + 'graphql')
  219. const rootCategories = () =>
  220. gql(`query cadz($q:String) {
  221. CategoryFind(query:$q){
  222. _id name
  223. }
  224. }`, {q: JSON.stringify([{parent: null}])})
  225. const actionRootCategories = () =>
  226. actionPromise('rootCategories', rootCategories())
  227. const categoryById = _id => //добавьте сюда подкатегории и родителя - пригодятся
  228. gql(`query catById($qCat:String){
  229. CategoryFindOne(query:$qCat){
  230. _id name
  231. parent {
  232. _id name
  233. }
  234. subCategories {
  235. name _id parent {
  236. _id name
  237. }
  238. }
  239. goods{
  240. _id name price images{
  241. url
  242. }
  243. }
  244. }
  245. }`, {qCat: JSON.stringify([{_id}])})
  246. const actionCategoryById = _id =>
  247. actionPromise('catById', categoryById(_id))
  248. const goodById = _id =>
  249. gql(`query goodById($goodId:String) {
  250. GoodFindOne(query:$goodId) {
  251. _id name price description images {
  252. url
  253. }
  254. }
  255. }`, {goodId: JSON.stringify([{_id}])})
  256. const actionGoodById = _id =>
  257. actionPromise('goodById', goodById(_id));
  258. const actionOrder = () =>
  259. async (dispatch, getState) => {
  260. const order = Object.values(getState().cart).map(orderGoods => ({good: {_id: orderGoods.good._id}, count: orderGoods.amount}));
  261. const myOrder = await dispatch(actionPromise('myOrder', gql(`mutation myOrder($order:OrderInput) {
  262. OrderUpsert(order:$order) {
  263. _id createdAt total
  264. }
  265. }`, {order: {orderGoods: order}})));
  266. if(myOrder) {
  267. dispatch(actionCartClear());
  268. }
  269. }
  270. const orders = () =>
  271. gql(`query myOrders {
  272. OrderFind(query:"[{}]"){
  273. _id total orderGoods{
  274. price count total good{
  275. _id name images{
  276. url
  277. }
  278. }
  279. }
  280. }
  281. }`, {})
  282. const actionOrders = () =>
  283. actionPromise('myOrders', orders())
  284. const actionLogin = (login, password) =>
  285. actionPromise('login', gql(`query log($login:String, $password:String) {
  286. login(login:$login, password:$password)
  287. }`, {login, password}));
  288. const actionRegister = (login, password) =>
  289. actionPromise('register', gql(`mutation register($login:String, $password:String) {
  290. UserUpsert(user:{login:$login, password:$password}) {
  291. _id login createdAt
  292. }
  293. }`, {login, password}));
  294. const actionFullLogin = (login, password) =>
  295. async dispatch => {
  296. const token = await dispatch(actionLogin(login, password))
  297. if (token){
  298. dispatch(actionAuthLogin(token));
  299. goToMainPage()
  300. }
  301. }
  302. const actionFullRegister = (login, password) =>
  303. async dispatch => {
  304. const user = await dispatch(actionRegister(login, password))
  305. if(user) {
  306. dispatch(actionFullLogin(login, password))
  307. }
  308. }
  309. const actionFullOrders = () =>
  310. async dispatch => {
  311. await dispatch(actionOrders());
  312. if(Object.keys(store.getState().auth).length === 0) {
  313. goToMainPage()
  314. }
  315. }
  316. function Password(parent, open) {
  317. const input = document.createElement('input');
  318. input.id = 'password'
  319. input.type = 'password';
  320. parent.appendChild(input);
  321. const button = document.createElement('button');
  322. button.type = 'button';
  323. button.textContent = 'показать';
  324. parent.appendChild(button);
  325. button.addEventListener('click', () => {
  326. this.setOpen(open !== true);
  327. });
  328. this.setValue = newValue => input.value = newValue;
  329. this.getValue = () => input.value;
  330. this.setOpen = openUpdate => {
  331. open = openUpdate;
  332. if(typeof this.onOpenChange === 'function') {
  333. this.onOpenChange(openUpdate);
  334. }
  335. button.textContent = (openUpdate) ? 'показать' : 'скрыть';
  336. input.type = (openUpdate) ? 'password' : 'text';
  337. }
  338. this.getOpen = () => open;
  339. input.addEventListener('input', event => {
  340. if (typeof this.onChange === 'function'){
  341. this.onChange(event.target.value);
  342. }
  343. });
  344. }
  345. function LoginForm(parent) {
  346. const createDivider = () => parent.appendChild(document.createElement('br'));
  347. const input = document.createElement('input');
  348. input.id = 'login';
  349. input.type = 'text';
  350. parent.appendChild(input);
  351. const button = document.createElement('button');
  352. button.type = 'button';
  353. button.textContent = 'Логин';
  354. button.disabled = true;
  355. input.addEventListener('input', event => {
  356. if (typeof this.onChange === 'function'){
  357. this.onChange(event.target.value);
  358. }
  359. });
  360. button.addEventListener('click', event => {
  361. if (typeof this.onLogin === 'function') {
  362. this.onLogin(event.target);
  363. }
  364. });
  365. this.getLogin = () => input.value;
  366. createDivider();
  367. const password = new Password(parent, true);
  368. const getPassword = () => password.getValue();
  369. createDivider();
  370. parent.appendChild(button);
  371. const isDisabled = () => button.disabled = (!(getPassword() !== '' && this.getLogin() !== ''));
  372. password.onChange = () => isDisabled();
  373. this.onChange = () => isDisabled();
  374. this.setButtonText = newText => button.textContent = newText;
  375. }
  376. store.subscribe(() => {
  377. const rootCats = store.getState().promise.rootCategories?.payload
  378. if (rootCats){
  379. aside.innerHTML = ''
  380. for (let {_id, name} of rootCats){
  381. const a = document.createElement('a')
  382. a.innerText = name
  383. a.href = `#/category/${_id}`
  384. aside.append(a)
  385. }
  386. }
  387. })
  388. store.subscribe(() => {
  389. const catById = store.getState().promise.catById?.payload
  390. const [,route] = location.hash.split('/')
  391. if (catById && route === 'category'){
  392. const {name, goods, parent, subCategories} = catById;
  393. main.innerHTML = `<h1>${name}</h1>`;
  394. if(parent) {
  395. const {_id, name} = parent;
  396. const breadcrumbs = document.createElement('a');
  397. breadcrumbs.innerText = name;
  398. breadcrumbs.href = `#/category/${_id}`;
  399. main.prepend(breadcrumbs);
  400. }
  401. if(subCategories) {
  402. const listSubCategories = document.createElement('ul');
  403. for (let {_id, name} of subCategories) {
  404. listSubCategories.innerHTML += `
  405. <li>
  406. <a href="#/category/${_id}">${name}</a>
  407. </li>
  408. `;
  409. }
  410. main.append(listSubCategories);
  411. }
  412. for (let good of goods){
  413. const {_id, name, price, images} = good
  414. const a = document.createElement('a')
  415. a.classList.add('card')
  416. a.innerHTML = `
  417. <div>
  418. <img class="card__image" alt="${name}" src="${backendURL + images[0]?.url}" />
  419. <h2>${name}</h2>
  420. <strong>${price}</strong>
  421. </div>
  422. `
  423. a.href = `#/good/${_id}`
  424. const button = document.createElement('button')
  425. button.type = 'button';
  426. button.innerText = 'добавить в корзину'
  427. button.onclick = () => {
  428. store.dispatch(actionCartAdd(good))
  429. }
  430. main.append(a)
  431. main.append(button)
  432. }
  433. }
  434. })
  435. store.subscribe(() => {
  436. const goodById = store.getState().promise.goodById?.payload
  437. const [,route] = location.hash.split('/')
  438. if (goodById && route === 'good'){
  439. const {name, description, price, images} = goodById
  440. main.innerHTML = `
  441. <div>
  442. <h1>${name}</h1>
  443. <p>${description}</p>
  444. <strong>${price}</strong>
  445. </div>
  446. `;
  447. const button = document.createElement('button')
  448. button.type = 'button';
  449. button.innerText = 'добавить в корзину'
  450. button.onclick = () => {
  451. store.dispatch(actionCartAdd(goodById))
  452. }
  453. main.append(button);
  454. let imageGroup = document.createElement('div');
  455. imageGroup.classList.add('good-images')
  456. for (const img in images) {
  457. imageGroup.innerHTML += `
  458. <div>
  459. <img class="good-images__element" src="${backendURL + images[img]?.url}" alt="${name} photo-${img}">
  460. </div>
  461. `
  462. }
  463. main.append(imageGroup)
  464. }
  465. })
  466. const drawCart = () => {
  467. const cart = store.getState().cart;
  468. const [,route] = location.hash.split('/');
  469. if (cart && route === 'cart'){
  470. if(Object.keys(cart).length === 0) {
  471. main.innerHTML = '<h1>Корзина пустая</h1>';
  472. } else {
  473. main.innerHTML = '';
  474. const cartBlock = document.createElement('div');
  475. cartBlock.classList.add('cart');
  476. const totalAmountByPosition = [];
  477. for (const good of Object.values(cart)) {
  478. const {_id, name, price, images} = good.good;
  479. totalAmountByPosition.push(price * good.amount);
  480. const cartElement = document.createElement('div');
  481. cartElement.classList.add('cart__element')
  482. cartElement.innerHTML = `
  483. <figure class="cart__figure">
  484. <a class="cart__image-link" href="#/good/${_id}">
  485. <img class="cart__image" src="${backendURL + images[0]?.url}" alt="${name}">
  486. </a>
  487. <figcaption class="cart__caption">
  488. <a href="#/good/${_id}" class="cart__name">${name}</a>
  489. <strong class="cart__price">Цена - ${price}</strong>
  490. <p class="cart__amount">Количество - ${good.amount} шт.</p>
  491. <p class="cart__amount-total">Итого по позиции - ${price * good.amount}</p>
  492. </figcaption>
  493. </figure>
  494. `;
  495. const cartQuantity = document.createElement('fieldset');
  496. cartQuantity.classList.add('cart__quantity');
  497. const cartQuantityAmount = document.createElement('span');
  498. const incDecButton = (text, classStr, incDec) => {
  499. const button = document.createElement('button');
  500. button.type = 'button';
  501. button.id = `${(incDec ? 'decrease' : 'increase')}`
  502. button.innerText = text;
  503. button.classList.add('cart__quantity-button', `cart__quantity-button--${classStr}`);
  504. button.onclick = () => {
  505. store.dispatch(actionCartAdd(good.good, (incDec ? -1 : +1)));
  506. if(cart[_id].amount > 1 && button.id === 'decrease') {
  507. button.disabled = false
  508. }
  509. }
  510. if(cart[_id].amount === 1 && button.id === 'decrease') {
  511. button.disabled = true;
  512. }
  513. return button;
  514. }
  515. cartQuantityAmount.classList.add('cart__quantity-amount');
  516. cartQuantityAmount.innerText = good.amount;
  517. cartElement.append(cartQuantity);
  518. cartQuantity.append(incDecButton('-', 'decrease', true));
  519. cartQuantity.append(cartQuantityAmount);
  520. cartQuantity.append(incDecButton('+', 'increase', false));
  521. const deleteButton = document.createElement('button');
  522. deleteButton.type = 'button'
  523. deleteButton.innerText = 'Удалить товар'
  524. deleteButton.classList.add('cart__delete-button')
  525. deleteButton.onclick = () => {
  526. store.dispatch(actionCartDelete(good.good))
  527. }
  528. cartElement.append(deleteButton);
  529. cartBlock.append(cartElement);
  530. }
  531. main.append(cartBlock)
  532. const totalPrice = document.createElement('p');
  533. totalPrice.innerHTML = `Итого - <b>${totalAmountByPosition.reduce((a, b) => a + b, 0)}</b>`;
  534. main.append(totalPrice);
  535. const makeOrderButton = document.createElement('button');
  536. makeOrderButton.type = 'button';
  537. makeOrderButton.innerText = 'Оформить заказ';
  538. makeOrderButton.classList.add('cart-button');
  539. makeOrderButton.onclick = () => {
  540. store.dispatch(actionOrder());
  541. }
  542. main.append(makeOrderButton)
  543. }
  544. }
  545. }
  546. store.subscribe(drawCart);
  547. store.subscribe(() => {
  548. const orders = store.getState().promise.myOrders?.payload;
  549. const [,route] = location.hash.split('/')
  550. if(orders && route === 'orderhistory') {
  551. main.innerHTML = '<h1>Мои заказы</h1>';
  552. const orderCartGroup = document.createElement('div');
  553. orderCartGroup.classList.add('order-cart-group');
  554. for (const [index, value] of Object.entries(orders)) {
  555. const orderCartGroupElement = document.createElement('div');
  556. orderCartGroupElement.classList.add('order-cart-group__element')
  557. orderCartGroupElement.innerHTML = `<h2>Заказ №${+index+1} (ID заказа - ${value._id})</h2>`;
  558. const orderCartElements = document.createElement('div');
  559. orderCartElements.classList.add('order-cart__elements');
  560. const totalAll = document.createElement('p');
  561. totalAll.innerHTML = `<p>Итого - <b>${value.total}</b></p>`;
  562. for (const goodElement of Object.values(value.orderGoods)) {
  563. const {price, count, total, good} = goodElement;
  564. const orderCartElement = document.createElement('div');
  565. orderCartElement.classList.add('order-cart__element');
  566. orderCartElement.innerHTML = `
  567. <figure class="order-cart__figure">
  568. <a class="order-cart__image-link"
  569. href="#/good/${good._id}"
  570. >
  571. <img class="order-cart__image"
  572. src="${backendURL + good.images[0]?.url}"
  573. alt="${good.name}"
  574. >
  575. </a>
  576. <figcaption class="order-cart__caption">
  577. <a class="order-cart__headline-link"
  578. href="#/good/${good._id}"
  579. >
  580. <h3 class="order-cart__headline">${good.name}</h3>
  581. </a>
  582. <p>Цена - ${price}</p>
  583. <p>Количество - ${count}</p>
  584. <p>Итого по позиции - ${total}</p>
  585. </figcaption>
  586. </figure>
  587. `;
  588. orderCartElement.classList.add('order-cart__element');
  589. orderCartElements.append(orderCartElement)
  590. }
  591. orderCartGroupElement.append(orderCartElements);
  592. orderCartGroupElement.append(totalAll);
  593. orderCartGroup.append(orderCartGroupElement)
  594. }
  595. main.append(orderCartGroup)
  596. }
  597. })
  598. const drawUserName = () => {
  599. const buttonLogout = '<button onclick="store.dispatch(actionAuthLogout())" type="button">Выйти</button>';
  600. const buttonLogin = '<a href="#/login/">Войти</a>';
  601. const buttonRegister = '<a href="#/register/">Регистрация</a>';
  602. authSection.innerHTML = store.getState().auth.token ? `Пользователь - <a href="#/orderhistory">${store.getState().auth.payload.sub.login}</a><div>${buttonLogout}</div>` :`Пользователь - <i>anon</i> <div>${buttonLogin} ${buttonRegister}</div>`;
  603. }
  604. drawUserName() //работаем безусловно при перезагрузке страницы
  605. store.subscribe(drawUserName) //а так же при обновлении redux
  606. // честно стырил отсюда - https://gist.github.com/realmyst/1262561?permalink_comment_id=2299442#gistcomment-2299442
  607. const declOfNum = (n, titles) => titles[(n % 10 === 1 && n % 100 !== 11) ? 0 : n % 10 >= 2 && n % 10 <= 4 && (n % 100 < 10 || n % 100 >= 20) ? 1 : 2];
  608. const drawCardAmount = () => {
  609. if(store.getState().cart && store.getState().auth.token) {
  610. let totalAmount = Object.values(store.getState().cart).reduce((a, b) => a + b.amount, 0);
  611. cartIcon.innerHTML = `<a href="#/cart">Корзина</a> - <b>${totalAmount}</b> ${declOfNum(totalAmount, ['товар', 'товара', 'товаров'])}`;
  612. }
  613. }
  614. drawCardAmount()
  615. store.subscribe(drawCardAmount)
  616. store.dispatch(actionAuthLogin(localStorage.authToken))
  617. store.dispatch(actionRootCategories())
  618. //#/category/АЙДИШНИК
  619. //#/good/АЙДИШНИК
  620. window.onhashchange = () => {
  621. const [,route, _id] = location.hash.split('/')
  622. const routes = {
  623. category() {
  624. store.dispatch(actionCategoryById(_id))
  625. },
  626. good(){
  627. store.dispatch(actionGoodById(_id))
  628. },
  629. login(){
  630. main.innerHTML = '';
  631. const loginForm = new LoginForm(main);
  632. loginForm.onLogin = () => store.dispatch(actionFullLogin(login.value, password.value))
  633. },
  634. register(){
  635. main.innerHTML = '';
  636. const registerForm = new LoginForm(main);
  637. registerForm.setButtonText('Регистрация');
  638. registerForm.onLogin = () => store.dispatch(actionFullRegister(login.value, password.value));
  639. },
  640. cart(){
  641. drawCart();
  642. },
  643. orderhistory(){
  644. store.dispatch(actionFullOrders())
  645. }
  646. }
  647. if (route in routes){
  648. routes[route]()
  649. }
  650. }
  651. window.onhashchange()