123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779 |
- 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 authReducer(state, { type, token }) {
- if (!state) {
- if (localStorage.authToken) {
- type = "AUTH_LOGIN";
- token = localStorage.authToken;
- } else {
- return {};
- }
- }
- if (type === "AUTH_LOGIN") {
- localStorage.authToken = token;
- let payload = jwtDecode(token);
- if (typeof payload !== "object") {
- return {};
- }
- return { token, payload };
- }
- if (type === "AUTH_LOGOUT") {
- localStorage.authToken = "";
- return {};
- }
- return state;
- }
- const actionAuthLogin = (token) => ({
- type: "AUTH_LOGIN",
- token,
- });
- const actionAuthLogout = () => ({ type: "AUTH_LOGOUT" });
- function cartReducer(state = {}, { type, good = {}, count }) {
- const { _id } = good;
- const types = {
- CART_ADD() {
- count = +count;
- if (!count) {
- return state;
- }
- return {
- ...state,
- [_id]: { good, count: count + (state[_id]?.count || 0) },
- };
- },
- CART_CHANGE() {
- count = +count;
- if (!count) {
- return state;
- }
- return {
- ...state,
- [_id]: { good, count },
- };
- },
- CART_REMOVE() {
- let { [_id]: remove, ...goods } = state;
- return goods;
- },
- CART_CLEAR() {
- return {};
- },
- CART_SHOW() {
- state = JSON.parse(localStorage.cart);
- return state;
- },
- };
- if (type in types) {
- return types[type]();
- }
- return state;
- }
- function promiseReducer(state = {}, { type, status, payload, errors, name }) {
- if (!state) {
- return {};
- }
- if (type === "PROMISE") {
- return {
- ...state,
- [name]: { status, payload, errors },
- };
- }
- return state;
- }
- const actionPending = (name) => ({ type: "PROMISE", status: "PENDING", name });
- const actionResolved = (name, payload) => ({
- type: "PROMISE",
- status: "RESOLVED",
- name,
- payload,
- });
- const actionRejected = (name, errors) => ({
- type: "PROMISE",
- status: "REJECTED",
- name,
- errors,
- });
- const actionPromise = (name, promise) => async (dispatch) => {
- dispatch(actionPending(name));
- try {
- let data = await promise;
- dispatch(actionResolved(name, data));
- return data;
- } catch (error) {
- dispatch(actionRejected(name, error));
- }
- };
- function combineReducers(reducers) {
- return (state = {}, action) => {
- const newState = {};
- let newSubState;
- for (const [reducerName, reducer] of Object.entries(reducers)) {
- newSubState = reducer(state[reducerName], action);
- if (state[reducerName] !== newSubState) {
- newState[reducerName] = newSubState;
- }
- }
- if (Object.keys(newState).length !== 0) {
- return { ...state, ...newState };
- } else {
- return state;
- }
- };
- }
- const combinedReducer = combineReducers({
- promise: promiseReducer,
- auth: authReducer,
- cart: cartReducer,
- });
- const store = createStore(combinedReducer);
- const actionCartAdd = (good, count) => ({
- type: "CART_ADD",
- good,
- count,
- });
- const actionCartChange = (good, count) => ({
- type: "CART_CHANGE",
- good,
- count,
- });
- const actionCartRemove = (good) => ({ type: "CART_REMOVE", good });
- const actionCartClear = () => ({ type: "CART_CLEAR" });
- const actionCartShow = () => ({ type: "CART_SHOW" });
- const actionOrder = () => async (dispatch, getState) => {
- let { cart } = getState();
- const orderGoods = Object.entries(cart).map(([_id, { count }]) => ({
- good: { _id },
- count,
- }));
- let result = await dispatch(
- actionPromise(
- "order",
- gql(
- `
- mutation newOrder($order:OrderInput){
- OrderUpsert(order:$order)
- { _id total }
- }
- `,
- { order: { orderGoods } }
- )
- )
- );
- if (result?._id) {
- dispatch(actionCleanCart());
- }
- };
- const getGQL =
- (url) =>
- async (query, variables = {}) => {
- let obj = await fetch(url, {
- method: "POST",
- headers: {
- "Content-Type": "application/json",
- },
- body: JSON.stringify({ query, variables }),
- });
- let a = await obj.json();
- if (!a.data && a.errors) throw new Error(JSON.stringify(a.errors));
- return a.data[Object.keys(a.data)[0]];
- };
- const backURL = "http://shop-roles.asmer.fs.a-level.com.ua";
- const gql = getGQL(backURL + "/graphql");
- 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
- }
- },
- subCategories{
- name, subCategories{
- name
- }
- }
- }
- }`,
- { q: JSON.stringify([{ _id }]) }
- )
- );
- const actionGoodById = (_id) =>
- actionPromise(
- "goodById",
- gql(
- `query goodById($q: String){
- GoodFindOne(query: $q){
- _id name description price images{
- url
- }
- }
- }`,
- { q: JSON.stringify([{ _id }]) }
- )
- );
- const actionLogin = (login, password) =>
- actionPromise(
- "login",
- gql(
- `query log($login: String, $password: String) {
- login(login: $login, password: $password)
- }`,
- { login: login, password: password }
- )
- );
- const actionFullLogin = (login, password) => async (dispatch) => {
- console.log(login, password);
- let token = await dispatch(actionLogin(login, password));
- console.log(token);
- if (token) {
- dispatch(actionAuthLogin(token));
- }
- };
- const actionRegister = (login, password) =>
- actionPromise(
- "registration",
- gql(
- `mutation reg2($user:UserInput) {
- UserUpsert(user:$user) {
- _id login
- }
- }
- `,
- { user: { login: login, password: password } }
- )
- );
- const actionFullRegister = (login, password) => async (dispatch) => {
- console.log(login, password);
- let check = await dispatch(actionRegister(login, password));
- console.log(check);
- if (check) {
- dispatch(actionFullLogin(login, password));
- }
- };
- store.dispatch(actionRootCats());
- store.dispatch(actionGoodById());
- store.subscribe(() => {
- const { promise } = store.getState();
- if (promise?.rootCats?.payload) {
- asideList.innerHTML = "";
- let count = -1;
- for (const { _id, name } of promise.rootCats.payload) {
- count++;
- const link = document.createElement("a");
- link.href = `#/category/${_id}`;
- link.innerText = name;
- link.className = "nav-link";
- link.id = `rootCat${count}`;
- asideList.appendChild(link);
- }
- }
- });
- const openCart = () => {
- const [, route] = location.hash.split("/");
- if (route === "cart") {
- main.innerHTML = "";
- const { cart } = store.getState();
- for (let good in cart) {
- let {
- good: {
- _id: id,
- name: name,
- price: price,
- images: [{ url }],
- },
- count,
- } = cart[good];
- let cardMain = document.createElement("div");
- cardMain.id = "cardMain";
- let goodImgBlock = document.createElement("div");
- goodImgBlock.innerHTML = `
- <img src="${backURL}/${url}" width="400" height="200"/>
- `;
- cardMain.appendChild(goodImgBlock);
- let goodInfoBlock = document.createElement("div");
- let goodName = document.createElement("h2");
- goodName.innerText = `${name}`;
- let goodPrice = document.createElement("p");
- goodPrice.innerText = `${price * count}$`;
- let goodCount = document.createElement("p");
- goodCount.innerText = `${count} шт.`;
- goodInfoBlock.appendChild(goodName);
- goodInfoBlock.appendChild(goodPrice);
- goodInfoBlock.appendChild(goodCount);
- cardMain.appendChild(goodInfoBlock);
- let br = document.createElement("br");
- let goodEditBlock = document.createElement("div");
- goodEditBlock.className = "form-outline";
- let deleteBtn = document.createElement("button");
- deleteBtn.innerText = "Delete";
- deleteBtn.id = "deleteBtn";
- deleteBtn.type = "button";
- deleteBtn.className = "btn btn-danger";
- goodEditBlock.appendChild(deleteBtn);
- goodEditBlock.appendChild(br);
- deleteBtn.onclick = () => {
- store.dispatch(actionCartRemove(cart[good].good));
- cardMain.remove();
- console.log(store.getState());
- };
- let countLabel = document.createElement("label");
- countLabel.className = "form-label";
- countLabel.htmlFor = "countField";
- countLabel.innerText = "Change count";
- let countField = document.createElement("input");
- countField.type = "number";
- countField.value = cart[good].count;
- countField.min = "1";
- countField.id = "countField";
- countField.className = "form-control";
- goodEditBlock.appendChild(countLabel);
- goodEditBlock.appendChild(countField);
- countField.oninput = () => {
- goodPrice.innerText = `${price * +countField.value}$`;
- goodCount.innerText = `${countField.value} шт.`;
- store.dispatch(actionCartChange(cart[good].good, countField.value));
- };
- cardMain.appendChild(goodEditBlock);
- main.appendChild(cardMain);
- }
- let clearBtn = document.createElement("button");
- clearBtn.innerText = "Clear Cart";
- clearBtn.id = "clearBtn";
- clearBtn.type = "button";
- clearBtn.className = "btn btn-dark";
- let makeOrder = document.createElement("button");
- makeOrder.innerText = "Make Order";
- makeOrder.id = "makeOrder";
- makeOrder.type = "button";
- makeOrder.className = "btn btn-success";
- if (Object.keys(cart).length !== 0) {
- main.appendChild(makeOrder);
- main.appendChild(clearBtn);
- } else {
- main.innerHTML = "<h2>Your cart is empty!</h2>";
- }
- makeOrder.onclick = () => {
- store.dispatch(actionOrder());
- alert("Thanks for your order!");
- store.dispatch(actionCartClear());
- document.location.reload();
- };
- clearBtn.onclick = () => {
- store.dispatch(actionCartClear());
- document.location.reload();
- };
- }
- };
- window.onhashchange = () => {
- const [, route, _id] = location.hash.split("/");
- let signOut = document.createElement("button");
- signOut.innerText = "Sign Out";
- signOut.id = "signoutBtn";
- signOut.className = "btn btn-secondary";
- const routes = {
- category() {
- store.dispatch(actionCatById(_id));
- },
- good() {
- store.dispatch(actionGoodById(_id));
- },
- login() {
- let loginInput = document.getElementById("loginInput");
- let passwordInput = document.getElementById("pass1Input");
- if (
- loginInput &&
- passwordInput &&
- loginInput.value &&
- passwordInput.value
- ) {
- store.dispatch(
- actionFullLogin(
- loginInput.value.slice(0, loginInput.value.indexOf("@")),
- passwordInput.value
- )
- );
- } else {
- throw new Error("Error login");
- }
- },
- register() {
- let regInput = document.getElementById("regInput");
- let passwordInput = document.getElementById("pass2Input");
- if (regInput && passwordInput && regInput.value && passwordInput.value) {
- store.dispatch(
- actionFullRegister(
- regInput.value.slice(0, regInput.value.indexOf("@")),
- passwordInput.value
- )
- );
- } else {
- throw new Error("Error register");
- }
- },
- cart() {
- openCart();
- },
- };
- if (route in routes) routes[route]();
- };
- window.onhashchange();
- store.subscribe(() => {
- const [, route] = location.hash.split("/");
- const { cart } = store.getState();
- if (Object.keys(cart).length === 0 && route === "cart") {
- let makeOrderBtn = document.getElementById("makeOrder");
- let clearCartBtn = document.getElementById("clearBtn");
- if (makeOrderBtn) {
- makeOrderBtn.remove();
- }
- if (clearCartBtn) {
- clearCartBtn.remove();
- }
- localStorage.cart = "";
- main.innerHTML = "<h2>Your cart is empty!</h2>";
- } else if (Object.keys(cart).length !== 0) {
- let cartStr = JSON.stringify(cart);
- localStorage.cart = cartStr;
- }
- });
- window.onunload = () => {
- if (localStorage.cart !== "") {
- store.dispatch(actionCartShow());
- openCart();
- }
- };
- window.onunload();
- store.subscribe(() => {
- const { promise } = store.getState();
- const [, route, _id] = location.hash.split("/");
- if (promise?.catById?.payload && route === "category") {
- const { name } = promise.catById.payload;
- main.innerHTML = `<h1>${name}</h1>`;
- if (promise.catById.payload?.subCategories) {
- for (const { _id, name } of promise.catById.payload.subCategories) {
- const rootCat = document.querySelector(
- `a[href="#/category/${promise.catById.payload._id}"]`
- );
- const subNav = document.createElement("nav");
- subNav.className = "nav nav-pills flex-column";
- const link = document.createElement("a");
- link.href = `#/category/${_id}`;
- link.innerText = name;
- link.className = "nav-link ms-3 my-1";
- subNav.appendChild(link);
- rootCat.appendChild(subNav);
- }
- }
- if (promise.catById.payload?.goods) {
- for (const good of promise.catById.payload.goods) {
- const { _id, name, price, images } = good;
- const card = document.createElement("div");
- const link = document.createElement("a");
- card.innerHTML = `<h2>${name}</h2>
- <img src="${backURL}/${images[0].url}" width="400" height="200"/><br/>
- <strong>${price}$</strong><br/>
- `;
- link.innerText = name;
- link.href = `#/good/${_id}`;
- card.appendChild(link);
- let buyBtn = document.createElement("button");
- buyBtn.className = "btn btn-success";
- buyBtn.textContent = "Buy";
- buyBtn.onclick = () => {
- store.dispatch(actionCartAdd(good, 1));
- };
- card.appendChild(buyBtn);
- main.append(card);
- }
- }
- }
- });
- store.subscribe(() => {
- const { cart } = store.getState();
- let countGoods = 0;
- for (let good of Object.keys(cart)) {
- countGoods += cart[good].count;
- }
- cartIcon.innerText = `Товаров в корзине: ${countGoods}`;
- cartIcon.onclick = () => {
- window.location.href = "#/cart";
- };
- });
- store.subscribe(() => {
- const { promise } = store.getState();
- const [, route, _id] = location.hash.split("/");
- if (promise?.goodById?.payload && route === "good") {
- main.innerHTML = "";
- const { name, description, price, images } = promise.goodById.payload;
- main.innerHTML = `
- <div>
- <div>
- <img src="${backURL}/${images[0].url}" width="400" height="200"/>
- </div>
- <div>
- <h2>${name}</h2>
- <p><strong>${price}$</strong></p>
- <p><span>${description}</span></p>
- </div>
- </div>`;
- }
- });
- store.subscribe(() => console.log(store.getState()));
- function jwtDecode(token) {
- try {
- return JSON.parse(atob(token.split(".")[1]));
- } catch (error) {
- console.log(error);
- }
- }
- store.subscribe(async () => {
- const { promise } = store.getState();
- let signinBtn = document.getElementById("signinBtn");
- let signupBtn = document.getElementById("signupBtn");
- if (promise?.rootCats?.payload && !signinBtn && !signupBtn) {
- let signIn = document.createElement("button");
- signIn.innerText = "Sign In";
- signIn.id = "signinBtn";
- signIn.type = "button";
- signIn.className = "btn btn-light";
- let signUp = document.createElement("button");
- signUp.innerText = "Sign Up";
- signUp.id = "signupBtn";
- signUp.type = "button";
- signUp.className = "btn btn-light";
- let authBlock = document.createElement("div");
- authBlock.id = "authBlock";
- authBlock.appendChild(signIn);
- authBlock.appendChild(signUp);
- authnav.appendChild(authBlock);
- }
- });
- store.subscribe(async () => {
- let signinBtn = document.getElementById("signinBtn");
- if (signinBtn) {
- signinBtn.onclick = () => {
- try {
- let registerFields = document.getElementById("registerFields");
- if (registerFields !== null) {
- registerFields.style.display = "none";
- }
- signinBtn.style.display = "none";
- signupBtn.style.display = "inline";
- let fieldsBlock = document.createElement("div");
- fieldsBlock.className = "fields";
- fieldsBlock.id = "loginFields";
- let email = document.createElement("input");
- email.type = "email";
- email.placeholder = "Enter your email";
- email.className = "form-control";
- email.id = "loginInput";
- let password = document.createElement("input");
- password.type = "password";
- password.placeholder = "Enter your password";
- password.className = "form-control";
- password.id = "pass1Input";
- let login = document.createElement("button");
- login.innerText = "Login";
- login.id = "loginBtn";
- login.className = "btn btn-primary";
- fieldsBlock.appendChild(email);
- fieldsBlock.appendChild(password);
- fieldsBlock.appendChild(login);
- authBlock.prepend(fieldsBlock);
- loginBtn.onclick = () => {
- window.location.href = "#/login";
- console.log(store.getState());
- };
- } catch (error) {
- console.log(error);
- }
- };
- }
- });
- store.subscribe(async () => {
- let signupBtn = document.getElementById("signupBtn");
- if (signupBtn) {
- signupBtn.onclick = () => {
- try {
- let loginFields = document.getElementById("loginFields");
- if (loginFields !== null) {
- loginFields.style.display = "none";
- }
- signinBtn.style.display = "inline";
- signupBtn.style.display = "none";
- let fieldsBlock = document.createElement("div");
- fieldsBlock.className = "fields";
- fieldsBlock.id = "registerFields";
- let email = document.createElement("input");
- email.type = "email";
- email.placeholder = "Enter your email";
- email.className = "form-control";
- email.id = "regInput";
- let password = document.createElement("input");
- password.type = "password";
- password.placeholder = "Enter your password";
- password.className = "form-control";
- password.id = "pass2Input";
- let register = document.createElement("button");
- register.innerText = "Register";
- register.id = "regBtn";
- register.className = "btn btn-primary";
- fieldsBlock.appendChild(email);
- fieldsBlock.appendChild(password);
- fieldsBlock.appendChild(register);
- authBlock.prepend(fieldsBlock);
- regBtn.onclick = () => {
- window.location.href = "#/register";
- console.log(store.getState());
- };
- } catch (error) {
- console.log(error);
- }
- };
- }
- });
- store.subscribe(async () => {
- const { promise, auth } = store.getState();
- let accountLink = document.getElementById("accountLink");
- let signoutBtn = document.getElementById("signoutBtn");
- let signinBtn = document.getElementById("signinBtn");
- let signupBtn = document.getElementById("signupBtn");
- if (auth?.token && !signoutBtn && !accountLink) {
- signinBtn.style.display = "none";
- signupBtn.style.display = "none";
- if (promise?.register?.status === "RESOLVED") {
- let registerBlock = document.getElementById("registerFields");
- registerBlock.style.display = "none";
- signupBtn.style.display = "none";
- window.location.href = window.location.href.replace("#/register", "");
- console.log(store.getState());
- }
- if (promise?.login?.status === "RESOLVED") {
- let loginBlock = document.getElementById("loginFields");
- loginBlock.style.display = "none";
- signinBtn.style.display = "none";
- window.location.href = window.location.href.replace("#/login", "");
- console.log(store.getState());
- }
- let signOut = document.createElement("button");
- signOut.innerText = "Sign Out";
- signOut.id = "signoutBtn";
- signOut.className = "btn btn-secondary";
- let authBlock = document.createElement("div");
- authBlock.id = "authBlock";
- authBlock.appendChild(signOut);
- let nickname = auth.payload.sub.login;
- let account = document.createElement("button");
- account.textContent = nickname;
- account.type = "button";
- account.id = "accountLink";
- account.className = "btn btn-success";
- authBlock.prepend(account);
- authnav.appendChild(authBlock);
- signOut.onclick = () => {
- signOut.style.display = "none";
- store.dispatch(actionAuthLogout());
- signinBtn.style.display = "inline";
- signupBtn.style.display = "inline";
- account.remove();
- };
- }
- });
|