main.js 9.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288
  1. function createStore(reducer){
  2. let state = reducer(undefined, {}) //стартовая инициализация состояния, запуск редьюсера со state === undefined
  3. let cbs = [] //массив подписчиков
  4. const getState = () => state //функция, возвращающая переменную из замыкания
  5. const subscribe = cb => (cbs.push(cb), //запоминаем подписчиков в массиве
  6. () => cbs = cbs.filter(c => c !== cb)) //возвращаем функцию unsubscribe, которая удаляет подписчика из списка
  7. const dispatch = action => {
  8. if (typeof action === 'function'){ //если action - не объект, а функция
  9. return action(dispatch, getState) //запускаем эту функцию и даем ей dispatch и getState для работы
  10. }
  11. const newState = reducer(state, action) //пробуем запустить редьюсер
  12. if (newState !== state){ //проверяем, смог ли редьюсер обработать action
  13. state = newState //если смог, то обновляем state
  14. for (let cb of cbs) cb() //и запускаем подписчиков
  15. }
  16. }
  17. return {
  18. getState, //добавление функции getState в результирующий объект
  19. dispatch,
  20. subscribe //добавление subscribe в объект
  21. }
  22. }
  23. function jwtDecode(token){
  24. try {
  25. return JSON.parse(atob(token.split('.')[1]))
  26. }
  27. catch(e){
  28. }
  29. }
  30. function authReducer(state={}, {type, token}){
  31. //{
  32. // token, payload
  33. //}
  34. if (type === 'AUTH_LOGIN'){
  35. //пытаемся токен раскодировать
  36. const payload = jwtDecode(token)
  37. if (payload){ //и если получилось
  38. return {
  39. token, payload //payload - раскодированный токен;
  40. }
  41. }
  42. }
  43. if (type === 'AUTH_LOGOUT'){
  44. return {}
  45. }
  46. return state;
  47. }
  48. const actionAuthLogin = (token) =>
  49. (dispatch, getState) => {
  50. const oldState = getState()
  51. dispatch({type: 'AUTH_LOGIN', token})
  52. const newState = getState()
  53. if (oldState !== newState)
  54. localStorage.authToken = token
  55. }
  56. const actionAuthLogout = () =>
  57. dispatch => {
  58. dispatch({type: 'AUTH_LOGOUT'})
  59. localStorage.removeItem('authToken')
  60. }
  61. const actionGoodById = _id =>
  62. actionPromise('goodById', gql(backendURL,`query goodByID($id: String) {
  63. GoodFindOne(query: $id) {
  64. _id
  65. name
  66. description
  67. price
  68. images {
  69. url
  70. }
  71. owner {
  72. nick
  73. avatar {
  74. url
  75. text
  76. }
  77. }
  78. categories {
  79. name
  80. subCategories {
  81. name
  82. goods {
  83. name
  84. price
  85. }
  86. }
  87. }
  88. }
  89. }`, {"id": JSON.stringify([{_id}])}))
  90. function promiseReducer(state = {}, { type, name, status, payload, error }) {
  91. if (type === "PROMISE") {
  92. return {
  93. ...state, //.......скопировать старый state
  94. [name]: { status, payload, error }, //....... перекрыть в нем один name на новый объект со status, payload и error
  95. };
  96. }
  97. return state;
  98. }
  99. const actionPending = (name) => ({type: 'PROMISE', status: 'PENDING', name})
  100. const actionFulfilled = (name, payload) => ({type: 'PROMISE', status: 'FULFILLED', name, payload})
  101. const actionRejected = (name, error) => ({type: 'PROMISE', status: 'REJECTED', name, error})
  102. const actionPromise = (name, promise) =>
  103. async dispatch => {
  104. try {
  105. dispatch(actionPending(name))
  106. let payload = await promise
  107. dispatch(actionFulfilled(name, payload))
  108. return payload
  109. }
  110. catch(e){
  111. dispatch(actionRejected(name, e))
  112. }
  113. }
  114. // const delay = ms => new Promise(ok => setTimeout(() => ok(ms), ms))
  115. function combineReducers(reducers){ //пачку редьюсеров как объект {auth: authReducer, promise: promiseReducer}
  116. function combinedReducer(combinedState={}, action){ //combinedState - типа {auth: {...}, promise: {....}}
  117. const newCombinedState = {}
  118. for (const [reducerName, reducer] of Object.entries(reducers)){
  119. const newSubState = reducer(combinedState[reducerName], action)
  120. if (newSubState !== combinedState[reducerName]){
  121. newCombinedState[reducerName] = newSubState
  122. }
  123. }
  124. if (Object.keys(newCombinedState).length === 0){
  125. return combinedState
  126. }
  127. return {...combinedState, ...newCombinedState}
  128. }
  129. return combinedReducer //нам возвращают один редьюсер, который имеет стейт вида {auth: {...стейт authReducer-а}, promise: {...стейт promiseReducer-а}}
  130. }
  131. const store = createStore(combineReducers({auth: authReducer, promise: promiseReducer})) //не забудьте combineReducers если он у вас уже есть
  132. if (localStorage.authToken){
  133. store.dispatch(actionAuthLogin(localStorage.authToken))
  134. }
  135. //const store = createStore(combineReducers({promise: promiseReducer, auth: authReducer, cart: cartReducer}))
  136. // store.subscribe(() => console.log(store.getState()))
  137. const gql = (url, query, variables) => fetch(url, {
  138. method: 'POST',
  139. headers: {
  140. "Content-Type": "application/json",
  141. Accept: "application/json",
  142. },
  143. body: JSON.stringify({query, variables})
  144. }).then(res => res.json())
  145. const url = 'http://shop-roles.node.ed.asmer.org.ua'
  146. const backendURL = 'http://shop-roles.node.ed.asmer.org.ua/graphql'
  147. const actionRootCats = () =>
  148. actionPromise('rootCats', gql(backendURL, `query {
  149. CategoryFind(query: "[{\\"parent\\":null}]"){
  150. _id name
  151. }
  152. }`))
  153. const actionCatById = (_id) => //добавить подкатегории
  154. actionPromise('catById', gql(backendURL, `query catById($q: String){
  155. CategoryFindOne(query: $q){
  156. _id name goods {
  157. _id name price images {
  158. url
  159. }
  160. }
  161. subCategories {
  162. name
  163. _id
  164. }
  165. }
  166. }`, {q: JSON.stringify([{_id}])}))
  167. const actionLogin = (login, password) =>
  168. actionPromise('login', gql(backendURL,`query log($login:String, $password:String){
  169. login(login:$login, password:$password)
  170. }`, {login, password}))
  171. store.dispatch(actionRootCats())
  172. store.dispatch(actionLogin('test456', '123123'))
  173. //show root categories aside
  174. store.subscribe( () => {
  175. const rootCats = store.getState().promise.rootCats?.payload?.data.CategoryFind
  176. if (rootCats){
  177. aside.innerHTML = ''
  178. for (let {_id, name} of rootCats){
  179. const a = document.createElement('a')
  180. a.href = `#/category/${_id}`
  181. a.innerHTML = name
  182. aside.append(a)
  183. }
  184. }
  185. })
  186. // store.subscribe(() => {
  187. // const catById = store.getState().catById?.payload?.data.CategoryFindOne
  188. // })
  189. //cards showing
  190. store.subscribe( () => {
  191. let {catById} = store.getState().promise;
  192. const [,route, _id] = location.hash.split('/')
  193. let subCats = catById.payload?.data.CategoryFindOne.subCategories
  194. if(catById?.status === 'PENDING') { // if pending, show loading gif
  195. main.innerHTML = `<img src="Loading_icon.gif">`
  196. } else if(catById?.payload && route === 'category'){ // if pending stopped
  197. main.innerHTML = ''
  198. const {name, id, goods} = catById.payload?.data.CategoryFindOne
  199. main.innerHTML = `<h1>${name}</h1>`
  200. if(subCats.length > 0) {
  201. for(let sub of subCats) {
  202. console.log(sub)
  203. let subCat = document.createElement('a')
  204. subCat.text = sub.name
  205. subCat.href = `#/category/${sub._id}`
  206. main.append(subCat)
  207. }
  208. }
  209. // ask about showing subcats
  210. console.log(store.getState().promise)
  211. for(let good of goods) {
  212. let {name, price, images} = good
  213. let card = document.createElement('div')
  214. card.className = 'card'
  215. let cardBody = document.createElement('div')
  216. let title = document.createElement('p')
  217. title.className = 'title'
  218. let img = document.createElement('img')
  219. let desc = document.createElement('p')
  220. let cardPrice = document.createElement('p')
  221. title.innerHTML = name;
  222. // img.src = `${url}/${images[0].url}` //ACTIVATE THIS CODE AFTER FINISHING
  223. cardPrice.innerHTML = `${price} UAH`
  224. cardBody.append(title, desc, cardPrice) //ADD IMG
  225. card.append(cardBody)
  226. main.append(card);
  227. }
  228. }
  229. });
  230. //SHOW GOOD
  231. store.subscribe(() => {
  232. // console.log(store.getState().promise)
  233. let {goodById} = store.getState().promise
  234. })
  235. window.onhashchange = () => {
  236. const [,route, _id] = location.hash.split('/')
  237. const routes = {
  238. category(){
  239. store.dispatch(actionCatById(_id))
  240. },
  241. good(){
  242. store.dispatch(actionGoodById(_id));
  243. }
  244. }
  245. console.log(route)
  246. if (route in routes) {
  247. routes[route]()
  248. }
  249. routes.good()
  250. console.log(route)
  251. }
  252. window.onhashchange()