index.js 7.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239
  1. function createStore(reducer){
  2. let state = reducer(undefined, {}) //стартовая инициализация состояния, запуск редьюсера со state === undefined
  3. let cbs = [] //массив подписчиков
  4. const getState = () => state //функция, возвращающая переменную из замыкания
  5. const subscribe = (cb) => (
  6. cbs.push(cb),
  7. () => (cbs = cbs.filter((c) => c !== cb))
  8. )
  9. const dispatch = (action) => {
  10. if (typeof action === 'function'){ //если action - не объект, а функция
  11. return action(dispatch, getState) //запускаем эту функцию и даем ей dispatch и getState для работы
  12. }
  13. const newState = reducer(state, action) //пробуем запустить редьюсер
  14. if (newState !== state){ //проверяем, смог ли редьюсер обработать action
  15. state = newState //если смог, то обновляем state
  16. for (let cb of cbs) cb() //и запускаем подписчиков
  17. }
  18. }
  19. return {
  20. getState, //добавление функции getState в результирующий объект
  21. dispatch,
  22. subscribe //добавление subscribe в объект
  23. }
  24. }
  25. function jwtDecode(token) {
  26. try{
  27. return JSON.parse(atob(token.split('.')[1]))
  28. }
  29. catch(e){
  30. }
  31. }
  32. function authReducer(state = {}, {type, token}) {
  33. if(type === "AUTH_LOGIN"){
  34. const payload = jwtDecode(token)
  35. if(payload) {
  36. return {
  37. token, payload
  38. }
  39. }
  40. }
  41. if(type === "AUTH_LOGOUT") {
  42. return {}
  43. }
  44. return state
  45. }
  46. const actionAuthLogin = (token) =>
  47. (dispatch, getState) => {
  48. const oldState = getState()
  49. dispatch({type: "AUTH_LOGIN", token})
  50. const newState = getState()
  51. if(oldState !== newState) {
  52. localStorage.authToken = token
  53. }
  54. }
  55. const actionAuthLogout = () =>
  56. dispatch => {
  57. dispatch({type: "AUTH_LOGOUT"})
  58. localStorage.removeItem('authToken')
  59. }
  60. function promiseReducer(state={}, {type, name, status, payload, error}){
  61. ////?????
  62. //ОДИН ПРОМИС:
  63. //состояние: PENDING/FULFILLED/REJECTED
  64. //результат
  65. //ошибка:
  66. //{status, payload, error}
  67. //{
  68. // name1:{status, payload, error}
  69. // name2:{status, payload, error}
  70. // name3:{status, payload, error}
  71. //}
  72. if (type === 'PROMISE'){
  73. return {
  74. ...state,
  75. [name]:{status, payload, error}
  76. }
  77. }
  78. return state
  79. }
  80. const actionPending = (name) => ({type: 'PROMISE', status: 'PENDING', name})
  81. const actionFulfilled = (name, payload) => ({type: 'PROMISE', status: 'FULFILLED', name, payload})
  82. const actionRejected = (name, error) => ({type: 'PROMISE', status: 'REJECTED', name, error})
  83. const actionPromise = (name, promise) =>
  84. async (dispatch) => {
  85. try {
  86. dispatch(actionPending(name))
  87. let payload = await promise
  88. dispatch(actionFulfilled(name, payload))
  89. return payload
  90. }
  91. catch(e){
  92. dispatch(actionRejected(name, e))
  93. }
  94. }
  95. const delay = (ms) => new Promise((ok) => setTimeout(() => ok(ms), ms))
  96. function combineReducers(reducers) {
  97. function combinedReducer(combinedState={}, action){
  98. const newCombinedState = {}
  99. for (const [reducerName, reducer] of Object.entries(reducers)){
  100. const newSubState = reducer(combinedState[reducerName], action)
  101. if(newSubState !== combinedState[reducerName]){
  102. newCombinedState[reducerName] = newSubState
  103. }
  104. }
  105. if (Object.keys(newCombinedState).length === 0){
  106. return combinedState
  107. }
  108. return {...combinedState, ...newCombinedState}
  109. }
  110. return combinedReducer
  111. }
  112. const store = createStore(combineReducers({auth: authReducer, promise: promiseReducer})) //не забудьте combineReducers если он у вас уже есть
  113. if(localStorage.authToken) {
  114. store.dispatch(actionAuthLogin(localStorage.authToken))
  115. }
  116. //const store = createStore(combineReducers({promise: promiseReducer, auth: authReducer, cart: cartReducer}))
  117. store.subscribe(() => console.log(store.getState()))
  118. /* store.dispatch(actionPromise("delay1000", delay(1000)))
  119. store.dispatch(actionPromise("delay3000", delay(3000))) */
  120. //store.dispatch(actionPromise('delay1000'))
  121. //delay(1000).then(result => store.dispatch(actionFulfilled('delay1000', result)),
  122. //error => store.dispatch(actionRejected('delay1000', error)))
  123. //store.dispatch(actionPending('delay3000'))
  124. //delay(3000).then(result => store.dispatch(actionFulfilled('delay3000', result)),
  125. //error => store.dispatch(actionRejected('delay3000', error)))
  126. const gql = (url, query, variables) => fetch(url, {
  127. method: 'POST',
  128. headers: {
  129. "Content-Type": "application/json",
  130. Accept: "application/json",
  131. },
  132. body: JSON.stringify({query, variables})
  133. }).then(res => res.json())
  134. const backendURL = 'http://shop-roles.node.ed.asmer.org.ua/graphql'
  135. const actionRootCats = () =>
  136. actionPromise('rootCats', gql(backendURL, `query {
  137. CategoryFind(query: "[{\\"parent\\":null}]"){
  138. _id name
  139. }
  140. }`))
  141. const actionCatById = (_id) => //добавить подкатегории
  142. actionPromise('catById', gql(backendURL, `query catById($q: String){
  143. CategoryFindOne(query: $q){
  144. _id name goods {
  145. _id name price images {
  146. url
  147. }
  148. }
  149. }
  150. }`, {q: JSON.stringify([{_id}])}))
  151. const actionLogin = (login, password) =>
  152. actionPromise('login', gql(backendURL,
  153. `query log($login: String, $password: String){
  154. login(login:$login, password: $password)
  155. }`
  156. , {login, password}))
  157. store.dispatch(actionRootCats())
  158. store.dispatch(actionLogin('levshin95', '123123'))
  159. store.subscribe(() => {
  160. const rootCats = store.getState().promise.rootCats?.payload?.data.CategoryFind
  161. if (!rootCats) {
  162. aside.innerHTML = '<img src="Loading_icon.gif">'
  163. } else {
  164. aside.innerHTML = ''
  165. for(let {_id, name} of rootCats){
  166. const a = document.createElement('a')
  167. a.href = "#/category/" + _id
  168. a.innerHTML = name
  169. aside.append(a)
  170. }
  171. }
  172. })
  173. store.subscribe(() => {
  174. const catById = store.getState().promise.catById?.payload?.data.CategoryFindOne
  175. const [,route] = location.hash.split('/')
  176. if (catById && route === 'category') {
  177. const {name, goods, _id} = catById
  178. main.innerHTML = `<h1>${name}</h1>`
  179. for(let {_id, name, price, images} of goods){
  180. const a = document.createElement('a')
  181. a.href = "#/good/" + _id
  182. a.innerHTML = name
  183. main.append(a)
  184. }
  185. }
  186. })
  187. window.onhashchange = () => {
  188. const [,route, _id] = location.hash.split('/')
  189. console.log(route, _id)
  190. const routes = {
  191. category(){
  192. store.dispatch(actionCatById(_id))
  193. },
  194. good(){
  195. }
  196. }
  197. if(route in routes){
  198. routes[route]()
  199. }
  200. }
  201. window.onhashchange()
  202. //
  203. //