Compare commits

..

61 Commits

Author SHA1 Message Date
Sergey
cab595a36e
Merge pull request #4 from sergeyyarkov/dependabot/npm_and_yarn/websocket-extensions-0.1.4
Bump websocket-extensions from 0.1.3 to 0.1.4
2020-07-07 18:40:26 +03:00
dependabot[bot]
13398920bf
Bump websocket-extensions from 0.1.3 to 0.1.4
Bumps [websocket-extensions](https://github.com/faye/websocket-extensions-node) from 0.1.3 to 0.1.4.
- [Release notes](https://github.com/faye/websocket-extensions-node/releases)
- [Changelog](https://github.com/faye/websocket-extensions-node/blob/master/CHANGELOG.md)
- [Commits](https://github.com/faye/websocket-extensions-node/compare/0.1.3...0.1.4)

Signed-off-by: dependabot[bot] <support@github.com>
2020-06-08 06:27:44 +00:00
14777421b6 added markdown 2020-05-17 01:59:55 +03:00
844b4e4dd0 fixed event 2020-05-09 15:13:35 +03:00
cea0057f06 fix json post request 2020-02-13 21:48:09 +03:00
2799b3e7fe categories component fixed 2020-02-13 21:10:11 +03:00
34f4829c63 buyers component fixed 2020-02-13 20:46:27 +03:00
a50caef3ce fixed items component 2020-02-12 22:22:05 +03:00
6a577b8dc4 Merge branch 'master' of https://github.com/sergeyyarkov/digital-store 2020-02-11 20:26:30 +03:00
532b77d7af styles 2020-02-11 20:25:44 +03:00
Sergey
ccb151a00e
Merge pull request #2 from mayerdev/patch-2
Delete package-lock.json
2020-02-11 10:30:01 +03:00
0d9393cb4d Merge branch 'master' of https://github.com/sergeyyarkov/digital-store 2020-02-10 19:44:07 +03:00
fc74b3303c materialize toast 2020-02-10 19:42:44 +03:00
Sergey
da896dd1d3
Merge pull request #3 from mayerdev/patch-3
Update server.js
2020-02-10 19:04:05 +03:00
Sergey
b26777d4f3
Merge pull request #1 from mayerdev/patch-1
Update README.md
2020-02-10 19:00:36 +03:00
Artem Mayer
16ceabe107
Update server.js 2020-02-10 18:56:13 +03:00
Artem Mayer
875963b52c
Delete package-lock.json 2020-02-10 18:50:49 +03:00
Artem Mayer
13acda9581
Update README.md 2020-02-10 18:46:55 +03:00
7f7ce78f83 heroku doc deploy 2020-02-10 18:31:10 +03:00
17f5dc4084 heroku doc deploy 2020-02-10 18:28:25 +03:00
362b0d5dbd heroku doc deploy 2020-02-10 18:26:35 +03:00
0acd466d2d herkou doc deploy 2020-02-10 18:25:54 +03:00
4f6b1f5962 resave session true & readme 2020-02-01 22:34:18 +03:00
bedfca7b18 pkg 2020-02-01 15:13:13 +03:00
ace04d43e4 server file structure 2020-02-01 14:55:34 +03:00
78c102ea94 readme 2020-01-31 22:34:19 +03:00
ef3a9ba19e readme 2020-01-31 22:16:09 +03:00
30d3fe79a5 readme 2020-01-30 23:34:21 +03:00
ada1bdae51 origin header 2020-01-28 19:56:20 +03:00
a79bf89986 readme 2020-01-27 19:08:05 +03:00
51f7549ae8 build 2020-01-26 21:59:42 +03:00
f782b1be05 pagination fixed 2020-01-26 21:59:08 +03:00
2fd54eeae3 readme & disqus 2020-01-25 22:25:49 +03:00
df2ca7b555 disqus comments 2020-01-25 22:14:31 +03:00
bfd2b3f23d readme 2020-01-25 20:31:49 +03:00
e07d879279 added db dump 2020-01-25 20:27:35 +03:00
09df988cba js 2020-01-25 20:12:20 +03:00
5bbe5e68f7 img type & load 2020-01-25 20:11:12 +03:00
b8dc1a55e9 date success 2020-01-25 19:14:08 +03:00
e6a2a54342 log 2020-01-25 19:02:39 +03:00
4da4c1d68c readme 2020-01-25 18:30:40 +03:00
84bb83d63c readme & env 2020-01-25 17:51:53 +03:00
ace07a534b readme 2020-01-25 16:39:31 +03:00
618f044976 readme & session secret 2020-01-25 16:36:02 +03:00
21833480c8 readme 2020-01-25 00:44:10 +03:00
d1f249efb8 def date 2020-01-23 21:00:46 +03:00
76db27811e date ISO str 2020-01-23 20:26:23 +03:00
f33fd4f857 theme site 2020-01-22 23:00:00 +03:00
8a9b907b40 recommend 2020-01-22 21:05:57 +03:00
b7f382dfde css 2020-01-21 21:05:06 +03:00
529d1171bf css 2020-01-21 20:33:58 +03:00
479343048c comments by Commento 2020-01-21 20:20:17 +03:00
20954ef318 admin templates refactor 2020-01-21 19:43:02 +03:00
1ad0e1c2e3 dashboard data info 2020-01-21 19:31:19 +03:00
c95973a05a pug 2020-01-20 20:51:03 +03:00
3fdac50ce8 prealoader orders 2020-01-20 20:47:30 +03:00
b32656e675 front 2020-01-19 21:41:29 +03:00
54f9bc9a61 component orders 2020-01-19 21:25:29 +03:00
bb0e513f1a git settings 2020-01-18 19:32:27 +03:00
d85a1c10b6 readmd 2020-01-18 18:46:02 +03:00
adefcaec06 api def location 2020-01-18 18:30:32 +03:00
58 changed files with 4764 additions and 3067 deletions

9
.babelrc Normal file
View File

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

View File

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

View File

@ -19,7 +19,7 @@ main.main-section
p p
strong Для того, чтобы купить аккаунты в магазине, нужно проделать следующие шаги: strong Для того, чтобы купить аккаунты в магазине, нужно проделать следующие шаги:
p &nbsp; p &nbsp;
p(style="white-space: pre-wrap;") #{store.howToBuy} != 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
p(style="white-space: pre-wrap;") #{store.info} != 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 Информация
p(style="white-space: pre-wrap;") #{store.infoBottom} != 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
p #{description} != description
.product-right__content .product-right__content
h2 Тип аккаунтов #{category} h2 Тип аккаунтов #{category}
p #{type} != type
h2 Формат аккаунтов #{category} h2 Формат аккаунтов #{category}
p #{format} != format
hr hr
h3 Рекомендации к покупке аккаунтов. h3 Рекомендации к покупке аккаунтов.
p #{store.recommendations} != store.recommendations
h3 Отзывы о товаре h3 Отзывы о товаре
#disqus_thread #disqus_thread

View File

@ -1,21 +0,0 @@
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: ''
}
}],
};

