Bladeren bron

+js/17(shop)/index.html; +js/17(shop)/index.js

ilya_shyian 3 jaren geleden
bovenliggende
commit
0ee570628a
2 gewijzigde bestanden met toevoegingen van 615 en 0 verwijderingen
  1. 41 0
      js/17(shop)/index.html
  2. 574 0
      js/17(shop)/index.js

+ 41 - 0
js/17(shop)/index.html

@@ -0,0 +1,41 @@
+<!DOCTYPE html>
+<html>
+    <head>
+        <title>GQL</title>
+        <meta charset="utf8" />
+        <style>
+            * {
+                margin: 0;
+                padding: 0;
+            }
+            #mainContainer {
+                display: flex;
+                padding: 15px 50px;
+            }
+            #aside {
+                width: 30%;
+            }
+            #aside > a {
+                display: block;
+            }
+            #main {
+                width: 50%;
+                margin: 0 auto;
+            }
+            #header {
+                padding: 15px 50px;
+            }
+            button {
+                padding: 5px 7px;
+            }
+        </style>
+    </head>
+    <body>
+        <header id="header"></header>
+        <div id="mainContainer">
+            <aside id="aside">Категории</aside>
+            <main id="main">Контент</main>
+        </div>
+        <script src="index.js"></script>
+    </body>
+</html>

+ 574 - 0
js/17(shop)/index.js

@@ -0,0 +1,574 @@
+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 = "<a href = '#/cart'>Cart</a>";
+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 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.innerText = 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);
+        }
+    }
+});
+
+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));
+            };
+        },
+        cart() {
+            const updateCart = () => {
+                let {
+                    cart,
+                    auth: { token },
+                } = store.getState();
+                if (!token) {
+                    return;
+                }
+
+                main.innerHTML = "";
+                let orderList = document.createElement("div");
+                let clearButton = document.createElement("button");
+                clearButton.innerHTML = "Clear cart";
+                clearButton.style.display = "block";
+                clearButton.style.marginLeft = "auto";
+
+                Object.keys(cart).length > 0 ? (clearButton.disabled = false) : (clearButton.disabled = true);
+
+                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 = `
+                    <div >
+                        <div><img style="height:100px;" src = "${backendURL}/${images ? images[0].url : ""}"></div>
+                        <div><h3>${name}</h3></div>
+                        <div>${order.count} * ${price} = ${+order.count * +price}</div>
+                    </div>
+                    `;
+
+                    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());
+                main.append(orderList);
+                main.append(clearButton);
+            };
+            updateCart();
+            store.subscribe(() => {
+                let { cart } = store.getState();
+                const [, route, _id] = location.hash.split("/");
+                if (cart && route === "cart") {
+                    updateCart();
+                }
+            });
+        },
+    };
+
+    if (route in routes) routes[route]();
+};
+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 = `<h1>${name}</h1> ТУТ ДОЛЖНЫ БЫТЬ ПОДКАТЕГОРИИ`;
+        for (const { _id, name, price, images } of catById.payload.goods) {
+            const card = document.createElement("div");
+            card.innerHTML = `<h2>${name}</h2>
+                              <img src="${backendURL}/${images[0].url}" />
+                              <strong>${price}</strong>
+                                <a href="#/good/${_id}">${name}</a>
+                                `;
+            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 = `<h1>${name}</h1>`;
+
+        const card = document.createElement("div");
+        card.innerHTML = `<h2>${name}</h2>
+                            <img src="${backendURL}/${images[0].url}" />
+                            <strong>${price}</strong>
+                            `;
+
+        if (token) {
+            let buyButton = document.createElement("button");
+
+            buyButton.innerHTML = "Купить";
+
+            buyButton.onclick = () => store.dispatch(actionCartAdd(good.payload));
+            card.append(buyButton);
+        }
+        main.append(card);
+    }
+});