// функция createStore 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 (newState !== state) { state = newState for (let cb of cbs) cb() } } return { dispatch, getState() { return state }, subscribe(cb) { cbs.push(cb) return () => cbs = cbs.filter(c => c !== cb) } } } // функция promiseReducer function promiseReducer(state = {}, { type, status, payload, error, name }) { if (type === 'PROMISE') { return { ...state, [name]: { status, payload, error } } } return state } // функция cartReducer 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]: count, ...copyWithout } = state return copyWithout } if (type === 'CART_CLEAR') { return {} } return state } let signatureToken = (token) => JSON.parse(atob(token.split(".")[1])) function authReducer(state, { type, token }) { if (state === undefined) { if (localStorage.authToken) { type = "LOGIN" token = localStorage.authToken } else { return {} } } if (type === "LOGIN") { console.log('LOGIN') localStorage.authToken = token return {token, payload: signatureToken(token)} } if (type === "LOGOUT") { console.log('LOGOUT') localStorage.removeItem("authToken") return {} } return state } // reducers let reducers = { promise: promiseReducer, cart: cartReducer, auth: authReducer } // функция combineReducers 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 => (query, variables = {}) => 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 variables = { "id": JSON.stringify([{ "_id": id }]) } let res = await shopGQL(query, variables) console.log(res) return res } let goodById = async (id) => { let query = `query fndgood($id: String) { GoodFind(query: $id){ name description price images { url } } }` let variables = { "id": JSON.stringify([{ "_id": id }]) } let res = await shopGQL(query, variables) return res } const actionRootCategories = () => actionPromise('rootCategories', shopGQL(`query cats($query:String) { CategoryFind(query:$query) { _id name } }`, {query: JSON.stringify([{parent:null}])})) //регистрация let reg = async(login, password) => { let query = `mutation reg($login:String, $password:String) { UserUpsert(user:{ login: $login, password: $password }){ _id } }` let variables = {"login":login, "password":password} let res = await shopGQL(query, variables) return res } //логин let log = async(login, password) => { let query = `query login($login:String, $password:String) { login(login: $login, password: $password) }` let variables = {"login":login, "password":password} let token = await shopGQL(query, variables) return token.data.login } //отправка заказа 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 newOrder($order:OrderInput) { OrderUpsert(order:$order) { _id } }` let variables = { "order": {"orderGoods": orderGoods} } let res = await shopGQL(query, variables) 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 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" }) const actionAuthLogin = token => ({type: 'LOGIN', token}) const actionAuthLogout = () => ({type: 'LOGOUT'}) const actionLogin = (login, password) => actionPromise("log", log(login, password)) const actionReg = (login, password) => actionPromise("reg", reg(login, password)) const actionFullLogin = (login, password) => async(dispatch) => { let result = await dispatch(actionLogin(login, password)) if (result !== null){ dispatch(actionAuthLogin(result)) } else { alert ('Такой пользователь не существует!') } } const actionFullRegister = (login, password) => async(dispatch) => { let result = await dispatch(actionReg(login, password)) if(result.data.UserUpsert !==null) { dispatch(actionFullLogin(login, password)) } else { alert('Такой пользователь уже существует!') } } 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) } if (route === "login") { drawLog(main) } if (route === "registration") { drawReg(main) } } function drawMainMenu() { let cats = store.getState().promise.rootCategories.payload if (cats) { 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("h3") let goodImg = document.createElement("img") let description = document.createElement("p") let price = document.createElement("p") let count = document.createElement("input") let goodBtnBox = document.createElement("p") 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 goodName.textContent = obj.name description.textContent = "Описание: " + obj.description count.type = "number" count.min = 1 count.value = 1 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") img.style.width ='400px' img.src = "http://shop-roles.asmer.fs.a-level.com.ua/" + obj.images[0].url let price = document.createElement("p") let nameGood = document.createElement("h5") nameGood.textContent = obj.name price.innerText = "Цена:" + obj.price + "грн" box.append(img, nameGood, price,) 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("h3") cartName.textContent = "Ваши заказы:" parent.append(cartName) let order = {} for (let key in obj) { order[key] = obj[key].count let cartItem = document.createElement("p") let name = document.createElement("p") let count = document.createElement("input") let btnDelItem = document.createElement("button") btnDelItem .textContent="Удалить" let btnPlus = document.createElement("button") btnPlus.textContent="+" let btnMinus = document.createElement("button") btnMinus.textContent="-" count.min = 1 count.value = obj[key].count 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("p") let btnOrder = document.createElement("button") let btnClear = document.createElement("button") btnClear.textContent = "ОЧИСТИТЬ КОРЗИНУ" btnOrder.textContent = "КУПИТЬ" btnOrder.onclick = () => { store.dispatch(actionBuyGood(order)) store.dispatch(actionCartClear()) } btnClear.onclick = () => { store.dispatch(actionCartClear()) } btnBox.append(btnOrder, btnClear) parent.append(btnBox) } let statusCart = () => { 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 } let checkUser = (parent = thecart) => { if (parent.children.length > 1) { parent.lastChild.remove() } let status = store.getState().auth let box = document.createElement("div") let userName = document.createElement("p") box.append(userName) parent.append(box) if (status.payload) { let btn = document.createElement("button") userName.textContent = status.payload.sub.login btn.textContent = "Выход" btn.style.cursor="pointer" btn.style.color="rgb(86, 93, 189)" btn.onclick = () => { store.dispatch(actionAuthLogout()) } box.append(btn) } else { let btn = document.createElement("a") let btnREG = document.createElement("a") btn.textContent = "Вход" btnREG.textContent = "Регистрация" btn.href = "#/login" btnREG.href = "#/registration" box.append(btn, btnREG) } } function Reg(parent, type, open) { let h3 = document.createElement("h3") let loginInput = document.createElement("input") let passwordInput = document.createElement("input") let checkbox = document.createElement("input") let btn = document.createElement("button") btn.style.marginRight="10px" let form = document.createElement("span") let box = document.createElement("p") box.append(passwordInput, checkbox) loginInput.id = "login" loginInput.placeholder = "логин" loginInput.style.textAlign="center" passwordInput.id = "password" passwordInput.placeholder = "пароль" passwordInput.style.textAlign="center" btn.id = "btn" checkbox.type = "checkbox" btn.disabled = true; if (type === "reg") { h3.textContent = "РЕГИСТРАЦИЯ" btn.textContent = "Зарегистрироваться" } else { h3.textContent = "ВХОД" btn.textContent = "Войти" } form.append(h3, loginInput, box, btn) form.className = "form" parent.append(form) let btnOpen = () => { if (type === "reg" && checkbox.checked === false) { passwordInput.value !== "" && password.value === passwordVerify.value ? btn.disabled = false : btn.disabled = true } else { (loginInput.value != "" && passwordInput.value != "") ? btn.disabled = false : btn.disabled = true } } let checker = (check) => { if (check) { passwordInput.type = "text" checkbox.checked = true if (type === "reg" && passwordVerify) { passwordVerify.remove() } } else { passwordInput.type = "password" checkbox.checked = false if (type === "reg") { let passwordInput2 = document.createElement("input") passwordInput2.placeholder = "повторите пароль" passwordInput2.style.textAlign="center" passwordInput2.id = "passwordVerify" passwordInput2.type = "password" passwordInput2.oninput = () => { btnOpen(); } form.append(passwordInput2) } } } checker(open) loginInput.oninput = () => { btnOpen(); if (typeof this.onChange === "function") { this.onChange([loginInput.value, passwordInput.value]) } } passwordInput.oninput = () => { btnOpen(); if (typeof this.onChange === "function") { this.onChange([loginInput.value, passwordInput.value]) } } checkbox.onchange = () => { checker(checkbox.checked) btnOpen() if (typeof this.onOpenChange === "function") { this.onOpenChange(checkbox.checked) } } this.getValue = () => [loginInput.value, passwordInput.value] this.setValue = (valueLogin, valuePassword) => { loginInput.value = valueLogin; passwordInput.value = valuePassword btnOpen() } this.getOpen = () => checkbox.checked this.setOpen = (open) => { checker(open) } } let drawLog = (parent) => { while (parent.lastChild) { parent.lastChild.remove() } if (store.getState().auth.payload) { let h2 = document.createElement("h2") h2.textContent = "Вход выполнен успешно!" h2.style.color = "rgb(86, 93, 189)" parent.append(h2) } else { new Reg(parent) btn.onclick = () => { store.dispatch(actionFullLogin(login.value, password.value)) } } } let drawReg = (parent) => { while (parent.lastChild) { parent.lastChild.remove() } if (store.getState().auth.payload) { let h2 = document.createElement("h2") h2.textContent = "Регистрация прошла успешно!" h2.style.color = "rgb(86, 93, 189)" parent.append(h2) } else { new Reg(parent, "reg") btn.onclick = () => { store.dispatch(actionFullRegister(login.value, password.value)) } } } //subscribers store.subscribe(drawMainMenu) store.subscribe(() => console.log(store.getState())) store.subscribe(statusCart) store.subscribe(checkUser) 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() } aside.innerText = '' let cats = document.createElement('p') cats.innerText = "Вы в категории ==>" + " " + catById.data.CategoryFind[0].name mainMenu = document.createElement('a') mainMenu.style.cursor = 'pointer' mainMenu.innerText = "<== Назад в главное меню" mainMenu.onclick = () => { main.innerText = '' drawMainMenu() } let cards = document.createElement("section") for (let key of catById.data.CategoryFind[0].goods) { cardDraw(key, cards) } main.append(cards) aside.append(mainMenu, cats) } } if (route === 'good') { const goodById = store.getState().promise.goodById?.payload const catById = store.getState().promise.catById?.payload if (goodById) { while (main.lastChild) { main.lastChild.remove() } aside.innerText = '' let cats = document.createElement('p') cats.innerText = "Вы в категории ==>" + " " + catById.data.CategoryFind[0].name mainMenu.style.cursor = 'pointer' mainMenu.onclick = () => { main.innerText = '' drawMainMenu() } aside.append(cats, mainMenu) goodDraw(goodById.data.GoodFind[0], main, id) } } if (route === 'cart') { cartDraw(store.getState().cart, main) } if (route === 'login') { drawLog(main) } if (route === "registration") { drawReg(main) } })