123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476 |
- cartIcon.addEventListener("click", () => {
- document.querySelector("#cartList").classList.remove("hide");
- });
- const backendURL = "http://shop-roles.asmer.fs.a-level.com.ua";
- //store
- 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") {
- 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 в объект
- };
- }
- 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");
- function promiseReducer(state = {}, { type, name, status, payload, error }) {
- if (type === "PROMISE") {
- return {
- ...state, //.......скопировать старый state
- [name]: { status, payload, error }, //....... перекрыть в нем один name на новый объект со status, payload и error
- };
- }
- return state;
- }
- 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));
- }
- };
- //actions
- 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 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
- price
- description
- categories{_id name}
- images{url}
- }
- } `,
- { q: JSON.stringify([{ _id }]) }
- )
- );
- const actionLogin = (login, password) =>
- actionPromise(
- "login",
- gql(
- `query login($login: String, $password: String){
- login(login: $login, password: $password)
- }`,
- { login: login, password: password }
- )
- );
- const actionRegister = (login, password) =>
- actionPromise(
- "register",
- gql(
- `mutation register($login:String, $password:String) {
- UserUpsert(user: {login:$login, password: $password}) {
- _id login
- }
- }`,
- { login: login, password: password }
- )
- );
- const actionAddToCard = (good, amount = 1) => ({
- type: "ADD_TO_CARD",
- good,
- amount,
- });
- const actionChangeAmount = (good, amount = 1) => ({
- type: "CHANGE_AMOUNT",
- good,
- amount,
- });
- const actionRemoveFromCard = (good, amount = 1) => ({
- type: "REMOVE_FROM_CARD",
- good,
- amount,
- });
- const actionRemoveCard = () => ({ type: "REMOVE_CARD" });
- const actionFullLogin = (login, password) => async (dispatch) => {
- let token = await dispatch(actionLogin(login, password));
- if (token) {
- dispatch(actionAuthLogin(token));
- }
- };
- const actionFullRegister = (login, password) => async (dispatch) => {
- try {
- await dispatch(actionRegister(login, password));
- } catch (error) {
- return console.log(error);
- }
- await dispatch(actionFullLogin(login, password));
- };
- const actionAuthLogin = (token) => ({ type: "AUTH_LOGIN", token });
- const actionAuthLogout = () => ({ type: "AUTH_LOGOUT" });
- const jwtDecode = (token) => {
- try {
- const payload = JSON.parse(atob(token.split(".")[1]));
- return payload;
- } catch (e) {}
- };
- //reducers
- function authReducer(state, { type, token }) {
- if (state === undefined && localStorage.authToken) {
- token = localStorage.authToken;
- type = "AUTH_LOGIN";
- }
- if (type === "AUTH_LOGIN") {
- // раскодируем токен
- let decode = jwtDecode(token); // (пишем отдельно функцию jwtDecode, и да будет в ней try-catch)
- if (decode) {
- // серединка, atob, JSON.parse
- localStorage.authToken = token; //если получилось пишем его в localStorage
- return { token, payload: decode }; // return{token, payload}
- }
- }
- if (type === "AUTH_LOGOUT") {
- localStorage.removeItem("authToken"); //чистим localStorage.authToken
- return {}; //возвращаем {}
- }
- return state || {};
- }
- function cartReducer(state = {}, { type, good = {}, amount = 1 }) {
- const id = good._id;
- const types = {
- ADD_TO_CARD() {
- return {
- ...state,
- [id]: { good, count: +amount + +(state[id] ? +state[id].count : 0) },
- };
- },
- CHANGE_AMOUNT() {
- return {
- ...state,
- [id]: { good, count: +amount },
- };
- },
- REMOVE_FROM_CARD() {
- let newState = { ...state };
- delete newState[id];
- return {
- ...newState,
- };
- },
- REMOVE_CARD() {
- return {};
- },
- };
- if (type in types) return types[type]();
- return state;
- }
- function combineReducers(reducers) {
- return (state = {}, action) => {
- const newState = {};
- for (const [nameOfReducer, currentReducer] of Object.entries(reducers)) {
- let newCurrentState = currentReducer(state[nameOfReducer], action);
- if (newCurrentState !== state[nameOfReducer]) {
- newState[nameOfReducer] = newCurrentState;
- }
- }
- return Object.keys(newState).length !== 0
- ? { ...state, ...newState }
- : state;
- };
- }
- const combinedReducer = combineReducers({
- goods: promiseReducer,
- auth: authReducer,
- cart: cartReducer,
- });
- const store = createStore(combinedReducer);
- store.dispatch(actionAuthLogout());
- store.dispatch(actionRootCats());
- //subscribes
- store.subscribe(() => console.log(store.getState()));
- store.subscribe(() => {
- const { rootCats } = store.getState().goods;
- 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);
- }
- }
- });
- store.subscribe(() => {
- const { catById } = store.getState().goods;
- 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}">Перейти</a> `; // ТУТ ДОЛЖНА БЫТЬ ССЫЛКА НА СТРАНИЦУ ТОВАРА ВИДА #/good/АЙДИ
- main.append(card);
- }
- }
- });
- store.subscribe(() => {
- //ТУТ ДОЛЖНА БЫТЬ ПРОВЕРКА НА НАЛИЧИЕ goodById в редакс
- const { goodById } = store.getState().goods;
- const [, route, _id] = location.hash.split("/");
- if (goodById?.payload && route === "good") {
- const { name, images, price, description } = goodById.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>
- <button class='buy buy${_id}'>Купить</button>
- <p>${description}</p>
- `;
- main.append(card);
- document.querySelector(`.buy${_id}`).addEventListener("click", () => {
- store.dispatch(actionAddToCard(goodById.payload));
- });
- }
- });
- store.subscribe(() => {
- const cartState = store.getState().cart;
- cartList.innerHTML = `<div id='closeListBtn'>X</div>`;
- closeListBtn.addEventListener("click", () => {
- document.querySelector("#cartList").classList.add("hide");
- });
- let sum = 0;
- for (const id in cartState) {
- const { count, good } = cartState[id];
- const cartListItem = document.createElement("div");
- cartListItem.classList.add("cartListItem");
- cartListItem.innerHTML += `<p>- ${good.name}: </p>`;
- const inputCount = document.createElement("input");
- inputCount.type = "number";
- inputCount.value = count;
- inputCount.addEventListener("change", (e) => {
- store.dispatch(actionChangeAmount(good, e.target.value));
- });
- cartListItem.appendChild(inputCount);
- const goodPrice = document.createElement("p");
- goodPrice.innerText = `цена: ${good.price}`;
- cartListItem.appendChild(goodPrice);
- const goodCost = document.createElement("strong");
- goodCost.innerText = `всего ${+good.price * +count} грн.`;
- sum += +good.price * +count;
- cartListItem.appendChild(goodCost);
- const removeBtn = document.createElement("button");
- removeBtn.innerText = "remove";
- removeBtn.addEventListener("click", () => {
- store.dispatch(actionRemoveFromCard(good));
- });
- cartListItem.appendChild(removeBtn);
- cartList.appendChild(cartListItem);
- }
- if (cartList.querySelectorAll(".cartListItem").length) {
- const btnToOrder = document.createElement("button");
- btnToOrder.innerText = "Заказать";
- btnToOrder.classList = "btnToOrder";
- cartList.appendChild(btnToOrder);
- btnToOrder.addEventListener("click", () => {
- // store.dispatch(actionToOrder());
- console.log("Ваш заказ в процессе обработки");
- });
- const priceOfOrder = document.createElement("div");
- priceOfOrder.classList = "priceOfOrder";
- priceOfOrder.innerText = `Итого : ${sum}`;
- cartList.appendChild(priceOfOrder);
- const btnDeleteAll = document.createElement("button");
- btnDeleteAll.classList = "btnDeleteAll";
- btnDeleteAll.innerText = "Очистить всё";
- btnDeleteAll.addEventListener("click", () => {
- store.dispatch(actionRemoveCard());
- });
- cartList.appendChild(btnDeleteAll);
- }
- });
- window.onhashchange = () => {
- const [, route, _id] = location.hash.split("/");
- const routes = {
- category() {
- store.dispatch(actionCatById(_id));
- },
- good() {
- //задиспатчить actionGoodById
- store.dispatch(actionGoodById(_id));
- },
- login() {
- loginBlock.classList.add("hide");
- const innerContent = `
- <div class="loginWrapper">
- <input class="login">
- <input type="password" class="password">
- <button class="logInButton">LogIn</button>
- </div>
- `;
- header.innerHTML = innerContent + header.innerHTML;
- const inpLogIn = document.querySelector(".login");
- const inpPassword = document.querySelector(".password");
- const loginWrapper = document.querySelector(".loginWrapper");
- document
- .querySelector(".logInButton")
- .addEventListener("click", async () => {
- if (inpLogIn.value.length && inpPassword.value.length) {
- await store.dispatch(
- actionFullLogin(inpLogIn.value, inpPassword.value)
- );
- const login = store.getState().auth.payload.sub.login;
- userData.innerHTML = `<p>Привет, ${login}</p>`;
- header.removeChild(loginWrapper);
- const btnLogout = document.createElement("button");
- btnLogout.textContent = "Exit";
- btnLogout.addEventListener("click", () => {
- userData.innerHTML = "";
- store.dispatch(actionAuthLogout());
- loginBlock.style.display = "block";
- oders.classList.add("hide");
- });
- orders.classList.remove("hide");
- userData.append(btnLogout);
- }
- });
- },
- register() {
- loginBlock.classList.add("hide");
- const innerContent = `
- <div class="loginWrapper">
- <input class="loginRegister">
- <input type="password" class="passwordRegister">
- <button class="registrationButton">Registration</button>
- </div>
- `;
- header.innerHTML = innerContent + header.innerHTML;
- const loginRegister = document.querySelector(".loginRegister");
- const passwordRegister = document.querySelector(".passwordRegister");
- document
- .querySelector(".registrationButton")
- .addEventListener("click", () => {
- if (loginRegister.value.length && passwordRegister.value.length) {
- store.dispatch(
- actionFullRegister(loginRegister.value, passwordRegister.value)
- );
- loginBlock.classList.remove("hide");
- }
- });
- },
- };
- if (route in routes) routes[route]();
- };
- window.onhashchange();
|