let loginValue = ""; let passwordValue = ""; const authDiv = document.createElement("div"); const noAuthDiv = document.createElement("div"); const cartIcon = document.createElement("div"); const loginButton = document.createElement("button"); const registerButton = document.createElement("button"); const nameDiv = document.createElement("div"); const logoutButton = document.createElement("button"); cartIcon.innerHTML = "Cart"; loginButton.innerText = "Login"; loginButton.onclick = () => { window.location = "#/login"; }; registerButton.innerText = "Register"; registerButton.onclick = () => { window.location = "#/register"; }; logoutButton.innerText = "Logout"; noAuthDiv.append(loginButton); noAuthDiv.append(registerButton); authDiv.append(nameDiv); authDiv.append(logoutButton); authDiv.append(cartIcon); cartIcon.style.marginLeft = "auto"; authDiv.style.display = "flex"; header.append(authDiv); header.append(noAuthDiv); const jwtDecode = (token) => { try { let payload = JSON.parse(atob(token.split(".")[1])); return payload; } catch (e) { console.log(e); } }; function createStore(reducer) { let state = reducer(undefined, {}); let cbs = []; const getState = () => state; const subscribe = (cb) => (cbs.push(cb), () => (cbs = cbs.filter((c) => c !== cb))); const dispatch = (action) => { if (typeof action === "function") { return action(dispatch, getState); } const newState = reducer(state, action); if (newState !== state) { state = newState; for (let cb of cbs) cb(); } }; return { getState, dispatch, subscribe, }; } function cartReducer(state = {}, { type, good, count = 1 }) { if (!Object.keys(state).length) { let savedData = (localStorage[jwtDecode(localStorage.authToken)?.sub.login] && JSON.parse(localStorage[jwtDecode(localStorage.authToken)?.sub.login])) || null; if (savedData) { state = savedData?.cart || {}; } else { state = {}; } } if (count <= 0) { type = "CART_DELETE"; } if (type === "CART_ADD") { return { ...state, [good["_id"]]: { good, count: good["_id"] in state ? state[good._id].count + count : count, }, }; } if (type === "CART_CHANGE") { return { ...state, [good["_id"]]: { good, count: count, }, }; } if (type === "CART_DELETE") { let { [good._id]: toRemove, ...newState } = state; return newState; } if (type === "CART_CLEAR") { return {}; } return state; } function promiseReducer(state = {}, { type, name, status, payload, error }) { if (type === "PROMISE") { return { ...state, [name]: { status, payload, error }, }; } return state; } function authReducer(state, { type, token }) { if (state === undefined) { if (localStorage.authToken) { token = localStorage.authToken; type = "AUTH_LOGIN"; state = {}; } } if (type === "AUTH_LOGIN") { if (!token || !jwtDecode(token)) return {}; localStorage.authToken = token; return { ...state, token: token, payload: jwtDecode(token), }; } if (type === "AUTH_LOGOUT") { localStorage.removeItem("authToken"); return {}; } return state || {}; } const combineReducers = (reducers) => (state = {}, action) => { let newState = {}; for (let [key, reducer] of Object.entries(reducers)) { let reducerResult = reducer(state[key], action); if (state[key] !== reducerResult) { newState = { ...newState, [key]: reducerResult }; } } if (Object.keys(newState).length) { state = { ...state, ...newState }; } return state; }; const store = createStore(combineReducers({ auth: authReducer, promise: promiseReducer, cart: cartReducer })); store.subscribe(() => console.log(store.getState())); store.subscribe(() => { let { cart, auth: { payload }, } = store.getState(); let localStorageLoginSave = {}; let login = payload?.sub.login || null; if (!login) { return; } if (localStorage[login]) { localStorageLoginSave = JSON.parse(localStorage[login]); if (Object.keys(cart).length === 0) { let { cart, ...newState } = localStorageLoginSave; localStorage[login] = JSON.stringify(newState); return; } } if (Object.keys(cart).length) { localStorage[login] = JSON.stringify({ ...localStorageLoginSave, cart }); } }); const actionPending = (name) => ({ type: "PROMISE", name, status: "PENDING" }); const actionFulfilled = (name, payload) => ({ type: "PROMISE", name, status: "FULFILLED", payload }); const actionRejected = (name, error) => ({ type: "PROMISE", name, status: "REJECTED", error }); const actionPromise = (name, promise) => async (dispatch) => { dispatch(actionPending(name)); try { let payload = await promise; dispatch(actionFulfilled(name, payload)); return payload; } catch (error) { dispatch(actionRejected(name, error)); } }; 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()) .then((data) => { if (data.errors) { throw new Error(JSON.stringify(data.errors)); } else return Object.values(data.data)[0]; }); const backendURL = "http://shop-roles.asmer.fs.a-level.com.ua"; const gql = getGQL(backendURL + "/graphql"); const actionRootCats = () => actionPromise( "rootCats", gql(`query { CategoryFind(query: "[{\\"parent\\":null}]"){ _id name } }`) ); const actionGoodById = (_id) => actionPromise( "good", gql( `query GoodById($q: String){ GoodFindOne(query: $q){ _id name price images{ url } } }`, { q: JSON.stringify([{ _id }]) } ) ); const actionCatById = ( _id //добавить подкатегории ) => actionPromise( "catById", gql( `query catById($q: String){ CategoryFindOne(query: $q){ _id name goods { _id name price images { url } } } }`, { q: JSON.stringify([{ _id }]) } ) ); const actionOrders = () => (dispatch) => dispatch( actionPromise( "orders", gql(` query orders{ OrderFind(query:"[{}]"){ _id total createdAt orderGoods{ _id count price good{ name _id price images{ url } } } } } `) ) ); const actionNewOrder = (orderGoods = []) => async (dispatch, getState) => { await dispatch( actionPromise( "newOrder", gql( `mutation newOrder($order:OrderInput){ OrderUpsert(order:$order){ _id total } } `, { order: { orderGoods: orderGoods, }, } ) ) ); let { promise: { newOrder }, } = getState(); if (newOrder.status === "FULFILLED") { dispatch(actionCartClear()); } }; const actionAuthLogin = (token) => ({ type: "AUTH_LOGIN", token: token, }); const actionLogin = (login, password) => async (dispatch) => { const token = await dispatch( actionPromise( "login", gql( `query log($login:String,$password:String){ login(login:$login,password:$password) } `, { login: login, password: password, } ) ) ); await dispatch(actionAuthLogin(token)); window.location = "#/home"; }; const actionAuthLogout = () => ({ type: "AUTH_LOGOUT" }); const actionLogout = () => async (dispatch) => { await dispatch(actionAuthLogout()); await dispatch(actionCartClear()); window.location = "#/home"; }; const actionRegister = (login, password) => async (dispatch) => { await dispatch( actionPromise( "register", gql( `mutation register($login:String,$password:String){ UserUpsert(user:{login:$login,password:$password}){ _id login } }`, { login: login, password: password, } ) ) ); await dispatch(actionLogin(loginValue, passwordValue)); }; const actionCartAdd = (good, count = 1) => ({ type: "CART_ADD", good, count }); const actionCartChange = (good, count = 1) => ({ type: "CART_CHANGE", good, count }); const actionCartDelete = (good) => ({ type: "CART_DELETE", good }); const actionCartClear = () => ({ type: "CART_CLEAR" }); store.dispatch(actionRootCats()); // store.dispatch(actionCartAdd({ _id: "пиво", name: "одеколонь", price: 30 })); // //{пиво: {count: 1, good: {_id: 'пиво', name: 'одеколонь', price: 30}}} // store.dispatch(actionCartAdd({ _id: "чипсы", name: "одеколонь", price: 30 })); // //{пиво: {count: 1, good: {_id: 'пиво', name: 'одеколонь', price: 30}}, // // чипсы: {count: 1, good: {_id: 'чипсы', name: 'огурец с молоком', price: 50}}, // //} // store.dispatch(actionCartAdd({ _id: "пиво", name: "одеколонь", price: 30 })); //count: 2 // //{пиво: {count: 2, good: {_id: 'пиво', name: 'одеколонь', price: 30}}, // // чипсы: {count: 1, good: {_id: 'чипсы', name: 'огурец с молоком', price: 50}}, // //} // store.dispatch(actionCartChange({ _id: "чипсы", name: "одеколонь", price: 30 }, 2)); // //{пиво: {count: 2, good: {_id: 'пиво', name: 'одеколонь', price: 30}}, // // чипсы: {count: 2, good: {_id: 'чипсы', name: 'огурец с молоком', price: 50}}, // //} // store.dispatch(actionCartDelete({ _id: "пиво", name: "одеколонь", price: 30 })); // //{чипсы: {count: 2, good: {_id: 'чипсы', name: 'огурец с молоком', price: 50}}, // //} // store.dispatch(actionCartClear()); // {} logoutButton.onclick = () => store.dispatch(actionLogout()); store.subscribe(() => { const { auth: { token, payload }, } = store.getState(); if (token) { nameDiv.innerHTML = `${payload.sub.login}`; authDiv.style.display = "flex"; noAuthDiv.style.display = "none"; } else { authDiv.style.display = "none"; noAuthDiv.style.display = "block"; } }); store.subscribe(() => { const { promise: { rootCats }, } = store.getState(); if (rootCats?.payload) { aside.innerHTML = ""; for (const { _id, name } of rootCats?.payload) { const link = document.createElement("a"); link.href = `#/category/${_id}`; link.innerText = name; aside.append(link); } } }); const updateCart = () => { let { cart, auth: { token }, } = store.getState(); if (!token) { return; } let orderGoods = []; Object.entries(cart).map(([index, order]) => { orderGoods[orderGoods.length] = { count: order.count, good: { _id: index } }; }); console.log(orderGoods); main.innerHTML = ""; let orderList = document.createElement("div"); let clearButton = document.createElement("button"); let sum = 0; let sumField = document.createElement("div"); let submitButton = document.createElement("button"); Object.entries(cart).length ? (submitButton.disabled = false) : (submitButton.disabled = true); submitButton.innerText = "Buy"; submitButton.style.display = "block"; submitButton.style.marginLeft = "auto"; submitButton.style.marginTop = "10px"; sumField.style.marginLeft = "100%"; sumField.style.fontWeight = "bold"; clearButton.innerHTML = "Clear cart"; clearButton.style.display = "block"; clearButton.style.marginLeft = "auto"; Object.keys(cart).length > 0 ? (clearButton.disabled = false) : (clearButton.disabled = true); Object.keys(cart).length > 0 ? (sumField.style.display = "block") : (sumField.style.display = "none"); for (let [id, order] of Object.entries(cart)) { let { name, images, price } = order.good; let orderWrapper = document.createElement("div"); orderWrapper.style.display = "flex"; orderWrapper.style.width = "100%"; let goodBox = document.createElement("div"); goodBox.style = "display:flex; flex:1;"; let countBox = document.createElement("div"); let addButton = document.createElement("button"); addButton.innerHTML = "+"; let removeButton = document.createElement("button"); removeButton.innerHTML = "-"; let deleteButton = document.createElement("button"); deleteButton.innerHTML = "X"; let countField = document.createElement("span"); countField.innerHTML = order.count; countField.innerHTML = order.count; goodBox.innerHTML = `

${name}

${order.count} * ${price} = ${+order.count * +price}
`; sum += +order.count * +price; countBox.append(addButton); countBox.append(countField); countBox.append(removeButton); countBox.append(deleteButton); orderWrapper.append(goodBox, countBox); addButton.onclick = () => { store.dispatch(actionCartChange(order.good, ++order.count)); }; removeButton.onclick = () => { store.dispatch(actionCartChange(order.good, --order.count)); }; deleteButton.onclick = () => { store.dispatch(actionCartDelete(order.good)); }; orderList.append(orderWrapper); } clearButton.onclick = () => store.dispatch(actionCartClear()); submitButton.onclick = () => store.dispatch(actionNewOrder(orderGoods)); sumField.innerText = sum; main.append(orderList); main.append(sumField); main.append(clearButton); main.append(submitButton); }; window.onhashchange = () => { const [, route, _id] = location.hash.split("/"); const routes = { home() { main.innerHTML = "Контент"; }, category() { store.dispatch(actionCatById(_id)); }, good() { //задиспатчить actionGoodById store.dispatch(actionGoodById(_id)); }, login() { const loginInput = document.createElement("input"); const passwordInput = document.createElement("input"); const loginSubmitButton = document.createElement("button"); loginSubmitButton.innerHTML = "Login"; main.innerHTML = ""; main.append(loginInput); main.append(passwordInput); main.append(loginSubmitButton); loginSubmitButton.onclick = () => { loginValue = loginInput.value; passwordValue = passwordInput.value; store.dispatch(actionLogin(loginValue, passwordValue)); }; }, register() { const loginInput = document.createElement("input"); const passwordInput = document.createElement("input"); const registerSubmitButton = document.createElement("button"); registerSubmitButton.innerHTML = "Register"; main.innerHTML = ""; main.append(loginInput); main.append(passwordInput); main.append(registerSubmitButton); registerSubmitButton.onclick = () => { loginValue = loginInput.value; passwordValue = passwordInput.value; store.dispatch(actionRegister(loginValue, passwordValue)); }; }, async dashboard() { await store.dispatch(actionOrders()); let { promise: { orders }, auth: { token }, } = store.getState(); if (!token || orders.status === "REJECTED") { return; } main.innerHTML = ""; let orderList = document.createElement("div"); for (let order of orders.payload) { let orderBlock = document.createElement("div"); let orderGoodsBlock = document.createElement("div"); let orderHeaderBlock = document.createElement("div"); let orderSumBlock = document.createElement("div"); let createdAtBlock = document.createElement("div"); createdAtBlock.innerHTML = ("" + new Date(+order.createdAt)).slice(0, 25); for (let orderGood of order.orderGoods) { let { count, good: { name, price, images }, } = orderGood; let goodBlock = document.createElement("div"); goodBlock.innerHTML = `
${name}
${count}x${price} = ${ +price * +count }
`; goodBlock.style.display = "flex"; goodBlock.style.width = "100%"; goodBlock.style.marginTop = "7px"; goodBlock.style.background = "#EAEAEA"; orderGoodsBlock.append(goodBlock); } orderSumBlock.innerHTML = `TOTAL:${order.total}`; orderHeaderBlock.style.padding = "10px"; orderHeaderBlock.style.display = "flex"; orderHeaderBlock.style.justifyContent = "space-between"; orderHeaderBlock.append(createdAtBlock); orderHeaderBlock.append(orderSumBlock); orderBlock.append(orderHeaderBlock); orderBlock.append(orderGoodsBlock); orderGoodsBlock.style.marginTop = "10px"; orderBlock.style.marginTop = "20px"; orderBlock.style.borderBottom = "1px solid black"; orderBlock.style.padding = "10px"; orderList.append(orderBlock); } main.append(orderList); }, cart() { updateCart(); }, }; if (route in routes) routes[route](); }; store.subscribe(() => { let { cart } = store.getState(); const [, route, _id] = location.hash.split("/"); if (cart && route === "cart") { updateCart(); } }); window.onhashchange(); store.subscribe(() => { const { promise: { catById }, } = store.getState(); const [, route, _id] = location.hash.split("/"); if (catById?.payload && route === "category") { const { name } = catById.payload; main.innerHTML = `

${name}

ТУТ ДОЛЖНЫ БЫТЬ ПОДКАТЕГОРИИ`; for (const { _id, name, price, images } of catById.payload.goods) { const card = document.createElement("div"); card.innerHTML = `

${name}

${price} ${name} `; main.append(card); } } }); store.subscribe(() => { const { promise: { good }, auth: { token }, } = store.getState(); const [, route, _id] = location.hash.split("/"); if (good?.payload && route === "good") { const { name, price, images } = good.payload; main.innerHTML = `

${name}

`; const card = document.createElement("div"); card.innerHTML = `

${name}

${price} `; if (token) { let buyButton = document.createElement("button"); buyButton.innerHTML = "Купить"; buyButton.onclick = () => store.dispatch(actionCartAdd(good.payload)); card.append(buyButton); } main.append(card); } });