123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727 |
- 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() //и запускаем подписчиков
- }
- }
- return {
- getState, //добавление функции getState в результирующий объект
- dispatch,
- subscribe //добавление subscribe в объект
- }
- }
- function jwtDecode(token) {
- try {
- return JSON.parse(atob(token.split('.')[1]))
- }
- catch (e) {
- }
- }
- function authReducer(state = {}, { type, token }) {
- //{
- // token, payload
- //}
- if (type === 'AUTH_LOGIN') {
- //пытаемся токен раскодировать
- const payload = jwtDecode(token)
- if (payload) { //и если получилось
- return {
- token, payload //payload - раскодированный токен;
- }
- }
- }
- if (type === 'AUTH_LOGOUT') {
- return {}
- }
- return state;
- }
- function countReducer(state = { count: 0 }, { type }) {
- if (type === "COUNT_INC") {
- return {
- count: state.count + 1
- }
- }
- if (type === "COUNT_DEC") {
- return {
- count: state.count - 1
- }
- }
- return state
- }
- function localStoreReducer(reducer, localStorageKey) {
- function localStoredReducer(state, action) {
- // Если state === undefined, то достать старый state из local storage
- if (state === undefined) {
- try {
- return JSON.parse(localStorage[localStorageKey])
- } catch (e) { }
- }
- const newState = reducer(state, action)
- // Сохранить newState в local storage
- localStorage[localStorageKey] = JSON.stringify(newState)
- return newState
- }
- return localStoredReducer
- }
- function promiseReducer(state = {}, { type, name, status, payload, error }) {
- ////?????
- //ОДИН ПРОМИС:
- //состояние: PENDING/FULFILLED/REJECTED
- //результат
- //ошибка:
- //{status, payload, error}
- //{
- // name1:{status, payload, error}
- // name2:{status, payload, error}
- // name3:{status, payload, error}
- //}
- 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
- })
- const actionPromise = (name, promise) =>
- async dispatch => {
- try {
- dispatch(actionPending(name))
- let payload = await promise
- dispatch(actionFulfilled(name, payload))
- return payload
- }
- catch (e) {
- dispatch(actionRejected(name, e))
- }
- }
- const delay = ms => new Promise(ok => setTimeout(() => ok(ms), ms))
- function combineReducers(reducers) { //пачку редьюсеров как объект {auth: authReducer, promise: promiseReducer}
- function combinedReducer(combinedState = {}, action) { //combinedState - типа {auth: {...}, promise: {....}}
- const newCombinedState = {}
- for (const [reducerName, reducer] of Object.entries(reducers)) {
- const newSubState = reducer(combinedState[reducerName], action)
- if (newSubState !== combinedState[reducerName]) {
- newCombinedState[reducerName] = newSubState
- }
- }
- if (Object.keys(newCombinedState).length === 0) {
- return combinedState
- }
- return { ...combinedState, ...newCombinedState }
- }
- return combinedReducer //нам возвращают один редьюсер, который имеет стейт вида {auth: {...стейт authReducer-а}, promise: {...стейт promiseReducer-а}}
- }
- function cartReducer(state = {}, { type, count = 1, good }) {
- // type CART_ADD CART_REMOVE CART_CLEAR CART_DEL
- // {
- // id1: {count: 1, good: {name, price, images, id}}
- // }
- if (type === "CART_ADD") {
- return {
- ...state,
- [good._id]: { count: count + (state[good._id]?.count || 0), good },
- };
- }
- if (type === "CART_DELETE") {
- if (state[good._id].count > 1) {
- return {
- ...state,
- [good._id]: {
- count: -count + (state[good._id]?.count || 0),
- good,
- },
- };
- }
- if (state[good._id].count === 1) {
- let { [good._id]: id1, ...newState } = state; //o4en strashnoe koldunstvo
- //delete newState[good._id]
- return newState;
- }
- }
- if (type === "CART_CLEAR") {
- return {};
- }
- if (type === "CART_REMOVE") {
- // let newState = {...state}
- let { [good._id]: id1, ...newState } = state; //o4en strashnoe koldunstvo
- //delete newState[good._id]
- return newState;
- }
- return state;
- }
- const backendURL = 'http://shop-roles.node.ed.asmer.org.ua/'
- //store.dispatch(actionPromise('delay1000', delay(1000)))
- //store.dispatch(actionPromise('delay3000', delay(3000)))
- //store.dispatch(actionPending('delay1000'))
- //delay(1000).then(result => store.dispatch(actionFulfilled('delay1000', result)),
- //error => store.dispatch(actionRejected('delay1000', error)))
- //store.dispatch(actionPending('delay3000'))
- //delay(3000).then(result => store.dispatch(actionFulfilled('delay3000', result)),
- //error => store.dispatch(actionRejected('delay3000', 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.data) {
- return Object.values(data.data)[0];
- } else throw new Error(JSON.stringify(data.errors));
- });
- const gql = getGQL(backendURL + "graphql");
- // const gql = (url, query, variables) => fetch(url, {
- // method: 'POST',
- // headers: {
- // "Content-Type": "application/json",
- // Accept: "application/json",
- // },
- // body: JSON.stringify({ query, variables })
- // }).then(res => res.json())
- // const backendURL = 'http://shop-roles.node.ed.asmer.org.ua/graphql'
- const actionRootCats = () =>
- actionPromise(
- 'rootCats',
- gql(
- `query {
- CategoryFind(query: "[{\\"parent\\":null}]"){
- _id name
- }
- }`
- )
- )
- 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 actionGoodById = (_id) =>
- actionPromise(
- 'goodByID',
- gql(
- `query goodByID($q:String){
- GoodFindOne(query: $q){
- _id
- name
- description
- price
- categories{
- _id
- name
- }
- images{
- url
- }
- }
- }`,
- { q: JSON.stringify([{ _id }]) }
- )
- )
- const actionRegistr = (login, password) =>
- actionPromise(
- 'registr',
- gql(
- `mutation register($login:String, $password:String){
- UserUpsert(user: {login:$login, password:$password}){
- _id login
- }
- }`,
- { login: login, password: password }
- )
- )
- const actionLogin = (login, password) =>
- actionPromise(
- 'login',
- gql(
- `query log($login:String, $password:String){
- login(login:$login, password:$password)
- }`,
- { login: login, password: password }
- )
- )
- const actionOrder = () => async (dispatch, getState) => {
- let { cart } = getState();
- const orderGoods = Object.entries(cart).map(([_id, { count }]) => ({
- good: { _id },
- count,
- }));
-
- let result = await dispatch(
- actionPromise(
- "order",
- gql(
- `
- mutation newOrder($order:OrderInput){
- OrderUpsert(order:$order)
- { _id total }
- }
- `,
- { order: { orderGoods } }
- )
- )
- );
- if (result?._id) {
- dispatch(actionCartClear());
- document.location.hash = "#/cart/";
- alert("Покупка успішна")
- }
-
- };
- const orderHistory = () =>
- actionPromise(
- "history",
- gql(` query OrderFind{
- OrderFind(query:"[{}]"){
- _id total createdAt orderGoods{
- count good{
- _id name price images{
- url
- }
- }
- owner{
- _id login
- }
- }
- }
- }
- `)
- );
- const actionAuthLogin = (token) =>
- (dispatch, getState) => {
- const oldState = getState()
- dispatch({ type: 'AUTH_LOGIN', token })
- const newState = getState()
- if (oldState !== newState)
- localStorage.authToken = token
- }
- const actionAuthLogout = () =>
- dispatch => {
- dispatch({ type: 'AUTH_LOGOUT' })
- localStorage.removeItem('authToken')
- }
- const actionCartAdd = (good, count = 1) => ({
- type: "CART_ADD",
- good,
- count
- });
- const actionCartChange = (good, count = 1) => ({
- type: "CART_CHANGE",
- good,
- count,
- }); ///oninput меняяем полностью
- const actionCartDelete = (good) => ({
- type: "CART_DELETE",
- good
- });
- const actionCartClear = () => ({
- type: "CART_CLEAR"
- });
- const actionFullLogin = (login, password) => async (dispatch) => {
- let token = await dispatch(actionLogin(login, password))
- if (token) {
- dispatch(actionAuthLogin(token))
- }
- }
- const actionFullRegistr = (login, password) => async (dispatch) => {
- try {
- await dispatch(actionRegistr(login, password))
- }
- catch (e) {
- return console.log(e)
- }
- await dispatch(actionFullLogin(login, password))
- }
- const store = createStore(combineReducers({ auth: authReducer, promise: promiseReducer, cart: localStoreReducer(cartReducer, "cart") })) //не забудьте combineReducers если он у вас уже есть
- if (localStorage.authToken) {
- store.dispatch(actionAuthLogin(localStorage.authToken))
- }
- //const store = createStore(combineReducers({promise: promiseReducer, auth: authReducer, cart: cartReducer}))
- store.subscribe(() => console.log(store.getState()))
- store.dispatch(actionRootCats())
- // store.dispatch(actionLogin('test456', '123123'))
- store.subscribe(() => {
- const { rootCats } = store.getState().promise
- if (rootCats?.payload) {
- aside.innerHTML = ''
- for (let { _id, name } of rootCats?.payload) {
- const a = document.createElement('a')
- a.href = `#/category/${_id}`
- a.innerHTML = name
- aside.append(a)
- }
- }
- })
- store.subscribe(() => {
- const { catById } = store.getState().promise
- const [, route, _id] = location.hash.split('/')
- if (catById?.payload && route === 'category') {
- const { name, goods, _id } = catById?.payload
- main.innerHTML = `<h1>${name}</h1>`
- for (let { _id, name, price, images } of goods) {
- const cards = document.createElement("div")
- cards.innerHTML = `<h2>${name}</h2>
- <img src="${backendURL}/${images[0].url}"/>
- <strong>Ціна ${price} грн</strong>
- <a href="#/good/${_id}">Перейти</a> `;
- main.append(cards);
- // const a = document.createElement('a')
- // const p = document.createElement('p')
- // p.href = `#/good/${_id}`
- // a.href = `#/good/${_id}`
- // a.innerHTML = name
- // p.innerHTML = price
- // main.append(a)
- // main.append(p)
- }
- }
- })
- store.subscribe(() => {
- const { goodByID } = store.getState().promise
- const [, route, _id] = location.hash.split('/')
- if (goodByID?.payload && route === 'good') {
- main.innerHTML = ""
- const { name, images, price, description } = goodByID?.payload
- // main.innerHTML = `<h1>${name}</h1> Продукт`
- const cards = document.createElement("div")
- cards.innerHTML = `<h2>${name}</h2>
- <img src="${backendURL}/${images[0].url}"/>
- <strong> Ціна ${price} грн</strong>
- <button id='buy'> Придбати</button>
- <p>${description}</p>`;
- main.append(cards)
- cards.style.marginTop = "10px"
- var btn = document.getElementById('buy')
- btn.onclick = () => {
- store.dispatch(actionCartAdd(goodByID.payload))
- }
- }
- })
- const bPopupContent = document.createElement("div");
- const obertkaDlyaTovara = document.createElement("div")
- const all = document.createElement('h2')
- const checkout = document.createElement("button")
- const clearToCart = document.createElement("button")
- store.subscribe(() => {
- obertkaDlyaTovara.innerHTML = ""
- const cartById = store.getState().cart
- let productCount = 0;
- let productPrice = 0
- for (let gPC of Object.values(cartById)) {
- const { good,count } = gPC
- productCount += count
- productPrice += good.price * count
- const tovar = document.createElement("div")
- tovar.id = "tovar"
- tovar.style.border = "3px solid blue"
- tovar.style.marginTop = "10px"
- const name = document.createElement('h1')
- const price = document.createElement('h3')
- const countById = document.createElement('p')
- const divDlyaKnopok = document.createElement("div")
- const plus = document.createElement("button")
- const minus = document.createElement("button")
- plus.innerText = "+"
- minus.innerText = "-"
- tovar.append(name)
- tovar.append(price)
- tovar.append(countById)
- divDlyaKnopok.append(plus)
- divDlyaKnopok.append(minus)
- tovar.append(divDlyaKnopok)
- name.innerHTML = good.name
- price.innerHTML = good.price
- countById.innerHTML = count
- obertkaDlyaTovara.append(tovar)
- bPopupContent.append(obertkaDlyaTovara)
-
- plus.onclick = () => {
- store.dispatch(actionCartAdd(good))
- }
- minus.onclick = () => {
- store.dispatch(actionCartDelete(good))
- }
-
- }
- clearToCart.id = "clearToCart"
- clearToCart.innerHTML = "Очистити кошик"
- clearToCart.style.margin = "0 auto"
- clearToCart.style.marginBottom = "20px"
- clearToCart.style.background = "blue"
- clearToCart.style.color = "yellow"
- bPopupContent.append(clearToCart)
- checkout.id = "checkout"
- checkout.innerHTML = "Оформити замовлення"
- checkout.style.margin = "0 auto"
- checkout.style.background = "blue"
- checkout.style.color = "yellow"
- bPopupContent.append(checkout)
- all.id = "all"
- all.innerHTML = "Всього: " + productPrice
- bPopupContent.append(all)
- all.style.marginLeft = "90%"
- clearToCart.onclick = () => {
- all.innerHTML = " "
- store.dispatch(actionCartClear())
-
- }
- checkout.onclick = () => {
- all.innerHTML = " "
- store.dispatch(actionOrder());
- store.dispatch(orderHistory());
-
- }
- })
- store.subscribe(() => {
- batton.onclick = () => {
- store.dispatch(actionFullLogin(login.value, password.value))
- }
- battonchik.onclick = () => {
- store.dispatch(actionFullRegistr(login.value, password.value))
- }
- battonSMakom.onclick = () => {
- store.dispatch(actionAuthLogout())
- }
- const payload = store.getState().auth.token;
- if (payload) {
- korzina.style.display = "block"
- login.style.display = "none"
- password.style.display = "none"
- batton.style.display = "none"
- battonchik.style.display = "none"
- battonSMakom.style.display = "block"
- accaunt.style.display = "block"
- accaunt.innerText = jwtDecode(payload).sub.login;
- purchaseHistory.style.display = "block"
- } else {
- korzina.style.display = "none"
- battonSMakom.style.display = "none"
- login.style.display = "block"
- password.style.display = "block"
- batton.style.display = "block"
- battonchik.style.display = "block"
- accaunt.style.display = "none"
- purchaseHistory.style.display = "none"
- }
- })
- store.dispatch(orderHistory());
- const h2 = document.createElement("h2")
- store.subscribe(() => {
- const { history } = store.getState().promise;
- const [, route] = location.hash.split("/");
- purchaseHistory.onclick = () => {
- const bPopup = document.createElement("div");
- const bPopupContent = document.createElement("div");
- bPopup.id = "b-popup";
- bPopup.className = "b-popup";
- bPopupContent.className = "b-popup-content b-poput-container-flex";
- header.append(bPopup);
- bPopup.append(bPopupContent);
- const buttonCloseCart = document.createElement("button");
- buttonCloseCart.innerText = "×";
- buttonCloseCart.id = "buttonCloseCartId";
- bPopupContent.append(buttonCloseCart);
- buttonCloseCart.onclick = () => {
- var parent = document.getElementById("header");
- var child = document.getElementById("b-popup");
- parent.removeChild(child);
- };
-
- for (let [key, value] of Object.entries(history.payload)) {
- const { _id, createdAt, total, orderGoods } = value;
- const h2 = document.createElement("h2");
- h2.className = "h2History"
- const dateOfOrder = new Date(+createdAt);
- h2.innerHTML = `${dateOfOrder.toLocaleDateString()} ${dateOfOrder.toLocaleTimeString()}
- Order ID: ${_id} от , c ${orderGoods.length} goods worth: ${total}`;
- bPopupContent.append(h2);
-
-
- }
- if (Object.keys(history.payload).length == 0) {
- const p = document.createElement("p");
- p.innerHTML = "<p>Ще немає покупок</p>";
- card.append(p);
- }
-
- };
-
- });
- const buttonCloseCart = document.createElement("button");
- buttonCloseCart.innerText = `×`;
- buttonCloseCart.id = "buttonCloseCartId";
- bPopupContent.append(buttonCloseCart);
- buttonCloseCart.onclick = () => {
- var parent = document.getElementById("header");
- var child = document.getElementById("b-popup");
- parent.removeChild(child);
- };
- function bPopupCreate() {
- const bPopup = document.createElement("div");
- bPopup.id = "b-popup";
- bPopup.className = "b-popup";
- bPopupContent.className = "b-popup-content b-poput-container-flex";
- header.append(bPopup);
- bPopup.append(bPopupContent);
- }
- korzina.onclick = () => {
- bPopupCreate()
- }
- window.onhashchange = () => {
- const [, route, _id] = location.hash.split('/')
- console.log(route, _id)
- const routes = {
- category() {
- store.dispatch(actionCatById(_id))
- },
- good() {
- store.dispatch(actionGoodById(_id))
- },
- dashboard() {
- store.dispatch(orderHistory());
- },
- }
- if (route in routes) {
- routes[route]()
- }
- }
- window.onhashchange()
|