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(state) //и запускаем подписчиков } } return { getState, //добавление функции getState в результирующий объект dispatch, subscribe //добавление subscribe в объект } } const jwtDecode = (token) => { try { let payload = JSON.parse(atob(token.split('.')[1])) console.log(payload) return payload } catch (e) { return undefined } } //---------------------------------------getGql--------------------------------------------- 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.data) { return Object.values(data.data)[0] } else throw new Error(JSON.stringify(data.errors)) }) const url = 'http://shop-roles.node.ed.asmer.org.ua/' const gql = getGql(url + 'graphql') //------------------------------------------------PromiseReducer--------------------------------- function promiseReducer(state={}, {type, status, payload, error, name}){ 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}) //-----------------------------------------------------actionPromise------------------------------------------- const actionPromise = (name, promise) => async dispatch => { dispatch(actionPending(name)) //сигнализируем redux, что промис начался try{ const payload = await promise //ожидаем промиса dispatch(actionFulfilled(name, payload)) //сигнализируем redux, что промис успешно выполнен return payload //в месте запуска store.dispatch с этим thunk можно так же получить результат промиса } catch (error){ dispatch(actionRejected(name, error)) //в случае ошибки - сигнализируем redux, что промис несложился } } //----------------------------------------------------authReducer--------------------------------------- function authReducer(state={}, {type, token}) { if (type === 'AUTH_LOGOUT'){ window.localStorage.removeItem('authToken'); return {} } if(type === "AUTH_LOGIN"){ try{ window.localStorage.setItem('authToken',token); return { token: token, payload: jwtDecode(token) } }catch (e) { } } return state } const actionAuthLogin = token => ({type: 'AUTH_LOGIN', token}) const actionAuthLogout = () => ({type: 'AUTH_LOGOUT'}) //--------------------------------------------------cartReducer------------------------------------------ function cartReducer (state = {}, {type, good, count=1}) { if (type === 'CART_ADD') { return { ...state, [good._id]: { good, count: +count} } } if (type === 'CART_SUB') { if (state([good._id].count - count) <= 0) { delete state[good._id] } else { return { ...state, [good._id]: { good, count: state[good._id].count - count} } } } if (type === 'CART_DEL') { delete state[good._id] return {...state} } if (type === 'CART_SET') { return { ...state, [good._id]: { good, count} } } if (type === 'CART_CLEAR') { state = {} } return state } const actionCartAdd = (good, count=1) => ({type: 'CART_ADD', count, good}) const actionCartSub = (good, count=1) => ({type: 'CART_SUB', count, good}) const actionCartDel = (good) => ({type: 'CART_DEL', good}) const actionCartSet = (good, count=1) => ({type: 'CART_SET', count, good}) const actionCartClear = () => ({type: 'CART_CLEAR'}) //---------------------------------------------localStoredReducer--------------------------------- function localStoredReducer(originalReducer, localStorageKey) { function wrapper(state, action) { if (!state) { try { return JSON.parse(localStorage[localStorageKey]) } catch { } } let res = originalReducer(state, action) localStorage[localStorageKey] = JSON.stringify(res) return res; } return wrapper } // -------------------------------------------CombineReducers--------------------------------------- function combineReducers(reducers){ function totalReducer(state={}, action){ const newTotalState = {} for (const [reducerName, reducer] of Object.entries(reducers)){ const newSubState = reducer(state[reducerName], action) if (newSubState !== state[reducerName]){ newTotalState[reducerName] = newSubState } } if (Object.keys(newTotalState).length){ return {...state, ...newTotalState} } return state } return totalReducer } const totalReducer = combineReducers({ promise: promiseReducer, auth: localStoredReducer(authReducer,'auth'), cart: localStoredReducer(cartReducer,'cart') }) const store = createStore(totalReducer) store.subscribe(() => console.log(store.getState())) //Запрос на список корневых категорий const actionRootCats = () => actionPromise('rootCats', gql(`query rootCats2{ CategoryFind(query: "[{\\"parent\\": null}]"){ _id name } }`)) store.dispatch(actionRootCats()) //Запрос для получения одной категории с товарами и картинками const oneCatWithGoods = (_id) => actionPromise('oneCatWithGoods', gql(`query oneCatWithGoods ($q:String) { CategoryFindOne (query: $q){ _id name parent{ _id name} subCategories { _id name }, goods { _id name price description images { url } } }}`, {q: JSON.stringify([{_id}])} )) //Запрос на получение товара с описанием и картинками const goodWithDescAndImg = (_id) => actionPromise('goodWithDescAndImg', gql(`query goodWithDescAndImg ($q:String) { GoodFindOne (query: $q){ _id name price description images { url } }}`, {q: JSON.stringify([{_id}])} )) // Запрос на регистрацию const registration = (login, password) => actionPromise ('registration', gql(`mutation registration ($login:String, $password: String) { UserUpsert (user: {login: $login, password: $password}) { _id createdAt } }`, {"login" : login, "password": password} )) // Запрос на логин const loginUser = (login, password) => actionPromise( 'login', gql( `query log($login: String, $password: String) { login(login: $login, password: $password) }`, {login, password} ) ) // Запрос истории заказов const historyOfOrders = () => actionPromise('historyOfOrders', gql(`query historyOfOrders ($q: String) { OrderFind(query: $q) { _id total createdAt orderGoods { good { name } price count total } total } }`, {q: JSON.stringify([{}])} )) store.dispatch(actionRootCats()) // Запрос оформления заказа const NewOrder = (orderGoods) => actionPromise('NewOrder', gql(`mutation NewOrder($order: OrderInput) { OrderUpsert(order: $order) { _id orderGoods { _id price count total good { name _id price images { url } } } } }`, {order: {orderGoods}} )) //-----------------------------------Отрисовка категорий------------------------------------- store.subscribe(() => { const {status, payload, error} = store.getState().promise.rootCats if (status === 'FULFILLED'){ aside.innerHTML = '' for (const {_id, name} of payload){ aside.innerHTML += `${name}` } } }) //--------------------------------------отрисовка товаров в категории----------------------- store.subscribe(() => { const {status, payload, error} = store.getState().promise?.oneCatWithGoods || {} const [,route] = location.hash.split('/') if(route !== 'category') { return } if (status === 'FULFILLED'){ main.innerHTML = '' const {name, goods, subCategories} = payload main.innerHTML = `
${description}
${price} грн.
` const buyButton = document.getElementById('buy') cartIcon.innerHTML = '' buyButton.onclick = function () { store.dispatch(actionCartAdd({_id: name, price: price, img: images})) } } } ) //----------------------------------Отрисовка цифры в корзине------------------------------- store.subscribe(() => { const {cart} = store.getState() let summ = 0 for(const {count} of Object.values(cart)) { summ += +count } cartIcon.innerHTML = `${summ}` }) //-----------------------------------------Логин---------------------------------------- const loginButton = document.getElementById('login') loginForm.append(loginButton) loginButton.onclick = () => location.href = `#/login` const actionFullLogin = (login, password) => async (dispatch) => { const token = await dispatch(loginUser(login, password)) if(typeof token === "string"){ dispatch(actionAuthLogin(token)) main.innerHTML = `Вы ввели неправильные логин или пароль. Повторите попытку
` const loginRepeat = document.getElementById('buttonRepeat') loginRepeat.onclick = () => { location.reload() location.href = `#/login` } } } //-----------------------------------------Авторизация------------------------------------- store.subscribe(() => { if(!store.getState().auth) return; const {payload} = store.getState().auth; if(payload){ loginForm.innerHTML = ` ` loginButton.hidden = true registration.hidden = true const historyButton = document.getElementById('history') historyButton.onclick = function () { location.href = `#/history` } const logOutButton = document.getElementById('logOut') logOutButton.onclick = function () { store.dispatch(actionAuthLogout()) main.innerHTML = ` ` loginForm.innerHTML = ` ` loginButton.hidden = false registration.hidden = false } } }) //------------------------------------Регистрация-------------------------------------------- const registrationButton = document.getElementById('registration') loginForm.append(registrationButton) registrationButton.onclick = () => location.href = `#/register` const actionFullRegister = (login, password) => async (dispatch) => { let userReg = await dispatch(registration(login, password)) if(userReg){ dispatch(actionFullLogin(login,password)) } else { main.innerHTML = `Регистрация не удалась. Повторите попытку ещё раз. ` const buttonRepeatReg = document.getElementById('buttonRepeatReg') buttonRepeatReg.onclick = () => { location.reload() location.href = `#/register` } } } //-------------------------------------------Заказ------------------------------------- const newOrder = () => async (dispatch, getState) => { let { cart } = getState(); const orderGoods = Object.entries(cart).map(([_id, { count }]) => ({ good: { _id }, count })); let result = await dispatch(NewOrder(orderGoods)) if (result?._id) { dispatch(actionCartClear()) } } //--------------------------------------Корзина------------------------------------------ store.subscribe ( () => { let cartIcon = document.getElementById('cartIcon') cartIcon.onclick = function myCart() { location.href = `#/cartIcon` console.log(store.getState().cart) let storeCart = store.getState().cart main.innerHTML = `${store.getState().cart[name].good._id}
` for (const img of store.getState().cart[name].good.img) { order.innerHTML += `` } order.innerHTML += `${store.getState().cart[name].count} шт
Итого: ${store.getState().cart[name].count * store.getState().cart[name].good.price}
` let input = document.createElement('input') input.type = 'number' input.value = store.getState().cart[name].count order.append(input) let divForBtn = document.createElement('div') order.append(divForBtn) let button = document.createElement('button') button.id = 'delCartBtn' button.innerText = 'Удалить товар' divForBtn.append(button) input.oninput = function () { if (input.value <= 0){ store.dispatch(actionCartDel({_id: name})) myCart() } console.log(input.value, name) store.dispatch(actionCartSet({_id: name, price: store.getState().cart[name].good.price, img: store.getState().cart[name].good.img}, input.value)) myCart() } button.onclick = function () { store.dispatch(actionCartDel({_id: name})) myCart() } } let btnCreateOrder = document.createElement('button') btnCreateOrder.id = 'createOrder' btnCreateOrder.innerText = 'Оформить заказ' main.append(btnCreateOrder) const idCreateOrderBtn = document.getElementById('createOrder') if (Object.keys(store.getState().auth).length === 0) { idCreateOrderBtn.disabled = true } if (Object.keys(store.getState().auth).length !== 0) { idCreateOrderBtn.disabled = false idCreateOrderBtn.onclick = function () { store.dispatch(newOrder()) store.dispatch(actionCartClear()) myCart() } } } }) //--------------------------------------------История заказов-------------------------------------- store.subscribe ( () => { const {status, payload, error} = store.getState().promise?.historyOfOrders || { } const [,route] = location.hash.split('/') if(route !== 'history') { return } if (status === 'FULFILLED'){ main.innerHTML = `Номер заказа: ${_id}
Всего: ${total} денег