main.js 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473
  1. let aside = document.querySelector(".aside");
  2. let content = document.querySelector(".main__main-content");
  3. let registration = document.querySelector(".header__registration");
  4. let signIn = document.querySelector(".header__sign-in");
  5. let cart = document.querySelector(".imaginary-shopping-cart--content");
  6. let cartButton = document.querySelector(".header__cart");
  7. let dasboard = document.querySelector(".header__dashboard");
  8. registration.addEventListener("click", function() {
  9. location.href = "#/register";
  10. });
  11. signIn.addEventListener("click", function() {
  12. location.href = "#/login";
  13. });
  14. cartButton.addEventListener("click", function() {
  15. location.href = "#/cart";
  16. });
  17. dasboard.addEventListener("click", function() {
  18. location.href = "#/dasboard";
  19. });
  20. let createStore = function(reducer) {
  21. let state = reducer(undefined, {});
  22. let cbs = [];
  23. let getState = () => state;
  24. let subscribe = function(cb) {
  25. cbs.push(cb);
  26. return () => cbs = cbs.filter(c => c !== cb);
  27. };
  28. let dispatch = function(action) {
  29. if(typeof(action) == "function") {
  30. return action(dispatch, getState);
  31. };
  32. let newState = reducer(state, action);
  33. if (newState !== state){
  34. state = newState;
  35. for (let cb of cbs) {
  36. cb();
  37. };
  38. };
  39. };
  40. return {
  41. getState,
  42. subscribe,
  43. dispatch
  44. };
  45. };
  46. const promiseReducer = function(state={}, {type, name, status, payload, error}) {
  47. if (type == 'PROMISE'){
  48. return {
  49. ...state,
  50. [name]:{status, payload, error}
  51. }
  52. }
  53. return state;
  54. };
  55. const actionPending = name => ({type: "PROMISE", name, status: 'PENDING'});
  56. const actionFulfilled = (name,payload) => ({type: "PROMISE", name, status: 'FULFILLED', payload});
  57. const actionRejected = (name,error) => ({type: "PROMISE", name, status: 'REJECTED', error});
  58. const actionPromise = function(name, promise) {
  59. return async dispatch => {
  60. dispatch(actionPending(name));
  61. try {
  62. let payload = await promise
  63. dispatch(actionFulfilled(name, payload))
  64. return payload
  65. }
  66. catch(error){
  67. dispatch(actionRejected(name, error))
  68. };
  69. };
  70. };
  71. let jwtDecode = function(token) {
  72. let payloadInBase64;
  73. let payloadInJson;
  74. let payload;
  75. try {
  76. payloadInBase64 = token.split(".")[1];
  77. payloadInJson = atob(payloadInBase64);
  78. payload = JSON.parse(payloadInJson);
  79. return payload;
  80. }
  81. catch(err) {
  82. }
  83. };
  84. const authReducer = function(state, {type, token}) {
  85. let payload;
  86. if (state == undefined) {
  87. if(localStorage.authToken) {
  88. type = "AUTH_LOGIN";
  89. token = localStorage.authToken;
  90. } else {
  91. type = "AUTH_LOGOUT";
  92. };
  93. };
  94. if (type == "AUTH_LOGIN") {
  95. payload = jwtDecode(token);
  96. if(payload) {
  97. localStorage.authToken = token;
  98. return {
  99. token: token,
  100. payload: payload
  101. }
  102. }
  103. };
  104. if (type == "AUTH_LOGOUT") {
  105. localStorage.removeItem("authToken");
  106. return {};
  107. };
  108. return state || {};
  109. };
  110. const actionAuthLogin = token => ({type: "AUTH_LOGIN", token});
  111. const actionAuthLogout = () => ({type: "AUTH_LOGOUT"});
  112. let cartReducer = function(state={}, {type, good, count=1}) {
  113. if(type == "CART_ADD") {
  114. let newState = {...state};
  115. if(good["_id"] in state) {
  116. newState[good._id].count = newState[good._id].count + count;
  117. }
  118. else {
  119. newState = {
  120. ...state,
  121. [good._id]: {count, good}
  122. };
  123. };
  124. return newState;
  125. };
  126. if(type == "CART_CHANGE") {
  127. let newState = {...state,
  128. [good._id]: {count, good}
  129. };
  130. return newState;
  131. };
  132. if(type == "CART_DELETE") {
  133. let newState = {...state};
  134. delete newState[good._id];
  135. return newState;
  136. };
  137. if(type == "CART_CLEAR") {
  138. return {};
  139. };
  140. return state;
  141. };
  142. const actionCartAdd = (good, count=1) => ({type: 'CART_ADD', good, count})
  143. const actionCartChange = (good, count=1) => ({type: 'CART_CHANGE', good, count})
  144. const actionCartDelete = (good) => ({type: 'CART_DELETE', good})
  145. const actionCartClear = () => ({type: 'CART_CLEAR'})
  146. const combineReducers = function(reducers) {
  147. return function (state={}, action) {
  148. const newState = {};
  149. for(let [reducerName, reducer] of Object.entries(reducers)) {
  150. let checkState = reducer(state[reducerName], action);
  151. if(state[reducerName] != checkState) {
  152. newState[reducerName] = checkState;
  153. };
  154. };
  155. if(Object.keys(newState).length == 0) {
  156. return state;
  157. };
  158. return {...Object.assign(state, newState)};
  159. };
  160. };
  161. const store = createStore(combineReducers({promise: promiseReducer, auth: authReducer, cart: cartReducer}));
  162. store.subscribe(() => console.log(store.getState()));
  163. const getGQL = function(url) {
  164. return async function(query, variables) {
  165. const res = await fetch(url, {
  166. method: "POST",
  167. headers: {
  168. "Content-Type": "application/json",
  169. ...(localStorage.authToken ? { "Authorization": "Bearer " + localStorage.authToken } : {})
  170. },
  171. body: JSON.stringify({ query, variables })
  172. });
  173. const data = await res.json();
  174. if (data.data) {
  175. return Object.values(data.data)[0];
  176. }
  177. else {
  178. throw new Error(JSON.stringify(data.errors));
  179. };
  180. };
  181. };
  182. const backendURL = 'http://shop-roles.asmer.fs.a-level.com.ua';
  183. const gql = getGQL(backendURL + '/graphql');
  184. let actionRootCats = function() {
  185. return actionPromise("rootCats", gql(`query {
  186. CategoryFind(query: "[{\\"parent\\":null}]"){
  187. _id name
  188. }
  189. }`));
  190. };
  191. store.dispatch(actionRootCats());
  192. let actionCatById = function(_id) {
  193. return actionPromise("catById", gql(`query catById($q: String){
  194. CategoryFindOne(query: $q){
  195. _id name goods {
  196. _id name price images {
  197. url
  198. }
  199. }
  200. }
  201. }`,
  202. {q: JSON.stringify([{_id}])}
  203. ));
  204. };
  205. let actionGoodById = function(_id) {
  206. return actionPromise("goodById", gql(`query findGood($goodQuery: String) {
  207. GoodFindOne(query:$goodQuery) {
  208. _id name price images {
  209. url
  210. }
  211. }
  212. }`,
  213. {goodQuery: JSON.stringify([{"_id": _id}])}
  214. ));
  215. };
  216. let actionFullLogin = async function(login, password) {
  217. let token = await gql("query userLogin($login: String, $password: String) {login(login: $login, password: $password)}", {"login": login, "password": password});
  218. store.dispatch(actionAuthLogin(token));
  219. };
  220. let actionFullRegister = function(login, password, nick) {
  221. return actionPromise("userRegister", gql(`mutation userRegister($login:String, $password:String, $nick:String) {
  222. UserUpsert(user: {login:$login, password:$password, nick:$nick}) {
  223. _id login nick
  224. }
  225. }`,
  226. {
  227. "login": login,
  228. "password": password,
  229. "nick": nick
  230. }))
  231. };
  232. let actionOrders = async function() {
  233. let order = await gql(`mutation makeOrder($order:OrderInput){
  234. OrderUpsert(order: $order){
  235. _id
  236. }
  237. }`, {
  238. "order": {
  239. orderGoods: Object.entries(store.getState().cart).map(([_id, count]) =>({"count": count.count, "good": {_id}}))
  240. }
  241. });
  242. store.dispatch(actionCartClear());
  243. }
  244. let createCart = function() {
  245. const [,route, _id] = location.hash.split('/');
  246. if(route == "cart") {
  247. let str = "<h2>Корзина</h2><table id='table'>";
  248. for(let goodId of Object.entries(store.getState().cart)) {
  249. str += `<tr>
  250. <td class="visually-hidden">${goodId[0]}</td>
  251. <td>${goodId[1].good.name}</td>
  252. <td><img src="${backendURL}/${goodId[1].good.images[0].url}"/></td>
  253. <td>Цена: <span id="price">${goodId[1].good.price}</span></td>
  254. <td>Кол-во: <input id="count" type="number" value="${goodId[1].count}"></td>
  255. <td><button id="clear">Удалить товар</button></td>
  256. </tr>`;
  257. };
  258. str += "</table><button id='order'>Сделать заказ</button>";
  259. content.innerHTML = str;
  260. table.addEventListener("click", function(evt) {
  261. if(evt.target.tagName == "BUTTON") {
  262. store.dispatch(actionCartDelete(store.getState().cart[evt.path[2].firstElementChild.textContent]?.good))
  263. };
  264. });
  265. order.addEventListener("click", function() {
  266. actionOrders();
  267. });
  268. }
  269. };
  270. let createDashbord = async function() {
  271. const [,route, _id] = location.hash.split('/');
  272. if(route == "dasboard") {
  273. let orders = await gql(`query ordersFind($query:String) {
  274. OrderFind(query: $query) {
  275. createdAt orderGoods {
  276. count good {
  277. name price images {
  278. url
  279. }
  280. }
  281. }
  282. }
  283. }`,
  284. {
  285. "query": JSON.stringify([{}])
  286. });
  287. let str = "<h2>История заказов</h2><table>";
  288. for(let order of orders) {
  289. str += "<tr>";
  290. str += `<td>${(new Date(+order.createdAt)).toLocaleString()}</td>`;
  291. str += "<td>"
  292. for(let orderGood of order.orderGoods) {
  293. str += `<ul>
  294. <li>${orderGood.good.name}</li>
  295. <li><img src="${backendURL}/${orderGood.good.images[0].url}"/></li>
  296. <li>Цена: ${orderGood.good.price}</li>
  297. </ul>`;
  298. }
  299. str += "</td>"
  300. str += "</tr>";
  301. };
  302. str += "</table>";
  303. content.innerHTML = str;
  304. }
  305. };
  306. window.onhashchange = () => {
  307. const [, route, _id] = location.hash.split('/');
  308. const routes = {
  309. category(){
  310. store.dispatch(actionCatById(_id));
  311. },
  312. good(){
  313. store.dispatch(actionGoodById(_id));
  314. },
  315. login(){
  316. content.innerHTML = `<h2>Вход на сайт</h2>
  317. <input id="login" type="text" name="login" placeholder="Введите ваш логин">
  318. <input id="password" type="password" name="password" placeholder="Введите ваш пароль">
  319. <button id="sign_in">Войти</button>`;
  320. sign_in.addEventListener("click", function() {
  321. actionFullLogin(login.value, password.value);
  322. // store.dispatch(actionFullLogin(login.value, password.value));
  323. });
  324. },
  325. register(){
  326. content.innerHTML = `<h2>Регистрация</h2>
  327. <input id="login" type="text" name="reg-login" placeholder="Введите ваш будущий логин">
  328. <input id="password" type="text" name="reg-password" placeholder="Введите ваш будущий пароль">
  329. <input id="nick" type="text" name="reg-nick" placeholder="Введите ваш nick">
  330. <button id="registr">Зарегистрироваться</button>`;
  331. registr.addEventListener("click", function() {
  332. store.dispatch(actionFullRegister(login.value, password.value, nick.value));
  333. });
  334. },
  335. cart() {
  336. createCart();
  337. },
  338. dasboard() {
  339. createDashbord();
  340. }
  341. };
  342. if (route in routes) {
  343. routes[route]();
  344. };
  345. };
  346. window.onhashchange();
  347. store.subscribe(() => {
  348. const {rootCats} = store.getState().promise;
  349. if (rootCats?.payload){
  350. let ul = document.createElement("ul");
  351. aside.innerHTML = '';
  352. for (const {_id, name} of rootCats?.payload){
  353. let li = document.createElement("li");
  354. let link = document.createElement('a');
  355. link.href = `#/category/${_id}`;
  356. link.innerText = name;
  357. li.append(link);
  358. ul.append(li);
  359. }
  360. aside.append(ul);
  361. };
  362. });
  363. store.subscribe(() => {
  364. const {catById} = store.getState().promise;
  365. const [,route, _id] = location.hash.split('/')
  366. if (catById?.payload && route === 'category'){
  367. const {name} = catById.payload
  368. content.innerHTML = `<h1>${name}</h1> ТУТ ДОЛЖНЫ БЫТЬ ПОДКАТЕГОРИИ`
  369. for (const {_id, name, price, images} of catById.payload.goods){
  370. const card = document.createElement('div')
  371. card.innerHTML = `<h2>${name}</h2>
  372. <img src="${backendURL}/${images[0].url}" />
  373. <strong>${price}</strong>
  374. <a href="#/good/${_id}">ССЫЛКА НА СТРАНИЦУ ТОВАРА</a>`
  375. content.append(card);
  376. }
  377. }
  378. });
  379. store.subscribe(() => {
  380. const {goodById} = store.getState().promise;
  381. const [,route, _id] = location.hash.split('/');
  382. if (goodById?.payload && route == "good") {
  383. const {name} = goodById.payload;
  384. content.innerHTML = `<h1>${name}</h1>`;
  385. const card = document.createElement('div');
  386. card.innerHTML = `<h2>${goodById.payload.name}</h2>
  387. <img src="${backendURL}/${goodById.payload.images[0].url}" />
  388. <strong>${goodById.payload.price}</strong>
  389. <button id="goodAdd">Добавить в корзину</button>`;
  390. content.append(card);
  391. goodAdd.addEventListener("click", function() {
  392. store.dispatch(actionCartAdd(goodById.payload));
  393. });
  394. };
  395. });
  396. store.subscribe(createCart);