12
index.html Normal file
View File

@ -0,0 +1,12 @@
<!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>

4806
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -1,10 +1,12 @@
{ {
"name": "digital-store", "name": "digital-store",
"version": "1.1.1", "version": "1.0.0",
"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",
@ -27,6 +29,7 @@
"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,162 +1,178 @@
const ObjectID = require('mongodb').ObjectID; const ObjectID = require('mongodb').ObjectID
const nodemailer = require('../config/nodemailer.config'); const marked = require('marked')
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').find().toArray((err, result) => { db.collection('categories')
const categories = result.map(category => category.title[0].toUpperCase() + category.title.slice(1)); .find()
.toArray((err, result) => {
const categories = result.map(
(category) =>
category.title[0].toUpperCase() + category.title.slice(1)
)
res.render('main/index', { res.render('main/index', {
pageName: 'index', pageName: 'index',
title: `${store.title} | Главная`, title: `${store.title} | Главная`,
categories, categories,
store: { store: {
title: store.title, title: store.title,
info: store.info, info: marked(store.info),
infoBottom: store.infoBottom, infoBottom: marked(store.infoBottom),
color: store.color color: store.color,
} },
}); })
}); })
} catch { } catch {
res.status(500).render('main/404'); res.status(500).render('main/404')
} }
}); })
server.get('/how-to-buy', async (req, res) => { server.get('/how-to-buy', async (req, res) => {
try { try {
const store = await db.collection('content').findOne({}); const store = await db.collection('content').findOne({})
res.render('main/how-to-buy', { res.render('main/how-to-buy', {
pageName: 'how-to-buy', pageName: 'how-to-buy',
title: `${store.title} | Как купить товар`, title: `${store.title} | Как купить товар`,
store: { store: {
title: store.title, title: store.title,
howToBuy: store.howToBuy, howToBuy: marked(store.howToBuy),
color: store.color color: store.color,
} },
}); })
} catch { } catch {
res.status(500).render('main/404'); res.status(500).render('main/404')
} }
}); })
server.get('/contacts', async (req, res) => { server.get('/contacts', async (req, res) => {
try { try {
const store = await db.collection('content').findOne({}); const store = await db.collection('content').findOne({})
res.render('main/contacts', { res.render('main/contacts', {
pageName: 'contacts', pageName: 'contacts',
title: `${store.title} | Контакты`, title: `${store.title} | Контакты`,
store: { store: {
title: store.title, title: store.title,
contacts: store.contacts, contacts: marked(store.contacts),
color: store.color color: store.color,
} },
}); })
} catch { } catch {
res.status(500).render('main/404'); res.status(500).render('main/404')
} }
}); })
server.get('/comments', async (req, res) => { server.get('/comments', async (req, res) => {
try { try {
const store = await db.collection('content').findOne({}); const store = await db.collection('content').findOne({})
res.render('main/comments', { res.render('main/comments', {
pageName: 'comments', pageName: 'comments',
title: `${store.title} | Отзывы`, title: `${store.title} | Отзывы`,
store: { store: {
title: store.title, title: store.title,
color: store.color color: store.color,
} },
}); })
} catch { } catch {
res.status(500).render('main/404'); res.status(500).render('main/404')
} }
}); })
server.get('/my-orders', async (req, res) => { server.get('/my-orders', async (req, res) => {
try { try {
const store = await db.collection('content').findOne({}); const store = await db.collection('content').findOne({})
res.render('main/my-orders', { res.render('main/my-orders', {
pageName: 'my-orders', pageName: 'my-orders',
title: `${store.title} | Мои покупки`, title: `${store.title} | Мои покупки`,
store: { store: {
title: store.title, title: store.title,
color: store.color 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 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.then((item) => {
return new Promise((resolve, reject) => {
db.collection('categories').find({title: item.category}).toArray()
.then((categories) => {
const category = categories.find(category => category);
resolve({item, category});
});
});
}) })
.then(((data) => { } catch {
res.status(500).render('main/404')
}
})
server.get('/product/:id', async (req, res) => {
const store = await db.collection('content').findOne({})
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
.then((item) => {
return new Promise((resolve, reject) => {
db.collection('categories')
.find({ title: item.category })
.toArray()
.then((categories) => {
const category = categories.find((category) => category)
resolve({ item, category })
})
})
})
.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
.collection('buyers')
.find({ email: req.body.email })
.toArray()
if (buyers.length > 0) { if (buyers.length > 0) {
// рендерим html // рендерим html
const html = buyers.map((buyer) => { const html = buyers.map((buyer) => {
return ` return `
<b>Номер заказа: ${buyer.bill_id}</b><br> <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> <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

@ -0,0 +1,31 @@
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

@ -0,0 +1,53 @@
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

@ -0,0 +1,15 @@
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

@ -0,0 +1,28 @@
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

@ -0,0 +1,30 @@
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

@ -0,0 +1,41 @@
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

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

View File

@ -0,0 +1,135 @@
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

@ -0,0 +1,29 @@
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

@ -0,0 +1,52 @@
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

@ -0,0 +1,25 @@
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();
}
}

31
src/admin/core/modal.js Normal file
View File

@ -0,0 +1,31 @@
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

34
src/admin/index.js Normal file
View File

@ -0,0 +1,34 @@
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

@ -0,0 +1,46 @@
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

@ -0,0 +1,60 @@
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

@ -0,0 +1,46 @@
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

@ -0,0 +1,54 @@
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

@ -0,0 +1,160 @@
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

@ -0,0 +1,7 @@
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

@ -0,0 +1,3 @@
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

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

View File

@ -0,0 +1,13 @@
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

@ -0,0 +1,22 @@
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

@ -0,0 +1,81 @@
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

@ -0,0 +1,56 @@
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

@ -0,0 +1,18 @@
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

@ -0,0 +1,22 @@
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

@ -0,0 +1,119 @@
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

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

View File

@ -0,0 +1,50 @@
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

@ -0,0 +1,43 @@
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

@ -0,0 +1,9 @@
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

@ -0,0 +1,20 @@
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
}
}

33
src/main/core/modal.js Normal file
View File

@ -0,0 +1,33 @@
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

30
src/main/index.js Normal file
View File

@ -0,0 +1,30 @@
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

@ -0,0 +1,96 @@
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

@ -0,0 +1,47 @@
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

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

View File

@ -0,0 +1,61 @@
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

@ -0,0 +1,64 @@
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 + ' дней назад';
}

26
webpack.config.js Normal file
View File

@ -0,0 +1,26 @@
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'
},
}
}