main.js 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716
  1. function createStore(reducer) {
  2. let state = reducer(undefined, {})
  3. let cbs = []
  4. function dispatch(action) {
  5. if (typeof action === 'function') {
  6. return action(dispatch)
  7. }
  8. const newState = reducer(state, action)
  9. if (state !== newState) {
  10. state = newState
  11. cbs.forEach(cb => cb())
  12. }
  13. }
  14. return {
  15. dispatch,
  16. subscribe(cb) {
  17. cbs.push(cb)
  18. return () => cbs = cbs.filter(c => c !== cb)
  19. },
  20. getState() {
  21. return state
  22. }
  23. }
  24. }
  25. //reducers
  26. function promiseReducer(state = {}, { type, status, payload, error, name }) {
  27. if (type === 'PROMISE') {
  28. return {
  29. ...state,
  30. [name]: { status, payload, error }
  31. }
  32. }
  33. return state
  34. }
  35. function cartReducer(state = {}, { type, count = 1, _id, name }) {
  36. if (type === "CART_ADD") {
  37. return {
  38. ...state,
  39. [_id]: {
  40. name: name,
  41. count: state[_id] ? state[_id].count + count : count
  42. }
  43. }
  44. }
  45. if (type === "CART_CHANGE") {
  46. return {
  47. ...state,
  48. [_id]: {
  49. name: name,
  50. count: count
  51. }
  52. }
  53. }
  54. if (type === "CART_REMOVE") {
  55. let { [_id]: a, ...rest } = state
  56. return rest
  57. }
  58. if (type === "CART_CLEAR") {
  59. return {}
  60. }
  61. return state
  62. }
  63. function authReducer(state, { type, token }) {
  64. if (state === undefined) {
  65. if (localStorage.authToken) {
  66. type = "LOGIN"
  67. token = localStorage.authToken
  68. } else {
  69. return {}
  70. }
  71. }
  72. if (type === "LOGIN") {
  73. localStorage.authToken = token
  74. return { token, payload: jwt_decode(token) }
  75. }
  76. if (type === "LOGOUT") {
  77. localStorage.removeItem("authToken")
  78. return {}
  79. }
  80. return state
  81. }
  82. let reducers = {
  83. promise: promiseReducer,
  84. cart: cartReducer,
  85. auth: authReducer
  86. }
  87. let jwt_decode = (token) => {
  88. let result = JSON.parse(atob(token.split(".")[1]))
  89. return result
  90. }
  91. function combineReducers(reducers) {
  92. function commonReducer(state = {}, action) {
  93. let newState = {}
  94. for (let key in reducers) {
  95. let innerState = reducers[key](state[key], action)
  96. innerState === state[key] ? newState[key] = state[key] : newState[key] = innerState
  97. }
  98. return newState
  99. }
  100. return commonReducer
  101. }
  102. const store = createStore(combineReducers(reducers))
  103. //запросики
  104. const getGQL = url => {
  105. return (query, variables) => {
  106. return fetch(url, {
  107. method: 'POST',
  108. headers: {
  109. "content-type": "application/json",
  110. ...(localStorage.authToken ? { Authorization: "Bearer " + localStorage.authToken } : {})
  111. },
  112. body: JSON.stringify({ query, variables }),
  113. }).then(res => res.json())
  114. }
  115. }
  116. let shopGQL = getGQL('http://shop-roles.asmer.fs.a-level.com.ua/graphql')
  117. let categoryById = async (id) => {
  118. let query = `query fndcategory($id: String) {
  119. CategoryFind(query: $id){
  120. name goods{
  121. _id name price images {
  122. url
  123. }
  124. }
  125. }
  126. }`
  127. let qVariables = {
  128. "id": JSON.stringify([{ "_id": id }])
  129. }
  130. let res = await shopGQL(query, qVariables)
  131. console.log(res)
  132. return res
  133. }
  134. let goodById = async (id) => {
  135. let query = `query fndgood($id: String) {
  136. GoodFind(query: $id){
  137. name description price images {
  138. url
  139. }
  140. }
  141. }`
  142. let qVariables = {
  143. "id": JSON.stringify([{ "_id": id }])
  144. }
  145. let res = await shopGQL(query, qVariables)
  146. return res
  147. }
  148. let newOrder = async (obj) => {
  149. let option = Object.entries(obj);
  150. let orderGoods = [];
  151. for (let key of option) {
  152. let i = {
  153. "count": key[1],
  154. "good": { "_id": key[0] }
  155. }
  156. orderGoods.push(i);
  157. }
  158. let query = `mutation sndOrder($order: OrderInput) {
  159. OrderUpsert(order: $order) {
  160. _id createdAt total
  161. }
  162. }`
  163. let qVariables = {
  164. "order": {
  165. "orderGoods": orderGoods
  166. }
  167. }
  168. let res = await shopGQL(query, qVariables)
  169. console.log(res)
  170. return res
  171. }
  172. let log = async (login, password) => {
  173. let query = `query log($l: String, $p: String) {
  174. login(login: $l, password: $p)
  175. }`
  176. let qVariables = {
  177. "l": login,
  178. "p": password
  179. }
  180. let token = await shopGQL(query, qVariables)
  181. console.log(token)
  182. return token.data.login
  183. }
  184. let reg = async (login, password) => {
  185. let query = `mutation reg($l: String, $p: String) {
  186. UserUpsert(user: {
  187. login: $l,
  188. password: $p
  189. } ) {
  190. _id
  191. }
  192. }`
  193. let qVariables = {
  194. "l": login,
  195. "p": password
  196. }
  197. let res = await shopGQL(query, qVariables)
  198. return res
  199. }
  200. //actions
  201. const actionPending = name => ({ type: 'PROMISE', status: 'PENDING', name })
  202. const actionResolved = (name, payload) => ({ type: 'PROMISE', status: 'RESOLVED', name, payload })
  203. const actionRejected = (name, error) => ({ type: 'PROMISE', status: 'REJECTED', name, error })
  204. const actionPromise = (name, promise) =>
  205. async dispatch => {
  206. dispatch(actionPending(name))
  207. try {
  208. let payload = await promise
  209. dispatch(actionResolved(name, payload))
  210. return payload
  211. }
  212. catch (error) {
  213. dispatch(actionRejected(name, error))
  214. }
  215. }
  216. const actionRootCategories = () =>
  217. actionPromise('rootCategories', shopGQL(`
  218. query cats($query:String){
  219. CategoryFind(query:$query){
  220. _id name
  221. }
  222. }
  223. `, { query: JSON.stringify([{ parent: null }]) }))
  224. const actionCategoryById = id => actionPromise('catById', categoryById(id))
  225. const actionGoodById = id => actionPromise('goodById', goodById(id))
  226. const actionBuyGood = (obj) => actionPromise('newOrder', newOrder(obj))
  227. const actionCartAdd = (n, id, name) => ({ type: "CART_ADD", count: n, _id: id, name })
  228. const actionCartChange = (n, id, name) => ({ type: "CART_CHANGE", count: n, _id: id, name })
  229. const actionCartRemove = id => ({ type: "CART_REMOVE", _id: id })
  230. const actionCartClear = () => ({ type: "CART_CLEAR" })
  231. const actionAuthLogin = token => ({ type: "LOGIN", token })
  232. const actionAuthLogout = () => ({ type: "LOGOUT" })
  233. const actionLogin = (login, password) => actionPromise("login", log(login, password))
  234. const actionFullLogin = (login, password) => {
  235. return async (dispatch) => {
  236. let result = await dispatch(actionLogin(login, password))
  237. dispatch(actionAuthLogin(result))
  238. }
  239. }
  240. const actionRegister = (login, password) => actionPromise("register", reg(login, password))
  241. const actionFullRegister = (login, password) => {
  242. return async (dispatch) => {
  243. let result = await dispatch(actionRegister(login, password))
  244. if (result.data.UserUpsert !== null) {
  245. dispatch(actionFullLogin(login, password))
  246. }
  247. }
  248. }
  249. store.dispatch(actionRootCategories())
  250. window.onhashchange = () => {
  251. let { 1: route, 2: id } = location.hash.split('/')
  252. if (route === 'categories') {
  253. store.dispatch(actionCategoryById(id))
  254. }
  255. if (route === 'good') {
  256. store.dispatch(actionGoodById(id))
  257. }
  258. if (route === 'cart') {
  259. cartDraw(store.getState().cart, main)
  260. }
  261. if (route === "login") {
  262. loginDraw(main)
  263. }
  264. if (route === "registration") {
  265. registrationDraw(main)
  266. }
  267. }
  268. //рисовашки
  269. function drawMainMenu() {
  270. let cats = store.getState().promise.rootCategories.payload
  271. if (cats) { //каждый раз дорисовываются в body
  272. aside.innerText = ''
  273. for (let { _id, name } of cats.data.CategoryFind) {
  274. let catA = document.createElement('a')
  275. catA.href = `#/categories/${_id}`
  276. catA.innerText = name
  277. aside.append(catA)
  278. }
  279. }
  280. }
  281. let goodDraw = (obj, parent, _id) => {
  282. let box = document.createElement("div")
  283. let goodName = document.createElement("h2")
  284. let goodIMG = document.createElement("img")
  285. let description = document.createElement("p")
  286. let price = document.createElement("span")
  287. let count = document.createElement("input")
  288. let goodBtnBox = document.createElement("div")
  289. let btn1 = document.createElement("button")
  290. let btn2 = document.createElement("button")
  291. price.textContent = "Цена: " + obj.price + "грн"
  292. goodIMG.src = "http://shop-roles.asmer.fs.a-level.com.ua/" + obj.images[0].url
  293. box.style.width = "40%"
  294. box.style.margin = "20px"
  295. box.style.display = "flex"
  296. box.style.flexDirection = "column"
  297. goodName.textContent = obj.name
  298. description.textContent = obj.description
  299. count.type = "number"
  300. count.min = 1
  301. count.value = 1
  302. goodBtnBox.style.display = "flex"
  303. goodBtnBox.style.alignItems = "center"
  304. btn1.textContent = "Купить"
  305. btn2.textContent = "В корзину"
  306. goodBtnBox.append(count, btn2, btn1)
  307. let order = {
  308. [_id]: +count.value
  309. }
  310. count.oninput = () => order[_id] = +count.value
  311. btn1.onclick = () => {
  312. store.dispatch(actionBuyGood(order))
  313. }
  314. btn2.onclick = () => {
  315. store.dispatch(actionCartAdd(+count.value, _id, obj.name))
  316. }
  317. box.append(goodIMG, goodName, description, price, goodBtnBox)
  318. parent.append(box)
  319. }
  320. let cardDraw = (obj, parent) => {
  321. let box = document.createElement("div");
  322. let img = document.createElement("img");
  323. let price = document.createElement("span");
  324. let productName = document.createElement("h5");
  325. img.src = "http://shop-roles.asmer.fs.a-level.com.ua/" + obj.images[0].url;
  326. productName.textContent = obj.name
  327. price.textContent = "Цена: " + obj.price + " грн"
  328. let productBody = document.createElement("div")
  329. box.className = "card"
  330. productBody.append(productName, price)
  331. box.append(img, productBody)
  332. let a = document.createElement("a")
  333. a.href = "#/good/" + obj._id
  334. a.append(box)
  335. parent.append(a)
  336. }
  337. let cartDraw = (obj, parent) => {
  338. while (parent.lastChild) {
  339. parent.lastChild.remove()
  340. }
  341. let cartName = document.createElement("h2")
  342. cartName.textContent = "Ваш заказ"
  343. parent.append(cartName)
  344. let order = {}
  345. for (let key in obj) {
  346. order[key] = obj[key].count
  347. let cartItem = document.createElement("div")
  348. let name = document.createElement("span")
  349. let count = document.createElement("input")
  350. let btnDelItem = document.createElement("button")
  351. let btnPlus = document.createElement("button")
  352. let btnMinus = document.createElement("button")
  353. count.min = 1
  354. count.value = obj[key].count
  355. count.className = "cart-item-count"
  356. btnPlus.className = "btn-plus"
  357. btnMinus.className = "btn-minus"
  358. btnDelItem.className = "btn-del"
  359. cartItem.className = "cart-item"
  360. name.textContent = obj[key].name
  361. cartItem.append(btnMinus, count, btnPlus, name, btnDelItem)
  362. parent.append(cartItem)
  363. if (+count.value === 1) {
  364. btnMinus.disabled = "true"
  365. }
  366. count.oninput = () => {
  367. if (+count.value >= 1) {
  368. store.dispatch(actionCartChange(+count.value, key, obj[key].name))
  369. }
  370. }
  371. btnPlus.onclick = () => {
  372. store.dispatch(actionCartChange(+count.value + 1, key, obj[key].name))
  373. }
  374. btnMinus.onclick = () => {
  375. store.dispatch(actionCartChange(+count.value - 1, key, obj[key].name))
  376. }
  377. btnDelItem.onclick = () => {
  378. store.dispatch(actionCartRemove(key))
  379. }
  380. }
  381. let btnBox = document.createElement("div")
  382. let btnOrder = document.createElement("button")
  383. let btnClear = document.createElement("button")
  384. btnClear.textContent = "ОЧИСТИТЬ КОРЗИНУ"
  385. btnClear.className = "btn-clear"
  386. btnOrder.textContent = "КУПИТЬ"
  387. btnOrder.onclick = () => {
  388. store.dispatch(actionBuyGood(order))
  389. store.dispatch(actionCartClear())
  390. }
  391. btnClear.onclick = () => {
  392. store.dispatch(actionCartClear())
  393. }
  394. btnBox.append(btnOrder, btnClear)
  395. parent.append(btnBox)
  396. }
  397. let cartValueDraw = () => {
  398. if (cart.children.length === 1) {
  399. let span = document.createElement("span")
  400. cart.append(span)
  401. }
  402. let sum = 0;
  403. let cartState = store.getState().cart
  404. for (let key in cartState) {
  405. sum += cartState[key].count
  406. }
  407. cart.lastChild.textContent = sum
  408. }
  409. let userStatus = (parent = userpanel) => {
  410. if (parent.children.length > 1) {
  411. parent.lastChild.remove()
  412. }
  413. let status = store.getState().auth
  414. let box = document.createElement("div")
  415. box.style.display = "flex"
  416. box.style.alignItems = "center"
  417. let userName = document.createElement("span")
  418. box.append(userName)
  419. parent.append(box)
  420. if (status.payload) {
  421. let btn = document.createElement("button")
  422. userName.textContent = status.payload.sub.login
  423. btn.textContent = "Выход"
  424. btn.onclick = () => {
  425. store.dispatch(actionAuthLogout())
  426. }
  427. box.append(btn)
  428. } else {
  429. let btn = document.createElement("a")
  430. let btnREG = document.createElement("a")
  431. userName.textContent = "Гость"
  432. btn.textContent = "Вход"
  433. btnREG.textContent = "Регистрация"
  434. btn.href = "#/login"
  435. btnREG.href = "#/registration"
  436. box.append(btn, btnREG)
  437. }
  438. }
  439. function Form(parent, type, open) {
  440. let h3 = document.createElement("h3");
  441. let loginInput = document.createElement("input");
  442. let passwordInput = document.createElement("input");
  443. let checkbox = document.createElement("input");
  444. let btn = document.createElement("button");
  445. let form = document.createElement("div");
  446. let box = document.createElement("div")
  447. box.style.display = "flex"
  448. box.style.alignItems = "center"
  449. box.append(passwordInput, checkbox)
  450. loginInput.id = "login"
  451. loginInput.placeholder = "логин"
  452. passwordInput.id = "password"
  453. passwordInput.placeholder = "пароль"
  454. btn.id = "btn"
  455. checkbox.type = "checkbox";
  456. checkbox.style.marginLeft = "5px"
  457. btn.disabled = true;
  458. if (type === "reg") {
  459. h3.textContent = "РЕГИСТРАЦИЯ"
  460. btn.textContent = "ЗАРЕГИСТРИРОВАТЬСЯ";
  461. } else {
  462. h3.textContent = "ВХОД"
  463. btn.textContent = "Войти";
  464. }
  465. form.append(h3, loginInput, box, btn)
  466. form.className = "form"
  467. parent.append(form)
  468. let btnOpen = () => {
  469. if (type === "reg" && checkbox.checked === false) {
  470. passwordInput.value !== "" && password.value === passwordVerify.value ? btn.disabled = false : btn.disabled = true;
  471. } else {
  472. (loginInput.value != "" && passwordInput.value != "") ? btn.disabled = false : btn.disabled = true;
  473. }
  474. }
  475. let checker = (check) => {
  476. if (check) {
  477. passwordInput.type = "text";
  478. checkbox.checked = true;
  479. if (type === "reg" && passwordVerify) {
  480. passwordVerify.remove()
  481. }
  482. } else {
  483. passwordInput.type = "password";
  484. checkbox.checked = false;
  485. if (type === "reg") {
  486. let passwordInput2 = document.createElement("input");
  487. passwordInput2.placeholder = "повторите пароль"
  488. passwordInput2.id = "passwordVerify"
  489. passwordInput2.type = "password"
  490. passwordInput2.oninput = () => {
  491. btnOpen();
  492. }
  493. form.append(passwordInput2)
  494. }
  495. }
  496. }
  497. checker(open);
  498. loginInput.oninput = () => {
  499. btnOpen();
  500. if (typeof this.onChange === "function") {
  501. this.onChange([loginInput.value, passwordInput.value]);
  502. }
  503. }
  504. passwordInput.oninput = () => {
  505. btnOpen();
  506. if (typeof this.onChange === "function") {
  507. this.onChange([loginInput.value, passwordInput.value]);
  508. }
  509. }
  510. checkbox.onchange = () => {
  511. checker(checkbox.checked)
  512. btnOpen()
  513. if (typeof this.onOpenChange === "function") {
  514. this.onOpenChange(checkbox.checked);
  515. }
  516. }
  517. this.getValue = () => [loginInput.value, passwordInput.value];
  518. this.setValue = (valueLogin, valuePassword) => {
  519. loginInput.value = valueLogin;
  520. passwordInput.value = valuePassword;
  521. btnOpen();
  522. }
  523. this.getOpen = () => checkbox.checked;
  524. this.setOpen = (open) => {
  525. checker(open);
  526. }
  527. }
  528. let loginDraw = (parent) => {
  529. while (parent.lastChild) {
  530. parent.lastChild.remove()
  531. }
  532. if (store.getState().auth.payload) {
  533. let h2 = document.createElement("h2")
  534. h2.textContent = "ВЫ ВОШЛИ"
  535. h2.style.color = "grey"
  536. parent.append(h2)
  537. } else {
  538. let form = new Form(parent)
  539. btn.onclick = () => {
  540. store.dispatch(actionFullLogin(login.value, password.value))
  541. }
  542. }
  543. }
  544. let registrationDraw = (parent) => {
  545. while (parent.lastChild) {
  546. parent.lastChild.remove()
  547. }
  548. if (store.getState().auth.payload) {
  549. let h2 = document.createElement("h2")
  550. h2.textContent = "ВЫ УСПЕШНО ЗАРЕГИСТРИРОВАЛИСЬ"
  551. h2.style.color = "grey"
  552. parent.append(h2)
  553. } else if (store.getState().promise.register && store.getState().promise.register.status === "RESOLVED") {
  554. let h3 = document.createElement("h3")
  555. h3.textContent = "Error: " + store.getState().promise.register.payload.errors[0].message
  556. h3.style.color = "red"
  557. parent.append(h3)
  558. } else {
  559. let form = new Form(parent, "reg")
  560. btn.onclick = () => {
  561. store.dispatch(actionFullRegister(login.value, password.value))
  562. }
  563. }
  564. }
  565. //subscribers
  566. const unsubscribe1 = store.subscribe(() => console.log(store.getState()))
  567. store.subscribe(drawMainMenu)
  568. store.subscribe(cartValueDraw)
  569. store.subscribe(userStatus)
  570. store.subscribe(() => {
  571. const { 1: route, 2: id } = location.hash.split('/')
  572. if (route === 'categories') {
  573. const catById = store.getState().promise.catById?.payload
  574. if (catById) {
  575. while (main.lastChild) {
  576. main.lastChild.remove()
  577. }
  578. let categoryName = document.createElement("h2")
  579. let cards = document.createElement("div")
  580. cards.className = "cards"
  581. categoryName.textContent = catById.data.CategoryFind[0].name
  582. for (let key of catById.data.CategoryFind[0].goods) {
  583. cardDraw(key, cards)
  584. }
  585. main.append(categoryName, cards)
  586. }
  587. }
  588. if (route === 'good') {
  589. const goodById = store.getState().promise.goodById?.payload
  590. if (goodById) { //вывести в main страницу товара
  591. while (main.lastChild) {
  592. main.lastChild.remove()
  593. }
  594. goodDraw(goodById.data.GoodFind[0], main, id)
  595. }
  596. }
  597. if (route === 'cart') {
  598. cartDraw(store.getState().cart, main)
  599. }
  600. if (route === 'login') {
  601. loginDraw(main)
  602. }
  603. if (route === "registration") {
  604. registrationDraw(main)
  605. }
  606. })