Illia Kozyr 1 рік тому
батько
коміт
8160c4ea1c

BIN
Market GraphQL + Redux/Loading_icon.gif


+ 44 - 0
Market GraphQL + Redux/index.html

@@ -0,0 +1,44 @@
+<!DOCTYPE html>
+<html>
+    <head>
+        <title>GQL</title>
+        <meta charset="utf8" />
+        <link rel="stylesheet" href="style.css" />
+    </head>
+    <body>
+        <header id="header">
+            <div id="cartIcon">
+                <a href="#/category/6262ca7dbf8b206433f5b3d1">
+                    <img
+                        class="cartIcon"
+                        src="https://upload.wikimedia.org/wikipedia/commons/thumb/a/a9/Amazon_logo.svg/1200px-Amazon_logo.svg.png"
+                        alt=""
+                    />
+                </a>
+            </div>
+            <div class="qwer" id="qwer">
+                <div>
+                    <button id="shoppingCart">Cart</button>
+                </div>
+                <div>
+                    <button id="reg">Registration</button>
+                </div>
+                <div>
+                    <button id="login">login</button>
+                </div>
+            </div>
+        </header>
+        <main id="mainContainer">
+            <aside id="aside">Категории</aside>
+            <div class="mainBlock">
+                <div id="categoryName"></div>
+
+                <div id="productBlock"></div>
+            </div>
+        </main>
+        <footer class="footer">
+            <h2 class="footerText">illiaKozyr &#8482;</h2>
+        </footer>
+        <script src="script.js"></script>
+    </body>
+</html>

+ 12 - 0
Market GraphQL + Redux/index2.html

@@ -0,0 +1,12 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+    <meta charset="UTF-8">
+    <meta http-equiv="X-UA-Compatible" content="IE=edge">
+    <meta name="viewport" content="width=device-width, initial-scale=1.0">
+    <title>Document</title>
+</head>
+<body>
+    <script src="test.js"></script>
+</body>
+</html>

+ 724 - 0
Market GraphQL + Redux/script.js

