|
@@ -0,0 +1,429 @@
|
|
|
|
+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)
|
|
|
|
+ }
|
|
|
|
+})
|