const backendURL = "http://shop-roles.node.ed.asmer.org.ua/graphql"; const backendURLNotGraphQL = "http://shop-roles.node.ed.asmer.org.ua"; function createStore(reducer) { let state = reducer(undefined, {}); //стартовая инициализация состояния, запуск редьюсера со state === undefined let cbs = []; //массив подписчиков const getState = () => state; //функция, возвращающая переменную из замыкания const subscribe = (cb) => ( cbs.push(cb), //запоминаем подписчиков в массиве () => (cbs = cbs.filter((c) => c !== cb)) ); //возвращаем функцию unsubscribe, которая удаляет подписчика из списка const dispatch = (action) => { if (typeof action === "function") { //если action - не объект, а функция return action(dispatch, getState); //запускаем эту функцию и даем ей dispatch и getState для работы } const newState = reducer(state, action); //пробуем запустить редьюсер if (newState !== state) { //проверяем, смог ли редьюсер обработать action state = newState; //если смог, то обновляем state for (let cb of cbs) cb(); //и запускаем подписчиков } }; return { getState, //добавление функции getState в результирующий объект dispatch, subscribe, //добавление subscribe в объект }; } function jwtDecode(token) { try { return JSON.parse(atob(token.split(".")[1])); } catch (e) {} } function authReducer(state = {}, { type, token }) { //{ // token, payload //} if (type === "AUTH_LOGIN") { //пытаемся токен раскодировать const payload = jwtDecode(token); if (payload) { return { token, payload, //payload - раскодированный токен; }; } } if (type === "AUTH_LOGOUT") { return {}; } return state; } const actionAuthLogin = (token) => (dispatch, getState) => { const oldState = getState(); dispatch({ type: "AUTH_LOGIN", token }); const newState = getState(); if (oldState !== newState) localStorage.authToken = token; }; const actionAuthLogout = () => (dispatch) => { dispatch({ type: "AUTH_LOGOUT" }); localStorage.removeItem("authToken"); }; function promiseReducer(state = {}, { type, name, status, payload, error }) { if (type === "PROMISE") { return { ...state, [name]: { status, payload, error }, }; } return state; } const actionPending = (name) => ({ type: "PROMISE", status: "PENDING", name, }); const actionFulfilled = (name, payload) => ({ type: "PROMISE", status: "FULFILLED", name, payload, }); const actionRejected = (name, error) => ({ type: "PROMISE", status: "REJECTED", name, error, }); const actionPromise = (name, promise) => async (dispatch) => { try { dispatch(actionPending(name)); let payload = await promise; dispatch(actionFulfilled(name, payload)); return payload; } catch (e) { dispatch(actionRejected(name, e)); } }; function cartReducer(state = {}, { type, count = 1, good }) { // type CART_ADD CART_REMOVE CART_CLEAR CART_DEC // { // id1: {count: 1, good: {name, price, images, id}} // } if (type === "CART_ADD") { return { ...state, [good._id]: { count: count + (state[good._id]?.count || 0), good }, }; } if (type === "CART_CLEAR") { return {}; } if (type === "CART_REMOVE") { //let newState = {...state} let { [good._id]: poh, ...newState } = state; //o4en strashnoe koldunstvo //delete newState[good._id] return newState; } return state; } const actionCartAdd = (good, count = 1) => ({ type: "CART_ADD", good, count }); const actionCartChange = (good, count = 1) => ({ type: "CART_CHANGE", good, count, }); ///oninput меняяем полностью const actionCartDelete = (good) => ({ type: "CART_DELETE", good }); const actionCartClear = () => ({ type: "CART_CLEAR" }); function localStoreReducer(reducer, localStorageKey) { function localStoredReducer(state, action) { // Если state === undefined, то достать старый state из local storage if (state === undefined) { try { return JSON.parse(localStorage[localStorageKey]); } catch (e) {} } const newState = reducer(state, action); // Сохранить newState в local storage localStorage[localStorageKey] = JSON.stringify(newState); return newState; } return localStoredReducer; } const delay = (ms) => new Promise((ok) => setTimeout(() => ok(ms), ms)); function combineReducers(reducers) { //пачку редьюсеров как объект {auth: authReducer, promise: promiseReducer} function combinedReducer(combinedState = {}, action) { //combinedState - типа {auth: {...}, promise: {....}} const newCombinedState = {}; for (const [reducerName, reducer] of Object.entries(reducers)) { const newSubState = reducer(combinedState[reducerName], action); if (newSubState !== combinedState[reducerName]) { newCombinedState[reducerName] = newSubState; } } if (Object.keys(newCombinedState).length === 0) { return combinedState; } return { ...combinedState, ...newCombinedState }; } return combinedReducer; //нам возвращают один редьюсер, который имеет стейт вида {auth: {...стейт authReducer-а}, promise: {...стейт promiseReducer-а}} } const store = createStore( combineReducers({ auth: authReducer, promise: promiseReducer, cart: localStoreReducer(cartReducer, "cart"), }) ); //не забудьте combineReducers если он у вас уже есть if (localStorage.authToken) { store.dispatch(actionAuthLogin(localStorage.authToken)); } //const store = createStore(combineReducers({promise: promiseReducer, auth: authReducer, cart: cartReducer})) store.subscribe(() => console.log(store.getState())); const gql = (url, query, variables) => fetch(url, { method: "POST", headers: { "Content-Type": "application/json", Accept: "application/json", }, body: JSON.stringify({ query, variables }), }).then((res) => res.json()); const actionRootCats = () => actionPromise( "rootCats", gql( backendURL, `query { CategoryFind(query: "[{\\"parent\\":null}]"){ _id name } }` ) ); const actionCatById = ( _id //добавить подкатегории ) => actionPromise( "catById", gql( backendURL, `query catById($q: String){ CategoryFindOne(query: $q){ _id name goods { _id name price images { url } } } }`, { q: JSON.stringify([{ _id }]) } ) ); const actionLogin = (login, password) => actionPromise( "actionLogin", gql( backendURL, `query log($login:String, $password:String){ login(login:$login, password:$password) }`, { login, password } ) ); const actionGoodById = (_id) => actionPromise( "GoodFineOne", gql( backendURL, `query goodByid($goodId: String) { GoodFindOne(query: $goodId) { _id name price description images { url } } }`, { goodId: JSON.stringify([{ _id }]) } ) ); store.dispatch(actionRootCats()); const actionFullLogin = (login, password) => async (dispatch) => { let result = await dispatch(actionLogin(login, password)); if (result.data.login) { dispatch(actionAuthLogin(result.data.login)); } }; const actionFullRegister = (login, password) => async (dispatch) => { let user = await dispatch( actionPromise( "register", gql( backendURL, `mutation register($login: String, $password: String) { UserUpsert(user: {login: $login, password: $password}) { _id login } }`, { login: login, password: password } ) ) ); if (user) { dispatch(actionFullLogin(login, password)); } }; const actionOrders = () => actionPromise( "orders", gql( backendURL, `query findOrder($q: String) { OrderFind(query: $q) { _id total createdAt orderGoods { count good { name price } } } }`, { q: JSON.stringify([{}]) } ) ); store.subscribe(() => { const rootCats = store.getState().promise.rootCats?.payload?.data.CategoryFind; if (rootCats) { aside.innerHTML = ""; for (let { _id, name } of rootCats) { const a = document.createElement("a"); a.href = `#/category/${_id}`; a.innerHTML = name; aside.append(a); } } }); store.subscribe(() => { const catById = store.getState().promise.catById?.payload?.data.CategoryFindOne; const [, route] = location.hash.split("/"); if (catById && route === "category") { const { name, goods, _id } = catById; categoryName.innerHTML = `

${name}

`; var element = document.getElementById("productBlock"); while (element.firstChild) { element.removeChild(element.firstChild); } for (let { _id, name, price, images } of goods) { const description = document.createElement("div"); const textBlock = document.createElement("div"); const imgProduct = document.createElement("img"); const a = document.createElement("p"); const productPrice = document.createElement("p"); const b = document.getElementById(productBlock); const linkCard = document.createElement("a"); productBlock.append(linkCard); linkCard.href = `#/good/${_id}`; linkCard.append(description); description.setAttribute("class", "card"); description.append(imgProduct); imgProduct.src = `http://shop-roles.node.ed.asmer.org.ua/${images[0].url}`; description.append(textBlock); // a.href = `#/good/${_id}`; a.innerHTML = name; textBlock.append(a); productPrice.innerHTML = "price: " + price; textBlock.append(productPrice); const addToCartButton = document.createElement("p"); addToCartButton.innerText = "click to buy"; addToCartButton.className = "addToCartButton"; textBlock.append(addToCartButton); } } }); const bPoputDeleteBlock = document.createElement("div"); const bPoput = document.createElement("div"); bPoput.className = "b-popup"; bPoput.id = "b-popup"; const bPoputContainer = document.createElement("div"); bPoputContainer.className = "b-popup-content"; bPoputContainer.id = "b-popup-content"; const buttonGoodDeleteBlock = document.createElement('div') buttonGoodDeleteBlock.id = "buttonGoodDeleteBlock" const buttonCloseCart = document.createElement("button"); buttonCloseCart.innerText = `×`; buttonCloseCart.id = "buttonCloseCartId"; const buttonGoodDelete = document.createElement("button"); buttonGoodDelete.innerText = "delete"; buttonGoodDelete.id = "buttonDelete"; shoppingCart.onclick = () => { header.append(bPoput); bPoput.append(bPoputContainer); }; bPoputContainer.append(buttonGoodDeleteBlock); buttonGoodDeleteBlock.append(buttonGoodDelete) bPoputContainer.append(buttonCloseCart); const divToCardBlock = document.createElement("div"); store.subscribe(() => { divToCardBlock.innerHTML = "" toCartById = store.getState().cart; for (let value of Object.values(toCartById)) { const { count, good } = value; console.log(count, "its cartbyid") divToCardBlock.id = "divToCartBlock"; const divToCart = document.createElement("div"); const goodByIdImage = document.createElement("img"); const goodByIdName = document.createElement("h2"); const goodByIdCount = document.createElement("h2"); const buttonPlus = document.createElement("button"); const buttonMinus = document.createElement("button"); buttonPlus.innerHTML = "+"; buttonMinus.innerHTML = "-"; buttonPlus.id = "buttonPlus"; buttonMinus.id = "buttonMinus"; divToCart.id = "divToCart"; bPoputContainer.append(divToCardBlock); divToCardBlock.append(divToCart); divToCart.append(goodByIdImage); divToCart.append(goodByIdName); divToCart.append(goodByIdCount); divToCart.append(buttonPlus); divToCart.append(buttonMinus); goodByIdImage.src = `${backendURLNotGraphQL}/${value.good.images[0].url}`; goodByIdName.innerText = good.name; goodByIdCount.innerText = count; } buttonCloseCart.onclick = () => { var parent = document.getElementById("header"); var child = document.getElementById("b-popup"); parent.removeChild(child); }; const payload = store.getState().auth.token; if (payload) { shoppingCart.style.display = "block"; } else { shoppingCart.style.display = "none"; } }); buttonGoodDelete.onclick = () => { store.dispatch(actionCartClear()); let a = document.getElementById('divToCartBlock') a.innerHTML = ""; let b = document.getElementById('shoppingCart') b.innerHTML = "Cart" }; const buyButtom = document.createElement("button"); const productImg = document.createElement("img"); const productName = document.createElement("h1"); const productPrice = document.createElement("h2"); const textBlock = document.createElement("div"); const flexBlock = document.createElement("div"); const productDescription = document.createElement("p"); let number = 0; store.subscribe(() => { const goodById = store.getState().promise.GoodFineOne?.payload?.data.GoodFindOne; const [, route, _id] = location.hash.split("/"); if (goodById && route === "good") { var element = document.getElementById("productBlock"); while (element.firstChild) { element.removeChild(element.firstChild); } const { name, price, description, images } = goodById; flexBlock.id = "flexBlock"; productBlock.append(flexBlock); flexBlock.append(productImg); productImg.style.width = "500px"; productImg.style.height = "500px"; productImg.src = `http://shop-roles.node.ed.asmer.org.ua/${images[0].url}`; textBlock.id = "textBlock"; flexBlock.append(textBlock); productName.innerHTML = name; textBlock.append(productName); productPrice.innerHTML = "price: " + price; textBlock.append(productPrice); productDescription.innerHTML = description; textBlock.append(productDescription); buyButtom.id = "buyButtom"; buyButtom.innerHTML = "Buy"; textBlock.append(buyButtom); buyButtom.onclick = () => { store.dispatch(actionCartAdd(goodById)); let a = document.getElementById('shoppingCart') number += 1 a.innerHTML = "Cart: " + number; }; } }); store.subscribe(() => { const catById = store.getState().promise.catById?.payload?.data.CategoryFindOne; const [, route, _id] = location.hash.split("/"); if (catById && route === "good") { const { name, price, description, images } = catById; categoryName.innerHTML = `

${name}

`; } }); const h2text = document.createElement("h2"); h2text.id = "h2text"; qwer.append(h2text); const logoutButton = document.createElement("button"); logoutButton.id = "logoutButton"; qwer.append(logoutButton); store.subscribe(() => { const payload = store.getState().auth.token; if (payload) { buyButtom.style.display = "block"; logoutButton.style.display = "block"; logoutButton.innerHTML = "Logout"; login.style.display = "none"; reg.style.display = "none"; h2text.style.display = "block"; h2text.innerText = jwtDecode(payload).sub.login; } else { buyButtom.style.display = "none"; h2text.style.display = "none"; logoutButton.style.display = "none"; } }); const buttonLogin = document.createElement("button"); buttonLogin.id = "loginInputt"; buttonLogin.innerText = "Login"; const buttonReg = document.createElement("button"); buttonReg.id = "regInput"; buttonReg.innerText = "Registration"; function bPopupCreate(text) { const bPopup = document.createElement("div"); const bPopupContent = document.createElement("div"); bPopup.id = "b-popup"; bPopup.className = "b-popup"; bPopupContent.className = "b-popup-content b-poput-container-flex"; header.append(bPopup); bPopup.append(bPopupContent); const buttonCloseCart = document.createElement("button"); buttonCloseCart.innerText = `×`; buttonCloseCart.id = "buttonCloseCartId"; bPopupContent.append(buttonCloseCart); const loginText = document.createElement("h2"); const passwordText = document.createElement("h2"); loginText.innerText = "Enter Login:"; bPopupContent.append(loginText); const loginInput = document.createElement("input"); loginInput.type = "text"; bPopupContent.append(loginInput); loginInput.id = "loginInput"; loginInput.value = "illiaKozyr"; passwordText.innerText = "Enter Password:"; bPopupContent.append(passwordText); const loginInputPassword = document.createElement("input"); loginInputPassword.type = "password"; bPopupContent.append(loginInputPassword); loginInputPassword.id = "passwordInput"; loginInputPassword.value = "qwerty123456"; bPopupContent.append(text); buttonCloseCart.onclick = () => { var parent = document.getElementById("header"); var child = document.getElementById("b-popup"); parent.removeChild(child); }; } window.onhashchange = () => { const [, route, _id] = location.hash.split("/"); const routes = { category() { store.dispatch(actionCatById(_id)); }, good() { store.dispatch(actionGoodById(_id)); }, dashboard() { store.dispatch(actionOrders()); console.log("заказостраница"); }, }; if (route in routes) { routes[route](); } }; login.onclick = () => { bPopupCreate(buttonLogin); buttonLogin.onclick = () => { store.dispatch(actionFullLogin(loginInput.value, passwordInput.value)); logoutButton.style.display = "block"; var parent = document.getElementById("header"); var child = document.getElementById("b-popup"); parent.removeChild(child); }; }; reg.onclick = () => { bPopupCreate(buttonReg); buttonReg.onclick = () => { store.dispatch( actionFullRegister(loginInput.value, passwordInput.value) ); var parent = document.getElementById("header"); var child = document.getElementById("b-popup"); parent.removeChild(child); }; }; logoutButton.onclick = () => { store.dispatch(actionAuthLogout()); login.style.display = "block"; reg.style.display = "block"; };