@@ -0,0 +1,724 @@
+const backendURL = "http://shop-roles.node.ed.asmer.org.ua/graphql";
+const backendURLNotGraphQL = "http://shop-roles.node.ed.asmer.org.ua";
+
+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;
+}
+
+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");
+};
+
+function promiseReducer(state = {}, { type, name, 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));
+    }
+};
+
+function cartReducer(state = {}, { type, count = 1, good }) {
+    // type CART_ADD CART_REMOVE CART_CLEAR CART_DEC
+    // {
+    //  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_CLEAR") {
+        return {};
+    }
+    if (type === "CART_REMOVE") {
+        //let newState = {...state}
+        let { [good._id]: poh, ...newState } = state; //o4en strashnoe koldunstvo
+        //delete newState[good._id]
+        return newState;
+    }
+
+    return state;
+}
+
+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" });
+
+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;
+}
+
+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-а}}
+}
+
+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(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 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 getGQL = url =>
+// (query, variables) => fetch(url, {
+//     method: 'POST',
+//     headers: {
+//         "Content-Type": "application/json",
+//         // 'Accept' : '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 actionRootCats = () =>
+    actionPromise(
+        "rootCats",
+        gql(
+            backendURL,
+            `query {
+CategoryFind(query: "[{\\"parent\\":null}]"){
+_id name
+}
+}`
+        )
+    );
+
+const actionCatById = (
+    _id //добавить подкатегории
+) =>
+    actionPromise(
+        "catById",
+        gql(
+            backendURL,
+            `query catById($q: String){
+CategoryFindOne(query: $q){
+_id name goods {
+    _id name price images {
+        url
+    }
+}
+}
+}`,
+            { q: JSON.stringify([{ _id }]) }
+        )
+    );
+
+const actionLogin = (login, password) =>
+    actionPromise(
+        "actionLogin",
+        gql(
+            backendURL,
+            `query log($login:String, $password:String){
+                                  login(login:$login, password:$password)
+                                }`,
+            { login, password }
+        )
+    );
+
+const actionGoodById = (_id) =>
+    actionPromise(
+        "GoodFineOne",
+        gql(
+            backendURL,
+            `query goodByid($goodId: String) {
+        GoodFindOne(query: $goodId) {
+            _id
+          name
+          price
+          description
+          images {
+            url
+          }
+        }
+      }`,
+            { goodId: JSON.stringify([{ _id }]) }
+        )
+    );
+
+store.dispatch(actionRootCats());
+// store.dispatch(actionLogin("illiaKozyr", "qwerty123456"));
+
+const actionFullLogin = (login, password) => async (dispatch) => {
+    let result = await dispatch(actionLogin(login, password));
+    if (result.data.login) {
+        dispatch(actionAuthLogin(result.data.login));
+    }
+};
+
+const actionFullRegister = (login, password) => async (dispatch) => {
+    let user = await dispatch(
+        actionPromise(
+            "register",
+            gql(
+                backendURL,
+                `mutation register($login: String, $password: String) {
+                UserUpsert(user: {login: $login, password: $password}) {
+                   _id
+                   login
+                 }
+               }`,
+                { login: login, password: password }
+            )
+        )
+    );
+    if (user) {
+        dispatch(actionFullLogin(login, password));
+    }
+};
+
+const actionOrders = () =>
+    actionPromise(
+        "orders",
+        gql(
+            backendURL,
+            `query findOrder($q: String) {
+    OrderFind(query: $q) {
+      _id
+      total
+      createdAt
+      orderGoods {
+        count
+        good {
+          name
+          price
+        }
+      }
+    }
+  }`,
+            { q: JSON.stringify([{}]) }
+        )
+    );
+
+store.subscribe(() => {
+    const rootCats =
+        store.getState().promise.rootCats?.payload?.data.CategoryFind;
+    if (rootCats) {
+        aside.innerHTML = "";
+        for (let { _id, name } of rootCats) {
+            const a = document.createElement("a");
+            a.href = `#/category/${_id}`;
+            a.innerHTML = name;
+            aside.append(a);
+        }
+    }
+});
+
+store.subscribe(() => {
+    const catById =
+        store.getState().promise.catById?.payload?.data.CategoryFindOne;
+    const [, route] = location.hash.split("/");
+
+    if (catById && route === "category") {
+        const { name, goods, _id } = catById;
+        categoryName.innerHTML = `<h1>${name}</h1>`;
+
+        var element = document.getElementById("productBlock");
+        while (element.firstChild) {
+            element.removeChild(element.firstChild);
+        }
+
+        for (let { _id, name, price, images } of goods) {
+            const description = document.createElement("div");
+            const textBlock = document.createElement("div");
+            const imgProduct = document.createElement("img");
+            const a = document.createElement("p");
+            const productPrice = document.createElement("p");
+            const b = document.getElementById(productBlock);
+            const linkCard = document.createElement("a");
+
+            productBlock.append(linkCard);
+            linkCard.href = `#/good/${_id}`;
+
+            linkCard.append(description);
+            description.setAttribute("class", "card");
+
+            description.append(imgProduct);
+
+            imgProduct.src = `http://shop-roles.node.ed.asmer.org.ua/${images[0].url}`;
+
+            description.append(textBlock);
+
+            // a.href = `#/good/${_id}`;
+            a.innerHTML = name;
+            textBlock.append(a);
+
+            productPrice.innerHTML = "price: " + price;
+            textBlock.append(productPrice);
+
+            const addToCartButton = document.createElement("p");
+            addToCartButton.innerText = "click to buy";
+            addToCartButton.className = "addToCartButton";
+            textBlock.append(addToCartButton);
+
+            // var elem = document.getElementById("productBlock");
+            // elem.parentNode.removeChild(elem);
+        }
+    }
+});
+
+const bPoputDeleteBlock = document.createElement("div");
+const bPoput = document.createElement("div");
+bPoput.className = "b-popup";
+bPoput.id = "b-popup";
+const bPoputContainer = document.createElement("div");
+bPoputContainer.className = "b-popup-content";
+bPoputContainer.id = "b-popup-content";
+const buttonGoodDeleteBlock = document.createElement('div')
+buttonGoodDeleteBlock.id = "buttonGoodDeleteBlock"
+
+const buttonCloseCart = document.createElement("button");
+buttonCloseCart.innerText = `×`;
+buttonCloseCart.id = "buttonCloseCartId";
+const buttonGoodDelete = document.createElement("button");
+buttonGoodDelete.innerText = "delete";
+buttonGoodDelete.id = "buttonDelete";
+
+shoppingCart.onclick = () => {
+    header.append(bPoput);
+    bPoput.append(bPoputContainer);
+};
+
+bPoputContainer.append(buttonGoodDeleteBlock);
+buttonGoodDeleteBlock.append(buttonGoodDelete)
+bPoputContainer.append(buttonCloseCart);
+
+
+const divToCardBlock = document.createElement("div");
+
+store.subscribe(() => {
+    toCartById = store.getState().cart;
+    for (let value of Object.values(toCartById)) {
+        
+        const { count, good } = value;
+        console.log(count, "its cartbyid")
+
+        divToCardBlock.id = "divToCartBlock";
+        const divToCart = document.createElement("div");
+        const goodByIdImage = document.createElement("img");
+        const goodByIdName = document.createElement("h2");
+        const goodByIdCount = document.createElement("h2");
+        const buttonPlus = document.createElement("button");
+        const buttonMinus = document.createElement("button");
+        buttonPlus.innerHTML = "+";
+        buttonMinus.innerHTML = "-";
+        buttonPlus.id = "buttonPlus";
+        buttonMinus.id = "buttonMinus";
+
+        divToCart.id = "divToCart";
+        bPoputContainer.append(divToCardBlock);
+        divToCardBlock.append(divToCart);
+        divToCart.append(goodByIdImage);
+        divToCart.append(goodByIdName);
+        divToCart.append(goodByIdCount);
+        divToCart.append(buttonPlus);
+        divToCart.append(buttonMinus);
+
+        goodByIdImage.src = `${backendURLNotGraphQL}/${value.good.images[0].url}`;
+        goodByIdName.innerText = good.name;
+        goodByIdCount.innerText = count;
+    }
+
+    buttonCloseCart.onclick = () => {
+        var parent = document.getElementById("header");
+        var child = document.getElementById("b-popup");
+        parent.removeChild(child);
+    };
+
+    const payload = store.getState().auth.token;
+    if (payload) {
+        shoppingCart.style.display = "block";
+    } else {
+        shoppingCart.style.display = "none";
+    }
+
+    // bPoputContainer.append(buttonGoodDelete);
+});
+
+buttonGoodDelete.onclick = () => {
+    store.dispatch(actionCartClear());
+    let a = document.getElementById('divToCartBlock')
+    a.innerHTML = "";
+    let b = document.getElementById('shoppingCart')
+    b.innerHTML = "Cart"
+};
+
+const buyButtom = document.createElement("button");
+const productImg = document.createElement("img");
+const productName = document.createElement("h1");
+const productPrice = document.createElement("h2");
+const textBlock = document.createElement("div");
+const flexBlock = document.createElement("div");
+const productDescription = document.createElement("p");
+
+let number = 0;
+store.subscribe(() => {
+    const goodById =
+        store.getState().promise.GoodFineOne?.payload?.data.GoodFindOne;
+    const [, route, _id] = location.hash.split("/");
+
+    if (goodById && route === "good") {
+        var element = document.getElementById("productBlock");
+        while (element.firstChild) {
+            element.removeChild(element.firstChild);
+        }
+        const { name, price, description, images } = goodById;
+       
+
+        flexBlock.id = "flexBlock";
+
+        productBlock.append(flexBlock);
+
+        flexBlock.append(productImg);
+        productImg.style.width = "500px";
+        productImg.style.height = "500px";
+        productImg.src = `http://shop-roles.node.ed.asmer.org.ua/${images[0].url}`;
+
+        textBlock.id = "textBlock";
+        flexBlock.append(textBlock);
+
+        productName.innerHTML = name;
+        textBlock.append(productName);
+
+        productPrice.innerHTML = "price: " + price;
+        textBlock.append(productPrice);
+
+        productDescription.innerHTML = description;
+        textBlock.append(productDescription);
+
+        buyButtom.id = "buyButtom";
+        buyButtom.innerHTML = "Buy";
+        textBlock.append(buyButtom);
+        buyButtom.onclick = () => {
+            store.dispatch(actionCartAdd(goodById));
+            let a = document.getElementById('shoppingCart')
+            number += 1
+            a.innerHTML = "Cart: " + number;
+        };
+    }
+});
+
+store.subscribe(() => {
+    const catById =
+        store.getState().promise.catById?.payload?.data.CategoryFindOne;
+    const [, route, _id] = location.hash.split("/");
+
+    if (catById && route === "good") {
+        const { name, price, description, images } = catById;
+        categoryName.innerHTML = `<h1>${name}</h1>`;
+
+        // var element = document.getElementById("productBlock");
+        // while (element.firstChild) {
+        //     element.removeChild(element.firstChild);
+        // }
+    }
+});
+
+const h2text = document.createElement("h2");
+h2text.id = "h2text";
+qwer.append(h2text);
+const logoutButton = document.createElement("button");
+logoutButton.id = "logoutButton";
+qwer.append(logoutButton);
+
+store.subscribe(() => {
+    const payload = store.getState().auth.token;
+    if (payload) {
+        buyButtom.style.display = "block";
+        logoutButton.style.display = "block";
+        logoutButton.innerHTML = "Logout";
+        login.style.display = "none";
+        reg.style.display = "none";
+
+        h2text.style.display = "block";
+
+        h2text.innerText = jwtDecode(payload).sub.login;
+    } else {
+        buyButtom.style.display = "none";
+        h2text.style.display = "none";
+        logoutButton.style.display = "none";
+    }
+});
+
+const buttonLogin = document.createElement("button");
+buttonLogin.id = "loginInputt";
+buttonLogin.innerText = "Login";
+
+const buttonReg = document.createElement("button");
+buttonReg.id = "regInput";
+buttonReg.innerText = "Registration";
+
+function bPopupCreate(text) {
+    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);
+
+    const loginText = document.createElement("h2");
+    const passwordText = document.createElement("h2");
+    loginText.innerText = "Enter Login:";
+    bPopupContent.append(loginText);
+    const loginInput = document.createElement("input");
+    loginInput.type = "text";
+    bPopupContent.append(loginInput);
+    loginInput.id = "loginInput";
+    loginInput.value = "illiaKozyr";
+    passwordText.innerText = "Enter Password:";
+    bPopupContent.append(passwordText);
+    const loginInputPassword = document.createElement("input");
+    loginInputPassword.type = "password";
+    bPopupContent.append(loginInputPassword);
+    loginInputPassword.id = "passwordInput";
+    loginInputPassword.value = "qwerty123456";
+    bPopupContent.append(text);
+
+    buttonCloseCart.onclick = () => {
+        var parent = document.getElementById("header");
+        var child = document.getElementById("b-popup");
+        parent.removeChild(child);
+    };
+}
+
+window.onhashchange = () => {
+    const [, route, _id] = location.hash.split("/");
+
+    const routes = {
+        category() {
+            store.dispatch(actionCatById(_id));
+        },
+        good() {
+            store.dispatch(actionGoodById(_id));
+        },
+        dashboard() {
+            store.dispatch(actionOrders());
+            console.log("заказостраница");
+        },
+    };
+
+    if (route in routes) {
+        routes[route]();
+    }
+};
+
+login.onclick = () => {
+    bPopupCreate(buttonLogin);
+    buttonLogin.onclick = () => {
+        store.dispatch(actionFullLogin(loginInput.value, passwordInput.value));
+        logoutButton.style.display = "block";
+
+        var parent = document.getElementById("header");
+        var child = document.getElementById("b-popup");
+        parent.removeChild(child);
+    };
+};
+
+reg.onclick = () => {
+    bPopupCreate(buttonReg);
+    buttonReg.onclick = () => {
+        store.dispatch(
+            actionFullRegister(loginInput.value, passwordInput.value)
+        );
+        var parent = document.getElementById("header");
+        var child = document.getElementById("b-popup");
+        parent.removeChild(child);
+    };
+};
+
+// store.subscribe(() => {
+//     dashboardUl.innerHTML = ''
+//     const {orders} = store.getState().promise;
+//     const [,route, _id] = location.hash.split('/');
+//     if(orders?.payload && route === 'dashboard'){
+//         for(let {createdAt, total, orderGoods} of orders.payload){
+//             let date = new Date(+createdAt);
+//             let li = document.createElement("li");
+//             for(let {count, good} of orderGoods){
+//                 let div = document.createElement("div");
+//                 div.innerHTML = `<strong>${good.name}</strong>
+//                                 <span>${count} &#10006; ${good.price}</span>
+//                                 `
+//                 li.append(div);
+//             }
+//             li.innerHTML += `<div>${total}</div>
+//             <div>${date.getDate()}.${date.getMonth() + 1}.${date.getFullYear()}</div>
+//             <hr>`
+//             dashboardUl.append(li)
+//         }
+//     }
+
+// })
+
+logoutButton.onclick = () => {
+    store.dispatch(actionAuthLogout());
+    login.style.display = "block";
+    reg.style.display = "block";
+};

