123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667 |
- 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 += `<a href= "#/category/${_id}">${name}</a>`
- }
- }
- })
- //--------------------------------------отрисовка товаров в категории-----------------------
- 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 = `<h1>${name}</h1>`
- if (subCategories !== null) {
- for (const {_id, name} of subCategories) {
- main.innerHTML += `<a href= "#/category/${_id}">${name}</a>`
- console.log(name)
- }
- }
- for (const {_id, name, price, images} of goods){
- for (const img of images) {
- main.innerHTML += `<img src= "${url+ img.url}"> </br>`
- }
- main.innerHTML += `<a href= "#/good/${_id}">${name} </br> ${price} грн</a>`
- }
- }}
- )
- //-------------------------------------Отрисовка товара------------------------------------------
- store.subscribe(() => {
- const {status, payload, error} = store.getState().promise?.goodWithDescAndImg || { }
- const [,route] = location.hash.split('/')
- if(route !== 'good') {
- return
- }
- if (status === 'FULFILLED'){
- main.innerHTML = ''
- const {name, description, images, price} = payload
- main.innerHTML = `<h1>${name}</h1>`
- for (const img of images) {
- main.innerHTML += `<img src= "${url+ img.url}">`
- }
- main.innerHTML += `<p>${description}</p>
- <p>${price} грн. </p>
- <button id="buy"> В корзину </button>`
- 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 = `<b>${summ}</b>`
- })
- //-----------------------------------------Логин----------------------------------------
- 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 = `<h1>Вы вошли на сайт</h1>`
- } else {
- main.innerHTML =
- `<p>Вы ввели неправильные логин или пароль. Повторите попытку </p>
- <button id="buttonRepeat">Повторить попытку</button>`
- 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 =
- `<button id="history"> История заказов </button>
- <button id="logOut"> Выйти с сайта </button>`
- 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 = `Регистрация не удалась. Повторите попытку ещё раз.
- <button id="buttonRepeatReg">Повторить попытку</button>`
- 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 = `<h1>Корзина</h1>`
- for (let i=0; i<(Object.keys(storeCart).length); i++){
- let div = document.createElement('div')
- div.id = i
- main.append(div)
- let order = document.getElementById(i)
- let name = Object.keys(storeCart)[i]
- order.innerHTML += `<p>${store.getState().cart[name].good._id}</p>`
- for (const img of store.getState().cart[name].good.img) {
- order.innerHTML += `<p><img src= "${url+ img.url}"></p>`
- }
- order.innerHTML +=
- `<p>${store.getState().cart[name].count} шт</p>
- <p>Итого: ${store.getState().cart[name].count * store.getState().cart[name].good.price} </p>`
- 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 = `<h1> История заказов </h1>`
- const {_id, total} = payload
- console.log(payload)
- for(const order of payload){
- const {_id, total} = order
- main.innerHTML +=
- `<div style="width: 300px; border: solid skyblue;">
- <p>Номер заказа: ${_id}</p>
- <p>Всего: ${total} денег </p>
- </div>
- `
- }
- }
- })
- window.onhashchange = () => {
- const [,route, _id] = location.hash.split('/')
- const routes = {
- category() {
- store.dispatch(oneCatWithGoods(_id))
- },
- good(){
- store.dispatch(goodWithDescAndImg(_id))
- },
- login(){
- main.innerHTML =
- `<h2 id="inputTitle">Вход на сайт:</h2>
- <input id="loginInput" type="text" name="login" placeholder="Введите логин">
- <input id="passwordInput" type="password" name="password" placeholder="Введите пароль">
- <button id="sign_in">Войти</button>`
- const sign_inBtn = document.getElementById('sign_in')
- sign_inBtn.onclick = function () {
- store.dispatch(actionFullLogin(loginInput.value, passwordInput.value))
- }
- },
- register(){
- main.innerHTML =
- `<h2>Регистрация:</h2>
- <input id="loginReg" type="text" name="login" placeholder="Введите логин">
- <input id="passwordReg" type="password" name="password" placeholder="Введите пароль">
- <button id="reg">Зарегистрироваться</button>`
- const regBtn = document.getElementById('reg')
- regBtn.onclick = function () {
- store.dispatch(actionFullRegister(loginReg.value, passwordReg.value))
- }
- },
- cart(){},
- history(){
- store.dispatch(historyOfOrders())
- }
- }
- if (route in routes){ //если route есть в routes
- routes[route]() //то запустить функцию, которая там лежит
- }
- }
- window.onhashchange()
|