main.js 20 KB

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