Compare commits

..

42 Commits

Author SHA1 Message Date
020d8599b9 readme 2020-02-13 22:35:10 +03:00
84338c9566 v1.1.1 2020-02-13 22:34:17 +03:00
7282c740f4 heroku deploy doc 2020-02-10 18:32:05 +03:00
3a77ac81ab readme & resave session true 2020-02-01 22:35:00 +03:00
ec2cdc0e5b ecosys 2020-02-01 15:06:03 +03:00
2728293110 server file structure 2020-02-01 15:05:35 +03:00
3a17fa5235 readme & ecosys 2020-01-31 22:37:12 +03:00
0c964e2135 ecosystem env 2020-01-30 23:35:24 +03:00
af04a2619e origin header 2020-01-28 19:58:29 +03:00
73929244f4 .env 2020-01-27 22:54:14 +03:00
3f68af1521 readme 2020-01-27 19:08:36 +03:00
d4e1eca030 pkg json 2020-01-26 22:04:18 +03:00
104073cffe pagination fixed 2020-01-26 22:02:44 +03:00
7f321d0ddb readme & disqus 2020-01-25 22:26:40 +03:00
e43ea0f34b disqus comments 2020-01-25 22:16:37 +03:00
2623087082 readme 2020-01-25 20:32:20 +03:00
b44bb97b8b database_dump 2020-01-25 20:29:00 +03:00
e4fa09f07c js & img type 2020-01-25 20:14:11 +03:00
c20037d8ed date success 2020-01-25 19:14:48 +03:00
c2b6e7c31c log 2020-01-25 19:03:10 +03:00
aaa085c416 readme 2020-01-25 18:31:09 +03:00
2b4f4610a5 readme & env 2020-01-25 17:52:49 +03:00
8e79ce1e99 readmd & secret session 2020-01-25 17:05:13 +03:00
c444ac2cfd def date 2020-01-23 21:01:35 +03:00
3140c3a8ee date ISO str 2020-01-23 20:29:39 +03:00
a63ec56170 theme site 2020-01-22 23:05:43 +03:00
308dfb34de recommend 2020-01-22 21:09:06 +03:00
316bab9ad5 css 2020-01-21 20:42:19 +03:00
edd81cb451 comments 2020-01-21 20:22:40 +03:00
7d7e2ebccb bill data modal 2020-01-21 19:48:00 +03:00
ad4ff4f0f1 preloader orders 2020-01-20 20:53:20 +03:00
70ebf728fe orders component 2020-01-20 19:29:04 +03:00
6d6df14b27 env 2020-01-18 19:26:32 +03:00
332db3e55f dot env 2020-01-18 19:23:28 +03:00
3a0e325481 script href 2020-01-18 19:15:04 +03:00
a7d581dc96 . 2020-01-18 19:13:11 +03:00
Sergey
21cade8a17
Delete .gitignore 2020-01-18 19:10:29 +03:00
8ec78a9fae index 2020-01-18 18:58:24 +03:00
c469d8d56f deleted src 2020-01-18 18:53:40 +03:00
b7b67214ab deleted src 2020-01-18 18:53:28 +03:00
01b66f81f0 wp 2020-01-18 18:19:29 +03:00
629df78a22 1 2020-01-18 18:10:11 +03:00
58 changed files with 3122 additions and 4819 deletions

View File

@ -1,9 +0,0 @@
{
"presets": [
["@babel/preset-env", {
"targets": [
"last 5 versions"
]
}]
]
}

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -1,3 +1,2 @@
script(async defer src="https://buttons.github.io/buttons.js") script(async defer src="https://buttons.github.io/buttons.js")
//script(src='../js/admin.js') script(src='../js/admin.js')
script(src="http://localhost:8084/admin.js")

View File

@ -1,4 +1,3 @@
// Script JS // Script JS
//script(src='../js/main.js') script(src='../js/main.js')
script(src='http://localhost:8084/main.js')
script(src="../js/disqus.js") script(src="../js/disqus.js")

View File

@ -19,7 +19,7 @@ main.main-section
p p
strong Контакты strong Контакты
p   p  
!= store.contacts p(style="white-space: pre-wrap;") #{store.contacts}
//- Footer Template //- Footer Template

View File

@ -19,7 +19,7 @@ main.main-section
p p
strong Для того, чтобы купить аккаунты в магазине, нужно проделать следующие шаги: strong Для того, чтобы купить аккаунты в магазине, нужно проделать следующие шаги:
p   p  
!= store.howToBuy p(style="white-space: pre-wrap;") #{store.howToBuy}
//- Footer Template //- Footer Template

View File

@ -16,7 +16,7 @@ main.main-section
.information-title .information-title
h2.small-title Важная информация: h2.small-title Важная информация:
.information-text .information-text
!= store.info p(style="white-space: pre-wrap;") #{store.info}
.information-close .information-close
button#information-close.waves-effect.btn.red Закрыть button#information-close.waves-effect.btn.red Закрыть
// Information Block END // Information Block END
@ -95,7 +95,7 @@ main.main-section
.items-info__title.block-title .items-info__title.block-title
i.material-icons info i.material-icons info
h3.med-title Информация h3.med-title Информация
!= store.infoBottom p(style="white-space: pre-wrap;") #{store.infoBottom}
// Items Block END // Items Block END
//- Footer Template //- Footer Template

View File

@ -39,15 +39,15 @@ main.main-section
.product-right__title .product-right__title
span Описание: span Описание:
hr hr
!= description p #{description}
.product-right__content .product-right__content
h2 Тип аккаунтов #{category} h2 Тип аккаунтов #{category}
!= type p #{type}
h2 Формат аккаунтов #{category} h2 Формат аккаунтов #{category}
!= format p #{format}
hr hr
h3 Рекомендации к покупке аккаунтов. h3 Рекомендации к покупке аккаунтов.
!= store.recommendations p #{store.recommendations}
h3 Отзывы о товаре h3 Отзывы о товаре
#disqus_thread #disqus_thread

21
ecosystem.config.js Normal file
View File

@ -0,0 +1,21 @@
module.exports = {
apps : [{
name: 'server',
script: 'server/server.js',
autorestart: true,
watch: true,
instances: 'max',
exec_mode: 'cluster',
env: {
NODE_ENV: 'development'
},
env_production: {
NODE_ENV: 'production',
DATABASE_URI: 'mongodb://127.0.0.1:27017',
DATABASE_NAME: '',
EMAIL_LOGIN: '',
EMAIL_PASSWORD: '',
QIWI_SECRET_KEY: ''
}
}],
};

View File

@ -1,12 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Webpack output javascript</title>
</head>
<body>
<h1>Webpack output javascript</h1>
</body>
</html>

