|
@@ -0,0 +1,473 @@
|
|
|
|
+let aside = document.querySelector(".aside");
|
|
|
|
+let content = document.querySelector(".main__main-content");
|
|
|
|
+let registration = document.querySelector(".header__registration");
|
|
|
|
+let signIn = document.querySelector(".header__sign-in");
|
|
|
|
+let cart = document.querySelector(".imaginary-shopping-cart--content");
|
|
|
|
+let cartButton = document.querySelector(".header__cart");
|
|
|
|
+let dasboard = document.querySelector(".header__dashboard");
|
|
|
|
+
|
|
|
|
+registration.addEventListener("click", function() {
|
|
|
|
+ location.href = "#/register";
|
|
|
|
+});
|
|
|
|
+
|
|
|
|
+signIn.addEventListener("click", function() {
|
|
|
|
+ location.href = "#/login";
|
|
|
|
+});
|
|
|
|
+
|
|
|
|
+cartButton.addEventListener("click", function() {
|
|
|
|
+ location.href = "#/cart";
|
|
|
|
+});
|
|
|
|
+
|
|
|
|
+dasboard.addEventListener("click", function() {
|
|
|
|
+ location.href = "#/dasboard";
|
|
|
|
+});
|
|
|
|
+
|
|
|
|
+let createStore = function(reducer) {
|
|
|
|
+ let state = reducer(undefined, {});
|
|
|
|
+ let cbs = [];
|
|
|
|
+
|
|
|
|
+ let getState = () => state;
|
|
|
|
+ let subscribe = function(cb) {
|
|
|
|
+ cbs.push(cb);
|
|
|
|
+ return () => cbs = cbs.filter(c => c !== cb);
|
|
|
|
+ };
|
|
|
|
+ let dispatch = function(action) {
|
|
|
|
+ if(typeof(action) == "function") {
|
|
|
|
+ return action(dispatch, getState);
|
|
|
|
+ };
|
|
|
|
+
|
|
|
|
+ let newState = reducer(state, action);
|
|
|
|
+
|
|
|
|
+ if (newState !== state){
|
|
|
|
+ state = newState;
|
|
|
|
+
|
|
|
|
+ for (let cb of cbs) {
|
|
|
|
+ cb();
|
|
|
|
+ };
|
|
|
|
+ };
|
|
|
|
+ };
|
|
|
|
+
|
|
|
|
+ return {
|
|
|
|
+ getState,
|
|
|
|
+ subscribe,
|
|
|
|
+ dispatch
|
|
|
|
+ };
|
|
|
|
+};
|
|
|
|
+
|
|
|
|
+const promiseReducer = function(state={}, {type, name, status, payload, error}) {
|
|
|
|
+ if (type == 'PROMISE'){
|
|
|
|
+ return {
|
|
|
|
+ ...state,
|
|
|
|
+ [name]:{status, payload, error}
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ return state;
|
|
|
|
+};
|
|
|
|
+
|
|
|
|
+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 = function(name, promise) {
|
|
|
|
+ return async dispatch => {
|
|
|
|
+ dispatch(actionPending(name));
|
|
|
|
+ try {
|
|
|
|
+ let payload = await promise
|
|
|
|
+ dispatch(actionFulfilled(name, payload))
|
|
|
|
+ return payload
|
|
|
|
+ }
|
|
|
|
+ catch(error){
|
|
|
|
+ dispatch(actionRejected(name, error))
|
|
|
|
+ };
|
|
|
|
+ };
|
|
|
|
+};
|
|
|
|
+
|
|
|
|
+let jwtDecode = function(token) {
|
|
|
|
+ let payloadInBase64;
|
|
|
|
+ let payloadInJson;
|
|
|
|
+ let payload;
|
|
|
|
+
|
|
|
|
+ try {
|
|
|
|
+ payloadInBase64 = token.split(".")[1];
|
|
|
|
+ payloadInJson = atob(payloadInBase64);
|
|
|
|
+ payload = JSON.parse(payloadInJson);
|
|
|
|
+
|
|
|
|
+ return payload;
|
|
|
|
+ }
|
|
|
|
+ catch(err) {
|
|
|
|
+
|
|
|
|
+ }
|
|
|
|
+};
|
|
|
|
+
|
|
|
|
+const authReducer = function(state = {}, {type, token}) {
|
|
|
|
+ let payload;
|
|
|
|
+
|
|
|
|
+ if (state == undefined) {
|
|
|
|
+ if(localStorage.authToken) {
|
|
|
|
+ type = "AUTH_LOGIN";
|
|
|
|
+ token = localStorage.authToken;
|
|
|
|
+ } else {
|
|
|
|
+ type = "AUTH_LOGOUT";
|
|
|
|
+ };
|
|
|
|
+ };
|
|
|
|
+ if (type == "AUTH_LOGIN") {
|
|
|
|
+ payload = jwtDecode(token);
|
|
|
|
+
|
|
|
|
+ if(payload) {
|
|
|
|
+ localStorage.authToken = token;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ return {
|
|
|
|
+ token: token,
|
|
|
|
+ payload: payload
|
|
|
|
+ }
|
|
|
|
+ };
|
|
|
|
+ if (type == "AUTH_LOGOUT") {
|
|
|
|
+ localStorage.removeItem("authToken");
|
|
|
|
+
|
|
|
|
+ return {};
|
|
|
|
+ };
|
|
|
|
+
|
|
|
|
+ return state || {};
|
|
|
|
+};
|
|
|
|
+
|
|
|
|
+const actionAuthLogin = token => ({type: "AUTH_LOGIN", token});
|
|
|
|
+const actionAuthLogout = () => ({type: "AUTH_LOGOUT"});
|
|
|
|
+
|
|
|
|
+let cartReducer = function(state={}, {type, good, count=1}) {
|
|
|
|
+ if(type == "CART_ADD") {
|
|
|
|
+ let newState = {...state};
|
|
|
|
+
|
|
|
|
+ if(good["_id"] in state) {
|
|
|
|
+ newState[good._id].count = newState[good._id].count + count;
|
|
|
|
+ }
|
|
|
|
+ else {
|
|
|
|
+ newState = {
|
|
|
|
+ ...state,
|
|
|
|
+ [good._id]: {count, good}
|
|
|
|
+ };
|
|
|
|
+ };
|
|
|
|
+
|
|
|
|
+ return newState;
|
|
|
|
+ };
|
|
|
|
+ if(type == "CART_CHANGE") {
|
|
|
|
+ let newState = {...state,
|
|
|
|
+ [good._id]: {count, good}
|
|
|
|
+ };
|
|
|
|
+
|
|
|
|
+ return newState;
|
|
|
|
+ };
|
|
|
|
+ if(type == "CART_DELETE") {
|
|
|
|
+ let newState = {...state};
|
|
|
|
+
|
|
|
|
+ delete newState[good._id];
|
|
|
|
+
|
|
|
|
+ return newState;
|
|
|
|
+ };
|
|
|
|
+ if(type == "CART_CLEAR") {
|
|
|
|
+ return {};
|
|
|
|
+ };
|
|
|
|
+
|
|
|
|
+ return state;
|
|
|
|
+};
|
|
|
|
+
|
|
|
|
+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'})
|
|
|
|
+
|
|
|
|
+const combineReducers = function(reducers) {
|
|
|
|
+ return function (state={}, action) {
|
|
|
|
+ const newState = {};
|
|
|
|
+
|
|
|
|
+ for(let [reducerName, reducer] of Object.entries(reducers)) {
|
|
|
|
+ let checkState = reducer(state[reducerName], action);
|
|
|
|
+
|
|
|
|
+ if(state[reducerName] != checkState) {
|
|
|
|
+ newState[reducerName] = checkState;
|
|
|
|
+ };
|
|
|
|
+ };
|
|
|
|
+
|
|
|
|
+ if(Object.keys(newState).length == 0) {
|
|
|
|
+ return state;
|
|
|
|
+ };
|
|
|
|
+
|
|
|
|
+ return {...Object.assign(state, newState)};
|
|
|
|
+ };
|
|
|
|
+};
|
|
|
|
+
|
|
|
|
+const store = createStore(combineReducers({promise: promiseReducer, auth: authReducer, cart: cartReducer}));
|
|
|
|
+store.subscribe(() => console.log(store.getState()));
|
|
|
|
+
|
|
|
|
+const getGQL = function(url) {
|
|
|
|
+ return async function(query, variables) {
|
|
|
|
+ const res = await fetch(url, {
|
|
|
|
+ method: "POST",
|
|
|
|
+ headers: {
|
|
|
|
+ "Content-Type": "application/json",
|
|
|
|
+ ...(localStorage.authToken ? { "Authorization": "Bearer " + localStorage.authToken } : {})
|
|
|
|
+ },
|
|
|
|
+ body: JSON.stringify({ query, variables })
|
|
|
|
+ });
|
|
|
|
+ const data = await res.json();
|
|
|
|
+ if (data.data) {
|
|
|
|
+ return Object.values(data.data)[0];
|
|
|
|
+ }
|
|
|
|
+ else {
|
|
|
|
+ throw new Error(JSON.stringify(data.errors));
|
|
|
|
+ };
|
|
|
|
+ };
|
|
|
|
+};
|
|
|
|
+
|
|
|
|
+const backendURL = 'http://shop-roles.asmer.fs.a-level.com.ua';
|
|
|
|
+
|
|
|
|
+const gql = getGQL(backendURL + '/graphql');
|
|
|
|
+
|
|
|
|
+let actionRootCats = function() {
|
|
|
|
+ return actionPromise("rootCats", gql(`query {
|
|
|
|
+ CategoryFind(query: "[{\\"parent\\":null}]"){
|
|
|
|
+ _id name
|
|
|
|
+ }
|
|
|
|
+ }`));
|
|
|
|
+};
|
|
|
|
+
|
|
|
|
+store.dispatch(actionRootCats());
|
|
|
|
+
|
|
|
|
+let actionCatById = function(_id) {
|
|
|
|
+ return actionPromise("catById", gql(`query catById($q: String){
|
|
|
|
+ CategoryFindOne(query: $q){
|
|
|
|
+ _id name goods {
|
|
|
|
+ _id name price images {
|
|
|
|
+ url
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ }`,
|
|
|
|
+ {q: JSON.stringify([{_id}])}
|
|
|
|
+ ));
|
|
|
|
+};
|
|
|
|
+
|
|
|
|
+let actionGoodById = function(_id) {
|
|
|
|
+ return actionPromise("goodById", gql(`query findGood($goodQuery: String) {
|
|
|
|
+ GoodFindOne(query:$goodQuery) {
|
|
|
|
+ _id name price images {
|
|
|
|
+ url
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ }`,
|
|
|
|
+ {goodQuery: JSON.stringify([{"_id": _id}])}
|
|
|
|
+ ));
|
|
|
|
+};
|
|
|
|
+
|
|
|
|
+let actionFullLogin = async function(login, password) {
|
|
|
|
+ let token = await gql("query userLogin($login: String, $password: String) {login(login: $login, password: $password)}", {"login": login, "password": password});
|
|
|
|
+
|
|
|
|
+ store.dispatch(actionAuthLogin(token));
|
|
|
|
+};
|
|
|
|
+
|
|
|
|
+let actionFullRegister = function(login, password, nick) {
|
|
|
|
+ return actionPromise("userRegister", gql(`mutation userRegister($login:String, $password:String, $nick:String) {
|
|
|
|
+ UserUpsert(user: {login:$login, password:$password, nick:$nick}) {
|
|
|
|
+ _id login nick
|
|
|
|
+ }
|
|
|
|
+ }`,
|
|
|
|
+ {
|
|
|
|
+ "login": login,
|
|
|
|
+ "password": password,
|
|
|
|
+ "nick": nick
|
|
|
|
+ }))
|
|
|
|
+};
|
|
|
|
+
|
|
|
|
+let actionOrders = async function() {
|
|
|
|
+ let order = await gql(`mutation makeOrder($order:OrderInput){
|
|
|
|
+ OrderUpsert(order: $order){
|
|
|
|
+ _id
|
|
|
|
+ }
|
|
|
|
+ }`, {
|
|
|
|
+ "order": {
|
|
|
|
+ orderGoods: Object.entries(store.getState().cart).map(([_id, count]) =>({"count": count.count, "good": {_id}}))
|
|
|
|
+ }
|
|
|
|
+ });
|
|
|
|
+
|
|
|
|
+ store.dispatch(actionCartClear());
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+let createCart = function() {
|
|
|
|
+ const [,route, _id] = location.hash.split('/');
|
|
|
|
+
|
|
|
|
+ if(route == "cart") {
|
|
|
|
+ let str = "<h2>Корзина</h2><table id='table'>";
|
|
|
|
+
|
|
|
|
+ for(let goodId of Object.entries(store.getState().cart)) {
|
|
|
|
+ str += `<tr>
|
|
|
|
+ <td class="visually-hidden">${goodId[0]}</td>
|
|
|
|
+ <td>${goodId[1].good.name}</td>
|
|
|
|
+ <td><img src="${backendURL}/${goodId[1].good.images[0].url}"/></td>
|
|
|
|
+ <td>Цена: <span id="price">${goodId[1].good.price}</span></td>
|
|
|
|
+ <td>Кол-во: <input id="count" type="number" value="${goodId[1].count}"></td>
|
|
|
|
+ <td><button id="clear">Удалить товар</button></td>
|
|
|
|
+ </tr>`;
|
|
|
|
+ };
|
|
|
|
+ str += "</table><button id='order'>Сделать заказ</button>";
|
|
|
|
+
|
|
|
|
+ content.innerHTML = str;
|
|
|
|
+
|
|
|
|
+ table.addEventListener("click", function(evt) {
|
|
|
|
+ if(evt.target.tagName == "BUTTON") {
|
|
|
|
+ store.dispatch(actionCartDelete(store.getState().cart[evt.path[2].firstElementChild.textContent]?.good))
|
|
|
|
+ };
|
|
|
|
+ });
|
|
|
|
+
|
|
|
|
+ order.addEventListener("click", function() {
|
|
|
|
+ actionOrders();
|
|
|
|
+ });
|
|
|
|
+ }
|
|
|
|
+};
|
|
|
|
+
|
|
|
|
+let createDashbord = async function() {
|
|
|
|
+ const [,route, _id] = location.hash.split('/');
|
|
|
|
+
|
|
|
|
+ if(route == "dasboard") {
|
|
|
|
+ let orders = await gql(`query ordersFind($query:String) {
|
|
|
|
+ OrderFind(query: $query) {
|
|
|
|
+ createdAt orderGoods {
|
|
|
|
+ count good {
|
|
|
|
+ name price images {
|
|
|
|
+ url
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ }`,
|
|
|
|
+ {
|
|
|
|
+ "query": JSON.stringify([{}])
|
|
|
|
+ });
|
|
|
|
+ let str = "<h2>История заказов</h2><table>";
|
|
|
|
+
|
|
|
|
+ for(let order of orders) {
|
|
|
|
+ str += "<tr>";
|
|
|
|
+ str += `<td>${(new Date(+order.createdAt)).toLocaleString()}</td>`;
|
|
|
|
+ str += "<td>"
|
|
|
|
+ for(let orderGood of order.orderGoods) {
|
|
|
|
+ str += `<ul>
|
|
|
|
+ <li>${orderGood.good.name}</li>
|
|
|
|
+ <li><img src="${backendURL}/${orderGood.good.images[0].url}"/></li>
|
|
|
|
+ <li>Цена: ${orderGood.good.price}</li>
|
|
|
|
+ </ul>`;
|
|
|
|
+ }
|
|
|
|
+ str += "</td>"
|
|
|
|
+ str += "</tr>";
|
|
|
|
+ };
|
|
|
|
+
|
|
|
|
+ str += "</table>";
|
|
|
|
+
|
|
|
|
+ content.innerHTML = str;
|
|
|
|
+ }
|
|
|
|
+};
|
|
|
|
+
|
|
|
|
+window.onhashchange = () => {
|
|
|
|
+ const [, route, _id] = location.hash.split('/');
|
|
|
|
+
|
|
|
|
+ const routes = {
|
|
|
|
+ category(){
|
|
|
|
+ store.dispatch(actionCatById(_id));
|
|
|
|
+ },
|
|
|
|
+ good(){
|
|
|
|
+ store.dispatch(actionGoodById(_id));
|
|
|
|
+ },
|
|
|
|
+ login(){
|
|
|
|
+ content.innerHTML = `<h2>Вход на сайт</h2>
|
|
|
|
+ <input id="login" type="text" name="login" placeholder="Введите ваш логин">
|
|
|
|
+ <input id="password" type="password" name="password" placeholder="Введите ваш пароль">
|
|
|
|
+ <button id="sign_in">Войти</button>`;
|
|
|
|
+ sign_in.addEventListener("click", function() {
|
|
|
|
+ actionFullLogin(login.value, password.value);
|
|
|
|
+ // store.dispatch(actionFullLogin(login.value, password.value));
|
|
|
|
+ });
|
|
|
|
+ },
|
|
|
|
+ register(){
|
|
|
|
+ content.innerHTML = `<h2>Регистрация</h2>
|
|
|
|
+ <input id="login" type="text" name="reg-login" placeholder="Введите ваш будущий логин">
|
|
|
|
+ <input id="password" type="text" name="reg-password" placeholder="Введите ваш будущий пароль">
|
|
|
|
+ <input id="nick" type="text" name="reg-nick" placeholder="Введите ваш nick">
|
|
|
|
+ <button id="registr">Зарегистрироваться</button>`;
|
|
|
|
+ registr.addEventListener("click", function() {
|
|
|
|
+ store.dispatch(actionFullRegister(login.value, password.value, nick.value));
|
|
|
|
+ });
|
|
|
|
+ },
|
|
|
|
+ cart() {
|
|
|
|
+ createCart();
|
|
|
|
+ },
|
|
|
|
+ dasboard() {
|
|
|
|
+ createDashbord();
|
|
|
|
+ }
|
|
|
|
+ };
|
|
|
|
+
|
|
|
|
+ if (route in routes) {
|
|
|
|
+ routes[route]();
|
|
|
|
+ };
|
|
|
|
+};
|
|
|
|
+
|
|
|
|
+window.onhashchange();
|
|
|
|
+
|
|
|
|
+store.subscribe(() => {
|
|
|
|
+ const {rootCats} = store.getState().promise;
|
|
|
|
+ if (rootCats?.payload){
|
|
|
|
+ let ul = document.createElement("ul");
|
|
|
|
+
|
|
|
|
+ aside.innerHTML = '';
|
|
|
|
+
|
|
|
|
+ for (const {_id, name} of rootCats?.payload){
|
|
|
|
+ let li = document.createElement("li");
|
|
|
|
+ let link = document.createElement('a');
|
|
|
|
+ link.href = `#/category/${_id}`;
|
|
|
|
+ link.innerText = name;
|
|
|
|
+
|
|
|
|
+ li.append(link);
|
|
|
|
+ ul.append(li);
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ aside.append(ul);
|
|
|
|
+ };
|
|
|
|
+});
|
|
|
|
+
|
|
|
|
+store.subscribe(() => {
|
|
|
|
+ const {catById} = store.getState().promise;
|
|
|
|
+ const [,route, _id] = location.hash.split('/')
|
|
|
|
+ if (catById?.payload && route === 'category'){
|
|
|
|
+ const {name} = catById.payload
|
|
|
|
+ content.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}">ССЫЛКА НА СТРАНИЦУ ТОВАРА</a>`
|
|
|
|
+ content.append(card);
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+});
|
|
|
|
+
|
|
|
|
+store.subscribe(() => {
|
|
|
|
+ const {goodById} = store.getState().promise;
|
|
|
|
+ const [,route, _id] = location.hash.split('/');
|
|
|
|
+
|
|
|
|
+ if (goodById?.payload && route == "good") {
|
|
|
|
+ const {name} = goodById.payload;
|
|
|
|
+
|
|
|
|
+ content.innerHTML = `<h1>${name}</h1>`;
|
|
|
|
+
|
|
|
|
+ const card = document.createElement('div');
|
|
|
|
+
|
|
|
|
+ card.innerHTML = `<h2>${goodById.payload.name}</h2>
|
|
|
|
+ <img src="${backendURL}/${goodById.payload.images[0].url}" />
|
|
|
|
+ <strong>${goodById.payload.price}</strong>
|
|
|
|
+ <button id="goodAdd">Добавить в корзину</button>`;
|
|
|
|
+ content.append(card);
|
|
|
|
+ goodAdd.addEventListener("click", function() {
|
|
|
|
+ store.dispatch(actionCartAdd(goodById.payload));
|
|
|
|
+ });
|
|
|
|
+ };
|
|
|
|
+});
|
|
|
|
+
|
|
|
|
+store.subscribe(createCart);
|