function createStore(reducer) { let state = reducer(undefined, {}) let cbs = [] function dispatch(action) { if (typeof action === 'function') { return action(dispatch) } const newState = reducer(state, action) if (state !== newState) { state = newState cbs.forEach(cb => cb()) } } return { dispatch, subscribe(cb) { cbs.push(cb) return () => cbs = cbs.filter(c => c !== cb) }, getState() { return state } } } //reducers function promiseReducer(state = {}, { type, status, payload, error, name }) { if (type === 'PROMISE') { return { ...state, [name]: { status, payload, error } } } return state } function cartReducer(state = {}, { type, count = 1, _id, name }) { if (type === "CART_ADD") { return { ...state, [_id]: { name: name, count: state[_id] ? state[_id].count + count : count } } } if (type === "CART_CHANGE") { return { ...state, [_id]: { name: name, count: count } } } if (type === "CART_REMOVE") { let { [_id]: a, ...rest } = state return rest } if (type === "CART_CLEAR") { return {} } return state } let reducers = { promise: promiseReducer, cart: cartReducer } function combineReducers(reducers) { function commonReducer(state = {}, action) { let newState = {} for (let key in reducers) { let innerState = reducers[key](state[key], action) innerState === state[key] ? newState[key] = state[key] : newState[key] = innerState } return newState } return commonReducer } const store = createStore(combineReducers(reducers)) //запросики const getGQL = url => { return (query, variables) => { return fetch(url, { method: 'POST', headers: { "content-type": "application/json", ...(localStorage.authToken ? { Authorization: "Bearer " + localStorage.authToken } : {}) }, body: JSON.stringify({ query, variables }), }).then(res => res.json()) } } let shopGQL = getGQL('http://shop-roles.asmer.fs.a-level.com.ua/graphql') let categoryById = async (id) => { let query = `query fndcategory($id: String) { CategoryFind(query: $id){ name goods{ _id name price images { url } } } }` let qVariables = { "id": JSON.stringify([{ "_id": id }]) } let res = await shopGQL(query, qVariables) console.log(res) return res } let goodById = async (id) => { let query = `query fndgood($id: String) { GoodFind(query: $id){ name description price images { url } } }` let qVariables = { "id": JSON.stringify([{ "_id": id }]) } let res = await shopGQL(query, qVariables) return res } let newOrder = async (obj) => { let option = Object.entries(obj); let orderGoods = []; for (let key of option) { let i = { "count": key[1], "good": { "_id": key[0] } } orderGoods.push(i); } let query = `mutation sndOrder($order: OrderInput) { OrderUpsert(order: $order) { _id createdAt total } }` let qVariables = { "order": { "orderGoods": orderGoods } } let res = await shopGQL(query, qVariables) console.log(res) return res } //actions const actionPending = name => ({ type: 'PROMISE', status: 'PENDING', name }) const actionResolved = (name, payload) => ({ type: 'PROMISE', status: 'RESOLVED', name, payload }) const actionRejected = (name, error) => ({ type: 'PROMISE', status: 'REJECTED', name, error }) const actionPromise = (name, promise) => async dispatch => { dispatch(actionPending(name)) try { let payload = await promise dispatch(actionResolved(name, payload)) return payload } catch (error) { dispatch(actionRejected(name, error)) } } const actionRootCategories = () => actionPromise('rootCategories', shopGQL(` query cats($query:String){ CategoryFind(query:$query){ _id name } } `, { query: JSON.stringify([{ parent: null }]) })) const actionCategoryById = id => actionPromise('catById', categoryById(id)) const actionGoodById = id => actionPromise('goodById', goodById(id)) const actionBuyGood = (obj) => actionPromise('newOrder', newOrder(obj)) const actionCartAdd = (n, id, name) => ({ type: "CART_ADD", count: n, _id: id, name }) const actionCartChange = (n, id, name) => ({ type: "CART_CHANGE", count: n, _id: id, name }) const actionCartRemove = id => ({ type: "CART_REMOVE", _id: id }) const actionCartClear = () => ({ type: "CART_CLEAR" }) store.dispatch(actionRootCategories()) window.onhashchange = () => { let { 1: route, 2: id } = location.hash.split('/') if (route === 'categories') { store.dispatch(actionCategoryById(id)) } if (route === 'good') { store.dispatch(actionGoodById(id)) } if (route === 'cart') { cartDraw(store.getState().cart, main) } } //рисовашки function drawMainMenu() { let cats = store.getState().promise.rootCategories.payload if (cats) { //каждый раз дорисовываются в body aside.innerText = '' for (let { _id, name } of cats.data.CategoryFind) { let catA = document.createElement('a') catA.href = `#/categories/${_id}` catA.innerText = name aside.append(catA) } } } let goodDraw = (obj, parent, _id) => { let box = document.createElement("div") let goodName = document.createElement("h2") let goodIMG = document.createElement("img") let description = document.createElement("p") let price = document.createElement("span") let count = document.createElement("input") let goodBtnBox = document.createElement("div") let btn1 = document.createElement("button") let btn2 = document.createElement("button") price.textContent = "Цена: " + obj.price + "грн" goodIMG.src = "http://shop-roles.asmer.fs.a-level.com.ua/" + obj.images[0].url box.style.width = "40%" box.style.margin = "20px" box.style.display = "flex" box.style.flexDirection = "column" goodName.textContent = obj.name description.textContent = obj.description count.type = "number" count.min = 1 count.value = 1 goodBtnBox.style.display = "flex" goodBtnBox.style.alignItems = "center" btn1.textContent = "Купить" btn2.textContent = "В корзину" goodBtnBox.append(count, btn2, btn1) let order = { [_id]: +count.value } count.oninput = () => order[_id] = +count.value btn1.onclick = () => { store.dispatch(actionBuyGood(order)) } btn2.onclick = () => { store.dispatch(actionCartAdd(+count.value, _id, obj.name)) } box.append(goodIMG, goodName, description, price, goodBtnBox) parent.append(box) } let cardDraw = (obj, parent) => { let box = document.createElement("div"); let img = document.createElement("img"); let price = document.createElement("span"); let productName = document.createElement("h5"); img.src = "http://shop-roles.asmer.fs.a-level.com.ua/" + obj.images[0].url; productName.textContent = obj.name price.textContent = "Цена: " + obj.price + " грн" let productBody = document.createElement("div") box.className = "card" productBody.append(productName, price) box.append(img, productBody) let a = document.createElement("a") a.href = "#/good/" + obj._id a.append(box) parent.append(a) } let cartDraw = (obj, parent) => { while (parent.lastChild) { parent.lastChild.remove() } let cartName = document.createElement("h2") cartName.textContent = "Ваш заказ" parent.append(cartName) let order = {} for (let key in obj) { order[key] = obj[key].count let cartItem = document.createElement("div") let name = document.createElement("span") let count = document.createElement("input") let btnDelItem = document.createElement("button") let btnPlus = document.createElement("button") let btnMinus = document.createElement("button") count.min = 1 count.value = obj[key].count count.className = "cart-item-count" btnPlus.className = "btn-plus" btnMinus.className = "btn-minus" btnDelItem.className = "btn-del" cartItem.className = "cart-item" name.textContent = obj[key].name cartItem.append(btnMinus, count, btnPlus, name, btnDelItem) parent.append(cartItem) if (+count.value === 1) { btnMinus.disabled = "true" } count.oninput = () => { if (+count.value >= 1) { store.dispatch(actionCartChange(+count.value, key, obj[key].name)) } } btnPlus.onclick = () => { store.dispatch(actionCartChange(+count.value + 1, key, obj[key].name)) } btnMinus.onclick = () => { store.dispatch(actionCartChange(+count.value - 1, key, obj[key].name)) } btnDelItem.onclick = () => { store.dispatch(actionCartRemove(key)) } } let btnBox = document.createElement("div") let btnOrder = document.createElement("button") let btnClear = document.createElement("button") btnClear.textContent = "ОЧИСТИТЬ КОРЗИНУ" btnClear.className = "btn-clear" btnOrder.textContent = "КУПИТЬ" btnOrder.onclick = () => { store.dispatch(actionBuyGood(order)) store.dispatch(actionCartClear()) } btnClear.onclick = () => { store.dispatch(actionCartClear()) } btnBox.append(btnOrder, btnClear) parent.append(btnBox) } let cartValueDraw = () => { if (cart.children.length === 1) { let span = document.createElement("span") cart.append(span) } let sum = 0; let cartState = store.getState().cart for (let key in cartState) { sum += cartState[key].count } cart.lastChild.textContent = sum } //subscribers const unsubscribe1 = store.subscribe(() => console.log(store.getState())) store.subscribe(drawMainMenu) store.subscribe(cartValueDraw) store.subscribe(() => { const { 1: route, 2: id } = location.hash.split('/') if (route === 'categories') { const catById = store.getState().promise.catById?.payload if (catById) { while (main.lastChild) { main.lastChild.remove() } let categoryName = document.createElement("h2") let cards = document.createElement("div") cards.className = "cards" categoryName.textContent = catById.data.CategoryFind[0].name for (let key of catById.data.CategoryFind[0].goods) { cardDraw(key, cards) } main.append(categoryName, cards) } } if (route === 'good') { const goodById = store.getState().promise.goodById?.payload if (goodById) { //вывести в main страницу товара while (main.lastChild) { main.lastChild.remove() } goodDraw(goodById.data.GoodFind[0], main, id) } } if (route === 'cart') { cartDraw(store.getState().cart, main) } })