index.js 35 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908
  1. const delay = ms => new Promise(ok => setTimeout(() => ok(ms), ms))
  2. function createStore(reducer) {
  3. let state = reducer(undefined, {}) //стартовая инициализация состояния, запуск редьюсера со state === undefined
  4. let cbs = [] //массив подписчиков
  5. const getState = () => state //функция, возвращающая переменную из замыкания
  6. const subscribe = cb => (cbs.push(cb), //запоминаем подписчиков в массиве
  7. () => cbs = cbs.filter(c => c !== cb)) //возвращаем функцию unsubscribe, которая удаляет подписчика из списка
  8. const dispatch = action => {
  9. if (typeof action === 'function') { //если action - не объект, а функция
  10. return action(dispatch, getState) //запускаем эту функцию и даем ей dispatch и getState для работы
  11. }
  12. const newState = reducer(state, action) //пробуем запустить редьюсер
  13. if (newState !== state) { //проверяем, смог ли редьюсер обработать action
  14. state = newState //если смог, то обновляем state
  15. /*state = {
  16. ...state,
  17. ...newState
  18. }*/ //если смог, то обновляем state
  19. for (let cb of cbs) cb() //и запускаем подписчиков
  20. }
  21. }
  22. return {
  23. getState, //добавление функции getState в результирующий объект
  24. dispatch,
  25. subscribe //добавление subscribe в объект
  26. }
  27. }
  28. function promiseReducer(state = {}, { promiseName, type, status, payload, error }) {
  29. if (type === 'PROMISE') {
  30. return {
  31. ...state,
  32. [promiseName]: { status, payload, error }
  33. }
  34. }
  35. return state
  36. }
  37. const actionPending = promiseName => ({ promiseName, type: 'PROMISE', status: 'PENDING' })
  38. const actionFulfilled = (promiseName, payload) => ({ promiseName, type: 'PROMISE', status: 'FULFILLED', payload })
  39. const actionRejected = (promiseName, error) => ({ promiseName, type: 'PROMISE', status: 'REJECTED', error })
  40. const actionPromise = (promiseName, promise) =>
  41. async dispatch => {
  42. dispatch(actionPending(promiseName)) //сигнализируем redux, что промис начался
  43. try {
  44. const payload = await promise //ожидаем промиса
  45. dispatch(actionFulfilled(promiseName, payload)) //сигнализируем redux, что промис успешно выполнен
  46. return payload //в месте запуска store.dispatch с этим thunk можно так же получить результат промиса
  47. }
  48. catch (error) {
  49. dispatch(actionRejected(promiseName, error)) //в случае ошибки - сигнализируем redux, что промис несложился
  50. }
  51. }
  52. // const store = createStore(promiseReducer)
  53. // store.subscribe(() => console.log(store.getState()))
  54. // store.dispatch(actionPromise('delay', delay(1000)))
  55. // store.dispatch(actionPromise('luke', fetch("https://swapi.dev/api/people/1").then(res => res.json())))
  56. // store.dispatch(actionPromise('tatooine', fetch("https://swapi.dev/api/planets/1").then(res => res.json())))
  57. //по итогу должен получится какой-то такой state:
  58. /*
  59. {
  60. delay: {status: 'FULFILLED', payload: 1000, error: undefined},
  61. luke: {status: 'FULFILLED', payload: { ..... траливали про люка}, error: undefined},
  62. tatooine: {status: 'FULFILLED', payload: { ..... траливали про планету татуин}, error: undefined},
  63. }
  64. */
  65. /* authReducer
  66. Этот редьюсер предназначен для хранения и обработки состояния залогиненности пользователя. У него бывает два вида состояний:
  67. Залогинен. state вида: {token: "jwt токен", payload: {.....раскодированная инфа из токена}}
  68. Незалогинен. state вида: {} (пустой объект)
  69. Редьюсер обрабатывает два типа экшонов:
  70. AUTH_LOGIN. Логинимся. Редьюсер должен попытаться раскодировать токен, и если это получилось, вернуть новый стейт вида {token, payload} Если токен не удалось раскодировать, вернуть пустой объект (т. е. разлогиниться)
  71. AUTH_LOGOUT. Разлогиниваемся. Возвращаем пустой объект.
  72. Проверочный код (и экшенкриейторы бонусом):*/
  73. function jwtDecode(token) {
  74. let result;
  75. try {
  76. let secondPartToken = token.split('.')[1];
  77. result = JSON.parse(atob(secondPartToken));
  78. } catch (e) {
  79. }
  80. return result;
  81. }
  82. function authReducer(state = {}, { type, token }) {
  83. if (type === 'AUTH_LOGIN') {
  84. payload = jwtDecode(token)
  85. console.log(payload)
  86. if (payload) return { token, payload }
  87. }
  88. if (type === 'AUTH_LOGOUT') return {}
  89. return state
  90. }
  91. const actionAuthLogin = token => ({ type: 'AUTH_LOGIN', token })
  92. const actionAuthLogout = () => ({ type: 'AUTH_LOGOUT' })
  93. // const store = createStore(authReducer)
  94. // store.subscribe(() => console.log(store.getState()))
  95. // store.dispatch(actionAuthLogout())
  96. // store.dispatch(actionAuthLogin(token))
  97. /*{
  98. token: "eyJhbGc.....",
  99. payload: {
  100. "sub": {
  101. "id": "6377e133b74e1f5f2ec1c125",
  102. "login": "test5",
  103. "acl": [
  104. "6377e133b74e1f5f2ec1c125",
  105. "user"
  106. ]
  107. },
  108. "iat": 1668812458
  109. }
  110. }*/
  111. //store.dispatch(actionAuthLogout()) // {}
  112. /*cartReducer
  113. Редьюсер, который хранит состояние корзины во время серфинга по интернет-магазину. Похож на редьюсер ларька, только работает немного наоборот - в то время как в ларьке товар уменьшается, тут он добавляется.
  114. Состояние:
  115. {
  116. idТовара1: {count: количество1, good: {....инфа про товар с бэкэнда, включая цену, описание и картинки}},
  117. idТовара2: {count: количество2, good: {....инфа про товар с бэкэнда, включая цену, описание и картинки}},
  118. }
  119. Типы экшенов
  120. */
  121. // Добавление товара. Должен добавлять новый ключ в state, или обновлять, если ключа в state ранее не было, увеличивая количество
  122. const actionCartAdd = (good, count = 1) => ({ type: 'CART_ADD', count, good })
  123. // Уменьшение количества товара. Должен уменьшать количество товара в state, или удалять его если количество будет 0 или отрицательным
  124. const actionCartSub = (good, count = 1) => ({ type: 'CART_SUB', count, good })
  125. // Удаление товара. Должен удалять ключ из state
  126. const actionCartDel = (good) => ({ type: 'CART_DEL', good })
  127. // Задание количества товара. В отличие от добавления и уменьшения, не учитывает того количества, которое уже было в корзине, а тупо назначает количество поверху (или создает новый ключ, если в корзине товара не было). Если count 0 или отрицательное число - удаляем ключ из корзины;
  128. const actionCartSet = (good, count = 1) => ({ type: 'CART_SET', count, good })
  129. // Очистка корзины. state должен стать пустым объектом {}
  130. const actionCartClear = () => ({ type: 'CART_CLEAR' })
  131. /*
  132. Проверочный код
  133. Наличие good как ключа в state немного избыточно - для оформления заказа достаточно id и количества. Однако это позволит хранить в редьюсере всю нужную информацию, например, для отображения страницы корзины без лишних запросов на сервер. На странице корзины обычно есть и описание, и цена, и фоточки.
  134. Проверочный код ниже использует в качестве id более наглядные вещи.
  135. */
  136. function cartReducer(state = {}, { type, count, good }) {
  137. if (type === 'CART_ADD') {
  138. if (good._id in state) {
  139. return {
  140. ...state,
  141. [good._id]: {
  142. good,
  143. count: state[good._id].count + count
  144. }
  145. }
  146. } else {
  147. return {
  148. ...state,
  149. [good._id]: {
  150. good,
  151. count
  152. }
  153. }
  154. }
  155. }
  156. if (type === 'CART_SUB') {
  157. let result = {}
  158. if (good._id in state) {
  159. if ((state[good._id].count - count) <= 0) {
  160. newState = { ...state }
  161. delete newState[good._id]
  162. return newState
  163. } else {
  164. return {
  165. ...state,
  166. [good._id]: {
  167. good,
  168. count: state[good._id].count - count
  169. }
  170. }
  171. }
  172. }
  173. }
  174. if (type === 'CART_DEL') {
  175. let newState = { ...state }
  176. delete newState[good._id]
  177. return newState
  178. }
  179. // Задание количества товара. В отличие от добавления и уменьшения, не учитывает того количества, которое уже было в корзине, а тупо назначает количество поверху (или создает новый ключ, если в корзине товара не было). Если count 0 или отрицательное число - удаляем ключ из корзины;
  180. if (type === 'CART_SET') {
  181. if (good._id in state && count <= 0) {
  182. let newState = { ...state }
  183. delete newState[good._id]
  184. return newState
  185. } else {
  186. let newState = {
  187. ...state,
  188. [good._id]: {
  189. good,
  190. count
  191. }
  192. }
  193. return newState
  194. }
  195. }
  196. if (type === 'CART_CLEAR') return {}
  197. return state
  198. }
  199. function combineReducers(reducers) {
  200. function totalReducer(state = {}, action) {
  201. const newTotalState = {}
  202. for (const [reducerName, reducer] of Object.entries(reducers)) {
  203. const newSubState = reducer(state[reducerName], action)
  204. if (newSubState !== state[reducerName]) {
  205. newTotalState[reducerName] = newSubState
  206. }
  207. }
  208. if (Object.keys(newTotalState).length) {
  209. return { ...state, ...newTotalState }
  210. }
  211. return state
  212. }
  213. return totalReducer
  214. }
  215. const reducers = {
  216. promise: promiseReducer, //допилить много имен для многих промисо
  217. auth: authReducer, //часть предыдущего ДЗ
  218. cart: cartReducer, //часть предыдущего ДЗ
  219. }
  220. const totalReducer = combineReducers(reducers)
  221. function localStoredReducer(originalReducer, localStorageKey) {
  222. let counter = 0
  223. function wrapper(state, action) {
  224. if (counter === 0) {
  225. counter++
  226. if (localStorage[localStorageKey]) {
  227. let result = JSON.parse(localStorage[localStorageKey])
  228. if (result) return result
  229. }
  230. } else {
  231. let result = originalReducer(state, action)
  232. localStorage[localStorageKey] = JSON.stringify(result)
  233. return result
  234. }
  235. }
  236. return wrapper
  237. }
  238. const store = createStore(localStoredReducer(totalReducer, "state")) //не забудьте combineReducers если он у вас уже есть
  239. store.subscribe(() => console.log(store.getState()))
  240. const actionRootCats = () => actionPromise("RootCats", gqlRootCats())
  241. store.dispatch(actionRootCats())
  242. store.subscribe(() => {
  243. let result = store.getState().promise.RootCats.payload
  244. if (result) {
  245. aside.innerHTML = ""
  246. for (let { _id, name } of result) {
  247. aside.innerHTML += `<a href='#/category/${_id}'>${name}</a>`
  248. }
  249. }
  250. })
  251. const actionCategoryGoodsAndSubCategoryGoods = (_id) => actionPromise("CategoryGoodsAndSubCategoryGoods", gqlCategoryGoodsAndSubCategoryGoods(_id))
  252. store.dispatch(actionCategoryGoodsAndSubCategoryGoods('62c94b10b74e1f5f2ec1a0dd'))
  253. store.subscribe(() => {
  254. if (location.hash === '') {
  255. const { status, payload, error } = store.getState().promise.CategoryGoodsAndSubCategoryGoods
  256. if (status === 'PENDING') {
  257. main.innerHTML = `<img src='giphy.gif'/>`
  258. }
  259. if (status === 'FULFILLED') {
  260. if (payload) categotyToMain(payload)
  261. }
  262. if (status === 'REJECTED') {
  263. main.innerHTML = `Что-то пошло не так, но мы скоро всё починим ;( \n сообщить о проблеме: <a href="mailto:mail@example.com">blablatest123@gmail.com</a>`
  264. }
  265. }
  266. })
  267. function categotyToMain(result) {
  268. main.innerHTML = ""
  269. let breadcrumbsContainer = document.createElement('div')
  270. main.append(breadcrumbsContainer)
  271. breadcrumbsContainer.classList.add('breadcrumbsContainer')
  272. let breadcrumbsHome = document.createElement('a')
  273. breadcrumbsContainer.append(breadcrumbsHome)
  274. breadcrumbsHome.innerText = 'Mystore.com'
  275. breadcrumbsHome.href = location.origin + location.pathname
  276. if (result.parent){
  277. breadcrumbsContainer.innerHTML += `<div>></div><div>...</div><div>></div><a href='#/category/${result.parent._id}'>${result.parent.name}</a>`
  278. }
  279. let h1 = document.createElement('h1')
  280. h1.innerText = result.name
  281. main.append(h1)
  282. if (result.subCategories && result.subCategories.length>0){
  283. const containerSubcategoryLink = document.createElement('div')
  284. containerSubcategoryLink.classList.add('containerSubcategoryLink')
  285. main.append(containerSubcategoryLink)
  286. for(const {_id, name} of result.subCategories){
  287. console.log(name)
  288. let subcategoryLink = document.createElement('a')
  289. containerSubcategoryLink.append(subcategoryLink)
  290. subcategoryLink.href = `#/category/${_id}`
  291. subcategoryLink.classList.add('subcategoryLink')
  292. subcategoryLink.innerText = name
  293. }
  294. }
  295. if (!result.goods){
  296. let emptyCategoryMessage = document.createElement('div')
  297. main.append(emptyCategoryMessage)
  298. emptyCategoryMessage.innerText = 'В этой категории пока нет товаров :('
  299. return
  300. }
  301. for (let { _id, name, images, price} of result?.goods) {
  302. let card = document.createElement('a')
  303. card.id = _id
  304. card.href = `#/good/${_id}`
  305. card.classList.add('card')
  306. main.append(card)
  307. let cardImgWrapper = document.createElement('div')
  308. cardImgWrapper.classList.add('cardImgWrapper')
  309. card.append(cardImgWrapper)
  310. let cardImg = document.createElement('img')
  311. cardImg.classList.add('cardImg')
  312. cardImgWrapper.append(cardImg)
  313. if (images.length>0){
  314. let { url } = images[0]
  315. cardImg.src = hostname + url
  316. } else{
  317. cardImg.src = 'https://lukachi.com.ua/source/default-image.jpg'
  318. }
  319. let cardName = document.createElement('h2')
  320. cardName.classList.add('cardName')
  321. cardName.innerText = name
  322. card.append(cardName)
  323. let cardPrice = document.createElement('div')
  324. cardPrice.classList.add('cardPrice')
  325. cardPrice.innerText = price + " грн"
  326. card.append(cardPrice)
  327. }
  328. }
  329. let hostname = 'http://shop-roles.node.ed.asmer.org.ua/'
  330. // store.subscribe(() => {
  331. // let result = store.getState().promise.CategoryGoodsAndSubCategoryGoods?.payload
  332. // const [, route, _id] = location.hash.split('/')
  333. // if (result && route === "category") {
  334. // categotyToMain(result)
  335. // }
  336. // })
  337. store.subscribe(() => {
  338. const [, route, _id] = location.hash.split('/')
  339. if (route !== "category") return
  340. const { status, payload, error } = store.getState().promise.CategoryGoodsAndSubCategoryGoods
  341. if (status === 'PENDING') {
  342. main.innerHTML = `<img src='giphy.gif'/>`
  343. }
  344. if (status === 'FULFILLED' && payload) {
  345. categotyToMain(payload)
  346. }
  347. if (status === 'REJECTED') {
  348. main.innerHTML = `Что-то пошло не так, но мы скоро всё починим ;( \n сообщить о проблеме: <a href="mailto:mail@example.com">blablatest123@gmail.com</a>`
  349. }
  350. })
  351. // store.subscribe(() => {
  352. // let result = store.getState().promise.OneGoodWithDescriptionAndImages?.payload
  353. // const [, route, _id] = location.hash.split('/')
  354. // if (result && route === "good") {
  355. // main.innerHTML = ""
  356. // let h1 = document.createElement('h1')
  357. // h1.innerText = result.name
  358. // main.append(h1)
  359. // let imgSlider = document.createElement('div')
  360. // imgSlider.classList.add('imgSlider')
  361. // main.append(imgSlider)
  362. // for (let { url } of result.images) {
  363. // imgSlider.innerHTML += `<img class="sliderImg" src="${hostname + url}"></img>`
  364. // }
  365. // let asideDesriptionPriceCard = document.createElement('div')
  366. // asideDesriptionPriceCard.classList.add('asideDesriptionPriceCard')
  367. // main.append(asideDesriptionPriceCard)
  368. // let goodPrice = document.createElement('div')
  369. // goodPrice.classList.add('goodPrice')
  370. // goodPrice.innerText = result.price + ' грн'
  371. // asideDesriptionPriceCard.append(goodPrice)
  372. // let goodCountInput = document.createElement('input')
  373. // goodCountInput.classList.add('goodCountInput')
  374. // goodCountInput.type = 'number'
  375. // goodCountInput.min = 1
  376. // goodCountInput.value = 1
  377. // asideDesriptionPriceCard.append(goodCountInput)
  378. // let goodButtonAddToCart = document.createElement('a')
  379. // goodButtonAddToCart.classList.add('goodButtonAddToCart')
  380. // goodButtonAddToCart.innerText = 'В КОРЗИНУ'
  381. // goodButtonAddToCart.onclick = () => store.dispatch(actionCartAdd(result, +goodCountInput.value))
  382. // asideDesriptionPriceCard.append(goodButtonAddToCart)
  383. // let description = document.createElement('div')
  384. // description.classList.add('description')
  385. // description.innerText = result.description
  386. // asideDesriptionPriceCard.append(description)
  387. // }
  388. // })
  389. store.subscribe(() => {
  390. const [, route, _id] = location.hash.split('/')
  391. if (route !== "good") return
  392. const { status, payload, error } = store.getState().promise.OneGoodWithDescriptionAndImages
  393. if (status === 'PENDING') {
  394. main.innerHTML = `<img src='giphy.gif'/>`
  395. }
  396. if (status === 'FULFILLED' && payload) {
  397. main.innerHTML = ""
  398. let h1 = document.createElement('h1')
  399. h1.innerText = payload.name
  400. main.append(h1)
  401. let imgSlider = document.createElement('div')
  402. imgSlider.classList.add('imgSlider')
  403. main.append(imgSlider)
  404. for (let { url } of payload.images) {
  405. imgSlider.innerHTML += `<img class="sliderImg" src="${hostname + url}"></img>`
  406. }
  407. let asideDesriptionPriceCard = document.createElement('div')
  408. asideDesriptionPriceCard.classList.add('asideDesriptionPriceCard')
  409. main.append(asideDesriptionPriceCard)
  410. let goodPrice = document.createElement('div')
  411. goodPrice.classList.add('goodPrice')
  412. goodPrice.innerText = payload.price + ' грн'
  413. asideDesriptionPriceCard.append(goodPrice)
  414. let goodCountInput = document.createElement('input')
  415. goodCountInput.classList.add('goodCountInput')
  416. goodCountInput.type = 'number'
  417. goodCountInput.min = 1
  418. goodCountInput.value = 1
  419. asideDesriptionPriceCard.append(goodCountInput)
  420. let goodButtonAddToCart = document.createElement('a')
  421. goodButtonAddToCart.classList.add('goodButtonAddToCart')
  422. goodButtonAddToCart.innerText = 'В КОРЗИНУ'
  423. goodButtonAddToCart.onclick = () => store.dispatch(actionCartAdd(payload, +goodCountInput.value))
  424. asideDesriptionPriceCard.append(goodButtonAddToCart)
  425. let description = document.createElement('div')
  426. description.classList.add('description')
  427. description.innerText = payload.description
  428. asideDesriptionPriceCard.append(description)
  429. }
  430. if (status === 'REJECTED') {
  431. main.innerHTML = `Что-то пошло не так, но мы скоро всё починим ;( \n сообщить о проблеме: <a href="mailto:mail@example.com">blablatest123@gmail.com</a>`
  432. }
  433. })
  434. const actionOneGoodWithDescriptionAndImages = (_id) => actionPromise("OneGoodWithDescriptionAndImages", gqlOneGoodWithDescriptionAndImages(_id))
  435. store.subscribe(() => {
  436. let result = 0
  437. let arrResult = Object.values(store.getState().cart)
  438. for (let { count } of arrResult) {
  439. result += count
  440. }
  441. cartIcon.innerText = result
  442. })
  443. store.subscribe(() => {
  444. let arrResult = Object.values(store.getState().cart)
  445. const [, route, _id] = location.hash.split('/')
  446. if (arrResult && route === "cart") {
  447. cartPageAdd()
  448. }
  449. })
  450. window.onhashchange = () => {
  451. const [, route, _id] = location.hash.split('/')
  452. const routes = {
  453. category() {
  454. store.dispatch(actionCategoryGoodsAndSubCategoryGoods(_id))
  455. },
  456. good() {
  457. store.dispatch(actionOneGoodWithDescriptionAndImages(_id))
  458. // тут был store.dispatch goodById
  459. console.log('good', _id)
  460. },
  461. // login() {
  462. // console.log('А ТУТ ЩА ДОЛЖНА БЫТЬ ФОРМА ЛОГИНА')
  463. // //нарисовать форму логина, которая по нажатию кнопки Login делает store.dispatch(actionFullLogin(login, password))
  464. // },
  465. // register() {
  466. // //нарисовать форму регистрации, которая по нажатию кнопки Login делает store.dispatch(actionFullRegister(login, password))
  467. // },
  468. cart() {
  469. cartPageAdd()
  470. },
  471. orders() {
  472. store.dispatch(actionOrderFind())
  473. }
  474. }
  475. if (route in routes) {
  476. routes[route]()
  477. }
  478. }
  479. function cartPageAdd() {
  480. let arrResult = Object.values(store.getState().cart)
  481. const [, route, _id] = location.hash.split('/')
  482. if (arrResult && route === "cart") {
  483. main.innerHTML = ""
  484. let h1 = document.createElement('h1')
  485. h1.innerText = "Оформление заказа"
  486. main.append(h1)
  487. let orderTable = document.createElement('table')
  488. main.append(orderTable)
  489. orderTable.classList.add('table_price')
  490. let orderTableBody = document.createElement('tbody')
  491. orderTable.append(orderTableBody)
  492. let firstTr = document.createElement('tr')
  493. orderTableBody.append(firstTr)
  494. firstTr.innerHTML = `<th></th><th>Название товара</th><th>Цена шт.</th><th>Количество</th><th>Итого</th><th></th>`
  495. let counter = 1
  496. let summ = 0
  497. for (let { good, count } of arrResult) {
  498. console.log(count)
  499. let trGood = document.createElement('tr')
  500. orderTableBody.append(trGood)
  501. trGood.innerHTML = `
  502. <td>${counter}</td>
  503. <td>${good.name}</td>
  504. <td>${good.price}</td>`
  505. let tdInput = document.createElement('td')
  506. trGood.append(tdInput)
  507. let inputCount = document.createElement('input')
  508. inputCount.type = 'number'
  509. inputCount.min = 1
  510. inputCount.value = count
  511. inputCount.oninput = () => store.dispatch(actionCartSet(good, +inputCount.value))
  512. tdInput.append(inputCount)
  513. let tdSummGood = document.createElement('td')
  514. trGood.append(tdSummGood)
  515. tdSummGood.innerText = count * good.price
  516. let tdDelete = document.createElement('td')
  517. trGood.append(tdDelete)
  518. let deleteButton = document.createElement('button')
  519. tdDelete.append(deleteButton)
  520. deleteButton.classList.add('deleteButton')
  521. deleteButton.innerText = 'удалить'
  522. deleteButton.onclick = () => store.dispatch(actionCartDel(good))
  523. //НЕ РАБОТАЕТ!? trGood.innerHTML += `<td>${count * good.price}</td>`
  524. counter += 1
  525. summ += count * good.price
  526. }
  527. let trSummTotal = document.createElement('tr')
  528. orderTableBody.append(trSummTotal)
  529. let tdColspan = document.createElement('td')
  530. trSummTotal.append(tdColspan)
  531. tdColspan.colSpan = 4
  532. let tdSummTotal = document.createElement('td')
  533. trSummTotal.append(tdSummTotal)
  534. tdSummTotal.innerText = summ +'грн'
  535. tdSummTotal.classList.add('tdSumm')
  536. let tdDeleteTotal = document.createElement('td')
  537. trSummTotal.append(tdDeleteTotal)
  538. let DeleteTotalButton = document.createElement('button')
  539. tdDeleteTotal.append(DeleteTotalButton)
  540. DeleteTotalButton.innerText = 'ОЧИСТИТЬ КОРЗИНУ'
  541. DeleteTotalButton.onclick = () => store.dispatch(actionCartClear())
  542. //orderTableBody.innerHTML += `<tr><td colspan="4"></td><td class="tdSumm">${summ} грн</td></tr>`
  543. let addOrder = document.createElement('div')
  544. addOrder.classList.add('addOrder')
  545. addOrder.innerText = "ОФОРМИТЬ ЗАКАЗ"
  546. main.append(addOrder)
  547. let arrGoods = Object.values(store.getState().cart).map(({ count, good: { _id } }) => { return { count, good: { _id } } })
  548. addOrder.onclick = () => {
  549. store.dispatch(actionFullAddOrder(arrGoods))
  550. }
  551. }
  552. }
  553. const actionAddOrder = (arrGoods) => {
  554. return actionPromise("orderUpsert", OrderUpsert2(arrGoods))
  555. }
  556. const actionFullAddOrder = (arrGoods) =>
  557. async dispatch => {
  558. await dispatch(actionAddOrder(arrGoods))
  559. dispatch(actionCartClear())
  560. location.hash = '#/orders'
  561. }
  562. const actionLogin = (login, password) => {
  563. return actionPromise("logAndPass", gqllogin(login, password))
  564. }
  565. const actionRegistration = (login, password) => {
  566. return actionPromise("newUser", gqlUserAdd(login, password))
  567. }
  568. const actionFullLogin = (login, password) =>
  569. async dispatch => {
  570. //dispatch возвращает то, что вернул thunk, возвращаемый actionLogin, а там промис,
  571. //так как actionPromise возвращает асинхронную функцию
  572. const token = await dispatch(actionLogin(login, password))
  573. //проверьте что token - строка и отдайте его в actionAuthLogin
  574. if (typeof token === 'string')
  575. store.dispatch(actionAuthLogin(token))
  576. }
  577. const actionFullRegistration = (login, password) =>
  578. async dispatch => {
  579. const userAddPromis = await dispatch(actionRegistration(login, password))
  580. if (userAddPromis) store.dispatch(actionFullLogin(login, password))
  581. //dispatch возвращает то, что вернул thunk, возвращаемый actionLogin, а там промис,
  582. //так как actionPromise возвращает асинхронную функцию
  583. //const token = await dispatch(actionLogin(login, password))
  584. //проверьте что token - строка и отдайте его в actionAuthLogin
  585. //if (typeof token === 'string')
  586. // store.dispatch(actionAuthLogin(token))
  587. }
  588. const actionOrderFind = () =>
  589. actionPromise("OrderFind", gqlOrderFind())
  590. function LoginFormFunc(parent, passOpenDefault = open) {
  591. function Password(parent, open) {
  592. const inputPass = document.createElement('input')
  593. parent.append(inputPass)
  594. const checkboxPass = document.createElement('input')
  595. checkboxPass.type = 'checkbox'
  596. parent.append(checkboxPass)
  597. this.setValue = (value) => {
  598. inputPass.value = value
  599. if (typeof this.onChange === 'function') this.onChange(this.getValue()) // запускается по событию oninput в поле ввода, передает текст в колбэк
  600. } //задает значение
  601. this.getValue = () => inputPass.value //читает значение
  602. this.setOpen = (open) => {
  603. if (open) {
  604. checkboxPass.checked = true
  605. inputPass.type = "text"
  606. }
  607. if (!open) {
  608. checkboxPass.checked = false
  609. inputPass.type = "password"
  610. }
  611. if (typeof this.onOpenChange === 'function') this.onOpenChange(this.getOpen()) // запускается по изменению состояния открытости пароля
  612. } //задает открытость текста в поле ввода
  613. this.getOpen = () => checkboxPass.checked //читает открытость текста в поле ввода
  614. this.setOpen(open)
  615. inputPass.oninput = () => this.setValue(this.getValue())
  616. checkboxPass.oninput = () => this.setOpen(this.getOpen())
  617. }
  618. const LoginForm = document.createElement('div')
  619. parent.append(LoginForm)
  620. const inputLogin = document.createElement('input')
  621. LoginForm.append(inputLogin)
  622. let p = new Password(LoginForm, passOpenDefault)
  623. const inputButton = document.createElement('input')
  624. inputButton.type = 'button'
  625. inputButton.value = 'войти'
  626. inputButton.id = 'signIn'
  627. LoginForm.append(inputButton)
  628. inputButton.disabled = true
  629. const newUserAddButton = document.createElement('input')
  630. newUserAddButton.type = 'button'
  631. newUserAddButton.value = 'зарегистрироваться'
  632. newUserAddButton.id = 'register'
  633. LoginForm.append(newUserAddButton)
  634. p.onChange = () => checkButtonDisabled()
  635. inputLogin.oninput = () => checkButtonDisabled()
  636. function checkButtonDisabled() {
  637. if (p.getValue() && inputLogin.value) inputButton.disabled = false
  638. else inputButton.disabled = true
  639. }
  640. this.getLogin = () => inputLogin.value
  641. this.setLogin = (value) => {
  642. inputLogin.value = value
  643. checkButtonDisabled()
  644. }
  645. this.getPass = () => p.getValue()
  646. this.setPass = (value) => {
  647. p.setValue(value)
  648. checkButtonDisabled()
  649. }
  650. inputButton.onclick = () => {
  651. if (typeof this.inputButtonOnclick === 'function') this.inputButtonOnclick() //функция при нажатии на кнопку войти
  652. }
  653. newUserAddButton.onclick = () => {
  654. if (typeof this.newUserAddButtonOnclick === 'function') this.newUserAddButtonOnclick()
  655. }
  656. }
  657. // let LoginFormInHeader = new LoginFormFunc(user)
  658. // LoginFormInHeader.inputButtonOnclick = function () {
  659. // store.dispatch(actionFullLogin(this.getLogin(), this.getPass()))
  660. // }
  661. //vasya321986320915sf5654755ssddgfg пороль
  662. store.subscribe(() => {
  663. let result = store.getState().auth?.payload?.sub?.login
  664. if (result) {
  665. user.innerHTML = result
  666. const buttonExitLogin = document.createElement('button')
  667. buttonExitLogin.innerHTML = 'выйти'
  668. buttonExitLogin.classList.add('buttonExitLogin')
  669. buttonExitLogin.onclick = () => {
  670. store.dispatch(actionAuthLogout())
  671. }
  672. user.append(buttonExitLogin)
  673. localStorage.authToken = store.getState().auth.token
  674. const buttonOrders = document.createElement('button')
  675. buttonOrders.innerHTML = 'история заказов'
  676. buttonOrders.classList.add('buttonOrders')
  677. buttonOrders.onclick = () => location.hash = '#/orders'
  678. user.append(buttonOrders)
  679. } else {
  680. delete localStorage.authToken
  681. user.innerHTML = "Unknown User"
  682. let LoginFormInHeader = new LoginFormFunc(user)
  683. LoginFormInHeader.inputButtonOnclick = () => {
  684. store.dispatch(actionFullLogin(this.getLogin(), this.getPass()))
  685. }
  686. LoginFormInHeader.newUserAddButtonOnclick = function () {
  687. store.dispatch(actionFullRegistration(this.getLogin(), this.getPass()))
  688. }
  689. }
  690. })
  691. // store.subscribe(() => {
  692. // let arrResult = store.getState().promise.OrderFind?.payload
  693. // const [, route, _id] = location.hash.split('/')
  694. // if (arrResult && route === "orders") {
  695. // main.innerHTML = ""
  696. // let h1 = document.createElement('h1')
  697. // h1.innerText = "История заказов"
  698. // main.append(h1)
  699. // let orderTable = document.createElement('table')
  700. // main.append(orderTable)
  701. // orderTable.classList.add('table_price')
  702. // let firstTr = document.createElement('tr')
  703. // orderTable.append(firstTr)
  704. // firstTr.innerHTML = `<th></th><th>Номер заказа</th><th>Дата</th><th>Сумма, грн</th>`
  705. // let counter = 1
  706. // let summ = 0
  707. // for (let { _id, createdAt, total } of arrResult) {
  708. // let createdAtFormat = new Date(+createdAt)
  709. // let year = createdAtFormat.getFullYear()
  710. // let month = createdAtFormat.getMonth() < 9 ? "0" + (createdAtFormat.getMonth() + 1) : createdAtFormat.getMonth() + 1
  711. // let day = createdAtFormat.getDate() < 9 ? "0" + (createdAtFormat.getDate() + 1) : createdAtFormat.getDate() + 1
  712. // let hours = createdAtFormat.getHours() < 10 ? "0" + (createdAtFormat.getHours()) : createdAtFormat.getHours()
  713. // let minutes = createdAtFormat.getMinutes() < 10 ? "0" + (createdAtFormat.getMinutes()) : createdAtFormat.getMinutes()
  714. // const createdAtForTable = `${year}.${month}.${day} ${hours}:${minutes} `
  715. // orderTable.innerHTML += `<tr>
  716. // <td>${counter}</td>
  717. // <td>${_id}</td>
  718. // <td>${createdAtForTable}</td>
  719. // <td>${total}</td>
  720. // </tr>`
  721. // counter += 1
  722. // summ += total
  723. // }
  724. // orderTable.innerHTML += `<tr><td colspan="3"></td><td class="tdSumm">${summ} грн</td></tr>`
  725. // }
  726. // })
  727. function ordersToHTML (payload) {
  728. main.innerHTML = ""
  729. let h1 = document.createElement('h1')
  730. h1.innerText = "История заказов"
  731. main.append(h1)
  732. let orderTable = document.createElement('table')
  733. main.append(orderTable)
  734. orderTable.classList.add('table_price')
  735. let orderTableBody = document.createElement('tbody')
  736. orderTable.append(orderTableBody)
  737. let firstTr = document.createElement('tr')
  738. orderTableBody.append(firstTr)
  739. firstTr.innerHTML = `<th></th><th>Номер заказа</th><th>Дата</th><th>Сумма, грн</th>`
  740. let counter = 1
  741. let summ = 0
  742. let arrRevers = [...payload]
  743. for (let { _id, createdAt, total } of arrRevers.reverse()) {
  744. let createdAtFormat = new Date(+createdAt)
  745. let year = createdAtFormat.getFullYear()
  746. let month = createdAtFormat.getMonth() < 9 ? "0" + (createdAtFormat.getMonth() + 1) : createdAtFormat.getMonth() + 1
  747. let day = createdAtFormat.getDate() < 10 ? "0" + (createdAtFormat.getDate()) : createdAtFormat.getDate()
  748. let hours = createdAtFormat.getHours() < 10 ? "0" + (createdAtFormat.getHours()) : createdAtFormat.getHours()
  749. let minutes = createdAtFormat.getMinutes() < 10 ? "0" + (createdAtFormat.getMinutes()) : createdAtFormat.getMinutes()
  750. const createdAtForTable = `${year}.${month}.${day} ${hours}:${minutes} `
  751. orderTableBody.innerHTML += `<tr>
  752. <td>${counter}</td>
  753. <td>${_id}</td>
  754. <td>${createdAtForTable}</td>
  755. <td>${total}</td>
  756. </tr>`
  757. counter += 1
  758. summ += total
  759. }
  760. orderTableBody.innerHTML += `<tr><td colspan="3"></td><td class="tdSumm">${summ} грн</td></tr>`
  761. }
  762. store.subscribe(() => {
  763. const [, route, _id] = location.hash.split('/')
  764. if (route !== "orders") return
  765. const {status, payload, error} = store.getState().promise.OrderFind
  766. if (status === 'PENDING') {
  767. main.innerHTML = `<img src='giphy.gif'/>`
  768. }
  769. if (status === 'FULFILLED' && payload) {
  770. ordersToHTML (payload)
  771. }
  772. if (status === 'REJECTED') {
  773. main.innerHTML = `Что-то пошло не так, но мы скоро всё починим ;( \n сообщить о проблеме: <a href="mailto:mail@example.com">blablatest123@gmail.com</a>`
  774. }
  775. })