4936
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -1,12 +1,10 @@
{ {
"name": "digital-store", "name": "digital-store",
"version": "1.0.0", "version": "1.1.1",
"description": "", "description": "",
"main": "server/server.js", "main": "server/server.js",
"scripts": { "scripts": {
"start": "node server/server.js", "start": "node server/server.js"
"build": "webpack --mode production",
"wp-server": "webpack-dev-server --mode development"
}, },
"repository": { "repository": {
"type": "git", "type": "git",
@ -29,7 +27,6 @@
"express-flash": "0.0.2", "express-flash": "0.0.2",
"express-session": "^1.17.0", "express-session": "^1.17.0",
"log4js": "^6.1.0", "log4js": "^6.1.0",
"marked": "^1.1.0",
"method-override": "^3.0.0", "method-override": "^3.0.0",
"mongodb": "^3.3.4", "mongodb": "^3.3.4",
"multer": "^1.4.2", "multer": "^1.4.2",

View File

@ -1,178 +1,162 @@
const ObjectID = require('mongodb').ObjectID const ObjectID = require('mongodb').ObjectID;
const marked = require('marked') const nodemailer = require('../config/nodemailer.config');
const nodemailer = require('../config/nodemailer.config')
module.exports = function (server, db) { module.exports = function (server, db) {
server.get('/', async (req, res) => { server.get('/', async (req, res) => {
try { try {
const store = await db.collection('content').findOne({}) const store = await db.collection('content').findOne({});
db.collection('categories') db.collection('categories').find().toArray((err, result) => {
.find() const categories = result.map(category => category.title[0].toUpperCase() + category.title.slice(1));
.toArray((err, result) => { res.render('main/index', {
const categories = result.map( pageName: 'index',
(category) => title: `${store.title} | Главная`,
category.title[0].toUpperCase() + category.title.slice(1) categories,
) store: {
res.render('main/index', { title: store.title,
pageName: 'index', info: store.info,
title: `${store.title} | Главная`, infoBottom: store.infoBottom,
categories, color: store.color
store: { }
title: store.title, });
info: marked(store.info), });
infoBottom: marked(store.infoBottom), } catch {
color: store.color, res.status(500).render('main/404');
}, }
}) });
}) server.get('/how-to-buy', async (req, res) => {
} catch { try {
res.status(500).render('main/404') const store = await db.collection('content').findOne({});
} res.render('main/how-to-buy', {
}) pageName: 'how-to-buy',
server.get('/how-to-buy', async (req, res) => { title: `${store.title} | Как купить товар`,
try { store: {
const store = await db.collection('content').findOne({}) title: store.title,
res.render('main/how-to-buy', { howToBuy: store.howToBuy,
pageName: 'how-to-buy', color: store.color
title: `${store.title} | Как купить товар`, }
store: { });
title: store.title, } catch {
howToBuy: marked(store.howToBuy), res.status(500).render('main/404');
color: store.color, }
}, });
}) server.get('/contacts', async (req, res) => {
} catch { try {
res.status(500).render('main/404') const store = await db.collection('content').findOne({});
} res.render('main/contacts', {
}) pageName: 'contacts',
server.get('/contacts', async (req, res) => { title: `${store.title} | Контакты`,
try { store: {
const store = await db.collection('content').findOne({}) title: store.title,
res.render('main/contacts', { contacts: store.contacts,
pageName: 'contacts', color: store.color
title: `${store.title} | Контакты`, }
store: { });
title: store.title, } catch {
contacts: marked(store.contacts), res.status(500).render('main/404');
color: store.color, }
}, });
}) server.get('/comments', async (req, res) => {
} catch { try {
res.status(500).render('main/404') const store = await db.collection('content').findOne({});
} res.render('main/comments', {
}) pageName: 'comments',
server.get('/comments', async (req, res) => { title: `${store.title} | Отзывы`,
try { store: {
const store = await db.collection('content').findOne({}) title: store.title,
res.render('main/comments', { color: store.color
pageName: 'comments', }
title: `${store.title} | Отзывы`, });
store: { } catch {
title: store.title, res.status(500).render('main/404');
color: store.color, }
}, });
}) server.get('/my-orders', async (req, res) => {
} catch { try {
res.status(500).render('main/404') const store = await db.collection('content').findOne({});
} res.render('main/my-orders', {
}) pageName: 'my-orders',
server.get('/my-orders', async (req, res) => { title: `${store.title} | Мои покупки`,
try { store: {
const store = await db.collection('content').findOne({}) title: store.title,
res.render('main/my-orders', { color: store.color
pageName: 'my-orders', }
title: `${store.title} | Мои покупки`, });
store: { } catch {
title: store.title, res.status(500).render('main/404');
color: store.color, }
}, });
}) server.get('/product/:id', async (req, res) => {
} catch { const store = await db.collection('content').findOne({});
res.status(500).render('main/404') const product = new Promise((resolve, reject) => {
} db.collection('items').find({_id: ObjectID(req.params.id)}).toArray()
}) .then((items) => {
server.get('/product/:id', async (req, res) => { const item = items.find(item => item);
const store = await db.collection('content').findOne({}) resolve(item);
const product = new Promise((resolve, reject) => { });
db.collection('items') });
.find({ _id: ObjectID(req.params.id) })
.toArray()
.then((items) => {
const item = items.find((item) => item)
resolve(item)
})
})
product product.then((item) => {
.then((item) => { return new Promise((resolve, reject) => {
return new Promise((resolve, reject) => { db.collection('categories').find({title: item.category}).toArray()
db.collection('categories') .then((categories) => {
.find({ title: item.category }) const category = categories.find(category => category);
.toArray() resolve({item, category});
.then((categories) => { });
const category = categories.find((category) => category) });
resolve({ item, category })
})
}) })
}) .then(((data) => {
.then((data) => { const item = data.item,
const item = data.item, category = data.category;
category = data.category res.render('main/product', {
res.render('main/product', { title: item.title,
title: item.title, image: category.img,
image: category.img, count: item.count,
count: item.count, category: item.category[0].toUpperCase() + item.category.slice(1),
category: item.category[0].toUpperCase() + item.category.slice(1), price: item.price,
price: item.price, description: item.description,
description: marked(item.description), date: item.date,
date: item.date, id: item._id,
id: item._id, type: category.type,
type: marked(category.type), format: category.format,
format: marked(category.format), store: {
store: { title: store.title,
title: store.title, recommendations: store.recommendations,
recommendations: marked(store.recommendations), color: store.color
color: store.color, }
}, });
}) }))
}) .catch(() => {
.catch(() => { res.status(500).render('main/404');
res.status(500).render('main/404') });
}) });
}) server.post('/my-orders', async (req, res) => {
server.post('/my-orders', async (req, res) => { const buyers = await db.collection('buyers').find({email: req.body.email}).toArray();
const buyers = await db if (buyers.length > 0) {
.collection('buyers') // рендерим html
.find({ email: req.body.email }) const html = buyers.map((buyer) => {
.toArray() return `
if (buyers.length > 0) {
// рендерим html
const html = buyers.map((buyer) => {
return `
<b>Номер заказа: ${buyer.bill_id}</b><br> <b>Номер заказа: ${buyer.bill_id}</b><br>
<b>Дата оплаты: ${new Date( <b>Дата оплаты: ${new Date(buyer.date).toLocaleDateString()}</b><br>
buyer.date
).toLocaleDateString()}</b><br>
<b>Данные товара:</b><br> <b>Данные товара:</b><br>
<p>${buyer.data.join('<br>')}</p> <p>${buyer.data.join('<br>')}</p>
<p>======================================================================</p> <p>======================================================================</p>
` `;
}) });
// высылаем данные покупателю // высылаем данные покупателю
try { try {
await nodemailer.transporter.sendMail({ await nodemailer.transporter.sendMail({
from: `"Ваши покупки." <${process.env.EMAIL_LOGIN}>`, from: `"Ваши покупки." <${process.env.EMAIL_LOGIN}>`,
to: req.body.email, to: req.body.email,
subject: 'Данные купленных товаров находятся в этом письме.', subject: "Данные купленных товаров находятся в этом письме.",
html: html.join(''), html: html.join('')
}) });
res.json({ status: 'OK' }) res.json({status: 'OK'});
} catch (error) { } catch (error) {
res.json({ status: 'ERR' }) res.json({status: 'ERR'});
} }
} else { } else {
res.json({ status: 'NF' }) res.json({status: 'NF'});
} }
}) });
} }

View File

@ -1,31 +0,0 @@
import { Component } from '../core/component';
import axios from 'axios';
export class AdministratorComponent extends Component {
constructor(id) {
super(id);
}
setElems() {
this.$el.querySelector('form').addEventListener('submit', this.request.bind(this));
}
async request(e) {
e.preventDefault();
try {
const data = {
id: e.target.elements.id.value,
name: e.target.elements.name.value,
email: e.target.elements.email.value,
oldPass: e.target.elements.oldPass.value,
newPass: e.target.elements.newPass.value
}
const response = await axios.post('/control-panel/administrators/update', data);
alert(response.data);
document.location.reload(true);
} catch (error) {
alert('Невозможно выполнить запрос.');
}
}
}

View File

@ -1,53 +0,0 @@
import { Component } from "../core/component";
import { apiService } from "../services/api.service";
import { renderCategories } from "../templates/categories.template";
import { renderIcons } from "../templates/icons.template";
export class CategoriesComponent extends Component {
constructor(id) {
super(id);
}
setElems() {
// селекты в которых рендерится контент
this.$editSelect = this.$el.querySelector('#editCategory_select'); // редактирование категории
this.$dellSelect = this.$el.querySelector('#deleteCategory_select'); // удаление категории
this.$iconsSelect = this.$el.querySelector('#icons_select'); // выбор иконки
// обработка форм
this.$addForm = this.$el.querySelector('#addCategory');
this.$dellForm = this.$el.querySelector('#deleteCategory');
// добавим ивенты на наши формы
this.$addForm.addEventListener('submit', this.addCategory.bind(this));
this.$dellForm.addEventListener('submit', this.deleteCategory.bind(this));
}
async init() {
const categories = await apiService.getCategories(),
icons = await apiService.getIcons();
this.$editSelect.insertAdjacentHTML('beforeend', renderCategories(categories));
this.$dellSelect.insertAdjacentHTML('beforeend', renderCategories(categories));
this.$iconsSelect.insertAdjacentHTML('beforeend', renderIcons(icons));
M.FormSelect.init(document.querySelectorAll('select'));
}
async addCategory(e) {
e.preventDefault();
const categories = await apiService.getCategories(),
title = this.$addForm.elements[0].value.toLowerCase().trim(),
index = categories.map((category) => {return category.title}).indexOf(title);
index != -1 ? alert('Такая категория уже существует, придумайте другое название.') : this.$addForm.submit();
}
async deleteCategory(e) {
e.preventDefault();
const categories = await apiService.getCategories(),
title = this.$dellForm.elements[0].value.toLowerCase().trim(),
index = categories.map((category) => {return category.title}).indexOf(title);
index != -1 ? this.$dellForm.submit() : alert('Выберите категорию для удаления.');
}
}

View File

@ -1,15 +0,0 @@
import { Component } from '../core/component';
export class ContentComponent extends Component {
constructor(id) {
super(id);
}
setElems() {
this.$info = this.$el.querySelector('#info');
}
init() {
M.textareaAutoResize(this.$info);
}
}

View File

@ -1,28 +0,0 @@
import { Component } from "../core/component";
import { apiService } from "../services/api.service";
import { renderBuyers } from "../templates/buyers.template";
export class DashboardComponent extends Component {
constructor(id, {pagination}, {search}) {
super(id);
this.pagination = pagination;
this.search = search;
}
async init() {
const buyers = await apiService.getBuyers();
this.$table = this.$el.querySelector('table tbody');
this.search.initialize(this.$table, this.getBuyersByBillId);
this.pagination.initialize(buyers.length, this.$table, this.getBuyersByPage);
}
async getBuyersByBillId(bill_id) {
const buyer = await apiService.getBillId(bill_id);
return renderBuyers(buyer);
}
async getBuyersByPage(page) {
const buyers = await apiService.getBuyersOffset(page);
return renderBuyers(buyers);
}
}

View File

@ -1,30 +0,0 @@
import { Component } from "../core/component";
import { apiService } from "../services/api.service";
import { renderIcons } from "../templates/icons.template";
export class IconsComponent extends Component {
constructor(id) {
super(id);
}
setElems() {
this.$icons = this.$el.querySelector('#selectIcons');
this.$delete = this.$el.querySelector('#dellIcon');
this.$delete.addEventListener('submit', this.dellIcon.bind(this));
}
async init() {
const icons = await apiService.getIcons();
this.$icons.insertAdjacentHTML('beforeend', renderIcons(icons));
M.FormSelect.init(this.$icons);
}
async dellIcon(e) {
e.preventDefault();
const icons = await apiService.getIcons(),
currIcon = e.target.elements[0].value,
index = icons.indexOf(currIcon);
index != -1 ? e.target.submit() : alert('Выберите иконку для удаления.');
}
}

View File

@ -1,41 +0,0 @@
import { Component } from "../core/component";
import { apiService } from "../services/api.service";
import { renderItems } from "../templates/items.template";
import { renderCategories } from "../templates/categories.template";
export class ItemsComponent extends Component {
constructor(id, {pagination}, {search}) {
super(id);
this.pagination = pagination;
this.search = search;
}
setElems() {
this.$categories = this.$el.querySelector('#selectCategories');
}
async init() {
const items = await apiService.getItems(),
categories = await apiService.getCategories();
this.$table = this.$el.querySelector('table tbody');
this.search.initialize(this.$table, this.getItemsByText);
this.pagination.initialize(items.length, this.$table, this.getItemsByPage);
this.$categories.insertAdjacentHTML('beforeend', renderCategories(categories));
M.FormSelect.init(this.$categories);
}
async getItemsByText(text) {
const items = await apiService.getItemsQuery(text),
categories = await apiService.getCategories(),
data = await apiService.getItemsData();
return renderItems(items, categories, data);
}
async getItemsByPage(page) {
const items = await apiService.getItemsOffset(page),
categories = await apiService.getCategories(),
data = await apiService.getItemsData();
return renderItems(items, categories, data);
}
}

View File

@ -1,7 +0,0 @@
import { Component } from "../core/component";
export class LoaderComponent extends Component {
constructor(id) {
super(id);
}
}

View File

@ -1,135 +0,0 @@
import { Component } from "../core/component";
export class PaginationComponent extends Component {
constructor(id, {loader}) {
super(id);
this.loader = loader;
}
/**
*
* @param {number} length - кол-во товаров в магазине
* @param {object} body - куда вставляем данные о первой странице
* @param {function} request - функция async запроса
*/
initialize(length, body, request) {
// рендерим кнопку с первой страницой как независимую от других кнопок
this.$el.insertAdjacentHTML('afterbegin', '<li><a href="#" data-move="1">1</a></li><li><span style="position: relative;top: 4px;padding: 4px;">...</span></li>');
// рендерим сами кнопки и кнопки вперед - назад
this.renderBtns(length);
this.renderArrows();
// показ первой страницы
this.move(1, body, request);
// назначаем ивент на кнопки
this.$el.onclick = (e) => this.buttonHandler(e, body, request);
}
// страницу назад - вперед
renderArrows() {
const leftArrowHTML = '<li><a class="disabled" href="#" data-type="prev"><i class="material-icons">chevron_left</i></a></li>',
rightArrowHTML = '<li><a class="disabled" href="#" data-type="next"><i class="material-icons">chevron_right</i></a></li>';
this.$el.insertAdjacentHTML('afterbegin', leftArrowHTML);
this.$el.insertAdjacentHTML('beforeend', rightArrowHTML);
}
renderBtns(length) {
let count = Math.ceil(length / 5),
btnsHTML = '';
// проверяем - если товаров > 21, рендерим максимум до пяти кнопок
length > 21 ? count = 5 : count;
this.count = Math.ceil(length / 5);
for (let i = 2; i <= count; i++) {
btnsHTML += `<li><a href="#" data-move="${i}">${i}</a></li>`;
}
this.$el.insertAdjacentHTML('beforeend', btnsHTML);
}
async move(page, body, request) {
this.loader.show();
const $btns = Array.from(this.$el.querySelectorAll('li a'));
if (page === +$btns[$btns.length - 2].dataset.move && page < this.count) {
const numbers = $btns.filter(btn => btn.dataset.move);
for (let i = 1; i < numbers.length; i++) {
let newIndex = numbers[i].textContent;
++newIndex;
numbers[i].setAttribute('data-move', newIndex);
numbers[i].textContent = newIndex;
}
}
if (page === +$btns[2].dataset.move && page > 2) {
const numbers = $btns.filter(btn => btn.dataset.move);
for (let i = 1; i < numbers.length; i++) {
let newIndex = numbers[i].textContent;
--newIndex;
numbers[i].setAttribute('data-move', newIndex);
numbers[i].textContent = newIndex;
}
}
if (page <= this.count) {
// красим кнопки
const numbers = $btns.filter(btn => btn.dataset.move);
numbers.forEach(btn => btn.parentNode.classList.remove('active'));
numbers.forEach(btn => {
if (page === +btn.dataset.move) {
btn.parentNode.classList.add('active');
}
});
const response = await request(page);
body.innerHTML = '';
if (response != '') {
this.$el.setAttribute('data-page', page);
body.insertAdjacentHTML('afterbegin', response);
} else body.innerHTML = 'Данных нету.';
}
this.loader.hide();
}
buttonHandler(e, body, request) {
e.preventDefault();
if (e.target.dataset.move) {
const page = parseInt(e.target.dataset.move),
target = e.target.parentNode;
this.move(page, body, request);
} else if (e.target.tagName.toLowerCase() === 'i') {
const type = e.target.parentNode.dataset.type,
page = parseInt(this.$el.dataset.page);
switch (type) {
case 'next':
this.next(page, body, request);
break;
case 'prev':
this.prev(page, body, request);
default:
break;
}
}
}
next(page, body, request) {
this.move(++page, body, request);
}
prev(page, body, request) {
--page;
if (page < 1) {
return 0;
} else {
this.move(page, body, request);
}
}
}

View File

@ -1,29 +0,0 @@
import { Component } from "../core/component";
export class SearchComponent extends Component {
constructor(id, {loader}) {
super(id);
this.loader = loader;
}
initialize(insertTo, request) {
this.$el.onchange = (e) => {
this.search(e, insertTo, request);
}
}
async search(e, insertTo, request) {
try {
insertTo.innerHTML = '';
this.loader.show();
const query = e.target.value.trim(),
items = await request(query);
items != '' ? insertTo.insertAdjacentHTML('afterbegin', items) : insertTo.innerHTML = `По запросу "${query}" ничего не найдено...`;
e.target.value = '';
this.loader.hide();
} catch (error) {
console.error('Произошла ошибка.');
}
}
}

View File

@ -1,52 +0,0 @@
const M = require('../frameworks/materialize.min');
export class MaterializeConfig {
constructor() {
document.addEventListener('DOMContentLoaded', function () {
// Closest полифилл для ie11
(function() {
if (!Element.prototype.closest) {
Element.prototype.closest = function(css) {
var node = this;
while (node) {
if (node.matches(css)) return node;
else node = node.parentElement;
}
return null;
};
}
})();
// Matches полифилл для ie11
(function() {
if (!Element.prototype.matches) {
Element.prototype.matches = Element.prototype.matchesSelector ||
Element.prototype.webkitMatchesSelector ||
Element.prototype.mozMatchesSelector ||
Element.prototype.msMatchesSelector;
}
})()
M.Sidenav.init(document.querySelectorAll('.sidenav'));
M.Collapsible.init(document.querySelectorAll('.collapsible'));
M.Modal.init(document.querySelectorAll('.modal'));
document.querySelector('#type') ? M.textareaAutoResize(document.querySelector('#type')) : false
M.updateTextFields();
// окраска li по нажатию
if (document.querySelector('#currentPage')) {
const currentPage = document.querySelector('#currentPage').dataset.current;
const pages = Array.from(document.querySelectorAll('.collapsible li'));
pages.forEach(function (page) {
if (page.dataset.page) {
if (currentPage === page.dataset.page) {
document.querySelector('#home').classList.remove('active');
page.classList.add('active');
const collapse = M.Collapsible.getInstance(page.closest('.collapsible'));
collapse.open();
}
}
});
}
});
}
}

View File

@ -1,25 +0,0 @@
export class Component {
constructor(id) {
this.$el = document.getElementById(id);
this.$el ? this.setElems() : false;
this.$el ? this.init() : false;
}
init() {}
setElems() {}
onShow() {}
onHide() {}
hide() {
this.$el ? this.$el.classList.add('hide') : false;
this.onHide();
}
show() {
this.$el ? this.$el.classList.remove('hide') : false;
this.onShow();
}
}

View File

@ -1,31 +0,0 @@
export class Modal {
constructor(id, open, close) {
this.$el = document.getElementById(id);
this.open = document.getElementById(open);
this.close = document.getElementById(close);
this.init();
}
init() {
if (this.open) {
this.setElems();
this.open.addEventListener('click', this.show.bind(this));
this.close.addEventListener('click', this.hide.bind(this));
}
}
show(e) {
this.$el.classList.remove('hide');
document.body.style.overflowY = 'hidden';
this.openHandler(e);
}
hide() {
this.$el.classList.add('hide');
document.body.style.overflowY = '';
}
openHandler() {}
setElems() {}
}

File diff suppressed because one or more lines are too long

View File

@ -1,34 +0,0 @@
import { MaterializeConfig } from "./config/materialize.config.js";
import { CategoriesComponent } from "./components/categories.component.js";
import { ItemsComponent } from "./components/items.component.js";
import { LoaderComponent } from "./components/loader.component.js";
import { PaginationComponent } from "./components/pagination.component.js";
import { SearchComponent } from "./components/search.component.js";
import { IconsComponent } from "./components/icons.component.js";
import { ItemEditModal } from "./modals/item-edit.modal.js";
import { ItemDeleteModal } from "./modals/item-delete.modal.js";
import { CategoryEditModal } from "./modals/category-edit.modal";
import { ContentComponent } from "./components/content.component.js";
import { AdministratorComponent } from "./components/administrator.component.js";
import { DashboardComponent } from "./components/dashboard.component.js";
import { BillDataModal } from "./modals/bill-data.modal.js";
// Components
const loader = new LoaderComponent('loader');
const pagination = new PaginationComponent('pagination', {loader});
const search = new SearchComponent('search', {loader});
const categories = new CategoriesComponent('categories');
const icons = new IconsComponent('icons');
const items = new ItemsComponent('items', {pagination}, {search});
const dashboards = new DashboardComponent('dashboard', {pagination}, {search});
const content = new ContentComponent('content');
const administrator = new AdministratorComponent('administrator');
// Modals
const itemEdit = new ItemEditModal('editItemModal', 'edit', 'editItemClose', {loader});
const itemDelete = new ItemDeleteModal('deleteItemModal', 'edit', 'deleteItemClose');
const categoryEdit = new CategoryEditModal('editCategoryModal', 'editCategory_select', 'editCategoryClose', {loader});
const billData = new BillDataModal('billDataModal', 'dashboard', 'billDataClose', {loader});
const materialize = new MaterializeConfig();
console.log('Control-Panel v1.0.0');

View File

@ -1,46 +0,0 @@
import { Modal } from "../core/modal";
import { apiService } from "../services/api.service";
export class BillDataModal extends Modal {
constructor(id, open, close, {loader}) {
super(id, open, close);
this.loader = loader
}
show(e) {
if (e.target.id === 'billDataOpen') {
this.$el.classList.remove('hide');
document.body.style.overflowY = 'hidden';
this.openHandler(e);
}
}
setElems() {
this.$bill_id = this.$el.querySelector('#bill_id');
this.$email = this.$el.querySelector('#email');
this.$method = this.$el.querySelector('#method');
this.$date = this.$el.querySelector('#date');
this.$amount = this.$el.querySelector('#amount');
this.$data = this.$el.querySelector('#data');
}
async openHandler(e) {
try {
this.loader.show();
const buyer = await apiService.getBuyerById(e.target.parentNode.parentNode.dataset.id)
this.$bill_id.value = buyer.bill_id;
this.$email.value = buyer.email;
this.$method.value = buyer.method[0].toUpperCase() + buyer.method.slice(1);
this.$date.value = buyer.date;
this.$amount.value = buyer.amount;
this.$data.textContent = buyer.data.join('\n');
M.textareaAutoResize(this.$data);
M.updateTextFields();
this.loader.hide();
} catch (error) {
alert('Произошла ошибка, обновите страницу')
}
}
}

View File

@ -1,60 +0,0 @@
import { Modal } from "../core/modal";
import { apiService } from "../services/api.service";
import { renderIcons } from "../templates/icons.template";
export class CategoryEditModal extends Modal {
constructor(id, open, close, {loader}) {
super(id, open, close);
this.loader = loader;
}
// перепишем метод для перевода ивента open в событие onchange
init() {
if (this.open) {
this.setElems();
this.open.addEventListener('change', this.show.bind(this));
this.close.addEventListener('click', this.hide.bind(this));
this.$el.querySelector('form').addEventListener('submit', this.updateCategory.bind(this));
}
}
setElems() {
this.$id = this.$el.querySelector('.id');
this.$originalTitle = this.$el.querySelector('.originalTitle');
this.$title = this.$el.querySelector('.title');
this.$type = this.$el.querySelector('.type');
this.$format = this.$el.querySelector('.format');
this.$img = this.$el.querySelector('.img');
}
async openHandler(e) {
try {
this.loader.show();
const icons = await apiService.getIcons(),
category = await apiService.getCategoryById(e.target.value)
this.$img.innerHTML = `<option value='${category.img}' selected>Оставить прежнюю</option>`;
this.$id.value = category._id;
this.$originalTitle.value = category.title[0].toUpperCase() + category.title.slice(1);
this.$title.value = category.title[0].toUpperCase() + category.title.slice(1);
this.$type.value = category.type;
this.$format.value = category.format;
this.$img.insertAdjacentHTML('beforeend', renderIcons(icons));
M.FormSelect.init(this.$img);
M.textareaAutoResize(this.$type);
M.updateTextFields();
this.loader.hide();
} catch (error) {
alert('Произошла ошибка, обновите страницу')
}
}
async updateCategory(e) {
e.preventDefault();
const form = this.$el.querySelector('form');
const categories = await apiService.getCategories();
const id = form.elements[0].value.toLowerCase().trim();
const index = categories.map((category) => {return category._id}).indexOf(id);
index != -1 ? form.submit() : alert('Категории с таким ID не существует.');
}
}

View File

@ -1,46 +0,0 @@
import { Modal } from "../core/modal";
import { apiService } from "../services/api.service";
import axios from "axios";
export class ItemDeleteModal extends Modal {
constructor(id, open, close) {
super(id, open, close);
}
show(e) {
if (e.target.id === 'deleteItem') {
this.$el.classList.remove('hide');
document.body.style.overflowY = 'hidden';
this.openHandler(e);
}
}
setElems() {
this.$content = this.$el.querySelector('.modal-content__form p');
this.$button = this.$el.querySelector('.delete-item');
}
async openHandler(e) {
try {
const id = e.target.parentNode.parentNode.dataset.id,
item = await apiService.getItemById(id);
this.$content.innerHTML = `Вы действительно хотите удалить товар "${item.title}"? Находящиеся в нем аккаунты будут удалены.`;
this.$button.onclick = this.deleteHandler.bind(this, item._id);
} catch (error) {
alert('Произошла ошибка, попробуйте обновить страницу.')
}
}
async deleteHandler(id) {
try {
this.$button.classList.add('hide');
this.$content.innerHTML = '<div class="progress"><div class="indeterminate"></div></div>'
await axios.post('/control-panel/items/delete', {id});
alert('Товар был успешно удален.');
document.location.reload(true);
} catch (error) {
alert('Невозможно выполнить запрос.');
}
}
}

View File

@ -1,54 +0,0 @@
import { Modal } from "../core/modal";
import { apiService } from "../services/api.service";
import { renderCategories } from "../templates/categories.template";
export class ItemEditModal extends Modal {
constructor(id, open, close, {loader}) {
super(id, open, close);
this.loader = loader
}
show(e) {
if (e.target.id === 'editItem') {
this.$el.classList.remove('hide');
document.body.style.overflowY = 'hidden';
this.openHandler(e);
}
}
setElems() {
this.$id = this.$el.querySelector('.id');
this.$title = this.$el.querySelector('.title');
this.$price = this.$el.querySelector('.price');
this.$description = this.$el.querySelector('.description');
this.$category = this.$el.querySelector('.category');
this.$data = this.$el.querySelector('.data');
}
async openHandler(e) {
try {
this.loader.show();
const id = e.target.parentNode.parentNode.dataset.id,
item = await apiService.getItemById(id),
data = await apiService.getDataById(id),
categories = await apiService.getCategories(),
category = await apiService.getCategory(item.category);
item.data = data.data
this.$category.innerHTML = `<option value='${category[0]._id}' selected>Оставить прежнюю</option>`;
this.$id.value = item._id;
this.$title.value = item.title;
this.$price.value = item.price;
this.$description.value = item.description;
this.$category.insertAdjacentHTML('beforeend', renderCategories(categories));
this.$data.value = item.data;
M.FormSelect.init(this.$category);
M.textareaAutoResize(this.$description);
M.textareaAutoResize(this.$data);
M.updateTextFields();
this.loader.hide();
} catch (error) {
alert('Произошла ошибка, обновите страницу')
}
}
}

View File

@ -1,160 +0,0 @@
import axios from "axios";
class ApiService {
constructor(baseUrl) {
this.url = baseUrl;
}
async getCategory(category) {
try {
const request = `${this.url}/api/category/${encodeURIComponent(category)}`;
return useRequest(request);
} catch (err) {
console.error(err);
}
}
async getCategoryById(id) {
try {
const request = `${this.url}/api/category/id/${id}`;
return useRequest(request);
} catch (error) {
console.log(error)
}
}
async getCategories() {
try {
const request = `${this.url}/api/categories`;
return useRequest(request);
} catch (err) {
console.error(err);
}
}
async getItems(sorting) {
try {
if (sorting) {
const request = `${this.url}/api/items?sorting=${sorting}`;
return useRequest(request);
} else {
const request = `${this.url}/api/items`;
return useRequest(request);
}
} catch (err) {
console.error(err);
}
}
async getItemsOffset(page) {
try {
const request = `${this.url}/api/items/page/${page}`;
return useRequest(request);
} catch (err) {
console.error(err);
}
}
async getItemsQuery(text) {
try {
const request = `${this.url}/api/items?q=${text}`;
return useRequest(request);
} catch (err) {
console.error(err);
}
}
async getItemById(id) {
try {
const request = `${this.url}/api/items/id/${id}`;
return useRequest(request);
} catch (error) {
console.log(error)
}
}
async getDataById(id) {
try {
const request = `${this.url}/api/items/data/${id}`;
return useRequest(request);
} catch (error) {
console.log(error)
}
}
async getItemsOne(category, sorting) {
try {
if (sorting) {
const request = `${this.url}/api/items/${encodeURIComponent(category.toLowerCase())}?sorting=${sorting}`;
return useRequest(request);
} else {
const request = `${this.url}/api/items/${encodeURIComponent(category.toLowerCase())}`;
return useRequest(request);
}
} catch (err) {
console.error(err);
}
}
async getItemsData() {
try {
const request = `${this.url}/api/items/data`;
return useRequest(request);
} catch (err) {
console.log(err);
}
}
async getBuyers() {
try {
const request = `${this.url}/api/buyers`;
return useRequest(request);
} catch (err) {
console.error(err);
}
}
async getBuyerById(id) {
try {
const request = `${this.url}/api/buyers/${id}`;
return useRequest(request);
} catch (error) {
console.log(error)
}
}
async getBuyersOffset(page) {
try {
const request = `${this.url}/api/buyers/page/${page}`;
return useRequest(request);
} catch (err) {
console.error(err);
}
}
async getBillId(bill_id) {
try {
const request = `${this.url}/api/buyers/${bill_id}`;
return useRequest(request);
} catch (err) {
console.error(err);
}
}
async getIcons() {
try {
const request = `${this.url}/api/icons`;
return useRequest(request);
} catch (err) {
console.error(err);
}
}
}
async function useRequest(request) {
const response = await axios.get(request);
return await JSON.parse(response.request.response);
}
export const apiService = new ApiService(document.defaultView.location.origin);

View File

@ -1,7 +0,0 @@
export function renderBuyers(buyers) {
if (!buyers.error) {
return buyers.map(buyer => `<tr data-id="${buyer.bill_id}"><td>${buyer.email}</td><td>${buyer.method[0].toUpperCase() + buyer.method.slice(1)}</td><td>${new Date(buyer.date).toLocaleDateString()}</td><td>${buyer.amount} ₽</td><td><button id="billDataOpen" class="btn orange">Данные аккаунтов</button></td></tr>`).join('');
} else {
return 'Покупателей в магазине пока что нету';
}
}

View File

@ -1,3 +0,0 @@
export function renderCategories(categories) {
return categories.map(category => `<option value='${category._id}' data-icon="../img/service-icons/${category.img}">${category.title[0].toUpperCase() + category.title.slice(1)}</option>`).join('');
}

View File

@ -1,3 +0,0 @@
export function renderIcons(icons) {
return icons.map(icon => `<option value='${icon}' data-icon="../img/service-icons/${icon}">${icon}</option>`).join('');
}

View File

@ -1,13 +0,0 @@
export function renderItems(items, categories, info) {
if (!items.error) {
return categories.map(category => {
return items.map((item, i) => {
if (item.category.toLowerCase() === category.title.toLowerCase()) {
return `<tr data-id="${item._id}"><td><img src="../img/service-icons/${category.img}" alt="alt"></td><td>${item.title}</td><td>${item.category[0].toUpperCase() + item.category.slice(1)}</td><td>${new Date(item.date).toLocaleDateString()}</td><td>${item.count}</td><td>${item.price} ₽</td><td style="display: flex"><button id="deleteItem" class="btn red">Удалить</button><button id="editItem" class="btn orange">Изменить</button></td></tr>`;
}
}).join('');
}).join('');
} else {
return 'Данных нету.';
}
}

View File

@ -1,22 +0,0 @@
import { Component } from "../core/component";
export class BreadcrumbComponent extends Component {
constructor(id) {
super(id);
}
init() {
if(this.$el) {
const btn = this.$el.querySelector('#breadcrumbBtn');
btn.addEventListener('click', buttonHandler);
}
}
}
function buttonHandler(e) {
e.preventDefault();
if (e.target.dataset.category) {
localStorage.setItem('category', e.target.dataset.category.toLowerCase());
window.location.href = '/';
}
}

View File

@ -1,81 +0,0 @@
import { Component } from "../core/component";
import { apiService } from "../services/api.service";
import { renderItems } from "../templates/items.template";
export class CategoryComponent extends Component {
constructor(id, {items}, {filter}) {
super(id);
this.items = items;
this.filter = filter;
}
init() {
if (this.$el) {
this.$el.addEventListener('click', buttonHandler.bind(this));
if (localStorage.getItem('category')) {
localStorage.removeItem('sortByAll', 'true'); // установка значения для компонента Filter
const category = localStorage.getItem('category');
breadcrumbActiveLink.call(this, category); // окрашиваем категорию по нажатию на breadcrumb
showItems.call(this, category); // отображем айтемсы если был нажат breadcrumb
localStorage.removeItem('category'); // очищаем ключ category чтобы данные не показывались всегда
}
}
}
}
async function showItems(category) {
const targetCategory = await apiService.getCategory(category);
const fData = await apiService.getItemsOne(category),
html = renderItems(targetCategory, fData);
this.items.onHide();
this.items.insertItems(html);
}
async function buttonHandler(e) {
e.preventDefault();
if (e.target.dataset.category && e.target.dataset.category != 'all') { // Выполняем логику если нажали на кнопки кроме "Все товары"
localStorage.removeItem('sortByAll'); // установка значения для компонента Filter
this.filter.clearChecked();
this.items.onShow();
btnActiveLink(e);
const category = await apiService.getCategory(e.target.dataset.category),
fData = await apiService.getItemsOne(e.target.dataset.category),
html = renderItems(category, fData);
this.items.onHide();
this.items.insertItems(html);
} else if (e.target.dataset.category === 'all') { // Выполняем логику если нажали на кнопку "Все товары"
localStorage.setItem('sortByAll', 'true'); // установка значения для компонента Filter
this.filter.clearChecked();
this.items.onShow();
btnActiveLink(e);
const fData = await apiService.getItems();
const categories = await apiService.getCategories();
const html = renderItems(categories, fData);
this.items.onHide();
this.items.insertItems(html);
}
}
// Окраска li по нажатию
function btnActiveLink(e) {
Array.from(e.target.parentNode.parentNode.querySelectorAll('#catLi')).forEach(li => li.classList.remove('active'));
e.target.classList.add('active');
localStorage.setItem('currentCategory', e.target.dataset.category);
}
// Окраска li по нажатию на breadcrumb
function breadcrumbActiveLink(category) {
const links = this.$el.querySelectorAll('#catLi');
Array.from(links).forEach(li => li.classList.remove('active'));
Array.from(links).forEach(li => li.dataset.category === category ? li.classList.add('active') : false);
Array.from(links).forEach(li => li.dataset.category === category ? localStorage.setItem('currentCategory', li.dataset.category) : false);
}

View File

@ -1,56 +0,0 @@
import { Component } from "../core/component";
import { apiService } from "../services/api.service";
import { renderItems } from "../templates/items.template";
export class FilterComponent extends Component {
constructor(id, {items}) {
super(id);
this.items = items;
}
init() {
if (this.$el) {
this.$el.addEventListener('click', sortByAllHandler.bind(this));
this.$el.addEventListener('click', sortByOneHandler.bind(this));
}
}
clearChecked() {
if (this.$el) {
const options = this.$el.querySelectorAll('.filter-options form input');
Array.from(options).forEach(option => option.checked = false);
}
}
}
async function sortByAllHandler(e) {
if (localStorage.getItem('sortByAll')) {
if (e.target.id) {
const sorting = e.target.id;
this.items.onShow();
const fData = await apiService.getItems(sorting);
const categories = await apiService.getCategories();
const html = renderItems(categories, fData);
this.items.onHide();
this.items.insertItems(html);
}
}
}
async function sortByOneHandler(e) {
if (!localStorage.getItem('sortByAll')) {
if (e.target.id) {
const sorting = e.target.id;
this.items.onShow();
const category = await apiService.getCategory(localStorage.getItem('currentCategory')),
fData = await apiService.getItemsOne(localStorage.getItem('currentCategory'), sorting),
html = renderItems(category, fData);
this.items.onHide();
this.items.insertItems(html);
}
}
}

View File

@ -1,18 +0,0 @@
import { Component } from "../core/component";
import { insertCount } from "../other/counter";
export class HeaderComponent extends Component {
constructor(id) {
super(id);
}
init() {
if (this.$el) {
const currentPage = this.$el.querySelector('#desktop-nav');
const pages = Array.from(this.$el.querySelectorAll('#page'));
pages.forEach(li => li.dataset.page === currentPage.dataset.current ? li.classList.add('active') : false);
// ф-я счетика товаров
localStorage.getItem('cart') ? insertCount('counter', JSON.parse(localStorage.getItem('cart'))) : false;
}
}
}

View File

@ -1,22 +0,0 @@
import { Component } from "../core/component";
export class InformationComponent extends Component {
constructor(id) {
super(id);
}
init() {
if (this.$el) {
if (!localStorage.getItem('visited')) {
this.show();
}
const btn = this.$el.querySelector('#information-close');
btn.addEventListener('click', buttonHandler.bind(this));
}
}
}
function buttonHandler() {
localStorage.setItem('visited', JSON.stringify(true));
this.hide();
}

View File

@ -1,119 +0,0 @@
import { Component } from '../core/component'
import { apiService } from '../services/api.service'
import { insertCount } from '../other/counter'
import { renderItems } from '../templates/items.template'
export class ItemsComponent extends Component {
constructor(id, { loader }, { notification }) {
super(id)
this.loader = loader
this.notification = notification
this.$el ? (this.roster = this.$el.querySelector('#roster')) : false
if (localStorage.getItem('cart')) {
this.cart = JSON.parse(localStorage.getItem('cart'))
} else {
this.cart = []
}
}
async init() {
if (this.$el) {
localStorage.setItem('sortByAll', 'true') // установка значения для компонента Filter
this.$el.addEventListener('click', addItem.bind(this))
if (!localStorage.getItem('category')) {
// Получаем все товары и выводим их если нету ключа category в ls
const fData = await apiService.getItems()
const categories = await apiService.getCategories()
const html = renderItems(categories, fData)
this.insertItems(html)
this.btnColor()
this.loader.hide()
}
}
}
onHide() {
this.$el.querySelector('#roster').innerHTML = ''
this.loader.hide()
}
onShow() {
this.$el.querySelector('#roster').innerHTML = ''
this.loader.show()
}
btnColor() {
if (localStorage.getItem('cart')) {
// Окраска кнопок айтемов которые находятся в корзине
const obj = JSON.parse(localStorage.getItem('cart'))
obj.forEach((item) => {
if (
this.$el.querySelector(
`#rosterButtons[data-id="${item.id}"] #addItem`
)
) {
this.$el
.querySelector(`#rosterButtons[data-id="${item.id}"] #addItem`)
.classList.add('orange')
this.$el.querySelector(
`#rosterButtons[data-id="${item.id}"] #addItem i`
).innerHTML = 'close'
}
})
}
}
insertItems(html) {
this.roster.insertAdjacentHTML('afterbegin', html)
this.btnColor()
}
}
function addItem(e) {
if (e.target.localName === 'button' && e.target.id === 'addItem') {
const target = e.target
const item = {
id: e.target.parentNode.dataset.id,
title: e.target.parentNode.dataset.title,
count: e.target.parentNode.dataset.count,
price: e.target.parentNode.dataset.price,
}
renderObj(item, this, target)
}
}
function renderObj(obj, context, target) {
if (context.cart.length > 0) {
let insterted = false
let index
// Проверка на существующий айтем в корзине
context.cart.forEach((item, i) => {
if (item.id === obj.id) {
insterted = true
index = i
}
})
if (insterted === false) {
context.cart.push(obj)
localStorage.setItem('cart', JSON.stringify(context.cart))
target.classList.add('orange')
target.querySelector('i').innerHTML = 'close'
insertCount('counter', context.cart)
window.M.toast({ html: 'Товар был успешно добавлен в корзину.' })
} else {
context.cart.splice(index, 1)
localStorage.setItem('cart', JSON.stringify(context.cart))
target.classList.remove('orange')
target.querySelector('i').innerHTML = 'shopping_cart'
insertCount('counter', context.cart)
}
} else {
context.cart.push(obj)
localStorage.setItem('cart', JSON.stringify(context.cart))
target.classList.add('orange')
target.querySelector('i').innerHTML = 'close'
insertCount('counter', context.cart)
window.M.toast({ html: 'Товар был успешно добавлен в корзину.' })
}
}

View File

@ -1,7 +0,0 @@
import { Component } from "../core/component";
export class LoaderComponent extends Component {
constructor(id) {
super(id);
}
}

View File

@ -1,50 +0,0 @@
import { Component } from "../core/component";
export class NotificationComponent extends Component {
constructor(id) {
super(id);
}
notificate(text, color, delay) {
const card = document.createElement('div');
card.classList.add('card-panel');
card.style.backgroundColor = color;
card.innerHTML = `<span class="white-text">${text}</span>`;
this.$el.innerHTML += card.outerHTML;
this.removeNotificate(delay);
}
removeNotificate(delay) {
// Полифилл для IE ^9
(function() {
var arr = [window.Element, window.CharacterData, window.DocumentType];
var args = [];
arr.forEach(function (item) {
if (item) {
args.push(item.prototype);
}
});
(function (arr) {
arr.forEach(function (item) {
if (item.hasOwnProperty('remove')) {
return;
}
Object.defineProperty(item, 'remove', {
configurable: true,
enumerable: true,
writable: true,
value: function remove() {
this.parentNode.removeChild(this);
}
});
});
})(args);
})();
setTimeout(() => {
this.$el.querySelector('.card-panel').remove();
}, delay || 2000);
}
}

View File

@ -1,43 +0,0 @@
import { Component } from "../core/component";
import axios from "axios";
export class OrdersComponent extends Component {
constructor(id, {notification}, {loader}) {
super(id);
this.notification = notification;
this.loader = loader;
}
init() {
if (this.$el) {
let valid = true;
this.$el.querySelector('form').onsubmit = async (e) => {
e.preventDefault();
if (valid) {
valid = false;
this.loader.show();
const email = e.target.elements.email.value,
request = await axios.post('/my-orders', {email});
switch (request.data.status) {
case 'OK':
window.M.toast({html: `Данные были успешно высланы на почту "${email}".`})
break;
case 'NF':
window.M.toast({html: `Покупатель с почтой "${email}" не был найден.`})
break;
case 'ERR':
window.M.toast({html: `Произошла ошибка, попробуйте повторить запрос позже.`})
break;
default:
break;
}
this.loader.hide();
setTimeout(() => valid = true, 10000);
} else {
window.M.toast({html: `Подождите еще нескольно секунд прежде чем сделать запрос...`})
}
};
}
}
}

View File

@ -1,9 +0,0 @@
const M = require('../frameworks/materialize.min');
export class MaterializeConfig {
constructor() {
document.addEventListener('DOMContentLoaded', function () {
M.Sidenav.init(document.querySelectorAll('.sidenav'))
M.FormSelect.init(document.querySelectorAll('select'))
});
}
}

View File

@ -1,20 +0,0 @@
export class Component {
constructor(id) {
this.$el = document.getElementById(id);
this.init();
}
init() {}
onShow() {}
onHide() {}
hide() {
this.$el ? this.$el.classList.add('hide') : false
}
show() {
this.$el ? this.$el.classList.remove('hide') : false
}
}

View File

@ -1,33 +0,0 @@
export class Modal {
constructor(id, open, close) {
this.$el = document.getElementById(id);
this.open = document.getElementById(open);
this.close = document.getElementById(close);
this.init();
}
init() {
if (this.open != null) {
this.open.addEventListener('click', openHandler.bind(this));
this.close.addEventListener('click', closeHandler.bind(this));
}
}
show() {
this.$el.classList.remove('hide');
document.body.style.overflowY = 'hidden';
}
hide() {
this.$el.classList.add('hide');
document.body.style.overflowY = '';
}
}
function openHandler() {
this.show();
}
function closeHandler() {
this.hide();
}

File diff suppressed because one or more lines are too long

View File

@ -1,30 +0,0 @@
import { HeaderComponent } from "./components/header.component.js";
import { InformationComponent } from "./components/information.component.js";
import { LoaderComponent } from "./components/loader.component.js";
import { ItemsComponent } from "./components/items.component.js";
import { CategoryComponent } from "./components/category.component.js";
import { BreadcrumbComponent } from "./components/breadcrumb.component.js";
import { FilterComponent } from "./components/filter.component.js";
import { CartModal } from "./modals/cart.modal.js";
import { PayModal } from "./modals/pay.modal.js";
import { NotificationComponent } from "./components/notification.component.js";
import { MaterializeConfig } from "./config/materialize.config.js";
import { OrdersComponent } from "./components/orders.component.js";
// Components
const header = new HeaderComponent('header');
const loader = new LoaderComponent('loader');
const information = new InformationComponent('information');
const notification = new NotificationComponent('notification');
const items = new ItemsComponent('items', {loader}, {notification});
const filter = new FilterComponent('filter', {items});
const category = new CategoryComponent('category', {items}, {filter});
const breadcumb = new BreadcrumbComponent('breadcrumb');
const orders = new OrdersComponent('orders', {notification}, {loader});
// Modals
const cart = new CartModal('cart', 'cartOpen', 'cartClose', {items});
const pay = new PayModal('pay', 'pay-js', 'payClose');
const materialize = new MaterializeConfig();
console.log('Main Page');

View File

@ -1,96 +0,0 @@
import { Modal } from "../core/modal";
import { insertCount } from "../other/counter";
import axios from "axios";
export class CartModal extends Modal {
constructor(id, open, close, {items}) {
super(id, open, close);
this.items = items;
if (this.$el) {
this.open.addEventListener('click', this.openHandler.bind(this));
this.close.addEventListener('click', this.closeHandler.bind(this));
this.list = this.$el.querySelector('#modalList');
}
}
closeHandler() {
this.removeItem = null;
this.makePay = null;
}
openHandler() {
this.removeItem != null ? this.list.addEventListener('click', this.removeItem.bind(this)) : false
this.makePay != null ? this.$el.querySelector('form').addEventListener('submit', this.makePay.bind(this)) : false;
this.list.innerHTML = '';
this.insertItems(this.list);
}
insertItems(list) {
if (localStorage.getItem('cart')) {
const items = JSON.parse(localStorage.getItem('cart'));
if (items.length == 0) {
list.innerHTML = '<p style="text-align:center;padding:15px;">Вы не добавили ни один товар</p>';
this.$el.querySelector('form').classList.add('hide');
} else {
this.$el.querySelector('form').classList.remove('hide');
}
items.forEach(item => this.list.innerHTML += `<div data-id="${item.id}" data-title="${item.title}" data-price="${item.price}" data-count="1" class="modal-content__item"><div class="modal-content__title"><a href="/product/${item.id}"><b>${item.title}</b></a><br><span class="in-stock">В наличии: ${item.count} (шт)</span></div><div class="modal-content__right"><span id="price">${parseFloat(item.price).toFixed(2)}₽</span></div><div class="modal-content__remove"><span><i data-id="${item.id}" class="material-icons" id="removeItem">close</i></span></div></div>`);
insertCount('modalCounter', this.items.cart);
insertCount('counter', this.items.cart);
this.insertTotalPrice('totalPrice', this.items.cart);
} else {
list.innerHTML = '<p style="text-align:center;padding:15px;">Вы не добавили ни один товар</p>';
}
}
removeItem(e) {
if (e.target.id === 'removeItem') {
const id = e.target.dataset.id;
let index;
this.items.cart.forEach((item, i) => item.id === id ? index = i : false);
this.items.cart.splice(index, 1);
localStorage.setItem('cart', JSON.stringify(this.items.cart));
this.list.innerHTML = '';
this.insertItems(this.list);
this.defaultBtnColor(id);
}
}
insertTotalPrice(id, array) {
let totalPrice = 0;
array.forEach(item => totalPrice += parseFloat(item.price));
this.$el.querySelector(`#${id}`).innerHTML = `${totalPrice.toFixed(2)}₽`;
}
async makePay(e) {
e.preventDefault();
const payInfo = {items: this.items.cart.map(item => {
return {
id: item.id,
title: item.title,
price: parseFloat(item.price)
}
})};
let totalPrice = 0;
payInfo.items.forEach(item => totalPrice += parseFloat(item.price));
payInfo.totalPrice = Math.round(totalPrice * 100) / 100;
payInfo.payMethod = this.$el.querySelector('.payment-field input').value;
payInfo.email = this.$el.querySelector('.email-field input').value;
const response = await axios.put('/payment', payInfo);
response.data.payUrl ? document.location.href = response.data.payUrl : alert(response.data);
}
defaultBtnColor(id) {
if (this.items.roster) {
if (this.items.roster.querySelector(`#rosterButtons[data-id="${id}"] #addItem`)) {
this.items.roster.querySelector(`#rosterButtons[data-id="${id}"] #addItem`).classList.remove('orange');
this.items.roster.querySelector(`#rosterButtons[data-id="${id}"] #addItem i`).innerHTML = 'shopping_cart';
}
}
}
}

View File

@ -1,47 +0,0 @@
import { Modal } from "../core/modal";
import axios from "axios";
export class PayModal extends Modal {
constructor (id, open, close) {
super(id, open, close);
if (this.open != null) {
this.open.addEventListener('click', this.openHandler.bind(this));
this.close.addEventListener('click', this.closeHandler.bind(this));
}
}
closeHandler() {
this.$el.querySelector('form').onsubmit = null;
}
openHandler(e) {
if (e.target.id === 'payOpen') {
const targetInfo = e.target.parentNode.dataset;
this.$el.querySelector('.title-field input').value = targetInfo.title;
this.show();
this.$el.querySelector('form').onsubmit = (e) => {
this.makePay(e, targetInfo);
}
} else {
this.hide();
}
}
async makePay(e, info) {
e.preventDefault();
const payInfo = {
items: [{
id: info.id,
title: info.title
}],
totalPrice: parseFloat(info.price)
}
payInfo.payMethod = this.$el.querySelector('.payment-field input').value;
payInfo.email = this.$el.querySelector('.email-field input').value;
const response = await axios.put('/payment', payInfo);
response.data.payUrl ? document.location.href = response.data.payUrl : alert(response.data);
}
}

View File

@ -1,4 +0,0 @@
export function insertCount(id, array) {
const counter = document.getElementById(id);
counter.innerHTML = array.length;
}

View File

@ -1,61 +0,0 @@
import axios from 'axios';
class ApiService {
constructor(baseUrl) {
this.url = baseUrl;
}
async getCategory(category) {
try {
const request = `${this.url}/api/category/${encodeURIComponent(category)}`;
return useRequest(request);
} catch (err) {
console.error(err);
}
}
async getCategories() {
try {
const request = `${this.url}/api/categories`;
return useRequest(request);
} catch (err) {
console.error(err);
}
}
async getItems(sorting) {
try {
if (sorting) {
const request = `${this.url}/api/items?sorting=${sorting}`;
return useRequest(request);
} else {
const request = `${this.url}/api/items`;
return useRequest(request);
}
} catch (err) {
console.error(err);
}
}
async getItemsOne(category, sorting) {
try {
if (sorting) {
const request = `${this.url}/api/items/${encodeURIComponent(category.toLowerCase())}?sorting=${sorting}`;
return useRequest(request);
} else {
const request = `${this.url}/api/items/${encodeURIComponent(category.toLowerCase())}`;
return useRequest(request);
}
} catch (err) {
console.error(err);
}
}
}
async function useRequest(request) {
const response = await axios.get(request);
return await JSON.parse(response.request.response);
}
export const apiService = new ApiService(document.defaultView.location.origin);

View File

@ -1,64 +0,0 @@
export function renderItems(categories, items) {
let html = '';
try {
categories.forEach(category => {
html += `<div class="items-category">
<h2 class="small-title">${category.title}</h2>
</div>`;
category.itemsInCategory = 0; // счетчит для отслеживания категорий где нету айтемов
try {
items.forEach(item => {
if (item.category.toLowerCase() === category.title.toLowerCase()) {
category.itemsInCategory += 1;
html += `
<div class="items-list">
<div class="items-list__name">
<div class="list-icon">
<img src="./img/service-icons/${category.img}" alt="#">
</div>
<div class="list-title">
<a href="/product/${item._id}">${item.title}</a>
</div>
</div>
<div class="items-list__info">
<div class="list-date">
<small>${diffDates(new Date(), new Date(item.date))}</small>
</div>
<div class="list-count">
<span>${item.count} шт.</span>
</div>
<div class="list-one">
<span>Цена за 1 шт.</span>
</div>
<div class="list-price">
<span>${item.price.toFixed(2)}₽</span>
</div>
<div id="rosterButtons" data-id="${item._id}" data-title="${item.title}" data-count="${item.count}" data-price="${item.price} class="list-buttons">
<button id="payOpen" class="btn-small waves-effect waves-light">Купить</button>
<button id="addItem" class="btn-small waves-effect waves-light"><i
class="material-icons">shopping_cart</i></button>
</div>
</div>
</div>`;
}
})
category.itemsInCategory === 0
? html += '<div class="items-list"><span>В этой категории товаров пока что нету.</span></div>'
: false;
} catch {
html += '<div class="items-list"><span>В этой категории товаров пока что нету.</span></div>';
}
});
} catch {
html += '<div class="items-list"><span>Добавьте хотя бы одну категорию.</span></div>';
}
return html;
}
function diffDates(firstDate, secondDate) {
let diff = Math.floor((firstDate - secondDate) / (60 * 60 * 24 * 1000));
if (diff <= 0) {
return 'добавлено сегодня';
}
return diff + ' дней назад';
}

View File

@ -1,26 +0,0 @@
const path = require('path');
module.exports = {
entry: {
admin: ['@babel/polyfill', './src/admin/index.js'],
main: ['@babel/polyfill', './src/main/index.js']
},
output: {
filename: '[name].js',
path: path.join(__dirname, '/dist/public/js/')
},
resolve: {
extensions: ['.js']
},
module: {
rules: [
{ test: /\.js$/, exclude: /node_modules/, loader: 'babel-loader' }
]
},
devServer: {
contentBase: './dist/public/js',
proxy: {
"/": 'http://localhost:3000'
},
}
}