script.js 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665
  1. const backendURL = "http://shop-roles.node.ed.asmer.org.ua/graphql";
  2. const backendURLNotGraphQL = "http://shop-roles.node.ed.asmer.org.ua";
  3. function createStore(reducer) {
  4. let state = reducer(undefined, {}); //стартовая инициализация состояния, запуск редьюсера со state === undefined
  5. let cbs = []; //массив подписчиков
  6. const getState = () => state; //функция, возвращающая переменную из замыкания
  7. const subscribe = (cb) => (
  8. cbs.push(cb), //запоминаем подписчиков в массиве
  9. () => (cbs = cbs.filter((c) => c !== cb))
  10. ); //возвращаем функцию unsubscribe, которая удаляет подписчика из списка
  11. const dispatch = (action) => {
  12. if (typeof action === "function") {
  13. //если action - не объект, а функция
  14. return action(dispatch, getState); //запускаем эту функцию и даем ей dispatch и getState для работы
  15. }
  16. const newState = reducer(state, action); //пробуем запустить редьюсер
  17. if (newState !== state) {
  18. //проверяем, смог ли редьюсер обработать action
  19. state = newState; //если смог, то обновляем state
  20. for (let cb of cbs) cb(); //и запускаем подписчиков
  21. }
  22. };
  23. return {
  24. getState, //добавление функции getState в результирующий объект
  25. dispatch,
  26. subscribe, //добавление subscribe в объект
  27. };
  28. }
  29. function jwtDecode(token) {
  30. try {
  31. return JSON.parse(atob(token.split(".")[1]));
  32. } catch (e) {}
  33. }
  34. function authReducer(state = {}, { type, token }) {
  35. //{
  36. // token, payload
  37. //}
  38. if (type === "AUTH_LOGIN") {
  39. //пытаемся токен раскодировать
  40. const payload = jwtDecode(token);
  41. if (payload) {
  42. return {
  43. token,
  44. payload, //payload - раскодированный токен;
  45. };
  46. }
  47. }
  48. if (type === "AUTH_LOGOUT") {
  49. return {};
  50. }
  51. return state;
  52. }
  53. const actionAuthLogin = (token) => (dispatch, getState) => {
  54. const oldState = getState();
  55. dispatch({ type: "AUTH_LOGIN", token });
  56. const newState = getState();
  57. if (oldState !== newState) localStorage.authToken = token;
  58. };
  59. const actionAuthLogout = () => (dispatch) => {
  60. dispatch({ type: "AUTH_LOGOUT" });
  61. localStorage.removeItem("authToken");
  62. };
  63. function promiseReducer(state = {}, { type, name, status, payload, error }) {
  64. if (type === "PROMISE") {
  65. return {
  66. ...state,
  67. [name]: { status, payload, error },
  68. };
  69. }
  70. return state;
  71. }
  72. const actionPending = (name) => ({
  73. type: "PROMISE",
  74. status: "PENDING",
  75. name,
  76. });
  77. const actionFulfilled = (name, payload) => ({
  78. type: "PROMISE",
  79. status: "FULFILLED",
  80. name,
  81. payload,
  82. });
  83. const actionRejected = (name, error) => ({
  84. type: "PROMISE",
  85. status: "REJECTED",
  86. name,
  87. error,
  88. });
  89. const actionPromise = (name, promise) => async (dispatch) => {
  90. try {
  91. dispatch(actionPending(name));
  92. let payload = await promise;
  93. dispatch(actionFulfilled(name, payload));
  94. return payload;
  95. } catch (e) {
  96. dispatch(actionRejected(name, e));
  97. }
  98. };
  99. function cartReducer(state = {}, { type, count = 1, good }) {
  100. // type CART_ADD CART_REMOVE CART_CLEAR CART_DEC
  101. // {
  102. // id1: {count: 1, good: {name, price, images, id}}
  103. // }
  104. if (type === "CART_ADD") {
  105. return {
  106. ...state,
  107. [good._id]: { count: count + (state[good._id]?.count || 0), good },
  108. };
  109. }
  110. if (type === "CART_CLEAR") {
  111. return {};
  112. }
  113. if (type === "CART_REMOVE") {
  114. //let newState = {...state}
  115. let { [good._id]: poh, ...newState } = state; //o4en strashnoe koldunstvo
  116. //delete newState[good._id]
  117. return newState;
  118. }
  119. return state;
  120. }
  121. const actionCartAdd = (good, count = 1) => ({ type: "CART_ADD", good, count });
  122. const actionCartChange = (good, count = 1) => ({
  123. type: "CART_CHANGE",
  124. good,
  125. count,
  126. }); ///oninput меняяем полностью
  127. const actionCartDelete = (good) => ({ type: "CART_DELETE", good });
  128. const actionCartClear = () => ({ type: "CART_CLEAR" });
  129. function localStoreReducer(reducer, localStorageKey) {
  130. function localStoredReducer(state, action) {
  131. // Если state === undefined, то достать старый state из local storage
  132. if (state === undefined) {
  133. try {
  134. return JSON.parse(localStorage[localStorageKey]);
  135. } catch (e) {}
  136. }
  137. const newState = reducer(state, action);
  138. // Сохранить newState в local storage
  139. localStorage[localStorageKey] = JSON.stringify(newState);
  140. return newState;
  141. }
  142. return localStoredReducer;
  143. }
  144. const delay = (ms) => new Promise((ok) => setTimeout(() => ok(ms), ms));
  145. function combineReducers(reducers) {
  146. //пачку редьюсеров как объект {auth: authReducer, promise: promiseReducer}
  147. function combinedReducer(combinedState = {}, action) {
  148. //combinedState - типа {auth: {...}, promise: {....}}
  149. const newCombinedState = {};
  150. for (const [reducerName, reducer] of Object.entries(reducers)) {
  151. const newSubState = reducer(combinedState[reducerName], action);
  152. if (newSubState !== combinedState[reducerName]) {
  153. newCombinedState[reducerName] = newSubState;
  154. }
  155. }
  156. if (Object.keys(newCombinedState).length === 0) {
  157. return combinedState;
  158. }
  159. return { ...combinedState, ...newCombinedState };
  160. }
  161. return combinedReducer; //нам возвращают один редьюсер, который имеет стейт вида {auth: {...стейт authReducer-а}, promise: {...стейт promiseReducer-а}}
  162. }
  163. const store = createStore(
  164. combineReducers({
  165. auth: authReducer,
  166. promise: promiseReducer,
  167. cart: localStoreReducer(cartReducer, "cart"),
  168. })
  169. ); //не забудьте combineReducers если он у вас уже есть
  170. if (localStorage.authToken) {
  171. store.dispatch(actionAuthLogin(localStorage.authToken));
  172. }
  173. //const store = createStore(combineReducers({promise: promiseReducer, auth: authReducer, cart: cartReducer}))
  174. store.subscribe(() => console.log(store.getState()));
  175. const gql = (url, query, variables) =>
  176. fetch(url, {
  177. method: "POST",
  178. headers: {
  179. "Content-Type": "application/json",
  180. Accept: "application/json",
  181. },
  182. body: JSON.stringify({ query, variables }),
  183. }).then((res) => res.json());
  184. const actionRootCats = () =>
  185. actionPromise(
  186. "rootCats",
  187. gql(
  188. backendURL,
  189. `query {
  190. CategoryFind(query: "[{\\"parent\\":null}]"){
  191. _id name
  192. }
  193. }`
  194. )
  195. );
  196. const actionCatById = (
  197. _id //добавить подкатегории
  198. ) =>
  199. actionPromise(
  200. "catById",
  201. gql(
  202. backendURL,
  203. `query catById($q: String){
  204. CategoryFindOne(query: $q){
  205. _id name goods {
  206. _id name price images {
  207. url
  208. }
  209. }
  210. }
  211. }`,
  212. { q: JSON.stringify([{ _id }]) }
  213. )
  214. );
  215. const actionLogin = (login, password) =>
  216. actionPromise(
  217. "actionLogin",
  218. gql(
  219. backendURL,
  220. `query log($login:String, $password:String){
  221. login(login:$login, password:$password)
  222. }`,
  223. { login, password }
  224. )
  225. );
  226. const actionGoodById = (_id) =>
  227. actionPromise(
  228. "GoodFineOne",
  229. gql(
  230. backendURL,
  231. `query goodByid($goodId: String) {
  232. GoodFindOne(query: $goodId) {
  233. _id
  234. name
  235. price
  236. description
  237. images {
  238. url
  239. }
  240. }
  241. }`,
  242. { goodId: JSON.stringify([{ _id }]) }
  243. )
  244. );
  245. store.dispatch(actionRootCats());
  246. const actionFullLogin = (login, password) => async (dispatch) => {
  247. let result = await dispatch(actionLogin(login, password));
  248. if (result.data.login) {
  249. dispatch(actionAuthLogin(result.data.login));
  250. }
  251. };
  252. const actionFullRegister = (login, password) => async (dispatch) => {
  253. let user = await dispatch(
  254. actionPromise(
  255. "register",
  256. gql(
  257. backendURL,
  258. `mutation register($login: String, $password: String) {
  259. UserUpsert(user: {login: $login, password: $password}) {
  260. _id
  261. login
  262. }
  263. }`,
  264. { login: login, password: password }
  265. )
  266. )
  267. );
  268. if (user) {
  269. dispatch(actionFullLogin(login, password));
  270. }
  271. };
  272. const actionOrders = () =>
  273. actionPromise(
  274. "orders",
  275. gql(
  276. backendURL,
  277. `query findOrder($q: String) {
  278. OrderFind(query: $q) {
  279. _id
  280. total
  281. createdAt
  282. orderGoods {
  283. count
  284. good {
  285. name
  286. price
  287. }
  288. }
  289. }
  290. }`,
  291. { q: JSON.stringify([{}]) }
  292. )
  293. );
  294. store.subscribe(() => {
  295. const rootCats =
  296. store.getState().promise.rootCats?.payload?.data.CategoryFind;
  297. if (rootCats) {
  298. aside.innerHTML = "";
  299. for (let { _id, name } of rootCats) {
  300. const a = document.createElement("a");
  301. a.href = `#/category/${_id}`;
  302. a.innerHTML = name;
  303. aside.append(a);
  304. }
  305. }
  306. });
  307. store.subscribe(() => {
  308. const catById =
  309. store.getState().promise.catById?.payload?.data.CategoryFindOne;
  310. const [, route] = location.hash.split("/");
  311. if (catById && route === "category") {
  312. const { name, goods, _id } = catById;
  313. categoryName.innerHTML = `<h1>${name}</h1>`;
  314. var element = document.getElementById("productBlock");
  315. while (element.firstChild) {
  316. element.removeChild(element.firstChild);
  317. }
  318. for (let { _id, name, price, images } of goods) {
  319. const description = document.createElement("div");
  320. const textBlock = document.createElement("div");
  321. const imgProduct = document.createElement("img");
  322. const a = document.createElement("p");
  323. const productPrice = document.createElement("p");
  324. const b = document.getElementById(productBlock);
  325. const linkCard = document.createElement("a");
  326. productBlock.append(linkCard);
  327. linkCard.href = `#/good/${_id}`;
  328. linkCard.append(description);
  329. description.setAttribute("class", "card");
  330. description.append(imgProduct);
  331. imgProduct.src = `http://shop-roles.node.ed.asmer.org.ua/${images[0].url}`;
  332. description.append(textBlock);
  333. // a.href = `#/good/${_id}`;
  334. a.innerHTML = name;
  335. textBlock.append(a);
  336. productPrice.innerHTML = "price: " + price;
  337. textBlock.append(productPrice);
  338. const addToCartButton = document.createElement("p");
  339. addToCartButton.innerText = "click to buy";
  340. addToCartButton.className = "addToCartButton";
  341. textBlock.append(addToCartButton);
  342. }
  343. }
  344. });
  345. const bPoputDeleteBlock = document.createElement("div");
  346. const bPoput = document.createElement("div");
  347. bPoput.className = "b-popup";
  348. bPoput.id = "b-popup";
  349. const bPoputContainer = document.createElement("div");
  350. bPoputContainer.className = "b-popup-content";
  351. bPoputContainer.id = "b-popup-content";
  352. const buttonGoodDeleteBlock = document.createElement("div");
  353. buttonGoodDeleteBlock.id = "buttonGoodDeleteBlock";
  354. const buttonCloseCart = document.createElement("button");
  355. buttonCloseCart.innerText = `×`;
  356. buttonCloseCart.id = "buttonCloseCartId";
  357. const buttonGoodDelete = document.createElement("button");
  358. buttonGoodDelete.innerText = "delete";
  359. buttonGoodDelete.id = "buttonDelete";
  360. shoppingCart.onclick = () => {
  361. header.append(bPoput);
  362. bPoput.append(bPoputContainer);
  363. };
  364. bPoputContainer.append(buttonGoodDeleteBlock);
  365. buttonGoodDeleteBlock.append(buttonGoodDelete);
  366. bPoputContainer.append(buttonCloseCart);
  367. const divToCardBlock = document.createElement("div");
  368. store.subscribe(() => {
  369. divToCardBlock.innerHTML = "";
  370. toCartById = store.getState().cart;
  371. let countSum = 0;
  372. for (let value of Object.values(toCartById)) {
  373. const { count, good } = value;
  374. countSum += count;
  375. divToCardBlock.id = "divToCartBlock";
  376. const divToCart = document.createElement("div");
  377. const goodByIdImage = document.createElement("img");
  378. const goodByIdName = document.createElement("h2");
  379. const goodByIdCount = document.createElement("h2");
  380. const buttonPlus = document.createElement("button");
  381. const buttonMinus = document.createElement("button");
  382. buttonPlus.innerHTML = "+";
  383. buttonMinus.innerHTML = "-";
  384. buttonPlus.id = "buttonPlus";
  385. buttonMinus.id = "buttonMinus";
  386. divToCart.id = "divToCart";
  387. bPoputContainer.append(divToCardBlock);
  388. divToCardBlock.append(divToCart);
  389. divToCart.append(goodByIdImage);
  390. divToCart.append(goodByIdName);
  391. divToCart.append(goodByIdCount);
  392. divToCart.append(buttonPlus);
  393. divToCart.append(buttonMinus);
  394. goodByIdImage.src = `${backendURLNotGraphQL}/${value.good.images[0].url}`;
  395. goodByIdName.innerText = good.name;
  396. goodByIdCount.innerText = count;
  397. }
  398. shoppingCart.innerHTML = "Cart: " + countSum;
  399. buttonCloseCart.onclick = () => {
  400. var parent = document.getElementById("header");
  401. var child = document.getElementById("b-popup");
  402. parent.removeChild(child);
  403. };
  404. const payload = store.getState().auth.token;
  405. if (payload) {
  406. shoppingCart.style.display = "block";
  407. } else {
  408. shoppingCart.style.display = "none";
  409. }
  410. });
  411. buttonGoodDelete.onclick = () => {
  412. store.dispatch(actionCartClear());
  413. let a = document.getElementById("divToCartBlock");
  414. a.innerHTML = "";
  415. let b = document.getElementById("shoppingCart");
  416. b.innerHTML = "Cart";
  417. };
  418. const buyButtom = document.createElement("button");
  419. const productImg = document.createElement("img");
  420. const productName = document.createElement("h1");
  421. const productPrice = document.createElement("h2");
  422. const textBlock = document.createElement("div");
  423. const flexBlock = document.createElement("div");
  424. const productDescription = document.createElement("p");
  425. store.subscribe(() => {
  426. const goodById =
  427. store.getState().promise.GoodFineOne?.payload?.data.GoodFindOne;
  428. const [, route, _id] = location.hash.split("/");
  429. if (goodById && route === "good") {
  430. var element = document.getElementById("productBlock");
  431. while (element.firstChild) {
  432. element.removeChild(element.firstChild);
  433. }
  434. const { name, price, description, images } = goodById;
  435. flexBlock.id = "flexBlock";
  436. productBlock.append(flexBlock);
  437. flexBlock.append(productImg);
  438. productImg.style.width = "500px";
  439. productImg.style.height = "500px";
  440. productImg.src = `http://shop-roles.node.ed.asmer.org.ua/${images[0].url}`;
  441. textBlock.id = "textBlock";
  442. flexBlock.append(textBlock);
  443. productName.innerHTML = name;
  444. textBlock.append(productName);
  445. productPrice.innerHTML = "price: " + price;
  446. textBlock.append(productPrice);
  447. productDescription.innerHTML = description;
  448. textBlock.append(productDescription);
  449. buyButtom.id = "buyButtom";
  450. buyButtom.innerHTML = "Add to cart";
  451. textBlock.append(buyButtom);
  452. buyButtom.onclick = () => {
  453. store.dispatch(actionCartAdd(goodById));
  454. };
  455. }
  456. });
  457. store.subscribe(() => {
  458. const catById =
  459. store.getState().promise.catById?.payload?.data.CategoryFindOne;
  460. const [, route, _id] = location.hash.split("/");
  461. if (catById && route === "good") {
  462. const { name, price, description, images } = catById;
  463. categoryName.innerHTML = `<h1>${name}</h1>`;
  464. }
  465. });
  466. const h2text = document.createElement("h2");
  467. h2text.id = "h2text";
  468. qwer.append(h2text);
  469. const logoutButton = document.createElement("button");
  470. logoutButton.id = "logoutButton";
  471. qwer.append(logoutButton);
  472. store.subscribe(() => {
  473. const payload = store.getState().auth.token;
  474. if (payload) {
  475. buyButtom.style.display = "block";
  476. logoutButton.style.display = "block";
  477. logoutButton.innerHTML = "Logout";
  478. login.style.display = "none";
  479. reg.style.display = "none";
  480. h2text.style.display = "block";
  481. h2text.innerText = jwtDecode(payload).sub.login;
  482. } else {
  483. buyButtom.style.display = "none";
  484. h2text.style.display = "none";
  485. logoutButton.style.display = "none";
  486. }
  487. });
  488. const buttonLogin = document.createElement("button");
  489. buttonLogin.id = "loginInputt";
  490. buttonLogin.innerText = "Login";
  491. const buttonReg = document.createElement("button");
  492. buttonReg.id = "regInput";
  493. buttonReg.innerText = "Registration";
  494. function bPopupCreate(text) {
  495. const bPopup = document.createElement("div");
  496. const bPopupContent = document.createElement("div");
  497. bPopup.id = "b-popup";
  498. bPopup.className = "b-popup";
  499. bPopupContent.className = "b-popup-content b-poput-container-flex";
  500. header.append(bPopup);
  501. bPopup.append(bPopupContent);
  502. const buttonCloseCart = document.createElement("button");
  503. buttonCloseCart.innerText = `×`;
  504. buttonCloseCart.id = "buttonCloseCartId";
  505. bPopupContent.append(buttonCloseCart);
  506. const loginText = document.createElement("h2");
  507. const passwordText = document.createElement("h2");
  508. loginText.innerText = "Enter Login:";
  509. bPopupContent.append(loginText);
  510. const loginInput = document.createElement("input");
  511. loginInput.type = "text";
  512. bPopupContent.append(loginInput);
  513. loginInput.id = "loginInput";
  514. loginInput.value = "illiaKozyr";
  515. passwordText.innerText = "Enter Password:";
  516. bPopupContent.append(passwordText);
  517. const loginInputPassword = document.createElement("input");
  518. loginInputPassword.type = "password";
  519. bPopupContent.append(loginInputPassword);
  520. loginInputPassword.id = "passwordInput";
  521. loginInputPassword.value = "qwerty123456";
  522. bPopupContent.append(text);
  523. buttonCloseCart.onclick = () => {
  524. var parent = document.getElementById("header");
  525. var child = document.getElementById("b-popup");
  526. parent.removeChild(child);
  527. };
  528. }
  529. window.onhashchange = () => {
  530. const [, route, _id] = location.hash.split("/");
  531. const routes = {
  532. category() {
  533. store.dispatch(actionCatById(_id));
  534. },
  535. good() {
  536. store.dispatch(actionGoodById(_id));
  537. },
  538. dashboard() {
  539. store.dispatch(actionOrders());
  540. console.log("заказостраница");
  541. },
  542. };
  543. if (route in routes) {
  544. routes[route]();
  545. }
  546. };
  547. login.onclick = () => {
  548. bPopupCreate(buttonLogin);
  549. buttonLogin.onclick = () => {
  550. store.dispatch(actionFullLogin(loginInput.value, passwordInput.value));
  551. logoutButton.style.display = "block";
  552. var parent = document.getElementById("header");
  553. var child = document.getElementById("b-popup");
  554. parent.removeChild(child);
  555. };
  556. };
  557. reg.onclick = () => {
  558. bPopupCreate(buttonReg);
  559. buttonReg.onclick = () => {
  560. store.dispatch(
  561. actionFullRegister(loginInput.value, passwordInput.value)
  562. );
  563. var parent = document.getElementById("header");
  564. var child = document.getElementById("b-popup");
  565. parent.removeChild(child);
  566. };
  567. };
  568. logoutButton.onclick = () => {
  569. store.dispatch(actionAuthLogout());
  570. login.style.display = "block";
  571. reg.style.display = "block";
  572. };