123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772 |
- //
- // Корзина хранится в localstorage для всех пользователей.
- // Ключом для доступа к данным корзины конкретного пользователя служит его _id,
- // полученный из localStorage.authToken после авторизации.
- // Данные корзины для каждого пользователя сбрасываются только в момент покупки.
- // Наполнять корзину можно и анонимно. Но оформить этот заказ не получится.
- // В случае развития этого проекта можно организовать дополнительную логику для
- // анонимной корзины. Например: если после авторизации Ваша корзина из истории
- // оказалась пустой, то товары из анонимной корзины попадут в Вашу, но только в случае
- // если сессия не прерывалась.
- // Но мне кажется, что корзина должна быть организована на серверной стороне.
- //
- const delay = (ms) => new Promise((res) => setTimeout(() => res(ms), ms));
- // localStorage.removeItem("authToken");
- // "eyJzdWIiOnsibG9naW4iOiJfX2Fub255bW91c19fIn19"-- - {"sub":{"login": "__anonymous__"}}
- const urlConst = "http://shop-roles.asmer.fs.a-level.com.ua";
- const defaultLoginName = "__anonymous__";
- const defaultLoginId = "0";
- let loginName = defaultLoginName;
- let loginId = defaultLoginId;
- let basketObj = {};
- basketObj[defaultLoginId] = {};
- goLogin.onclick = () => {
- init();
- loginForm.style.display = "";
- };
- goLogoff.onclick = () => {
- localStorage.removeItem("authToken");
- loginName = defaultLoginName;
- init();
- };
- goRegistration.onclick = () => {
- init();
- registrationForm.style.display = "";
- };
- async function setLoginFromToken() {
- loginName = defaultLoginName;
- loginId = defaultLoginId;
- try {
- loginName = await JSON.parse(atob(localStorage.authToken.split(".")[1])).sub.login;
- loginId = await JSON.parse(atob(localStorage.authToken.split(".")[1])).sub.id;
- updateBasketObj();
- } catch (error) {
- console.log("Ошибка декодирования login из токена / либо пользователь разлогинился: ", error);
- }
- }
- const init = function () {
- if (!localStorage.basket) {
- localStorage.basket = JSON.stringify(basketObj);
- } else {
- basketObj = JSON.parse(localStorage.basket);
- }
- registrationForm.style.display = "none";
- loginForm.style.display = "none";
- forImage.style.display = "none";
- forBasket.style.display = "none";
- checkAuthToken();
- };
- async function checkAuthToken() {
- if (localStorage.authToken) {
- goLogin.style.display = "none";
- goRegistration.style.display = "none";
- goLogoff.style.display = "";
- historyDiv.style.display = "";
- nickNameDiv.style.display = "";
- await setLoginFromToken();
- nickNameSpan.innerHTML = loginName;
- return true;
- }
- goLogin.style.display = "";
- goRegistration.style.display = "";
- goLogoff.style.display = "none";
- historyDiv.style.display = "none";
- nickNameSpan.innerHTML = defaultLoginName;
- nickNameDiv.style.display = "none";
- setLoginFromToken();
- return false;
- }
- async function updateBasketObj() {
- if (!basketObj[loginId]) {
- basketObj[loginId] = {};
- }
- try {
- let temp = await JSON.stringify(basketObj);
- localStorage.basket = temp;
- } catch (error) {
- console.log("Ошибка localStorage.basket = JSON.stringify(basketObj) - ", error);
- }
- }
- const getGQL = (url) => (query, variables = {}) => {
- return fetch(url, {
- method: "POST",
- headers: {
- Accept: "application/json",
- "Content-Type": "application/json",
- ...(localStorage.authToken ? { Authorization: `Bearer ${localStorage.authToken}` } : {}),
- },
- body: JSON.stringify({ query, variables }),
- })
- .then((res) => res.json())
- .catch((err) => console.log(err));
- };
- const gql = getGQL(urlConst + `/graphql`);
- async function categories(parentEl = leftSide, parentID = null) {
- let result = await gql(
- `query categories ($query:String){
- CategoryFind(query:$query){
- _id
- name
- }
- }`,
- {
- query: JSON.stringify([{ "parent._id": parentID }, { sort: [{ name: 1 }] }]),
- }
- );
- if (result.errors) return;
- let ul = document.createElement("ul");
- parentEl.append(ul);
- for (let { name, _id } of result.data.CategoryFind) {
- let li = document.createElement("li");
- li.innerText = name;
- li.style.fontWeight = "";
- let loaded;
- li.onclick = (event) => {
- if (event) event.stopPropagation();
- li.parentElement.parentElement.style.fontWeight = "";
- [].forEach.call(li.parentElement.children, (el) => (el.style.fontWeight = ""));
- li.style.fontWeight = "700";
- if (!loaded) {
- categories(li, _id);
- loaded = true;
- } else {
- loaded = false;
- [].forEach.call(li.children, (el) => el.remove());
- }
- mainBlock.innerText = "";
- categoryTitle.innerText = "";
- subMenu.innerText = "";
- showAllInCategory(_id, name);
- };
- ul.append(li);
- let span = document.createElement("span");
- span.innerText = name;
- subMenu.append(span);
- span.onclick = () => {
- li.onclick();
- };
- }
- }
- categories();
- function showAllInCategory(_id, name) {
- let h = document.createElement("h1");
- h.append(name);
- categoryTitle.append(h);
- categoryTitle.append(`_${_id} - id категории`);
- showGoodsInCategory(mainBlock, _id);
- showAllGoodsInAllSubcategories(mainBlock, _id);
- }
- async function showGoodsInCategory(parentEl, _id) {
- let result = await gql(
- `query GoodsFromCatSort ($sort:String){
- GoodFind(query: $sort) {
- _id
- name
- description
- price
- images {
- _id
- createdAt
- text
- url
- originalFileName
- }
- }
- }`,
- {
- sort: JSON.stringify([{ "categories._id": _id }, { sort: [{ name: 1 }] }]),
- }
- );
- if (result.errors) return;
- for (let { name, _id, description, price, images } of result.data.GoodFind) {
- let shelfToker = document.createElement("div");
- shelfToker.classList.add("shelfToker");
- shelfToker.onclick = (e) => {
- e.stopPropagation();
- shelfToker.classList.add("shelfTokerBig");
- };
- parentEl.append(shelfToker);
- let h3 = document.createElement("h3");
- h3.append(name);
- shelfToker.append(h3);
- h3 = document.createElement("h3");
- h3.append("$ " + price);
- shelfToker.append(h3);
- let p = document.createElement("p");
- let h4 = document.createElement("h4");
- h4.append("Описание: ");
- p.append(h4);
- p.append(description);
- shelfToker.append(p);
- let divImg = document.createElement("div");
- divImg.classList.add("divImg");
- for (let img of images) {
- let divImgOne = document.createElement("div");
- divImgOne.classList.add("divImgOne");
- let imgNode = document.createElement("img");
- imgNode.src = urlConst + `/` + img.url;
- divImgOne.append(imgNode);
- divImg.append(divImgOne);
- let namberOfImg = 0;
- imgNode.onclick = (e) => {
- e.stopPropagation();
- forImage.style.display = "";
- forImgSrc.src = urlConst + `/` + images[namberOfImg].url;
- forImage.onclick = () => {
- forImgSrc.src = urlConst + `/` + images[namberOfImg++ % (images.lenght ? images.lenght : 1)].url;
- };
- let imgKeyEsc = (ev) => {
- if (ev.code === "Escape") {
- forImage.style.display = "none";
- window.removeEventListener("keydown", imgKeyEsc);
- }
- };
- window.addEventListener("keydown", imgKeyEsc);
- };
- }
- shelfToker.append(divImg);
- let count = document.createElement("input");
- count.setAttribute("type", "number");
- count.setAttribute("min", "1");
- count.value = "1";
- count.onclick = (e) => {
- e.stopPropagation();
- };
- shelfToker.append(count);
- let putInBasketBtn = document.createElement("button");
- putInBasketBtn.append("Добавить в корзину");
- putInBasketBtn.classList.add("putInBasketBtn");
- putInBasketBtn.onclick = (e) => {
- e.stopPropagation();
- addToBasket(_id, count.value);
- };
- shelfToker.append(putInBasketBtn);
- shelfToker.insertAdjacentHTML("beforeEnd", `<div>${_id} - id товара</div>`);
- let shelfTokerExitBtn = appendActionBtn(shelfToker, { onTop: false }, "Вернуться назад");
- shelfTokerExitBtn.classList.add("shelfTokerExitBtn");
- }
- }
- function addToBasket(_idValue, countValue) {
- basketObj[loginId][_idValue] = +countValue;
- updateBasketObj();
- alert(`Товар добавлен в корзину.`);
- }
- async function showAllGoodsInAllSubcategories(parentEl, catId) {
- let result = await gql(
- `query subCategories ($subcat:String){
- CategoryFind(query: $subcat) {
- name
- _id
-
- }
- }`,
- {
- subcat: JSON.stringify([{ "parent._id": catId }, { sort: [{ name: 1 }] }]),
- }
- );
- if (result.errors) return;
- for (let { _id } of result.data.CategoryFind) {
- showGoodsInCategory(parentEl, _id);
- showAllGoodsInAllSubcategories(parentEl, _id);
- }
- }
- function CreateInputField(parentNode, hidden = false, isCheckNeed = false) {
- let inpEl = document.createElement("input");
- inpEl.setAttribute("type", hidden ? "password" : "text");
- inpEl.setAttribute("placeholder", hidden ? "Password" : "Login");
- inpEl.oninput = (isUser = true) => {
- if (isUser && this.onChange && typeof this.onChange === "function") {
- this.onChange(inpEl.value);
- }
- };
- parentNode.append(inpEl);
- let div = document.createElement("div");
- let checkBox = document.createElement("input");
- checkBox.setAttribute("type", "checkbox");
- div.append(checkBox);
- let seePassword = document.createElement("span");
- seePassword.append("Показать пароль");
- div.append(seePassword);
- checkBox.oninput = () => {
- if (checkBox.checked) {
- inpEl.setAttribute("type", "text");
- } else inpEl.setAttribute("type", "password");
- };
- inpEl.addEventListener("keydown", (e) => {
- if (["Enter", "NumpadEnter"].includes(e.code)) {
- this.onEnter();
- }
- });
- if (isCheckNeed) {
- parentNode.append(div);
- }
- this.getValue = function () {
- return inpEl.value;
- };
- this.getCheckStatus = function () {
- return checkBox.checked;
- };
- this.setValue = function (value = "") {
- inpEl.value = value;
- };
- this.setCheckBox = function (value = false) {
- checkBox.checked = value;
- };
- this.onChange = () => {};
- this.onEnter = () => {};
- }
- function CreateLoginForm(parentNode, isModeLogin = true) {
- //isModeLogin = true - по умолчанию создается форма для логина
- // false - будет сождаваться форма для регистрации
- let loginField = new CreateInputField(parentNode);
- let passwordField = new CreateInputField(parentNode, true, true);
- // function CreateInputField(parentNode, hidden = false, isCheckNeed = false)
- let loginButton = document.createElement("button");
- loginButton.append(isModeLogin ? "Войти" : "Зарегистрироваться");
- loginButton.setAttribute("disabled", "disabled");
- parentNode.append(loginButton);
- let canselButton = document.createElement("button");
- canselButton.append("Отмена");
- parentNode.append(canselButton);
- loginField.onChange = passwordField.onChange = () => {
- if (loginField.getValue() && passwordField.getValue()) {
- loginButton.removeAttribute("disabled");
- } else loginButton.setAttribute("disabled", "disabled");
- };
- loginField.onEnter = passwordField.onEnter = () => loginButton.onclick();
- canselButton.onclick = () => {
- this.clearAndClose();
- };
- loginButton.onclick = () => {
- if (this.submit && typeof this.submit === "function") {
- let loginInfo = {
- login: loginField.getValue(),
- password: passwordField.getValue(),
- };
- this.submit(loginInfo);
- }
- };
- this.submit = () => {};
- this.clearAndClose = function () {
- loginField.setValue();
- passwordField.setValue();
- passwordField.setCheckBox();
- parentNode.style.display = "none";
- };
- }
- let loginFormObject = new CreateLoginForm(loginForm);
- let registrationFormObject = new CreateLoginForm(registrationForm, false);
- loginFormObject.submit = (loginInfo) => {
- loginToDB(loginInfo);
- };
- registrationFormObject.submit = (reginInfo) => {
- registrToDB(reginInfo);
- };
- async function loginToDB({ login, password } = {}) {
- let result = await gql(
- `query login($login: String, $password: String) {
- login(login: $login, password: $password)
- }`,
- { login, password }
- );
- if (result.errors) {
- alert("Ошибка сети");
- return;
- }
- if (result.data.login) {
- localStorage.authToken = result.data.login;
- loginFormObject.clearAndClose();
- loginForm.style.display = "none";
- registrationFormObject.clearAndClose();
- registrationForm.style.display = "none";
- checkAuthToken();
- } else alert("Ошибка!\nВведите правильные логин и пароль.");
- }
- async function registrToDB({ login, password } = {}) {
- let result = await gql(
- `mutation newUser($login: String, $password: String) {
- UserUpsert(user: {login: $login, password: $password}) {
- _id
- createdAt
- }
- }`,
- { login, password }
- );
- if (result.errors) {
- alert(`Ошибка регистрации:\n${result.errors[0].message}`);
- return;
- }
- alert("Вы успешно зарегистрированы.");
- loginToDB({ login, password });
- }
- historyDiv.onclick = () => {
- showOrderHistory(forBasket);
- };
- async function showOrderHistory(parentNode) {
- let result = await gql(
- `query FindOrders($lookOrders2:String){
- OrderFind(query:$lookOrders2){
- total
- orderGoods{
- good{
- name
- images{
- url
- }
- }
- price
- count
- total
- }
- }
- }`,
- { lookOrders2: JSON.stringify([{}]) }
- // { lookOrders2: JSON.stringify([{}, { sort: [{ total: 1 }] }]) }
- // ++++++++++++++ а вот эта сортировка ну никак не работает...??? +++++++++
- // потому что total - вычисляемое поле
- );
- if (result.errors) {
- alert("Ошибка сервера...");
- console.log(result.errors);
- return;
- }
- parentNode.style.display = "";
- let masterTotal = 0;
- forBasket.innerHTML = "";
- appendActionBtn(forBasket, { onTop: true }, "Вернуться назад");
- result.data.OrderFind.forEach((order) => {
- let div1order = document.createElement("div");
- forBasket.prepend(div1order);
- showOrder(div1order, order);
- let h = document.createElement("h4");
- div1order.append(h);
- h.append(`Всего за заказ: $${order.total}`);
- masterTotal += order.total;
- });
- h = document.createElement("h2");
- forBasket.prepend(h);
- h.append(`Общая сумма всех заказов: $${masterTotal}`);
- appendActionBtn(forBasket, { onTop: true }, "Вернуться назад");
- forBasket.scrollTop = 0;
- }
- const appendActionBtn = function (parent, { onTop = false }, innerText = "Вернуться назад") {
- let exitBtn = document.createElement("button");
- exitBtn.append(innerText);
- if (onTop) {
- parent.prepend(exitBtn);
- } else {
- parent.append(exitBtn);
- }
- exitBtn.onclick = () => {
- parent.style.display = "none";
- parent.innerHTML = "";
- };
- return exitBtn;
- };
- const showOrder = function (parent, { orderGoods: orderArray, total: total1Order }) {
- for (let { good, price, count, total: total1pozition } of orderArray) {
- if (!good) {
- good = {
- images: [
- {
- url: "",
- },
- ],
- name: "",
- };
- }
- // да, это костыль, но увы не смог составить запрос, чтобы вложенное поле
- // в обекте - в массиве не равнялось null
- // И вообще! Это ли не сервер должен следить, чтобы в базу чушь не заносили?
- div = document.createElement("div");
- parent.append(div);
- let img = document.createElement("img");
- div.append(img);
- img.src = urlConst + `/` + good.images[0].url;
- let p = document.createElement("p");
- div.append(p);
- p.append(good.name);
- p = document.createElement("p");
- div.append(p);
- p.append(`Цена: $${price}`);
- p = document.createElement("p");
- div.append(p);
- p.append(`Кол-во: ${count}`);
- p = document.createElement("p");
- div.append(p);
- p.append(`Всего: $${total1pozition}`);
- }
- };
- basketLogo.onclick = () => {
- showBasket(forBasket);
- };
- async function showBasket(parent) {
- parent.style.display = "";
- parent.innerHTML = "";
- appendActionBtn(parent, { onTop: true }, "Вернуться назад");
- if (!Object.keys(basketObj[loginId]).length) {
- let h = document.createElement("h3");
- h.append("Ваша корзина пустая");
- parent.append(h);
- } else {
- for (let [_id, count] of Object.entries(basketObj[loginId])) {
- await show1goodFromBasket(parent, _id, +count);
- }
- let hTotal = document.createElement("h3");
- hTotal.setAttribute("id", "hTotal");
- parent.append(hTotal);
- CountTotal(parent);
- }
- appendActionBtn(parent, { onTop: false }, "Вернуться назад");
- let btn = appendActionBtn(parent, { onTop: false }, "Оформить заказ (купить)");
- btn.setAttribute("id", "buyBtn");
- if (!Object.keys(basketObj[loginId]).length || loginId === "0") {
- btn.setAttribute("disabled", "disabled");
- }
- btn.onclick = async function () {
- parent.style.display = "none";
- parent.innerHTML = "";
- await buy();
- };
- if (loginId === "0") {
- let h = document.createElement("h3");
- h.append("Для оформления заказа необходимо авторизоваться");
- parent.append(h);
- }
- }
- const CountTotal = function (parent) {
- let totalCost = 0;
- let costFieldsAll = parent.querySelectorAll(".costField");
- [].forEach.call(costFieldsAll, (el) => {
- totalCost += +el.innerText.slice(1);
- });
- hTotal.innerText = `Общая сумма: $${totalCost}`;
- };
- async function get1goodFromDB(_id) {
- let result = await gql(
- `query oneGood ($_id:String){
- GoodFind(query:$_id ) {
- _id
- name
- price
- images {
- url
- }
- }
- }`,
- { _id: JSON.stringify([{ _id: _id }]) }
- );
- if (result.errors) {
- console.log("не смог купить:", result.errors);
- return;
- }
- return result.data.GoodFind[0];
- }
- async function show1goodFromBasket(parent, _id, count) {
- let item = await get1goodFromDB(_id);
- let div1 = document.createElement("div");
- parent.append(div1);
- let div = document.createElement("div");
- div1.append(div);
- let img = document.createElement("img");
- div.append(img);
- img.src = urlConst + `/` + item.images[0].url;
- let p = document.createElement("p");
- div.append(p);
- p.append(item.name);
- p = document.createElement("p");
- div.append(p);
- p.append(`Цена: $${item.price}`);
- let inpCount = document.createElement("input");
- inpCount.setAttribute("type", "number");
- inpCount.setAttribute("min", "1");
- inpCount.value = count;
- div.append(inpCount);
- let cost = document.createElement("p");
- cost.setAttribute("class", "costField");
- div.append(cost);
- cost.append(`$${Math.round(count * item.price)}`);
- let btn = appendActionBtn(div, { onTop: false }, "Удалить");
- inpCount.oninput = async function () {
- basketObj[loginId][_id] = +inpCount.value;
- await updateBasketObj();
- cost.innerHTML = `$${Math.round(inpCount.value * item.price)}`;
- CountTotal(parent);
- };
- btn.onclick = async function () {
- delete basketObj[loginId][_id];
- if (!Object.keys(basketObj[loginId]).length) {
- buyBtn.setAttribute("disabled", "disabled");
- }
- await updateBasketObj();
- // div.remove();
- CountTotal(parent);
- };
- // addEventListener();
- btn.onclick = async function () {
- delete basketObj[loginId][_id];
- if (!Object.keys(basketObj[loginId]).length) {
- buyBtn.setAttribute("disabled", "disabled");
- }
- await updateBasketObj();
- div.remove();
- CountTotal(parent);
- };
- }
- async function buy() {
- let order1 = { orderGoods: [] };
- for (let [_id, count] of Object.entries(basketObj[loginId])) {
- order1.orderGoods.push({
- count: count,
- good: { _id: _id },
- });
- }
- let result = await gql(
- `mutation newOrder1($order1:OrderInput) {
- OrderUpsert(order: $order1) {
- _id
- total
- }
- }`,
- { order1 }
- );
- if (result.errors) {
- console.log("не смог получить товар из DB для отображения в корзине:", result.errors);
- return;
- }
- console.log(result.data.OrderUpsert.total);
- alert(`Ваш заказ успешно оформлен. \nОбщая сумма заказа $${result.data.OrderUpsert.total}`);
- for (let key in basketObj[loginId]) {
- delete basketObj[loginId][key];
- }
- }
- window.onload = init;
|