index.js 28 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859
  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 combineReducers(reducers) {
  24. return (state={}, action) => {
  25. const newState = {}
  26. // перебрать все редьюсеры
  27. if (reducers) {
  28. for (const [reducerName, reducer] of Object.entries(reducers)) {
  29. const newSubState = reducer(state[reducerName], action)
  30. if (newSubState !== state[reducerName]) {
  31. newState[reducerName] = newSubState
  32. }
  33. }
  34. // если newState не пустой, то вернуть стейт в
  35. if (Object.keys(newState).length !== 0) {
  36. return {...state, ...newState}
  37. } else {
  38. return state
  39. }
  40. }
  41. }
  42. }
  43. const combinedReducer = combineReducers({promise: promiseReducer, auth: authReducer, cart: cartReducer})
  44. const store = createStore(combinedReducer)
  45. store.subscribe(() => console.log(store.getState()))
  46. function jwtDecode(token) {
  47. try {
  48. let decoded = JSON.parse(atob(token.split('.')[1]))
  49. return decoded
  50. } catch (err) {
  51. console.log(err)
  52. }
  53. }
  54. function authReducer(state, {type, token}) {
  55. if (!state) {
  56. if (localStorage.authToken) {
  57. token = localStorage.authToken
  58. type = 'AUTH_LOGIN'
  59. } else {
  60. return {}
  61. }
  62. }
  63. if (type === 'AUTH_LOGIN') {
  64. let payload = jwtDecode(token)
  65. if (typeof payload === 'object') {
  66. localStorage.authToken = token
  67. return {
  68. ...state,
  69. token,
  70. payload
  71. }
  72. } else {
  73. return state
  74. }
  75. }
  76. if (type === 'AUTH_LOGOUT') {
  77. delete localStorage.authToken
  78. return {}
  79. }
  80. return state
  81. }
  82. const actionAuthLogin = (token) => ({type: 'AUTH_LOGIN', token})
  83. const actionAuthLogout = () => ({type: 'AUTH_LOGOUT'})
  84. function cartReducer (state={}, {type, good={}, count=1}) {
  85. // if (!state) {
  86. // localStorage.cart = JSON.stringify({})
  87. // return {}
  88. // }
  89. if (Object.keys(state).length === 0 && localStorage.cart) {
  90. let currCart = JSON.parse(localStorage.cart)
  91. if (currCart && Object.keys(currCart).length !== 0) {
  92. state = currCart
  93. }
  94. }
  95. // только если в функции задан count по умолчанию вызывать тут
  96. // а так лучше вызвать в типах add и change
  97. // count = +count
  98. // if (!count) {
  99. // return state
  100. // }
  101. const {_id} = good
  102. const types = {
  103. CART_ADD() {
  104. count = +count
  105. if (!count) {
  106. return state
  107. }
  108. let newState = {
  109. ...state,
  110. [_id]: {good, count: (count + (state[_id]?.count || 0)) < 0 ? 0 : count + (state[_id]?.count || 0)}
  111. }
  112. localStorage.cart = JSON.stringify(newState)
  113. return newState
  114. },
  115. CART_CHANGE() {
  116. count = +count
  117. if (!count) {
  118. return state
  119. }
  120. let newState = {
  121. ...state,
  122. [_id]: {good, count: count < 0 ? 0 : count}
  123. }
  124. localStorage.cart = JSON.stringify(newState)
  125. return newState
  126. },
  127. CART_REMOVE() {
  128. let { [_id]: removed, ...newState } = state
  129. localStorage.cart = JSON.stringify(newState)
  130. return newState
  131. },
  132. CART_CLEAR() {
  133. localStorage.cart = JSON.stringify({})
  134. return {}
  135. },
  136. }
  137. if (type in types) {
  138. return types[type]()
  139. }
  140. return state
  141. }
  142. const actionCartAdd = (good, count) => ({type: 'CART_ADD', good, count})
  143. const actionCartChange = (good, count) => ({type: 'CART_CHANGE', good, count})
  144. const actionCartRemove = (good) => ({type: 'CART_REMOVE', good})
  145. const actionCartClear = () => ({type: 'CART_CLEAR'})
  146. // store.dispatch(actionCartAdd({_id: '111111'}))
  147. // store.dispatch(actionCartChange({_id: '111111'}, 10))
  148. // store.dispatch(actionCartAdd({_id: '22222'}))
  149. // store.dispatch(actionCartRemove({_id: '22222'}))
  150. // store.dispatch(actionCartClear())
  151. function promiseReducer(state={}, {type, status, payload, error, name}) {
  152. if (!state) {
  153. return {}
  154. }
  155. if (type === 'PROMISE') {
  156. return {
  157. ...state,
  158. [name]: {
  159. status: status,
  160. payload : payload,
  161. error: error,
  162. }
  163. }
  164. }
  165. return state
  166. }
  167. const actionPending = (name) => ({type: 'PROMISE', status: 'PENDING', name})
  168. const actionResolved = (name, payload) => ({type: 'PROMISE', status: 'RESOLVED', name, payload})
  169. const actionRejected = (name, error) => ({type: 'PROMISE', status: 'REJECTED', name, error})
  170. const actionPromise = (name, promise) => (
  171. async (dispatch) => {
  172. dispatch(actionPending(name))
  173. try {
  174. let data = await promise
  175. dispatch(actionResolved(name, data))
  176. return data
  177. }
  178. catch(error){
  179. dispatch(actionRejected(name, error))
  180. }
  181. }
  182. )
  183. const getGQL = url => (
  184. async (query, variables={}) => {
  185. let obj = await fetch(url, {
  186. method: 'POST',
  187. headers: {
  188. "Content-Type": "application/json",
  189. ...(localStorage.authToken ? {Authorization: "Bearer " + localStorage.authToken} : {})
  190. },
  191. body: JSON.stringify({ query, variables })
  192. })
  193. let a = await obj.json()
  194. if (!a.data && a.errors) {
  195. throw new Error(JSON.stringify(a.errors))
  196. } else {
  197. return a.data[Object.keys(a.data)[0]]
  198. }
  199. }
  200. )
  201. const backURL = 'http://shop-roles.asmer.fs.a-level.com.ua/'
  202. const gql = getGQL(backURL + 'graphql');
  203. const actionOrder = () => (
  204. async (dispatch, getState) => {
  205. let {cart} = getState()
  206. //магия по созданию структуры вида
  207. //let orderGoods = [{good: {_id}, count}, {good: {_id}, count} .......]
  208. //из структуры вида
  209. //{_id1: {good, count},
  210. //_id2: {good, count}}
  211. const orderGoods = Object.entries(cart)
  212. .map(([_id, {good, count}]) => ({good: {_id}, count}))
  213. let result = await dispatch(actionPromise('order', gql(`
  214. mutation newOrder($order:OrderInput){
  215. OrderUpsert(order:$order)
  216. { _id total}
  217. }
  218. `, {order: {orderGoods}})))
  219. if (result?._id) {
  220. dispatch(actionCartClear())
  221. }
  222. })
  223. const actionLogin = (login, password) => (
  224. actionPromise('login', gql(`query log($login: String, $password: String) {
  225. login(login: $login, password: $password)
  226. }`, {login, password}))
  227. )
  228. const actionFullLogin = (login, password) => (
  229. async (dispatch) => {
  230. let token = await dispatch(actionLogin(login, password))
  231. if (token) {
  232. // console.log(token)
  233. dispatch(actionAuthLogin(token))
  234. }
  235. }
  236. )
  237. const actionRegister = (login, password) => (
  238. actionPromise('register', gql(`mutation reg($user:UserInput) {
  239. UserUpsert(user:$user) {
  240. _id
  241. }
  242. }
  243. `, {user: {login, password}})
  244. )
  245. )
  246. const actionFullRegister = (login, password) => (
  247. async (dispatch) => {
  248. let regId = await dispatch(actionRegister(login, password))
  249. if (regId) {
  250. // console.log(regId)
  251. dispatch(actionFullLogin(login, password))
  252. }
  253. }
  254. )
  255. const actionRootCats = () => (
  256. actionPromise('rootCats', gql(`query {
  257. CategoryFind(query: "[{\\"parent\\":null}]"){
  258. _id name
  259. }
  260. }`))
  261. )
  262. const actionCatById = (_id) => (
  263. actionPromise('catById', gql(`query catById($q: String){
  264. CategoryFindOne(query: $q){
  265. _id name goods {
  266. _id name price images {
  267. url
  268. }
  269. }
  270. subCategories {
  271. _id name
  272. }
  273. }
  274. }`, {q: JSON.stringify([{_id}])}))
  275. )
  276. const actionGoodById = (_id) => (
  277. actionPromise('goodById', gql(`query goodById($q: String) {
  278. GoodFindOne(query: $q) {
  279. _id name price description images {
  280. url
  281. }
  282. }
  283. }`, {q: JSON.stringify([{_id}])}))
  284. )
  285. const actionGoodsByUser = (_id) => (
  286. actionPromise('goodByUser', gql(`query oUser($query: String) {
  287. OrderFind(query:$query){
  288. _id orderGoods{
  289. price count total good{
  290. _id name categories{
  291. name
  292. }
  293. images {
  294. url
  295. }
  296. }
  297. }
  298. owner {
  299. _id login
  300. }
  301. }
  302. }`,
  303. {query: JSON.stringify([{___owner: _id}])}))
  304. )
  305. const actionGoodFind = (word) => (
  306. actionPromise('goodFind', gql(`query goodById($q: String) {
  307. GoodFind(query: $q) {
  308. _id name price description images {
  309. url
  310. }
  311. }
  312. }`, {q: JSON.stringify([
  313. {
  314. $or: [{title: `/${word}/`}, {description: `/${word}/`}, {name: `/${word}/`}] //регулярки пишутся в строках
  315. },
  316. {
  317. sort: [{title: 1}] //сортируем по title алфавитно
  318. }
  319. ])
  320. }
  321. ))
  322. )
  323. store.subscribe(() => {
  324. const {promise} = store.getState()
  325. const {rootCats} = promise
  326. if (rootCats?.status === 'PENDING') {
  327. aside.innerHTML = `
  328. <div class="spinner-border text-primary" role="status">
  329. <span class="visually-hidden">Loading...</span>
  330. </div>
  331. `
  332. } else {
  333. if (rootCats?.payload) {
  334. aside.innerHTML = ''
  335. const regBtn = document.createElement('a')
  336. regBtn.href = '#/register'
  337. regBtn.classList = 'btn btn-primary logBtn'
  338. regBtn.innerText = 'Register'
  339. const loginBtn = document.createElement('a')
  340. loginBtn.href = `#/login`
  341. loginBtn.classList = 'btn btn-primary logBtn'
  342. loginBtn.innerText = 'Login'
  343. const logoutBtn = document.createElement('a')
  344. logoutBtn.innerText = 'Logout'
  345. logoutBtn.classList = 'btn btn-primary logBtn'
  346. aside.append(regBtn, loginBtn, logoutBtn)
  347. logoutBtn.onclick = () => {
  348. store.dispatch(actionAuthLogout())
  349. }
  350. for (const {_id, name} of rootCats?.payload) {
  351. const link = document.createElement('a')
  352. link.classList = 'catBtn bg-light'
  353. link.href = `#/category/${_id}`
  354. link.innerText = name
  355. aside.append(link)
  356. }
  357. }
  358. }
  359. })
  360. store.dispatch(actionRootCats())
  361. function createForm(parent, type, callback) {
  362. parent.innerHTML = `
  363. <div class="form">
  364. <div class="mb-3">
  365. <label for="login${type}" class="form-label">Логин</label>
  366. <input class="form-input form-control" id="login${type}" type="text"/>
  367. </div>
  368. <div class="mb-3">
  369. <label for="pass${type}" class="form-label">Пароль</label>
  370. <input class="form-input form-control" id="pass${type}" type="password"/>
  371. </div>
  372. <button class="btn btn-primary" id="btn${type}">${type}</button>
  373. </div>
  374. `
  375. return () => window[`btn${type}`].onclick = () => {
  376. store.dispatch(callback(window[`login${type}`].value, window[`pass${type}`].value))
  377. window[`pass${type}`].value = ''
  378. }
  379. }
  380. const createCartPage = (parent) => {
  381. parent.innerHTML = ''
  382. const {cart} = store.getState()
  383. const clearBtn = document.createElement('button')
  384. clearBtn.classList = 'btn btn-primary cartBtn'
  385. clearBtn.innerText = "ОЧИСТИТЬ КОРЗИНУ"
  386. if(Object.keys(cart).length !== 0) {
  387. parent.append(clearBtn)
  388. }
  389. clearBtn.onclick = () => {
  390. store.dispatch(actionCartClear())
  391. }
  392. const cartPage = document.createElement('div')
  393. cartPage.classList = 'cartPage'
  394. parent.append(cartPage)
  395. let cartCounter = 0
  396. for(const item in cart) {
  397. const {good} = cart[item]
  398. const {count, good: {_id: id, name: name, price: price, images: [{url}]}} = cart[item]
  399. cartCounter += count*price
  400. const card = document.createElement('div')
  401. card.classList = 'card cartCard'
  402. card.innerHTML = `
  403. <div class="card-header">
  404. <h4 class="card-title">${name}</h4>
  405. </div>
  406. <img class="card-img-top" src="${backURL}/${url}" />
  407. <div class="card-body">
  408. <p>${count} шт</p>
  409. <p>${price} грн</p>
  410. <h6>Итого: ${count*price} грн</h6>
  411. </div>
  412. `
  413. const inputGr = document.createElement('div')
  414. inputGr.classList = 'inputGr'
  415. card.lastElementChild.append(inputGr)
  416. const minusBtn = document.createElement('button')
  417. minusBtn.classList = 'btn btn-success'
  418. minusBtn.innerText = '-'
  419. inputGr.append(minusBtn)
  420. minusBtn.onclick = () => {
  421. store.dispatch(actionCartAdd(good, -1))
  422. }
  423. const changeCount = document.createElement('input')
  424. changeCount.type = 'number'
  425. changeCount.value = count
  426. inputGr.append(changeCount)
  427. changeCount.oninput = () => {
  428. store.dispatch(actionCartChange(good, changeCount.value))
  429. }
  430. const plusBtn = document.createElement('button')
  431. plusBtn.classList = 'btn btn-success'
  432. plusBtn.innerText = '+'
  433. inputGr.append(plusBtn)
  434. plusBtn.onclick = () => {
  435. store.dispatch(actionCartAdd(good))
  436. }
  437. const deleteGood = document.createElement('button')
  438. deleteGood.classList = 'btn btn-success'
  439. deleteGood.innerText = 'Удалить'
  440. deleteGood.style.display = 'block'
  441. card.lastElementChild.append(deleteGood)
  442. deleteGood.onclick = () => {
  443. store.dispatch(actionCartRemove(good))
  444. }
  445. cartPage.append(card)
  446. }
  447. const total = document.createElement('h5')
  448. total.classList = 'totalCart'
  449. total.innerText = `Всего к оплате: ${cartCounter} грн`
  450. parent.append(total)
  451. const sendOrder = document.createElement('button')
  452. sendOrder.classList = 'btn btn-primary cartBtn'
  453. sendOrder.innerText = "ОФОРМИТЬ ЗАКАЗ"
  454. if(Object.keys(cart).length !== 0) {
  455. parent.append(sendOrder)
  456. }
  457. const {auth} = store.getState()
  458. if (auth.token) {
  459. sendOrder.disabled = false
  460. } else {
  461. sendOrder.disabled = true
  462. }
  463. sendOrder.onclick = () => {
  464. store.dispatch(actionOrder())
  465. }
  466. }
  467. // location.hash - адресная строка после решетки
  468. window.onhashchange = () => {
  469. const [,route, _id] = location.hash.split('/')
  470. const routes = {
  471. category(){
  472. store.dispatch(actionCatById(_id))
  473. },
  474. good(){
  475. store.dispatch(actionGoodById(_id))
  476. },
  477. register(){
  478. let goRegister = createForm(main, 'Register', actionFullRegister)
  479. goRegister()
  480. },
  481. login(){
  482. let goLogin = createForm(main, 'Login', actionFullLogin)
  483. goLogin()
  484. },
  485. orders(){
  486. store.dispatch(actionGoodsByUser(_id))
  487. },
  488. cart(){
  489. createCartPage(main)
  490. },
  491. find(){
  492. },
  493. }
  494. if (route in routes) {
  495. routes[route]()
  496. }
  497. }
  498. store.subscribe(() => {
  499. const [,route] = location.hash.split('/')
  500. if (route === 'cart') {
  501. createCartPage(main)
  502. }
  503. })
  504. window.onhashchange()
  505. store.subscribe(() => {
  506. const {promise} = store.getState()
  507. const {catById} = promise
  508. const [,route, _id] = location.hash.split('/')
  509. if (catById?.status === 'PENDING') {
  510. main.innerHTML = `
  511. <div class="d-flex justify-content-center">
  512. <div class="spinner-border text-primary" role="status">
  513. <span class="visually-hidden">Loading...</span>
  514. </div>
  515. </div>
  516. `
  517. } else {
  518. if (catById?.payload && route === 'category'){
  519. main.innerHTML = ''
  520. const catBody = document.createElement('div')
  521. catBody.classList = 'catBody'
  522. main.append(catBody)
  523. const {name} = catById.payload;
  524. catBody.innerHTML = `<h1 class="catHead">${name}</h1>`
  525. if (catById.payload.subCategories) {
  526. const linkList = document.createElement('div')
  527. linkList.classList = 'list-group linkList'
  528. catBody.append(linkList)
  529. for(const {_id, name} of catById.payload.subCategories) {
  530. const link = document.createElement('a')
  531. link.classList = 'list-group-item list-group-item-action list-group-item-primary linkItem'
  532. link.href = `#/category/${_id}`
  533. link.innerText = name
  534. catBody.append(link)
  535. }
  536. }
  537. if (catById.payload.goods) {
  538. const cardBody = document.createElement('div')
  539. cardBody.classList = 'cardBody'
  540. main.append(cardBody)
  541. for (const good of catById.payload.goods){
  542. const {_id, name, price, images} = good
  543. const card = document.createElement('div')
  544. card.classList = 'card goodCard'
  545. card.innerHTML = `
  546. <img class="card-img-top" src="${backURL}/${images[0].url}" />
  547. <div class="card-body">
  548. <h4 class="card-title">${name}</h4>
  549. <h5>${price} грн</h5>
  550. <a class="btn btn-primary" href="#/good/${_id}">
  551. Подробнее
  552. </a>
  553. </div>
  554. `
  555. const btnCart = document.createElement('button')
  556. btnCart.innerText = 'В корзину'
  557. btnCart.classList = 'btn btn-success btnCart'
  558. btnCart.onclick = () => {
  559. store.dispatch(actionCartAdd(good))
  560. }
  561. card.lastElementChild.append(btnCart)
  562. cardBody.append(card)
  563. }
  564. }
  565. }
  566. }
  567. })
  568. store.subscribe(() => {
  569. const {promise} = store.getState()
  570. const {goodById} = promise
  571. const [,route, _id] = location.hash.split('/');
  572. if (goodById?.status === 'PENDING') {
  573. main.innerHTML = `
  574. <div class="d-flex justify-content-center">
  575. <div class="spinner-border text-primary" role="status">
  576. <span class="visually-hidden">Loading...</span>
  577. </div>
  578. </div>
  579. `
  580. } else {
  581. if (goodById?.payload && route === 'good') {
  582. main.innerHTML = ''
  583. const good = goodById.payload
  584. const {_id, name, images, price, description} = good
  585. const card = document.createElement('div')
  586. card.classList = 'goodPage'
  587. card.innerHTML = `<h2>${name}</h2>
  588. <img src="${backURL}/${images[0].url}" />
  589. <div>
  590. <h6>${description}</h6>
  591. <strong>Цена - ${price} грн</strong>
  592. </div>
  593. `
  594. const btnCart = document.createElement('button')
  595. btnCart.innerText = 'В корзину'
  596. btnCart.classList = 'btn btn-success btnCart'
  597. btnCart.onclick = () => {
  598. store.dispatch(actionCartAdd(good))
  599. }
  600. card.append(btnCart)
  601. main.append(card);
  602. }
  603. }
  604. }
  605. )
  606. store.subscribe(() => {
  607. const {auth} = store.getState()
  608. const {payload} = auth
  609. if (payload?.sub ) {
  610. topContaner.innerHTML = ''
  611. const {id, login} = payload.sub
  612. const name = document.createElement('div')
  613. name.innerText = `ПРИВЕТ, ${login}`
  614. topContaner.append(name)
  615. const myOrders = document.createElement('a')
  616. myOrders.innerText = 'Мои заказы'
  617. myOrders.href = `#/orders/${id}`
  618. topContaner.append(myOrders)
  619. } else {
  620. topContaner.innerHTML = ''
  621. }
  622. })
  623. store.subscribe(() => {
  624. const {promise} = store.getState()
  625. const {goodByUser} = promise
  626. const [,route] = location.hash.split('/')
  627. if (goodByUser?.status === 'PENDING') {
  628. main.innerHTML = `
  629. <div class="d-flex justify-content-center">
  630. <div class="spinner-border text-primary" role="status">
  631. <span class="visually-hidden">Loading...</span>
  632. </div>
  633. </div>
  634. `
  635. } else {
  636. if (goodByUser?.payload && route === 'orders'){
  637. main.innerHTML = ''
  638. const cardBody = document.createElement('div')
  639. cardBody.classList = 'cardBody'
  640. main.append(cardBody)
  641. if (goodByUser.payload) {
  642. let totalMoney = 0
  643. for (const order of goodByUser.payload) {
  644. if (order.orderGoods) {
  645. for (const {price, count, total, good} of order.orderGoods) {
  646. if (price !== null && count !== null && total !== null && good !== null) {
  647. totalMoney += total
  648. const {_id, name, images} = good
  649. const card = document.createElement('div')
  650. card.classList = 'card goodCard'
  651. card.innerHTML = `
  652. <img class="card-img-top" src="${backURL}/${images[0].url}" />
  653. <div class="card-body">
  654. <h4 class="card-title">${name}</h4>
  655. <h6>
  656. Куплено: ${count} по ${price} грн.
  657. </h6>
  658. <h6>
  659. Итого: ${total} грн
  660. </h6>
  661. <a class="btn btn-primary" href="#/good/${_id}">
  662. Подробнее
  663. </a>
  664. </div>
  665. `
  666. cardBody.append(card)
  667. }
  668. }
  669. }
  670. }
  671. const totalBlock = document.createElement('h3')
  672. totalBlock.innerText = 'Итого потрачено: ' + totalMoney + ' грн'
  673. main.append(totalBlock)
  674. }
  675. }
  676. }
  677. })
  678. store.subscribe(() => {
  679. const {cart} = store.getState()
  680. let counter = 0;
  681. for (const key in cart) {
  682. counter += cart[key].count
  683. }
  684. cartCounter.innerText = counter
  685. })
  686. store.subscribe(() => {
  687. const {promise} = store.getState()
  688. const {goodFind} = promise
  689. const [,route] = location.hash.split('/')
  690. if (goodFind?.status === 'PENDING') {
  691. main.innerHTML = `
  692. <div class="spinner-border text-primary" role="status">
  693. <span class="visually-hidden">Loading...</span>
  694. </div>
  695. `
  696. } else {
  697. if (goodFind?.payload && route === 'find') {
  698. main.innerHTML = ''
  699. if (goodFind?.payload.length > 0) {
  700. const cardBody = document.createElement('div')
  701. cardBody.classList = 'cardBody'
  702. main.append(cardBody)
  703. for (const good of goodFind.payload) {
  704. const {_id, name, price, images} = good
  705. const card = document.createElement('div')
  706. card.classList = 'card goodCard'
  707. card.innerHTML = `
  708. <img class="card-img-top" src="${backURL}/${images[0].url}" />
  709. <div class="card-body">
  710. <h4 class="card-title">${name}</h4>
  711. <h5>${price} грн</h5>
  712. <a class="btn btn-primary" href="#/good/${_id}">
  713. Подробнее
  714. </a>
  715. </div>
  716. `
  717. const btnCart = document.createElement('button')
  718. btnCart.innerText = 'В корзину'
  719. btnCart.classList = 'btn btn-success btnCart'
  720. btnCart.onclick = () => {
  721. store.dispatch(actionCartAdd(good))
  722. }
  723. card.lastElementChild.append(btnCart)
  724. cardBody.append(card)
  725. }
  726. } else {
  727. main.innerHTML = 'Результаты не найдены'
  728. }
  729. }
  730. }
  731. })
  732. findField.oninput = () => {
  733. window.location.hash = `#/find`
  734. store.dispatch(actionGoodFind(findField.value))
  735. }