+ 259 - 0
Market GraphQL + Redux/style.css

@@ -0,0 +1,259 @@
+body {
+    margin: 0;
+    background-color: aqua;
+}
+header {
+    display: flex;
+    justify-content: space-between;
+    background-color: yellow;
+    height: 100px;
+    max-width: 1800px;
+    
+}
+.qwer{
+    display: flex;
+    align-items: center;
+}
+button {
+    height: 50px;
+    width: 150px;
+    margin-right: 30px;
+    background-color:aqua;
+    border-radius: 15px;
+    color:darkblue;
+    font-size: 20px;
+}
+
+button:hover {
+    transition: 300ms;
+   background-color: aliceblue;
+}
+
+input{
+    height: 20px;
+    width: 100px;
+    margin-right: 30px;
+    border-radius: 15px;
+    padding-left: 10px;
+}
+.mainBlock {
+    background-color: aquamarine;
+    width: 1500px;
+    min-height: 2000px;
+    padding-left: 30px;
+}
+#mainContainer {
+    display: flex;
+}
+#aside {
+    padding: 20px 20px 20px 20px;
+    width: 30%;
+    width: 300px;
+}
+#aside > a {
+    display: block;
+}
+a,
+p {
+    font-size: 30px;
+    text-decoration: none;
+}
+a {
+    color: black;
+}
+a:hover {
+    opacity: 0.5;
+}
+
+#main {
+    font-size: 30px;
+}
+
+.cartIcon {
+    padding: 10px 0 0 20px;
+    height: 80px;
+}
+
+#productPicture {
+    width: 200px;
+    
+}
+
+#productBlock img {
+    width: 300px;
+    height: 300px;
+}
+
+#productBlock a:hover {
+   opacity: 0.8;
+}
+
+.card{
+    display: flex;
+    margin-bottom: 30px;
+}
+
+.card div{
+    padding: 40px;
+    border-bottom: 2px solid black;
+    border-right: 2px solid black;
+    border-top: 2px solid black;
+    min-width: 1030px;
+}
+
+#flexBlock {
+    display: flex;
+    padding-top: 30px;
+}
+
+#textBlock {
+    padding-left: 30px;
+}
+
+flexBlock img {
+    height: 400px;
+    width: 400px;
+}
+
+#h2text {
+    padding-right: 30px;
+}
+
+.b-container{
+    width:200px;
+    height:150px;
+    background-color: #ccc;
+    margin:0px auto;
+    padding:10px;
+    font-size:30px;
+    color: #fff;
+}
+.b-popup{
+    width:100%;
+    min-height:100%;
+    background-color: rgba(0,0,0,0.5);
+    overflow:hidden;
+    position:fixed;
+    top:0px;
+}
+.b-popup .b-popup-content{
+    margin:40px auto 0px auto;
+    width: 1000px;
+    min-height: 500px;
+    padding:10px;
+    background-color: yellow;
+    border-radius:5px;
+    box-shadow: 0px 0px 10px #000;
+}
+
+.b-popup-content {
+    padding: 300px;
+    display: flex;
+    
+    flex-direction: column
+}
+
+#divToCart {
+    align-items: center;
+    padding: 30px;
+    display: flex;
+    width: 700px;
+}
+
+#divToCart img{
+    max-width: 100px;
+    max-height: 100px;
+}
+
+#divToCart img,
+#divToCart h2{
+    margin-right: 30px;
+}
+#divToCart h2 {
+    align-items: center;
+}
+
+
+.b-popup-content button {
+    width: 30px;
+    height: 30px;
+    margin: 0;
+}
+
+#buttonCloseCartId {
+    position: absolute;
+    top: 70px;
+    right: 420px;
+}
+
+#buttonDelete{
+    width: 100px;
+    position: absolute;
+    top: 70px;
+    right: 500px;
+}
+
+.b-poput-container-flex {
+    justify-content: center;
+}
+
+.b-poput-container-flex input {
+    margin-bottom: 30px;
+    width: 300px;
+    height: 40px;
+    font-size: 20px;
+    margin: 20px auto;
+    text-align: center;
+}
+
+.b-poput-container-flex h2 {
+    margin: 10px auto;
+}
+
+.b-poput-container-flex #buttonCloseCartId {
+    bottom: 910px;
+}
+
+#regInput,
+#loginInputt {
+    width: 150px;
+    height: 50px;
+    margin: 10px auto;
+}
+
+.addToCartButton {
+    font-size: 14px;
+}
+
+#buttonPlus,
+#buttonMinus {
+    margin-right: 10px;
+}
+
+
+#pCount {
+    border-radius: 50%;
+    background-color: red;
+    width: 10px;
+    height: 10px;
+    font-size: 14px;
+}
+
+
+
+
+
+
+
+
+
+
+
+
+.footer{
+    padding-top: 20px;
+    color:white;
+    min-height: 80px;
+    background-color: tomato;
+    text-align: center;
+}

+ 71 - 0
Market GraphQL + Redux/test.js

@@ -0,0 +1,71 @@
+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 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
+}
+
+const store = createStore(localStoreReducer(countReducer, 'count'))
+
+store.subscribe(
+    () => console.log(store.getState())
+)
+store.dispatch({type: "COUNT_INC"})
+store.dispatch({type: "COUNT_INC"})
+store.dispatch({type: "COUNT_DEC"})
+