main.js 24 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647
  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 promiseReducer(state = {}, { type, name, status, payload, error }) {
  24. if (type === 'PROMISE') {
  25. return {
  26. ...state,
  27. [name]: { status, payload, error }
  28. }
  29. }
  30. return state
  31. }
  32. const jwtDecode = token => {
  33. try {
  34. let [, payload] = token.split(".");
  35. let payloadParsed = JSON.parse(atob(payload));
  36. return payloadParsed;
  37. }
  38. catch (e) {
  39. console.log(e);
  40. }
  41. }
  42. function authReducer(state, { type, token }) {
  43. if (state === undefined) {
  44. if (localStorage.authToken) {
  45. type = "AUTH_LOGIN";
  46. token = localStorage.authToken;
  47. } else {
  48. return {};
  49. }
  50. }
  51. if (type === 'AUTH_LOGIN') {
  52. let payload = jwtDecode(token);
  53. if (payload) {
  54. localStorage.authToken = token;
  55. return { token, payload };
  56. }
  57. }
  58. if (type === 'AUTH_LOGOUT') {
  59. localStorage.authToken = "";
  60. return {};
  61. }
  62. return state;
  63. }
  64. function combineReducers(reducers) {
  65. return (state = {}, action) => {
  66. const newState = {}
  67. for (const [reducerName, reducer] of Object.entries(reducers)) {
  68. let newSubState = reducer(state[reducerName], action)
  69. if (newSubState !== state[reducerName]) {
  70. newState[reducerName] = newSubState
  71. }
  72. }
  73. if (Object.keys(newState).length !== 0) {
  74. return { ...state, ...newState }
  75. } else {
  76. return state
  77. }
  78. }
  79. }
  80. function cartReducer(state = {}, { type, good = {}, count = 1 }) {
  81. //каков state
  82. //{
  83. // _id1: {count:1, good: {_id1, name, price, images}}
  84. // _id2: {count:1, good: {_id1, name, prica, images}}
  85. //}
  86. //
  87. const { _id } = good;
  88. if (type === 'CART_ADD') {
  89. return {
  90. ...state,
  91. [_id]: { count: (state[_id] ? (state[_id].count += count) : count), good: good }
  92. }
  93. }
  94. if (type === 'CART_CHANGE') {
  95. return {
  96. ...state,
  97. [_id]: { count: count, good: good }
  98. }
  99. }
  100. if (type === 'CART_DELETE') {
  101. let newState = { ...state };
  102. delete newState[_id];
  103. // const {_id, ...newState} = state;
  104. return {
  105. ...newState,
  106. }
  107. }
  108. if (type === 'CART_CLEAR') {
  109. return {}
  110. }
  111. return state;
  112. }
  113. const store = createStore(combineReducers({
  114. promise: promiseReducer,
  115. auth: authReducer, cart: cartReducer
  116. }));
  117. const actionCartAdd = (good, count = 1) => ({ type: 'CART_ADD', good, count });
  118. const actionCartChange = (good, count = 1) => ({ type: 'CART_CHANGE', good, count });
  119. const actionCartDelete = (good) => ({ type: 'CART_DELETE', good });
  120. const actionCartClear = () => ({ type: 'CART_CLEAR' });
  121. const cartEl = document.getElementsByClassName("cartN")[0];
  122. let username;
  123. const loginEl = document.getElementsByClassName("loginQ")[0];
  124. const logoEl = document.getElementById("logo");
  125. // const store = createStore(promiseReducer) //не забудьте combineReducers если он у вас уже есть
  126. store.subscribe(() => console.log(store.getState()));
  127. const actionPending = name => ({ type: 'PROMISE', name, status: 'PENDING' });
  128. const actionFulfilled = (name, payload) => ({ type: 'PROMISE', name, status: 'FULFILLED', payload });
  129. const actionRejected = (name, error) => ({ type: 'PROMISE', name, status: 'REJECTED', error });
  130. const actionAuthLogin = (token) => ({ type: 'AUTH_LOGIN', token });
  131. const actionAuthLogout = () => ({ type: 'AUTH_LOGOUT' });
  132. const actionPromise = (name, promise) =>
  133. async dispatch => {
  134. dispatch(actionPending(name))
  135. try {
  136. let payload = await promise
  137. dispatch(actionFulfilled(name, payload))
  138. return payload
  139. }
  140. catch (error) {
  141. dispatch(actionRejected(name, error))
  142. }
  143. }
  144. const getGQL = url =>
  145. (query, variables) => fetch(url, {
  146. method: 'POST',
  147. headers: {
  148. "Content-Type": "application/json",
  149. ...(localStorage.authToken ? { "Authorization": "Bearer " + localStorage.authToken } : {})
  150. },
  151. body: JSON.stringify({ query, variables })
  152. }).then(res => res.json())
  153. .then(data => {
  154. if (data.data) {
  155. return Object.values(data.data)[0]
  156. }
  157. else throw new Error(JSON.stringify(data.errors))
  158. })
  159. const backendURL = "http://shop-roles.node.ed.asmer.org.ua";
  160. const gql = getGQL(backendURL + '/graphql')
  161. const actionRootCats = () =>
  162. actionPromise('rootCats', gql(`query {
  163. CategoryFind(query: "[{\\"parent\\":null}]"){
  164. _id name
  165. }
  166. }`))
  167. const actionCatById = (_id) =>
  168. actionPromise(
  169. 'catById',
  170. gql(
  171. `query catById($q: String){
  172. CategoryFindOne(query: $q){
  173. subCategories{
  174. name _id goods {
  175. _id
  176. name
  177. }
  178. }
  179. _id name goods {
  180. _id name price images {
  181. url
  182. }
  183. }
  184. }
  185. }`, { q: JSON.stringify([{ _id }]) }));
  186. const actionGoodById = (_id) =>
  187. actionPromise('goodById', gql(`query goodz($q: String){
  188. GoodFindOne(query: $q) {
  189. _id name price description
  190. images{
  191. url
  192. }
  193. categories{
  194. name
  195. }
  196. }
  197. }`, { q: JSON.stringify([{ _id }]) }));
  198. const orderHistory = () =>
  199. actionPromise(
  200. 'history',
  201. gql(` query OrderFind{
  202. OrderFind(query:"[{}]"){
  203. _id total createdAt orderGoods{
  204. count good{
  205. _id name price images{
  206. url
  207. }
  208. }
  209. owner{
  210. _id login
  211. }
  212. }
  213. }
  214. }
  215. `));
  216. const actionLogin = (login, password) =>
  217. actionPromise(
  218. 'login',
  219. gql(
  220. `query log($login: String, $password: String) {
  221. login(login: $login, password: $password)
  222. }`,
  223. { login: login, password: password }
  224. )
  225. );
  226. const actionFullLogin = (login, password) => async (dispatch) => {
  227. let token = await dispatch(actionLogin(login, password));
  228. console.log(token);
  229. if (token) {
  230. dispatch(actionAuthLogin(token));
  231. }
  232. alerting();
  233. };
  234. const actionRegister = (login, password) =>
  235. actionPromise(
  236. 'register',
  237. gql(
  238. `mutation register ($login:String, $password:String){
  239. UserUpsert(user:{login:$login, password:$password}){
  240. _id login
  241. }
  242. }`,
  243. { login: login, password: password }
  244. )
  245. );
  246. const actionOrder = () => async (dispatch, getState) => {
  247. let { cart } = getState();
  248. const orderGoods = Object.entries(cart).map(([_id, { count }]) => ({ good: { _id }, count, }));
  249. let result = await dispatch(
  250. actionPromise(
  251. 'order',
  252. gql(
  253. `
  254. mutation newOrder($order:OrderInput){
  255. OrderUpsert(order:$order)
  256. { _id total }
  257. }
  258. `,
  259. { order: { orderGoods } }
  260. )
  261. )
  262. );
  263. if (result?._id) {
  264. dispatch(actionCartClear());
  265. document.location.hash = "#/cart/";
  266. alert("Поздравляем, вы оформили заказ!");
  267. }
  268. };
  269. const actionFullRegister = (login, password) => async (dispatch) => {
  270. let id = await dispatch(actionRegister(login, password));
  271. // console.log(id);
  272. alertingReg(id, login);
  273. if (id) {
  274. dispatch(actionFullLogin(login, password));
  275. }
  276. };
  277. store.dispatch(actionRootCats());
  278. store.subscribe(() => {
  279. const { rootCats } = store.getState().promise
  280. if (rootCats?.payload) {
  281. aside.innerHTML = ''
  282. for (const { _id, name } of rootCats?.payload) {
  283. const link = document.createElement('a')
  284. link.href = `#/category/${_id}`
  285. link.innerText = name
  286. aside.append(link)
  287. }
  288. }
  289. })
  290. window.onhashchange = () => {
  291. const [, route, _id] = location.hash.split('/')
  292. const routes = {
  293. category() {
  294. store.dispatch(actionCatById(_id));
  295. },
  296. good() {
  297. // console.log("good")
  298. //задиспатчить actionGoodById
  299. // console.log('ТОВАРОСТРАНИЦА')
  300. store.dispatch(actionGoodById(_id));
  301. },
  302. login() {
  303. main.innerHTML = `<h2>Для осуществления заказов:<br>если вы зарегистрированы,
  304. введите логин и пароль и нажмите Войти;<br>если не зарегистрированы - нужно зарегистрироваться</h2>`;
  305. const formEl = document.createElement("div");
  306. const p1el = document.createElement("p");
  307. const p2el = document.createElement("p");
  308. const p3el = document.createElement("p");
  309. const br1El = document.createElement("br");
  310. const br2El = document.createElement("br");
  311. const br3El = document.createElement("br");
  312. const input1El = document.createElement("input");
  313. const input2El = document.createElement("input");
  314. const buttonLogEl = document.createElement("button");
  315. const buttonRegEl = document.createElement("button");
  316. const buttonLogOutEl = document.createElement("button");
  317. p1el.innerText = "Введите логин:";
  318. p2el.innerText = "Введите пароль:";
  319. input1El.id = "inputLogin";
  320. input2El.id = "inputPassword";
  321. input2El.type = "password";
  322. buttonLogEl.innerText = "Войти";
  323. buttonRegEl.innerText = "Зарегистрироваться";
  324. buttonLogOutEl.innerText = "Выйти";
  325. if (username) {
  326. buttonLogEl.disabled = true;
  327. buttonRegEl.disabled = true;
  328. }
  329. buttonLogEl.onclick = () => {
  330. store.dispatch(actionFullLogin(input1El.value, input2El.value));
  331. // if (username) location.hash = "#/goods";
  332. }
  333. buttonRegEl.onclick = () => {
  334. store.dispatch(actionFullRegister(input1El.value, input2El.value));
  335. }
  336. buttonLogOutEl.onclick = () => {
  337. store.dispatch(actionAuthLogout());
  338. username = "";
  339. document.location.hash = "#/login/";
  340. }
  341. formEl.append(p1el, input1El, p2el, input2El, br1El, br2El, buttonLogEl, buttonLogOutEl, buttonRegEl);
  342. main.append(formEl);
  343. //по кнопке - store.dispatch(actionFullLogin(login, password))
  344. },
  345. register() {
  346. //отрисовка тут
  347. //по кнопке - store.dispatch(actionFullRegister(login, password))
  348. },
  349. dashboard() {
  350. store.dispatch(orderHistory());
  351. },
  352. cart() {
  353. const cart = store.getState().cart;
  354. if (Object.entries(cart).length > 0) {
  355. main.innerHTML = `<h2>Содержимое вашей корзины</h2>`;
  356. const { cart } = store.getState();
  357. let totalScore = 0;
  358. let totalCount = 0;
  359. const tableCartEl = document.createElement("table");
  360. const trEl = document.createElement("tr");
  361. const td1El = document.createElement("td");
  362. const td2El = document.createElement("td");
  363. const td3El = document.createElement("td");
  364. const td4El = document.createElement("td");
  365. const td5El = document.createElement("td");
  366. td1El.innerText = "Удалить";
  367. td2El.innerText = "Наименование товара";
  368. td3El.innerText = "Цена";
  369. td4El.innerText = "Количество";
  370. td5El.innerText = "Изменить";
  371. trEl.append(td1El, td2El, td3El, td4El, td5El);
  372. tableCartEl.append(trEl);
  373. if (Object.entries(cart).length > 0) {
  374. for (let [key, value] of Object.entries(cart)) {
  375. const { count, good } = value;
  376. const { name, _id, price } = good;
  377. const buttonDel = document.createElement("button");
  378. const buttonMinus = document.createElement("button");
  379. const buttonPlus = document.createElement("button");
  380. buttonMinus.innerText = "-";
  381. buttonPlus.innerText = "+";
  382. buttonDel.innerText = "Удалить";
  383. buttonMinus.onclick = () => {
  384. if (count == 1) {
  385. store.dispatch(actionCartDelete({ _id, name, price }));
  386. } else {
  387. store.dispatch(actionCartChange({ _id, name, price }, count - 1));
  388. }
  389. document.location.hash = `#/cart/${_id}/${count}`
  390. }
  391. buttonPlus.onclick = () => {
  392. store.dispatch(actionCartChange({ _id, name, price }, count + 1));
  393. document.location.hash = `#/cart/${_id}/${count}`
  394. }
  395. buttonDel.onclick = () => {
  396. store.dispatch(actionCartDelete({ _id, name, price }));
  397. document.location.hash = `#/cart/${_id}`;
  398. }
  399. const trEl = document.createElement("tr");
  400. const td1El = document.createElement("td");
  401. const td2El = document.createElement("td");
  402. const td3El = document.createElement("td");
  403. const td4El = document.createElement("td");
  404. const td5El = document.createElement("td");
  405. td1El.append(buttonDel);
  406. td2El.innerText = name;
  407. td3El.innerText = price;
  408. td4El.innerText = count;
  409. td5El.append(buttonMinus, buttonPlus);
  410. trEl.append(td1El, td2El, td3El, td4El, td5El);
  411. tableCartEl.append(trEl);
  412. totalScore += price * count;
  413. totalCount += count;
  414. }
  415. const trEl = document.createElement("tr");
  416. const td1El = document.createElement("td");
  417. const td2El = document.createElement("td");
  418. const td3El = document.createElement("td");
  419. const td4El = document.createElement("td");
  420. const td5El = document.createElement("td");
  421. td1El.innerText = "";
  422. td2El.innerText = "";
  423. td3El.innerText = totalScore;
  424. td4El.innerText = totalCount;
  425. td5El.innerText = "";
  426. trEl.append(td1El, td2El, td3El, td4El, td5El);
  427. tableCartEl.append(trEl);
  428. }
  429. main.append(tableCartEl);
  430. const buttonCartClear = document.createElement("button");
  431. const buttonSend = document.createElement("button");
  432. const buttonHistory = document.createElement("button");
  433. buttonSend.innerText = "Отправить заказ";
  434. buttonCartClear.innerText = "Очистить корзину";
  435. buttonHistory.innerText = "История заказов";
  436. buttonSend.onclick = () => {
  437. store.dispatch(actionOrder());
  438. }
  439. buttonCartClear.onclick = () => {
  440. store.dispatch(actionCartClear());
  441. document.location.hash = "#/cart/";
  442. }
  443. buttonHistory.onclick = () => {
  444. document.location.hash = "#/dashboard";
  445. }
  446. main.append(buttonSend, buttonCartClear, buttonHistory);
  447. } else {
  448. main.innerHTML = `<h2>Ваша корзина пуста...</h2><a href="#/dashboard">история заказов</a>`;
  449. }
  450. },
  451. }
  452. if (route in routes)
  453. routes[route]()
  454. }
  455. window.onhashchange()
  456. store.subscribe(() => {
  457. const { catById } = store.getState().promise;
  458. const [, route, _id] = location.hash.split('/');
  459. if (catById?.payload && route === 'category') {
  460. const { name } = catById.payload
  461. main.innerHTML = `<h1>${name}</h1>`
  462. if (catById.payload.subCategories) {
  463. for (const { _id, name } of catById.payload.subCategories) {
  464. const card = document.createElement('div')
  465. card.innerHTML = `<a href="#/category/${_id}"><h2>${name}</h2></a>`;
  466. main.append(card);
  467. }
  468. }
  469. for (const { _id, name, price, images } of catById.payload.goods) {
  470. const card = document.createElement('div')
  471. card.innerHTML = `<h2>${name}</h2>
  472. <img src="${backendURL}/${images[0].url}" /><br>
  473. <strong>Цена: ${price} грн</strong><br>
  474. <button onclick="store.dispatch(actionCartAdd({_id: '${_id}',
  475. name: '${name}', price: '${price}'}))">Купить</button>
  476. <a href="#/good/${_id}">описание товара</a>`
  477. main.append(card)
  478. }
  479. }
  480. })
  481. store.subscribe(() => {
  482. const { goodById } = store.getState().promise;
  483. const [, route, _id] = location.hash.split('/');
  484. //ТУТ ДОЛЖНА БЫТЬ ПРОВЕРКА НА НАЛИЧИЕ goodById в редакс
  485. //и проверка на то, что сейчас в адресной строке адрес ВИДА #/good/АЙДИ
  486. //в таком случае очищаем main и рисуем информацию про товар с подробностями
  487. if (goodById?.payload && route === 'good') {
  488. const { _id, name, price, description, images } = goodById.payload;
  489. main.innerHTML = `<h2>${name}</h2>`;
  490. const card = document.createElement('div');
  491. card.innerHTML = `<img src="${backendURL}/${images[0].url}" /><br>
  492. <strong>Цена: ${price} грн</strong><br>
  493. <button onclick="store.dispatch(actionCartAdd({_id: '${_id}',
  494. name: '${name}', price: '${price}'}))">Купить</button>
  495. <p>Описание товара:</p>
  496. <p>${description}</p>
  497. `
  498. main.append(card);
  499. }
  500. })
  501. store.subscribe(() => {
  502. const { cart } = store.getState();
  503. // console.log(cart);
  504. let numInCart = 0;
  505. if (Object.entries(cart).length > 0) {
  506. for (let [key, value] of Object.entries(cart)) {
  507. const { count } = value;
  508. numInCart += count;
  509. }
  510. }
  511. cartEl.innerText = numInCart;
  512. //достаем всю корзину
  513. //считаем общее количество всех позиций (3 айфона + 7 пицц = 10)
  514. //выводим число в кошик на странице через его HTML id
  515. });
  516. store.subscribe(() => {
  517. const { auth } = store.getState();
  518. // const [,route,] = location.hash.split('/');
  519. if (Object.keys(auth).length !== 0) {
  520. username = auth?.payload.sub.login;
  521. }
  522. if (localStorage.authToken) {
  523. // console.log("login");
  524. loginEl.innerText = "";
  525. logoEl.innerHTML = `\n <h1>${username}, добро пожаловать в интернет магазин NotРОЗЕТКА</h1>\n `
  526. } else {
  527. // console.log("logout");
  528. loginEl.innerText = "?";
  529. logoEl.innerHTML = `\n <h1>Добро пожаловать в интернет магазин</h1>\n `
  530. }
  531. });
  532. //store.dispatch(actionPromise('delay1000', delay(1000)))
  533. //store.dispatch(actionPromise('delay2000', delay(2000)))
  534. //store.dispatch(actionPromise('failedfetch', fetch('https://swapi.dev/api/people/1/')
  535. //.then(res => res.json())))
  536. store.subscribe(() => {
  537. const { history } = store.getState().promise;
  538. const [, route] = location.hash.split('/');
  539. if (history?.payload && route === 'dashboard') {
  540. main.innerHTML = "<p><b>История ваших заказов:</b></p>";
  541. const card = document.createElement('div');
  542. for (let [key, value] of Object.entries(history.payload)) {
  543. const { _id, createdAt, total, orderGoods } = value;
  544. const p1El = document.createElement("p");
  545. const dateOfOrder = new Date(+createdAt);
  546. p1El.innerHTML = `${dateOfOrder.toLocaleDateString()} ${dateOfOrder.toLocaleTimeString()}
  547. Заказ ID: ${_id} от , c ${orderGoods.length} товарами на сумму ${total} грн`;
  548. card.append(p1El);
  549. }
  550. if (Object.keys(history.payload).length == 0) {
  551. const p1El = document.createElement("p");
  552. p1El.innerHTML = "<p>Вы раньше еще ничего не заказывали</p>";
  553. card.append(p1El);
  554. }
  555. main.append(card);
  556. }
  557. });
  558. function alerting() {
  559. const { auth } = store.getState();
  560. if (Object.keys(auth).length !== 0) {
  561. username = auth?.payload.sub.login;
  562. }
  563. if (username) {
  564. alert(`${username}, вы успешно зашли на сайт!`);
  565. document.location.hash = "#/category/5dc49f4d5df9d670df48cc64";
  566. } else {
  567. alert("Вы ввели неправильно логин или пароль!");
  568. }
  569. }
  570. function alertingReg(id, login) {
  571. if (id) {
  572. alert(`Вы успешно зарегистрировались с логином ${login}`);
  573. } else {
  574. alert(`Логин ${login} уже занят, прыдумайте другой!`);
  575. }
  576. }