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 = `
${name}
`;
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 = `${name}
`;
// 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 = `${good.name}
// ${count} ✖ ${good.price}
// `
// li.append(div);
// }
// li.innerHTML += `${total}
// ${date.getDate()}.${date.getMonth() + 1}.${date.getFullYear()}
//
`
// dashboardUl.append(li)
// }
// }
// })
logoutButton.onclick = () => {
store.dispatch(actionAuthLogout());
login.style.display = "block";
reg.style.display = "block";
};