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"
]
}]
]
}

View File

@ -21,9 +21,9 @@ h1 {
html,
body {
font-family: 'Roboto', sans-serif;
font-family: "Roboto", sans-serif;
font-weight: normal;
background-color: #f3f3f3;
background-color: #F3F3F3;
height: 100%;
}
@ -43,42 +43,35 @@ hr {
height: 35px;
}
#addItem i {
pointer-events: none;
}
/* Custom Styles */
@keyframes fadein {
from {
opacity: 0;
opacity:0;
}
to {
opacity: 1;
opacity:1;
}
}
@-moz-keyframes fadein {
/* Firefox */
@-moz-keyframes fadein { /* Firefox */
from {
opacity: 0;
opacity:0;
}
to {
opacity: 1;
opacity:1;
}
}
@-webkit-keyframes fadein {
/* Safari and Chrome */
@-webkit-keyframes fadein { /* Safari and Chrome */
from {
opacity: 0;
opacity:0;
}
to {
opacity: 1;
opacity:1;
}
}
@-o-keyframes fadein {
/* Opera */
@-o-keyframes fadein { /* Opera */
from {
opacity: 0;
opacity:0;
}
to {
opacity: 1;
@ -91,7 +84,7 @@ hr {
.loader-fullscreen {
background-color: #000;
background-color: rgba(0, 0, 0, 0.8);
background-color: rgba(0,0,0,.8);
position: fixed;
height: 100%;
width: 100%;
@ -136,9 +129,7 @@ hr {
align-items: center;
}
.block-title h1,
.block-title h2,
.block-title h3 {
.block-title h1, .block-title h2, .block-title h3 {
padding-left: 10px;
}
@ -179,6 +170,7 @@ button#addItem {
margin-left: 5px;
}
/* Cart */
.cart-btn {
position: fixed;
@ -220,7 +212,7 @@ button#addItem {
.c_modal-wrap {
position: fixed;
background: rgba(0, 0, 0, 0.5);
background: rgba(0, 0, 0, .5);
top: 0;
right: 0;
left: 0;
@ -342,10 +334,10 @@ button#addItem {
transition: all 0.4s;
}
.modal-content__right input[type='number'] {
.modal-content__right input[type="number"] {
background: none;
height: 18px;
border: 1px solid #eaeced;
border: 1px solid #EAECED;
width: 30px;
margin: 0 5px;
color: #404751;
@ -359,11 +351,12 @@ button#addItem {
color: #99a1aa;
}
/* Information Block */
.information {
border-radius: 2px;
margin-bottom: 1rem;
background-color: #ffe8e8;
background-color: #FFE8E8;
color: #333;
font-size: 15px;
}
@ -386,7 +379,7 @@ button#addItem {
}
.pagination li.active {
background-color: #26a69a;
background-color: #26A69A;
}
/* Header */
@ -399,7 +392,7 @@ header.main-header {
}
nav {
background-color: #26a69a;
background-color: #26A69A;
font-weight: 300;
}
@ -447,7 +440,7 @@ footer.main-footer .container {
height: 42px;
line-height: 42px;
text-align: center;
background: #efefef;
background: #EFEFEF;
color: #929292;
margin: 3px;
transition: 0.2s;
@ -455,7 +448,7 @@ footer.main-footer .container {
}
.category-items li a:hover {
background: #26a69a;
background: #26A69A;
color: #fff;
}
@ -491,7 +484,7 @@ footer.main-footer .container {
}
.items-category {
background-color: #26a69a;
background-color: #26A69A;
height: 30px;
display: flex;
justify-content: center;
@ -507,7 +500,7 @@ footer.main-footer .container {
}
.items-list {
background-color: #f9f9f9;
background-color: #F9F9F9;
display: flex;
align-items: center;
justify-content: space-between;
@ -560,7 +553,7 @@ footer.main-footer .container {
.list-count,
.list-price {
height: 24px;
background: #e7e7e7;
background: #E7E7E7;
text-align: center;
border-radius: 15px;
min-width: 79px;
@ -593,6 +586,7 @@ footer.main-footer .container {
margin-top: 1rem;
}
.main-section .background {
background-color: #fff;
padding: 40px;
@ -616,7 +610,7 @@ footer.main-footer .container {
}
.product .breadcrumb li::after {
content: '/';
content: "/";
padding: 0 10px;
color: #b3b3b3;
}
@ -669,7 +663,7 @@ footer.main-footer .container {
.product-info__count span {
font-weight: 400;
font-size: 14px;
background: #e7e7e7;
background: #E7E7E7;
padding: 2px 13px;
border-radius: 15px;
color: #000;
@ -680,7 +674,7 @@ footer.main-footer .container {
.product-info__price span {
font-weight: 400;
font-size: 14px;
background: #e7e7e7;
background: #E7E7E7;
padding: 2px 13px;
border-radius: 15px;
color: #000;
@ -696,7 +690,7 @@ footer.main-footer .container {
/* Product-info Right */
.product-info__right {
background-color: #f9f9f9;
background-color: #F9F9F9;
min-height: 450px;
width: 100%;
border-radius: 4px;
@ -763,9 +757,7 @@ footer.main-footer {
.login-form__content button {
width: 100%;
}
.sidenav .collapsible-header,
.sidenav.fixed .collapsible-header,
.sidenav li > a {
.sidenav .collapsible-header, .sidenav.fixed .collapsible-header, .sidenav li>a {
padding: 0 20px;
}
.nav-wrapper .brand-logo {
@ -798,15 +790,14 @@ footer.main-footer {
.card .card-content h3 {
margin-top: 8px;
}
.table-buyers,
.table-items {
.table-buyers, .table-items {
max-height: 385px;
}
#items .card-content {
min-height: 519px;
overflow-y: scroll;
}
footer.admin-footer {
footer.admin-footer{
background-color: #fff;
border-top: 1px solid #e0e0e0;
color: #444;
@ -815,14 +806,14 @@ footer.admin-footer {
}
.admin-footer ul li a {
color: #999;
font-size: 0.9rem;
font-size: .9rem;
font-weight: 300;
margin: 5px 0;
}
.admin-footer h5 {
font-size: 1.64rem;
line-height: 110%;
margin: 1.0933333333rem 0 0.656rem 0;
margin: 1.0933333333rem 0 .656rem 0;
}
.admin-footer ul li {
margin-bottom: 5px;
@ -842,23 +833,19 @@ footer.admin-footer {
.modal .modal-content h4 {
margin-bottom: 10px;
}
div.table td,
div.table th {
div.table td, div.table th {
text-align: center;
}
div.table > table > tbody > tr > td:nth-child(1) > img {
div.table > table > tbody > tr > td:nth-child(1) > img{
height: 20px;
}
div.table > table > tbody > tr > td:nth-child(7) > button.btn.orange {
margin-left: 5px;
}
.sidenav .collapsible-body > ul:not(.collapsible) > li.active,
.sidenav.sidenav-fixed .collapsible-body > ul:not(.collapsible) > li.active {
background-color: #4e978c;
.sidenav .collapsible-body>ul:not(.collapsible)>li.active, .sidenav.sidenav-fixed .collapsible-body>ul:not(.collapsible)>li.active {
background-color: #4E978C;
}
.sidenav .collapsible-body > ul:not(.collapsible) > li.active a,
.sidenav.sidenav-fixed .collapsible-body > ul:not(.collapsible) > li.active a,
.sidenav.sidenav-fixed .collapsible-body > ul:not(.collapsible) > li.active i {
.sidenav .collapsible-body>ul:not(.collapsible)>li.active a, .sidenav.sidenav-fixed .collapsible-body>ul:not(.collapsible)>li.active a, .sidenav.sidenav-fixed .collapsible-body>ul:not(.collapsible)>li.active i {
color: #f6f6f6;
}
.card h5 {

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(src='../js/admin.js')
script(src="http://localhost:8084/admin.js")
script(src='../js/admin.js')

View File

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

View File

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

View File

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

View File

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

View File

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

4918
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

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

View File

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