main.js 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425
  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. const delay = ms => new Promise(ok => setTimeout(() => ok(ms), ms))
  24. const jwtDecode = token => {
  25. try {
  26. let arrToken = token.split('.')
  27. let base64Token = atob(arrToken[1])
  28. return JSON.parse(base64Token)
  29. }
  30. catch (e) {
  31. console.log('Лажа, Бро ' + e);
  32. }
  33. }
  34. function authReducer(state, { type, token }) {
  35. if (!state) {
  36. if (localStorage.authToken) {
  37. type = 'AUTH_LOGIN'
  38. token = localStorage.authToken
  39. } else state = {}
  40. }
  41. if (type === 'AUTH_LOGIN') {
  42. localStorage.setItem('authToken', token)
  43. let payload = jwtDecode(token)
  44. if (typeof payload === 'object') {
  45. return {
  46. ...state,
  47. token,
  48. payload
  49. }
  50. } else return state
  51. }
  52. if (type === 'AUTH_LOGOUT') {
  53. localStorage.removeItem('authToken')
  54. return {}
  55. }
  56. return state
  57. }
  58. const combineReducers = (reducers) => (state = {}, action) => {
  59. const newState = {}
  60. for (const [reducerName, reducer] of Object.entries(reducers)) {
  61. const newSubState = reducer(state[reducerName], action)
  62. if (newSubState !== state[reducerName]) {
  63. newState[reducerName] = newSubState
  64. }
  65. }
  66. if (Object.keys(newState).length !== 0) {
  67. return { ...state, ...newState }
  68. }
  69. else {
  70. return state
  71. }
  72. }
  73. const combineReducer = combineReducers({ promise: promiseReducer, auth: authReducer })
  74. const store = createStore(combineReducer)
  75. const actionAuthLogin = token => ({ type: 'AUTH_LOGIN', token })
  76. const actionAuthLogout = () => ({ type: 'AUTH_LOGOUT' })
  77. function promiseReducer(state = {}, { type, status, payload, error, name }) {
  78. if (type === 'PROMISE') {
  79. return {
  80. ...state,
  81. [name]: { status, payload, error }
  82. }
  83. }
  84. return state;
  85. }
  86. const actionPending = name => ({ type: 'PROMISE', status: 'PENDING', name })
  87. const actionResolved = (name, payload) => ({ type: 'PROMISE', status: 'RESOLVED', name, payload })
  88. const actionRejected = (name, error) => ({ type: 'PROMISE', status: 'REJECTED', name, error })
  89. const actionPromise = (name, promise) =>
  90. async dispatch => {
  91. dispatch(actionPending(name))
  92. try {
  93. let data = await promise
  94. dispatch(actionResolved(name, data))
  95. return data
  96. }
  97. catch (error) {
  98. dispatch(actionRejected(name, error))
  99. }
  100. }
  101. const getGQL = url =>
  102. async (query, variables = {}) => {
  103. let obj = await fetch(url, {
  104. method: 'POST',
  105. headers: {
  106. "Content-Type": "application/json",
  107. Authorization: localStorage.authToken ? 'Bearer ' + localStorage.authToken : {},
  108. },
  109. body: JSON.stringify({ query, variables })
  110. })
  111. let a = await obj.json()
  112. if (!a.data && a.errors)
  113. throw new Error(JSON.stringify(a.errors))
  114. return a.data[Object.keys(a.data)[0]]
  115. }
  116. const backURL = 'http://shop-roles.asmer.fs.a-level.com.ua'
  117. const gql = getGQL(backURL + '/graphql');
  118. const actionRootCats = () =>
  119. actionPromise('rootCats', gql(`query {
  120. CategoryFind(query: "[{\\"parent\\":null}]"){
  121. _id name
  122. }
  123. }`))
  124. const actionCatById = (_id) =>
  125. actionPromise('catById', gql(`query catById($q: String){
  126. CategoryFindOne(query: $q){
  127. subCategories{name, _id}
  128. _id name goods {
  129. _id name price images {
  130. url
  131. }
  132. }
  133. }
  134. }`, { q: JSON.stringify([{ _id }]) }))
  135. const actionGoodById = (_id) => //добавить подкатегории
  136. actionPromise('goodById', gql(`query goodByID($q: String) {
  137. GoodFind(query: $q){
  138. name _id description price images{url}
  139. }
  140. }`, {
  141. q: JSON.stringify([{ _id }])
  142. }))
  143. const actionLogin = (login, password) =>
  144. actionPromise('login', gql(`query NameForMe1($login:String, $password:String){
  145. login(login:$login, password:$password)
  146. }`, { login, password }))
  147. const actionMyOrders = () =>
  148. actionPromise('orderGood', gql(`query Order{
  149. OrderGoodFind(query:"[{}]"){
  150. good{ name _id} _id total price count
  151. }
  152. }`, {}))
  153. const actionFullLogin = (login, password) =>
  154. async dispatch => {
  155. let token = await dispatch(actionLogin(login, password))
  156. if (token) {
  157. dispatch(actionAuthLogin(token))
  158. }
  159. }
  160. const actionRegister = (login, password) =>
  161. actionPromise('register', gql(`
  162. mutation reg($login:String, $password:String){
  163. UserUpsert(user:{
  164. login:$login,
  165. password:$password,
  166. nick:$login}){
  167. _id login
  168. }
  169. }
  170. `, { login, password }))
  171. const actionFullRegister = (login, password) =>
  172. async dispatch => {
  173. await actionRegister(login, password)
  174. let token = await dispatch(actionLogin(login, password))
  175. if (token) {
  176. dispatch(actionAuthLogin(token))
  177. }
  178. }
  179. store.dispatch(actionRootCats())
  180. store.dispatch(actionGoodById())
  181. // store.dispatch(actionAuthLogin(token))
  182. // store.dispatch(actionPromise('delay2000', delay(1000)))
  183. window.onhashchange = () => {
  184. const [, route, _id] = location.hash.split('/')
  185. const routes = {
  186. category() {
  187. store.dispatch(actionCatById(_id))
  188. },
  189. good() { //задиспатчить actionGoodById
  190. store.dispatch(actionGoodById(_id))
  191. },
  192. login() {
  193. userAuthorizationFields(route)
  194. },
  195. register() {
  196. userAuthorizationFields(route)
  197. },
  198. order() {
  199. store.dispatch(actionMyOrders())
  200. }
  201. }
  202. if (route in routes)
  203. routes[route]()
  204. }
  205. window.onhashchange()
  206. function userAuthorizationFields(key) {
  207. const userBox = document.createElement('div')
  208. userBox.setAttribute('id', 'userBox')
  209. const h2 = document.createElement('h2')
  210. const inputNick = document.createElement('input')
  211. const inputPassword = document.createElement('input')
  212. inputNick.type = 'text'
  213. inputPassword.type = 'password'
  214. const btnEnter = document.createElement('a')
  215. const btnClose = document.createElement('a')
  216. btnClose.onclick = () => {
  217. userBox.remove()
  218. overlay.style.display = 'none'
  219. }
  220. btnClose.innerText = 'X'
  221. btnClose.classList.add('close')
  222. btnClose.href = '#'
  223. btnEnter.href = '#'
  224. overlay.style.display = 'block'
  225. if (key === 'login') {
  226. h2.innerText = 'Log In'
  227. btnEnter.innerText = 'Log In'
  228. btnEnter.setAttribute('id', 'logIn')
  229. btnEnter.onclick = () => store.dispatch(actionFullLogin(inputNick.value, inputPassword.value))
  230. } else {
  231. h2.innerText = 'Register'
  232. btnEnter.innerText = 'Register'
  233. btnEnter.setAttribute('id', 'register')
  234. btnEnter.onclick = () => store.dispatch(actionFullRegister(inputNick.value, inputPassword.value))
  235. }
  236. userBox.append(h2)
  237. userBox.append(inputNick)
  238. userBox.append(btnClose)
  239. userBox.append(inputPassword)
  240. userBox.append(btnEnter)
  241. user.append(userBox)
  242. }
  243. function noAuthorization() {
  244. const loginLink = document.createElement('a')
  245. loginLink.classList.add('user__link')
  246. loginLink.innerText = 'Log In'
  247. loginLink.href = '#/login'
  248. const registerLink = document.createElement('a')
  249. registerLink.href = '#/register'
  250. registerLink.innerText = 'Register'
  251. registerLink.classList.add('user__link')
  252. user.append(loginLink)
  253. user.append(registerLink)
  254. }
  255. // noAuthorization()
  256. // userAuthorization('login')
  257. store.subscribe(() => {
  258. const { rootCats } = store.getState().promise
  259. if (rootCats?.payload) {
  260. aside.innerHTML = ''
  261. for (const { _id, name } of rootCats?.payload) {
  262. const link = document.createElement('a')
  263. link.href = `#/category/${_id}`
  264. link.innerText = name
  265. aside.append(link)
  266. }
  267. }
  268. })
  269. store.subscribe(() => {
  270. const { catById } = store.getState().promise
  271. const [, route, _id] = location.hash.split('/')
  272. if (catById?.payload && route === 'category') {
  273. const { name, subCategories } = catById.payload
  274. main.innerHTML = `<h1>${name}</h1> `
  275. subCategories ? subCategories.map(s => {
  276. const link = document.createElement('a')
  277. link.href = `#/category/${s._id}`
  278. link.innerText = s.name
  279. main.append(link)
  280. }) : ''
  281. for (const { _id, name, price, images } of catById.payload.goods) {
  282. const card = document.createElement('div')
  283. card.innerHTML = `<h2>${name}</h2>
  284. <img src="${backURL}/${images[0].url}" />
  285. <strong>${price}</strong>`
  286. const link = document.createElement('a')
  287. link.href = `#/good/${_id}`
  288. link.innerText = name
  289. card.append(link)
  290. main.append(card)
  291. }
  292. }
  293. })
  294. store.subscribe(() => {
  295. const { orderGood } = store.getState().promise
  296. const [, route, _id] = location.hash.split('/')
  297. if (orderGood?.payload && route === 'order') {
  298. main.innerHTML = ''
  299. const table = document.createElement('table')
  300. for (const { good, price, count, total } of orderGood.payload) {
  301. if (good !== null) {
  302. const tr = document.createElement('tr')
  303. const tdName = document.createElement('td')
  304. const tdPrice = document.createElement('td')
  305. const tdCount = document.createElement('td')
  306. const tdTotal = document.createElement('td')
  307. tdName.innerHTML = `<a href="#/good/${good._id}">${good.name}</a>`
  308. tdPrice.innerText = price
  309. tdCount.innerText = count
  310. tdTotal.innerText = total
  311. tr.append(tdName)
  312. tr.append(tdPrice)
  313. tr.append(tdCount)
  314. tr.append(tdTotal)
  315. table.append(tr)
  316. }
  317. }
  318. main.append(table)
  319. }
  320. })
  321. store.subscribe(() => {
  322. const { goodById } = store.getState().promise
  323. const [, route, _id] = location.hash.split('/')
  324. if (goodById?.payload && route === 'good') {
  325. main.innerHTML = ''
  326. const { name, description, price, images } = goodById.payload[0]
  327. main.innerHTML = `
  328. <div div class="product" >
  329. <div class="product__img">
  330. <img src="${backURL}/${images[0].url}" />
  331. </div>
  332. <div class="product__inner">
  333. <h2 class="product_title">${name}</h2>
  334. <p class="product__price"> <strong>${price}</strong></p>
  335. <p class="product__description">
  336. <span>Обзор: ${description}</span>
  337. </p>
  338. </div>
  339. </div>`
  340. }
  341. })
  342. store.subscribe(() => {
  343. const { auth } = store.getState()
  344. const [, route, _id] = location.hash.split('/')
  345. user.innerHTML = ''
  346. overlay.style.display = 'none'
  347. if (auth?.payload) {
  348. const logOut = document.createElement('button')
  349. const order = document.createElement('a')
  350. order.classList.add('order')
  351. logOut.innerText = 'Log Out'
  352. logOut.onclick = () => {
  353. store.dispatch(actionAuthLogout())
  354. }
  355. order.href = "#/order"
  356. order.innerText = 'order'
  357. user.innerHTML = `<h1> Hello, ${auth.payload.sub.login}</h1>
  358. <div id="btn"></div>
  359. <div id="orderBox"></div>`
  360. btn.append(logOut)
  361. orderBox.append(order)
  362. } else if (route === 'login' || route === 'register') {
  363. userAuthorizationFields(route)
  364. noAuthorization()
  365. } else if (user.children.length === 0) {
  366. noAuthorization()
  367. }
  368. })
  369. console.log(store.getState());
  370. store.subscribe(() => console.log(store.getState()))
  371. // store.dispatch(actionPromise('', delay(1000)))
  372. // store.dispatch(actionPromise('delay2000', delay(2000)))
  373. // store.dispatch(actionPromise('luke', fetch('https://swapi.dev/api/people/1/').then(res => res.json())))