index.html 7.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243
  1. <!DOCTYPE html>
  2. <html>
  3. <head>
  4. <meta charset="utf-8">
  5. <title>thunk 2</title>
  6. <style>
  7. #mainContainer{
  8. display:flex;
  9. }
  10. #aside{
  11. width: 30%;
  12. }
  13. #aside > a{
  14. display: block;
  15. }
  16. img{
  17. width:300px;
  18. }
  19. </style>
  20. </head>
  21. <body>
  22. <header>Куда я попал?</header>
  23. <div id="mainContainer">
  24. <aside id="aside">
  25. Категории
  26. </aside>
  27. <main id="main">
  28. Контент
  29. </main>
  30. </div>
  31. <script>
  32. function createStore(reducer){
  33. let state = reducer(undefined, {})
  34. let cbs = []
  35. const getState = () => state
  36. const subscribe = cb => (cbs.push(cb),
  37. () => cbs = cbs.filter(c => c !== cb))
  38. const dispatch = action => {
  39. if (typeof action === 'function'){
  40. return action(dispatch, getState)
  41. }
  42. const newState = reducer(state, action)
  43. if (newState !== state){
  44. state = newState
  45. for (let cb of cbs) cb()
  46. }
  47. }
  48. return {
  49. getState,
  50. dispatch,
  51. subscribe
  52. }
  53. }
  54. function promiseReducer(state={},{type,status,payload,error,name}){
  55. //{login:{status,payload,error}
  56. //catById: {status,payload,error}
  57. //}
  58. if(type==="PROMISE"){
  59. //вернуть новый state, в котором один ключ поменян/добавлен на name
  60. //значение этого ключа - обьект вида {status,payload,error}
  61. return{
  62. ...state,
  63. [name]:{status,payload,error}
  64. }
  65. }
  66. return state;
  67. }
  68. const actionPending=name=>({type:'PROMISE', status:"PENDING",name})
  69. const actionResolved = (name,payload) =>({type:"PROMISE", status:"Resolved",name,payload})
  70. const actionRejected=(name,error)=>({type:'PROMISE',status:"Rejected",name,error})
  71. //const delay=ms=>new Promise(ok=>setTimeout(()=>ok(ms),ms))
  72. const store=createStore(promiseReducer);
  73. store.subscribe(()=>console.log(store.getState()))
  74. /*store.dispatch(actionPending('delay1000'))
  75. delay(1000).then(data=>store.dispatch(actionResolved('delay1000',data)),
  76. err=>dispatch(actionRejected('delay1000',error)))
  77. store.dispatch(actionPending('delay2000'))
  78. delay(2000).then(data=>store.dispatch(actionResolved('delay2000',data)),
  79. err=>dispatch(actionRejected('delay2000',error)))*/
  80. const actionPromise=(name,promise)=>
  81. async dispatch=>{
  82. dispatch(actionPending(name))
  83. /*let data= await promise.then(data=>dispatch(actionResolved(name,data)),
  84. error=>dispatch(actionRejected(name,error)))*/
  85. try{
  86. let data= await promise
  87. dispatch(actionResolved(name,data))
  88. return data
  89. }catch(error){
  90. dispatch(actionRejected(name,error))
  91. }
  92. }
  93. const getGQL = url =>
  94. (query, variables = {}) =>
  95. fetch(url, {
  96. method: 'POST',
  97. headers: {
  98. "Content-Type": "application/json",
  99. ...(localStorage.authToken ? { "Authorization": "Bearer " + localStorage.authToken } : {})
  100. },
  101. body: JSON.stringify({ query, variables })
  102. })
  103. .then(res => res.json())
  104. .then(data => {
  105. if (data.errors && !data.data)
  106. throw new Error(JSON.stringify(data.errors))
  107. return data.data[Object.keys(data.data)[0]]
  108. })
  109. const backURL='http://shop-roles.asmer.fs.a-level.com.ua'
  110. const gql=getGQL(backURL+'/graphql')
  111. const actionRootCats = () =>
  112. actionPromise('rootCats', gql(`query {
  113. CategoryFind(query: "[{\\"parent\\":null}]"){
  114. _id name
  115. }
  116. }`))
  117. const actionCatById = (_id) =>
  118. actionPromise('catById', gql(`query catById($q: String){
  119. CategoryFindOne(query: $q) {
  120. _id name goods{
  121. _id name price description images {
  122. url
  123. }
  124. }
  125. subCategories {
  126. name _id goods {
  127. _id name description
  128. }
  129. }
  130. }
  131. }`, { q: JSON.stringify([{_id}])}))
  132. const actionGoodById = (_id) =>
  133. actionPromise('goodById', gql(`query goodById($q: String){
  134. GoodFindOne(query: $q){
  135. _id name description price images{
  136. url
  137. }
  138. }
  139. }`, { q: JSON.stringify([{_id}]) }))
  140. store.dispatch(actionRootCats())
  141. store.subscribe(()=>{
  142. const {rootCats}=store.getState()
  143. if(rootCats?.payload){
  144. aside.innerHTML=''
  145. for(const {_id,name} of rootCats?.payload){
  146. const link=document.createElement('a')
  147. link.href=`#/category/${_id}`
  148. link.innerHTML=name
  149. aside.append(link)
  150. }
  151. }
  152. })
  153. window.onhashchange=()=>{
  154. const [,route,_id]=location.hash.split('/')
  155. const routes={
  156. category(){
  157. store.dispatch(actionCatById(_id))
  158. },
  159. good(){
  160. //задиспатчить actionGoodById
  161. store.dispatch(actionGoodById(_id))
  162. console.log('ТОВАРОСТРАНИЦА')
  163. }
  164. }
  165. if(route in routes){
  166. routes[route]()
  167. }
  168. }
  169. window.onhashchange()
  170. store.subscribe(() => {
  171. const {catById} = store.getState()
  172. const [, route, _id] = location.hash.split('/')
  173. if (catById?.payload && route === 'category'){
  174. const { name } = catById.payload
  175. main.innerHTML = `<h1>${name}</h1>`
  176. //Тут должны быть подкатегории
  177. if (catById.payload.subCategories) {
  178. for (let { _id, name } of catById.payload.subCategories) {
  179. const link = document.createElement('a')
  180. link.href = `#/category/${_id}`
  181. link.textContent = name
  182. main.append(link)
  183. }
  184. }
  185. for (const { _id, name, price, images } of catById.payload.goods) {
  186. const card = document.createElement('div')
  187. card.innerHTML = `<h2>${name}</h2>
  188. <img src="${backURL}/${images[0].url}"/>
  189. <div>
  190. <b>Стоимость:</b> <b><sub>${price}UAH</sub></b>
  191. <br><a href=#/good/${_id}>Страница товара</a>
  192. </div>`
  193. //Тут должны быть ссылка на страницу товара вида #/good/айди
  194. main.append(card)
  195. }
  196. }
  197. })
  198. store.subscribe(() => {
  199. const { goodById } = store.getState()
  200. const [, route, _id] = location.hash.split('/')
  201. if(goodById?.payload && route === 'good' && location.href.includes(`#/good/${_id}`)){
  202. main.innerHTML = ''
  203. let { _id, name, price, images, description } = goodById.payload
  204. let item = document.createElement('div')
  205. console.log(description);
  206. item.innerHTML = `<h2>${name}</h2>
  207. <img src="${backURL}/${images[0].url}"/>
  208. <div>
  209. <p><b>Стоимость:</b> <b><sub>${price} UAH</sub></b></strong></p>
  210. <p><b>Описание:</b> <sub>${description}</sub></p>
  211. </div>`
  212. main.append(item)
  213. }
  214. })
  215. /*store.dispatch(actionPromise('delay1000',delay(1000)))
  216. store.dispatch(actionPromise('delay2000',delay(2000)))
  217. store.dispatch(actionPromise('luke',fetch('https://swapi.dev/api/people/1/').then(data=>data.json())))*/
  218. </script>
  219. </body>
  220. </html>