index.js 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489
  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 combineReducers(reducers) {
  24. return (state={}, action) => {
  25. const newState = {}
  26. // перебрать все редьюсеры
  27. if (reducers) {
  28. for (const [reducerName, reducer] of Object.entries(reducers)) {
  29. const newSubState = reducer(state[reducerName], action)
  30. if (newSubState !== state[reducerName]) {
  31. newState[reducerName] = newSubState
  32. }
  33. }
  34. // если newState не пустой, то вернуть стейт в
  35. if (Object.keys(newState).length !== 0) {
  36. return {...state, ...newState}
  37. } else {
  38. return state
  39. }
  40. }
  41. }
  42. }
  43. const combinedReducer = combineReducers({promise: promiseReducer, auth: authReducer})
  44. const store = createStore(combinedReducer)
  45. // console.log(store.getState()) // {promise: {}, auth: {}}
  46. function jwtDecode(token) {
  47. try {
  48. let decoded = JSON.parse(atob(token.split('.')[1]))
  49. return decoded
  50. } catch (err) {
  51. console.log(err)
  52. }
  53. }
  54. function authReducer(state, {type, token}) {
  55. if (!state) {
  56. if (localStorage.authToken) {
  57. token = localStorage.authToken
  58. type = 'AUTH_LOGIN'
  59. } else {
  60. return {}
  61. }
  62. }
  63. if (type === 'AUTH_LOGIN') {
  64. let payload = jwtDecode(token)
  65. if (typeof payload === 'object') {
  66. localStorage.authToken = token
  67. return {
  68. ...state,
  69. token,
  70. payload
  71. }
  72. } else {
  73. return state
  74. }
  75. }
  76. if (type === 'AUTH_LOGOUT') {
  77. delete localStorage.authToken
  78. return {}
  79. }
  80. return state
  81. }
  82. const actionAuthLogin = (token) => ({type: 'AUTH_LOGIN', token})
  83. const actionAuthLogout = () => ({type: 'AUTH_LOGOUT'})
  84. // const loginStore = createStore(authReducer)
  85. store.subscribe(() => console.log(store.getState()))
  86. // const inputToken = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOnsiaWQiOiI2MWE0ZGIyOWM3NTBjMTJiYTZiYTQwMjIiLCJsb2dpbiI6ImVxd2VxZXdldyIsImFjbCI6WyI2MWE0ZGIyOWM3NTBjMTJiYTZiYTQwMjIiLCJ1c2VyIl19LCJpYXQiOjE2MzgxOTQ1NzZ9.Pi1GO6x7wdNrIrUKCQT-32-SsqmgFY-oFDrrXmw74-8'
  87. // loginStore.dispatch(actionAuthLogin(inputToken))
  88. // loginStore.dispatch(actionAuthLogout())
  89. // console.log(store.getState())
  90. function promiseReducer(state={}, {type, status, payload, error, name}) {
  91. if (!state) {
  92. return {}
  93. }
  94. if (type === 'PROMISE') {
  95. return {
  96. ...state,
  97. [name]: {
  98. status: status,
  99. payload : payload,
  100. error: error,
  101. }
  102. }
  103. }
  104. return state
  105. }
  106. const actionPending = (name) => ({type: 'PROMISE', status: 'PENDING', name})
  107. const actionResolved = (name, payload) => ({type: 'PROMISE', status: 'RESOLVED', name, payload})
  108. const actionRejected = (name, error) => ({type: 'PROMISE', status: 'REJECTED', name, error})
  109. // const promiceStore = createStore(promiseReducer)
  110. // store.subscribe(() => console.log(store.getState()))
  111. // const delay = (ms) => new Promise((ok) => setTimeout(() => ok(ms), ms))
  112. // // store.dispatch(actionPending('delay1000'))
  113. // // delay(1000).then(data => store.dispatch(actionResolved('delay1000', data)),
  114. // // error => store.dispatch(actionRejected('delay1000', error)))
  115. // // store.dispatch(actionPending('delay2000'))
  116. // // delay(2000).then(data => store.dispatch(actionResolved('delay2000', data)),
  117. // // error => store.dispatch(actionRejected('delay2000', error)))
  118. const actionPromise = (name, promise) =>
  119. async (dispatch) => {
  120. dispatch(actionPending(name))
  121. try {
  122. let data = await promise
  123. dispatch(actionResolved(name, data))
  124. return data
  125. }
  126. catch(error){
  127. dispatch(actionRejected(name, error))
  128. }
  129. }
  130. const getGQL = url =>
  131. async (query, variables={}) => {
  132. // try {
  133. let obj = await fetch(url, {
  134. method: 'POST',
  135. headers: {
  136. "Content-Type": "application/json",
  137. ...(localStorage.authToken ? {Authorization: "Bearer " + localStorage.authToken} : {})
  138. },
  139. body: JSON.stringify({ query, variables })
  140. })
  141. let a = await obj.json()
  142. if (!a.data && a.errors) {
  143. throw new Error(JSON.stringify(a.errors))
  144. } else {
  145. return a.data[Object.keys(a.data)[0]]
  146. }
  147. // }
  148. // catch (error) {
  149. // console.log('Что-то не так, Бро ' + error);
  150. // }
  151. }
  152. const backURL = 'http://shop-roles.asmer.fs.a-level.com.ua/'
  153. const gql = getGQL(backURL + 'graphql');
  154. const actionLogin = (login, password) => (
  155. actionPromise('login', gql(`query log($login: String, $password: String) {
  156. login(login: $login, password: $password)
  157. }`, {login, password}))
  158. )
  159. const actionFullLogin = (login, password) => (
  160. async (dispatch) => {
  161. let token = await dispatch(actionLogin(login, password))
  162. if (token) {
  163. // console.log(token)
  164. dispatch(actionAuthLogin(token))
  165. }
  166. }
  167. )
  168. const actionRegister = (login, password) => (
  169. actionPromise('register', gql(`mutation reg($user:UserInput) {
  170. UserUpsert(user:$user) {
  171. _id
  172. }
  173. }
  174. `, {user: {login, password}})
  175. )
  176. )
  177. const actionFullRegister = (login, password) => (
  178. async (dispatch) => {
  179. let regId = await dispatch(actionRegister(login, password))
  180. if (regId) {
  181. // console.log(regId)
  182. dispatch(actionFullLogin(login, password))
  183. }
  184. }
  185. )
  186. // regBtn.onclick = () => {
  187. // store.dispatch(actionFullRegister(loginReg.value, passReg.value))
  188. // }
  189. // loginBtn.onclick = () => {
  190. // store.dispatch(actionFullLogin(loginInput.value, passInput.value))
  191. // }
  192. // logoutBtn.onclick = () => {
  193. // store.dispatch(actionAuthLogout())
  194. // }
  195. const actionRootCats = () =>
  196. actionPromise('rootCats', gql(`query {
  197. CategoryFind(query: "[{\\"parent\\":null}]"){
  198. _id name
  199. }
  200. }`))
  201. const actionCatById = (_id) => //добавить подкатегории
  202. actionPromise('catById', gql(`query catById($q: String){
  203. CategoryFindOne(query: $q){
  204. _id name goods {
  205. _id name price images {
  206. url
  207. }
  208. }
  209. subCategories {
  210. _id name
  211. }
  212. }
  213. }`, {q: JSON.stringify([{_id}])}))
  214. // //actionGoodById по аналогии
  215. const actionGoodById = (_id) =>
  216. actionPromise('goodById', gql(`query goodById($q: String) {
  217. GoodFindOne(query: $q) {
  218. _id name price description images {
  219. url
  220. }
  221. }
  222. }`, {q: JSON.stringify([{_id}])}))
  223. const actionGoodsByUser = () =>
  224. actionPromise('goodByUser', gql(`query oUser($query: String) {
  225. OrderFind(query:$query){
  226. _id orderGoods{
  227. price count total good{
  228. _id name categories{
  229. name
  230. }
  231. images {
  232. url
  233. }
  234. }
  235. }
  236. owner {
  237. _id login
  238. }
  239. }
  240. }`,
  241. {query: JSON.stringify([{}])}))
  242. store.dispatch(actionRootCats())
  243. store.subscribe(() => {
  244. const {promise} = store.getState()
  245. const {rootCats} = promise
  246. if (rootCats?.payload) {
  247. aside.innerHTML = ''
  248. const regBtn = document.createElement('a')
  249. regBtn.href = `#/register`
  250. regBtn.innerText = 'Register'
  251. const loginBtn = document.createElement('a')
  252. loginBtn.href = `#/login`
  253. loginBtn.innerText = 'Login'
  254. const logoutBtn = document.createElement('button')
  255. logoutBtn.innerText = 'Logout'
  256. aside.append(regBtn, loginBtn, logoutBtn)
  257. logoutBtn.onclick = () => {
  258. store.dispatch(actionAuthLogout())
  259. }
  260. for (const {_id, name} of rootCats?.payload) {
  261. const link = document.createElement('a')
  262. link.href = `#/category/${_id}`
  263. link.innerText = name
  264. aside.append(link)
  265. }
  266. }
  267. })
  268. // location.hash - адресная строка после решетки
  269. window.onhashchange = () => {
  270. const [,route, _id] = location.hash.split('/')
  271. const routes = {
  272. category(){
  273. store.dispatch(actionCatById(_id))
  274. console.log('страница категорий')
  275. },
  276. good(){ //задиспатчить actionGoodById
  277. store.dispatch(actionGoodById(_id))
  278. console.log('страница товара')
  279. },
  280. register(){
  281. createForm(main, 'Register')
  282. btnRegister.onclick = () => {
  283. store.dispatch(actionFullRegister(loginRegister.value, passRegister.value))
  284. }
  285. },
  286. login(){
  287. createForm(main, 'Login')
  288. btnLogin.onclick = () => {
  289. store.dispatch(actionFullLogin(loginLogin.value, passLogin.value))
  290. }
  291. },
  292. orders(){
  293. store.dispatch(actionGoodsByUser())
  294. }
  295. }
  296. if (route in routes) {
  297. routes[route]()
  298. }
  299. }
  300. function createForm(parent, type) {
  301. parent.innerHTML = `
  302. <input id="login${type}" type="text"/>
  303. <input id="pass${type}" type="password"/>
  304. <button id="btn${type}">${type}</button>
  305. `
  306. }
  307. window.onhashchange()
  308. store.subscribe(() => {
  309. const {promise} = store.getState()
  310. const {catById} = promise
  311. const [,route, _id] = location.hash.split('/')
  312. if (catById?.payload && route === 'category'){
  313. const {name} = catById.payload;
  314. main.innerHTML = `<h1>${name}</h1>`
  315. if (catById.payload.subCategories) {
  316. for(const {_id, name} of catById.payload.subCategories) {
  317. const link = document.createElement('a');
  318. link.href = `#/category/${_id}`;
  319. link.innerText = name;
  320. main.append(link);
  321. }
  322. }
  323. if (catById.payload.goods) {
  324. for (const {_id, name, price, images} of catById.payload.goods){
  325. const card = document.createElement('div')
  326. card.innerHTML = `<h2>${name}</h2>
  327. <img src="${backURL}/${images[0].url}" />
  328. <strong>${price}</strong>
  329. <br>
  330. <a href="#/good/${_id}">Перейти на страницу товара</a>
  331. `
  332. main.append(card)
  333. }
  334. }
  335. }
  336. })
  337. store.subscribe(() => {
  338. const {promise} = store.getState()
  339. const {goodById} = promise
  340. const [,route, _id] = location.hash.split('/');
  341. if (goodById?.payload && route === 'good') {
  342. main.innerHTML = '';
  343. const {_id, name, images, price, description} = goodById.payload;
  344. const card = document.createElement('div');
  345. card.innerHTML = `<h2>${name}</h2>
  346. <img src="${backURL}/${images[0].url}" />
  347. <strong>${price}</strong>
  348. <h2>${description}</h2>
  349. `;
  350. main.append(card);
  351. }
  352. }
  353. //ТУТ ДОЛЖНА БЫТЬ ПРОВЕРКА НА НАЛИЧИЕ goodById в редакс
  354. //и проверка на то, что сейчас в адресной строке адрес ВИДА #/good/АЙДИ
  355. //в таком случае очищаем main и рисуем информацию про товар с подробностями
  356. )
  357. store.subscribe(() => {
  358. const {auth} = store.getState()
  359. const {payload} = auth
  360. if (payload?.sub ) {
  361. topContaner.innerHTML = ''
  362. const {id, login} = payload.sub
  363. const name = document.createElement('div')
  364. name.innerText = `ПРИВЕТ ${login}`
  365. topContaner.append(name)
  366. const myOrders = document.createElement('a')
  367. myOrders.innerText = 'Мои заказы'
  368. myOrders.href = `#/orders/${id}`
  369. topContaner.append(myOrders)
  370. } else {
  371. topContaner.innerHTML = ''
  372. }
  373. })
  374. store.subscribe(() => {
  375. const {promise} = store.getState()
  376. const {goodByUser} = promise
  377. const [,route, _id] = location.hash.split('/')
  378. if (goodByUser?.payload && route === 'orders'){
  379. main.innerHTML = ''
  380. if (goodByUser.payload.orderGoods) {
  381. for (const {price, count, total, good: {name, images}} of goodByUser.payload.orderGoods){
  382. const card = document.createElement('div')
  383. card.innerHTML = `<h2>${name}</h2>
  384. <img src="${backURL}/${images[0].url}" />
  385. <strong>Куплено ${count} по ${price} грн. Итого:${total}</strong>
  386. <br>
  387. <a href="#/good/${_id}">Перейти на страницу товара</a>
  388. `
  389. main.append(card)
  390. }
  391. }
  392. }
  393. })
  394. // store.dispatch(actionPromise('delay1000', delay(1000)))
  395. // // store.dispatch(actionPromise('delay2000', delay(2000)))
  396. // // store.dispatch(actionPromise('luke', fetch('https://swapi.dev/api/people/1/').then(res => res.json())))