main.js 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423
  1. //debugger;
  2. function createStore(reducer){
  3. let state = reducer(undefined, {}) //стартовая инициализация состояния, запуск редьюсера со state === undefined
  4. let cbs = [] //массив подписчиков
  5. const getState = () => state //функция, возвращающая переменную из замыкания
  6. const subscribe = cb => (cbs.push(cb), //запоминаем подписчиков в массиве
  7. () => cbs = cbs.filter(c => c !== cb)) //возвращаем функцию unsubscribe, которая удаляет подписчика из списка
  8. const dispatch = action => {
  9. if (typeof action === 'function'){ //если action - не объект, а функция
  10. return action(dispatch, getState) //запускаем эту функцию и даем ей dispatch и getState для работы
  11. }
  12. const newState = reducer(state, action) //пробуем запустить редьюсер
  13. if (newState !== state){ //проверяем, смог ли редьюсер обработать action
  14. state = newState //если смог, то обновляем state
  15. for (let cb of cbs) cb() //и запускаем подписчиков
  16. }
  17. }
  18. return {
  19. getState, //добавление функции getState в результирующий объект
  20. dispatch,
  21. subscribe //добавление subscribe в объект
  22. }
  23. }
  24. function promiseReducer(state={}, {type, name, status, payload, error}){
  25. // {
  26. // login: {status, payload, error}
  27. // catById: {status, payload, error}
  28. // }
  29. if (type === 'PROMISE'){
  30. return {
  31. ...state,
  32. [name]:{status, payload, error}
  33. }
  34. }
  35. return state
  36. }
  37. //const store = createStore(promiseReducer);
  38. function jwtDecode(token){
  39. try {let base64Url = token.split('.')[1];
  40. let base64 = atob(base64Url);
  41. return JSON.parse(base64);
  42. } catch { (err) =>
  43. console.log(err);
  44. }
  45. //раскодировать токен:
  46. //выкусить середочку
  47. //atob
  48. //JSON.parse
  49. //на любом этапе могут быть исключения
  50. }
  51. function authReducer(state, {type, token}){
  52. if (!state){
  53. if(localStorage.authToken){ //проверить localStorage.authToken на наличие
  54. return {
  55. 'type': 'AUTH_LOGIN',
  56. 'token':localStorage.authToken,
  57. }; //если есть - сделать так, что бы следующий if сработал
  58. } else { return state = {}} //если нет - вернуть {}
  59. }
  60. if (type === 'AUTH_LOGIN'){
  61. const bigToken = jwtDecode(token); //взять токен из action
  62. if (bigToken) { //попытаться его jwtDecode
  63. localStorage.setItem('authToken', token); //если удалось, то:
  64. return {
  65. token,
  66. payload: bigToken,
  67. }
  68. } //сохранить токен в localStorage
  69. } //вернуть объект вида {токен, payload: раскодированный токен}
  70. if (type === 'AUTH_LOGOUT'){
  71. localStorage.clear(); //почистить localStorage
  72. return {}; //вернуть пустой объект
  73. }
  74. return state
  75. }
  76. const actionAuthLogin = token => ({type: 'AUTH_LOGIN', token})
  77. const actionAuthLogout = () => ({type: 'AUTH_LOGOUT'})
  78. //const store = createStore(authReducer)
  79. //стартовое состояние может быть с токеном
  80. function combineReducers(reducers){ //перебрать все редьюсеры
  81. return (state={}, action) => { //запустить каждый их них
  82. const newState = {} //передать при этом в него ЕГО ВЕТВЬ общего state, и action как есть
  83. for (const [reducerName, reducer] of Object.entries(reducers)){ //получить newSubState
  84. let newSubState = reducer(state[reducerName], action); //если newSubState отличается от входящего, то записать newSubState в newState
  85. if (newSubState !== state[reducerName]) { //после цикла, если newState не пуст, то вернуть {...state, ...newState}
  86. newState[reducerName] = newSubState; //иначе вернуть state
  87. }
  88. } //{promise: {}, auth: {}}
  89. if (Object.keys(newState).length !== 0) {
  90. return {
  91. ... state,
  92. ... newState,
  93. }
  94. }
  95. return state;
  96. }
  97. }
  98. const combinedReducer = combineReducers({promise: promiseReducer, auth: authReducer});
  99. const store = createStore(combinedReducer);
  100. const actionPending = name => ({type: 'PROMISE', status: 'PENDING', name})
  101. const actionResolved = (name, payload) => ({type: 'PROMISE', status: 'RESOLVED', name, payload})
  102. const actionRejected = (name, error) => ({type: 'PROMISE', status: 'REJECTED', name, error})
  103. const actionPromise = (name, promise) =>
  104. async (dispatch) => {
  105. dispatch(actionPending(name))
  106. try{
  107. let payload = await promise
  108. dispatch(actionResolved(name, payload))
  109. return payload;
  110. }
  111. catch(error){
  112. dispatch(actionRejected(name, error))
  113. }
  114. }
  115. const getGQL = url =>
  116. (query, variables = {}) =>
  117. fetch(url, {
  118. //метод
  119. method: 'POST',
  120. headers: {
  121. //заголовок content-type
  122. "Content-Type": "application/json",
  123. ...(localStorage.authToken ? {"Authorization": "Bearer " + localStorage.authToken} :
  124. {})
  125. },
  126. //body с ключами query и variables
  127. body: JSON.stringify({query, variables})
  128. })
  129. .then(res => res.json())
  130. .then(data => {
  131. if (data.errors && !data.data)
  132. throw new Error(JSON.stringify(data.errors))
  133. return data.data[Object.keys(data.data)[0]]
  134. })
  135. const backURL = 'http://shop-roles.asmer.fs.a-level.com.ua'
  136. const gql = getGQL(`${backURL}/graphql`)
  137. //store.dispatch(actionPromise('delay1000', delay(1000)))//{promise: {delay1000: '''}, auth: {}}
  138. //store.dispatch(actionAuthLogin(token))//{promise: {delay1000: '''}, auth: {token .....}}
  139. //
  140. //+ ПЕРЕДЕЛАТЬ ОТОБРАЖЕНИЕ с поправкой на то, что теперь промисы не в корне state а в state.promise
  141. const actionLogin = (login, password) =>
  142. actionPromise('login', gql(`query authorize ($login:String, $password:String){
  143. login(login:$login, password:$password)}`
  144. ,
  145. {
  146. "login": `${login}`,
  147. "password":`${password}`
  148. }
  149. )
  150. )
  151. const actionFullLogin = (login, password) =>
  152. async dispatch => {
  153. let token = await dispatch(actionLogin(login, password));
  154. if (token){
  155. dispatch(actionAuthLogin(token))
  156. }
  157. }
  158. const actionRegister = (login, password) => //const actionRegister //actionPromise
  159. actionPromise('register', gql(`mutation register($user:UserInput){
  160. UserUpsert(user:$user){
  161. _id login
  162. }
  163. }`, {"user":{
  164. "login": `${login}`,
  165. "password": `${password}`
  166. }
  167. }
  168. )
  169. )
  170. const actionFullRegister = (login, password) => //const actionFullRegister = (login, password) => //actionRegister + actionFullLogin
  171. async dispatch => {
  172. dispatch(actionAuthLogout()); //+ интерфейс к этому - форму логина, регистрации, может повесить это на #/login #/register
  173. let reg = await dispatch(actionRegister(login, password));
  174. if(!reg){
  175. dispatch(actionFullLogin(login, password))
  176. } //+ #/orders показывает ваши бывшие заказы:
  177. } //сделать actionMyOrders
  178. //проверить:
  179. //поделать store.dispatch с разными action. Скопипастить токен
  180. //проверить перезагрузку страницы.
  181. //const store = createStore(promiseReducer)
  182. store.subscribe(() => console.log(store.getState()))
  183. const delay = ms => new Promise(ok => setTimeout(() => ok(ms), ms))
  184. const actionRootCats = () =>
  185. actionPromise('rootCats', gql(`query {
  186. CategoryFind(query: "[{\\"parent\\":null}]"){
  187. _id name
  188. }
  189. }`))
  190. const actionCatById = (_id) => //добавить подкатегории
  191. actionPromise('catById', gql(`query catById($q: String){
  192. CategoryFindOne(query:$q){
  193. name subCategories {
  194. _id
  195. name
  196. goods {
  197. _id name description price images{
  198. url
  199. }
  200. }
  201. }goods {
  202. _id name price images {
  203. url
  204. }
  205. }
  206. }
  207. }`, {"q": JSON.stringify([{_id}])}))
  208. // store.dispatch(actionRootCats())
  209. const actionGoodById = (_id) =>
  210. actionPromise('goodById', gql(`query goodById($goodId:String){
  211. GoodFindOne(query:$goodId){
  212. _id name description price images{
  213. _id text url
  214. }
  215. }
  216. }`, {"goodId": JSON.stringify([{_id}])}))
  217. store.subscribe(() => {
  218. const {rootCats} = store.getState().promise
  219. if (rootCats?.payload){
  220. aside.innerHTML = ''
  221. for (const {_id, name} of rootCats?.payload){
  222. const link = document.createElement('a')
  223. link.href = `#/category/${_id}`
  224. link.innerText = name
  225. aside.append(link)
  226. }
  227. }
  228. })
  229. window.onhashchange = () => {
  230. const [, route, _id] = location.hash.split('/');
  231. const routes = {
  232. category(){
  233. store.dispatch(actionCatById(_id))
  234. },
  235. good(){
  236. store.dispatch(actionGoodById(_id));//задиспатчить actionGoodById
  237. console.log('ТОВАРОСТРАНИЦА')
  238. },
  239. login(){
  240. document.getElementById('main').innerHTML = ""; //отрисовка тут
  241. const formRegistration = document.createElement('form'); //по кнопке - store.dispatch(actionFullLogin(login, password))
  242. const inputLogin = document.createElement('input');
  243. inputLogin.placeholder = "Login";
  244. inputLogin.style.display="block";
  245. const inputPassword = document.createElement('input');
  246. inputPassword.placeholder = "Password";
  247. inputPassword.style.display="block";
  248. inputPassword.style.marginTop = "10px";
  249. const buttonSignIn = document.createElement('button');
  250. buttonSignIn.innerText = "Sign in";
  251. buttonSignIn.style.marginTop = "10px";
  252. const buttonReset = document.createElement('button');
  253. buttonReset.type ="reset";
  254. buttonReset.innerText =" Reset";
  255. buttonReset.style.marginTop = "10px";
  256. buttonReset.style.marginLeft = "10px";
  257. main.append(formRegistration);
  258. formRegistration.append(inputLogin);
  259. formRegistration.append(inputPassword);
  260. formRegistration.append(buttonSignIn);
  261. formRegistration.append(buttonReset);
  262. buttonSignIn.addEventListener('click', async function enterStore () {
  263. console.log('clicked')
  264. await store.dispatch(actionFullLogin(inputLogin.value, inputPassword.value));
  265. const result = store.getState().promise.login.payload;
  266. if(result){
  267. document.getElementById('main').innerHTML = "";
  268. store.dispatch(actionRootCats());
  269. }
  270. })
  271. },
  272. register(){
  273. document.getElementById('main').innerHTML = ""; //отрисовка тут
  274. const formRegistration = document.createElement('form'); //по кнопке - store.dispatch(actionFullLogin(login, password))
  275. const inputLogin = document.createElement('input');
  276. inputLogin.placeholder = "Login";
  277. inputLogin.style.display="block";
  278. const inputPassword = document.createElement('input');
  279. inputPassword.placeholder = "Password";
  280. inputPassword.style.display="block";
  281. inputPassword.style.marginTop = "10px";
  282. const buttonSignIn = document.createElement('button');
  283. buttonSignIn.innerText = "Sign up";
  284. buttonSignIn.style.marginTop = "10px";
  285. const buttonReset = document.createElement('button');
  286. buttonReset.type ="reset";
  287. buttonReset.innerText =" Reset";
  288. buttonReset.style.marginTop = "10px";
  289. buttonReset.style.marginLeft = "10px";
  290. main.append(formRegistration);
  291. formRegistration.append(inputLogin);
  292. formRegistration.append(inputPassword);
  293. formRegistration.append(buttonSignIn);
  294. formRegistration.append(buttonReset);
  295. buttonSignIn.addEventListener('click', async function enterStore () {
  296. await store.dispatch(actionFullRegister(inputLogin.value, inputPassword.value));
  297. console.log('store.getState().promise', store.getState().promise)
  298. const result = store.getState().promise.register.payload; console.log("result",result)
  299. if(result){
  300. document.getElementById('main').innerHTML = "";
  301. store.dispatch(actionRootCats());
  302. }
  303. })
  304. },
  305. }
  306. if (route in routes)
  307. routes[route]()
  308. }
  309. window.onhashchange()
  310. store.subscribe(() => {
  311. const {catById} = store.getState().promise
  312. const [,route, _id] = location.hash.split('/')
  313. if (catById?.payload && route === 'category'){
  314. const {name, subCategories} = catById.payload;
  315. let str = '';
  316. if (subCategories){
  317. for(let subCateg of subCategories){
  318. str += `<h4><a href="#/subCategory/${subCateg._id}"> ${subCateg.name}</a></h4>`; console.log("subCateg", subCateg);
  319. const {goods} = subCateg;
  320. if (goods){
  321. for(let good of goods) {
  322. const {name, price, images, _id} = good;
  323. str += `<h2>${name}</h2>
  324. <img src="${backURL}/${images[0].url}" />
  325. <strong>${price}</strong>
  326. <a href="#/good/${_id}">${name}</a>`
  327. }
  328. }
  329. }
  330. } else {str += 'Подкатегории отсутствуют'}
  331. main.innerHTML = `<h1>${name}</h1> ${str} ` //ТУТ ДОЛЖНЫ БЫТЬ ПОДКАТЕГОРИИ
  332. for (const {_id, name, price, images} of catById.payload.goods){
  333. const card = document.createElement('div')
  334. card.innerHTML = `<h2>${name}</h2>
  335. <img src="${backURL}/${images[0].url}" />
  336. <strong>${price}</strong>
  337. <a href="#/good/${_id}">${name}</a> `
  338. main.append(card)
  339. }
  340. }
  341. })
  342. store.subscribe(() => {
  343. const {goodById} = store.getState().promise; //console.log("dfsdfs", store.getState().goodById)
  344. if(goodById) {
  345. const [, , _id] = location.hash.split('/'); //ТУТ ДОЛЖНА БЫТЬ ПРОВЕРКА НА НАЛИЧИЕ goodById в редакс
  346. if(goodById?.payload && location.hash == `#/good/${_id}`){ //и проверка на то, что сейчас в адресной строке адрес ВИДА #/good/АЙДИ
  347. document.getElementById('main').innerHTML = "";
  348. const {name, description, price, images} = goodById.payload;
  349. const card = document.createElement('div'); //в таком случае очищаем main и рисуем информацию про товар с подробностями
  350. card.innerHTML = `<h2>${name}</h2>
  351. <img src="${backURL}/${images[0].url}" />
  352. <strong>${price} $</strong>
  353. <p>${description}</p>`
  354. main.append(card);
  355. }
  356. }
  357. })