Compare commits

..

88 Commits

Author SHA1 Message Date
70a4d5713b dev #14250
правки
2023-06-01 10:41:08 +04:00
22858a2f49 dev #14294
сделал отправку запроса при выборе интервала 'между'
2023-06-01 10:28:38 +04:00
cc5b1e4ba9 dev #14250
Сделал менюху, сделал вывод заказов, некоторые правки
2023-06-01 10:09:57 +04:00
ac7709b64e dev #14258
убрал возможность редактирования имени при оформлении заказа, информацию из айка сделал на прошлых задачах
2023-05-29 07:36:34 +04:00
2428870ba8 dev #14253
Добавил состояние приложения(профиль)
Сделал проверку на активность карты, на то, хватает ли баланса для совершения заказа, доработал обработку ошибок, пофиксил некоторые баги
2023-05-26 01:35:37 +04:00
ab8e909b08 исправил ошибку 2023-05-03 15:38:03 +04:00
673afcf2b1 dev #14066
исправил логику добавления товаров из разных заведений
2023-05-02 11:05:23 +04:00
a6c0f98ec6 dev #14165
Добавил кнопку выхода
2023-05-01 17:02:29 +04:00
7f96fa4503 dev #13992
Сделал фильтрацию по дате, локализовал календарь на русский язык и формат
2023-03-30 11:13:35 +04:00
1a503b3163 dev #13888
сделал возможность посмотреть подробности по покупкам
2023-03-21 22:42:24 +04:00
00a47756b2 dev #13810
исправил баги
2023-02-28 00:13:40 +04:00
10ad10d922 dev #13810
перевел некоторые элементы с primeNG на AngularMaterial, так как в первое случае были баги с айфоном
добавил отображение описания в карточке товара,
подтянул имя при оформлении заказа,
добавил список терминалов при оформлении заказа, по умолчанию показывается активный терминал в disabled=true
2023-02-27 22:49:33 +04:00
ed2e73e598 dev #13797
1 и 2 пункты
2023-02-20 17:10:28 +04:00
ced1f14cff dev #13796
сделал фильтр транзакций
2023-02-20 17:02:57 +04:00
c24e94c255 dev #13306
поменял формат адреса, починил тип доставки
2023-02-19 22:23:20 +04:00
c282a6e521 dev #13306
поменял лого и правки
2023-02-17 12:25:50 +04:00
Kataev Denis
9e7b91e117 dev #13305
сделал объединение конфига с айки и с сайта
2023-02-08 15:53:13 +04:00
Kataev Denis
624d121006 dev #13306
правки
2023-02-08 15:29:47 +04:00
ce61d8b9aa support #13736
везде поменял название
2023-02-06 14:57:21 +04:00
5b0516f37e Merge branch 'demo-stand' of https://git.hlcompany.ru/git/usersite into demo-stand 2023-02-06 00:48:25 +04:00
1960c6a7d3 dev #13306
исправил ошибку отсутствия номера телефона при заказе, поправил модель данных заказа, добавил автоматическую смену даты выдачи раз в минуту
2023-02-06 00:48:16 +04:00
Kataev Denis
26aa47b004 dev #13306
Добавил orderid к заказу
2023-01-26 01:29:59 +04:00
e7a389b484 dev #13306
правки
2023-01-24 16:06:35 +04:00
df49c2a77e dev #13306
поправил модель данных при отправке заказа
2023-01-24 15:35:11 +04:00
2e3da3d0b2 dev #13306
правка в урле
2023-01-20 15:50:19 +04:00
6344de6931 dev #13306
правка в урле
2023-01-20 15:40:32 +04:00
72e308c706 dev #13306
правки в модалке продукта
2023-01-19 21:13:38 +04:00
30edd5d8c1 dev #13306
фикс баги
2023-01-13 04:18:55 +04:00
7b61806984 dev #13306
фикс баги
2023-01-13 04:15:27 +04:00
f6ffa897bd dev #13510
исправил ошибку сборки
2023-01-11 14:44:57 +04:00
661a5b700a dev #13510
изменил карточку товара и общую цветовую схему
2023-01-11 14:03:12 +04:00
88a88e7541 dev #13306
Доработки
2022-12-28 14:24:10 +04:00
3040cef0b8 dev #13306
правка
2022-12-27 16:38:08 +04:00
7fe1d8c22e dev #13306
Переделал роутинг с кастомного на ангуляровский, правки по меню, доделал страницу 404
2022-12-27 16:35:50 +04:00
6dad05b628 dev #13306
Правки
2022-12-26 22:45:54 +04:00
0beee8c5f7 dev #13306
Правки
2022-12-26 15:28:56 +04:00
ea63515f74 dev #13306
поправил дизайн
2022-12-19 07:51:11 +04:00
4c614ff492 dev #13307
Изменил пути и имена на demo-stand
2022-12-05 09:07:26 +04:00
6e05417f3b Merge branch 'sakura' of https://git.hlcompany.ru/git/usersite into sakura
# Conflicts:
#	angular/package.json
2022-12-05 08:51:13 +04:00
186351984f dev #13306
мелкие правки
2022-12-05 08:43:06 +04:00
7a12e6d4f7 dev #13305
добавил тестовый конфиг (+- подходит для нефтяников), сделал сайт конфигуряемым
2022-12-05 08:41:26 +04:00
c7db59110e dev #12928
доработки по корзине и отправке заказа, коризна теперь хранится в локалсторедж, а не в куки, адаптировал корзину под новые товары и модификаторы
2022-12-05 08:37:34 +04:00
801f33623b dev #12927
доработки по карточке товара, поменял работу модификаторов
2022-12-05 08:32:57 +04:00
63fac5b48d dev #12926
Доработки по списку товаров, доработки по дизайну
2022-12-05 08:30:16 +04:00
Kataev Denis
787e08c7be dev #13101
поменял версию и мелкая правка
2022-11-15 09:15:02 +04:00
3120528bb2 dev #13101
1 пункт сделан
2 нужно доработать
2022-11-15 07:38:37 +04:00
5d5a04c00f dev #13014
поправил список транзакций
2022-11-08 13:25:29 +04:00
1cd4cc1201 dev #13014
поменял урлу до icard-proxy
2022-11-08 13:11:05 +04:00
1cebb09849 dev #12928
доработал корзину под работу с терминалами, добавил очистку коризны
2022-11-08 10:36:41 +04:00
266601e59b dev #12926
доработал каталог товаров, добавил работу с терминалами
2022-11-08 10:33:31 +04:00
5a5acb53fd dev #13014
Переделал 'Ваша карта лояльности' и 'Ваши чеки' на icard-proxy
2022-11-08 10:28:10 +04:00
352986de93 dev #12928
мелкие правки
2022-10-31 09:31:52 +04:00
d9836f2b40 dev #12928
правка по отправке заказа
2022-10-28 21:18:25 +04:00
aea275e705 dev #12928
добавил и модифицировал корзину и отправку заказа
2022-10-28 21:13:08 +04:00
b5fd880379 dev #12926
реализовал каталог товаров
2022-10-28 21:11:05 +04:00
694dbbfb8d dev #12925
поменял версию, добавил static
2022-10-24 22:58:02 +04:00
0adafa4d1c dev #12925
убрал бабочек, поменял лого, доработал пути для получения json
2022-10-24 22:54:26 +04:00
ed41ab6627 dev #12925
dev #12926
dev #12927
изменил название клиента и пути для разворачивания проекта
Наработки по каталогу товаров
наработки по карточке товара
2022-10-24 22:20:41 +04:00
pumpurumer
192a7a5d2f поменял секреты на нормальные 2022-10-19 12:47:16 +04:00
dfd596cd56 dev #12797
Правки
2022-10-16 22:29:36 +04:00
b92b1e73ce dev #12797
Добавил обновление карты при обновлении данных пользователя
2022-10-16 22:24:38 +04:00
8ca151574a dev #12797
Доработки на фронте
2022-10-16 22:05:12 +04:00
Kataev Denis
9f4447fa79 package-lock 2022-10-12 11:46:37 +04:00
197c84f29c dev #12797
изменил расположение директорий и изменил файлы Jenkinsfile и gitignore
2022-10-11 10:40:59 +04:00
Kataev Denis
29d274e05b Merge branch 'fashion-logica' of https://git.hlcompany.ru/git/usersite into fashion-logica 2022-10-11 09:55:20 +04:00
Kataev Denis
e558fcabc0 dev #12797
генерация карты
2022-10-11 09:55:01 +04:00
1ee13ab73d dev #12618
поправил проверку на существование в реферальной системе
2022-09-30 13:07:02 +04:00
4414480d6e dev #12425
Поправил сохранения токенов для пушей
2022-09-30 13:02:57 +04:00
2ca121cbac Merge branch 'fashion-logica' of https://git.hlcompany.ru/git/usersite into fashion-logica 2022-09-29 21:47:34 +04:00
25a5db7337 dev #12401
правки по реферальной системе и версии приложения
2022-09-29 21:46:49 +04:00
Kataev Denis
134ee3f59d dev #12425
Поменял учетные данные
2022-09-29 18:41:33 +04:00
Kataev Denis
914b1ec5ed dev #12401
вернул как было
2022-09-29 10:32:06 +04:00
Kataev Denis
86eaca953e dev #12401
Страница 404 доработка
2022-09-29 10:15:21 +04:00
Kataev Denis
a22479dffd Merge branch 'fashion-logica' of https://git.hlcompany.ru/git/usersite into fashion-logica 2022-09-29 10:09:25 +04:00
Kataev Denis
bb455b2c45 dev #12401
Страница 404 и правки
2022-09-29 10:09:15 +04:00
pumpurumer
9b64746c2e test commit 2022-09-28 15:07:39 +04:00
pumpurumer
34080a0ee0 build commit 2022-09-28 14:58:06 +04:00
pumpurumer
ff0f8d6fad rebuild 2022-09-28 14:54:07 +04:00
pumpurumer
988a05e94d rebuild commit 2022-09-28 14:35:27 +04:00
pumpurumer
5b9be577e7 rebuild commit 2022-09-28 14:34:31 +04:00
pumpurumer
615f708600 rebuild commit 2022-09-28 14:29:32 +04:00
pumpurumer
ba71eee97f rebuild commit 2022-09-28 14:28:29 +04:00
pumpurumer
06e0b8efe1 rebuild commit 2022-09-28 14:27:33 +04:00
pumpurumer
7a490e4599 build commit 2022-09-28 14:25:53 +04:00
pumpurumer
8b6af5fa21 build commit 2022-09-28 14:25:16 +04:00
55486de524 dev #12738 2022-09-27 22:53:36 +04:00
Kataev Denis
836af4e1ae dev #12738 2022-09-27 20:15:18 +04:00
Kataev Denis
9352d06e73 dev #12738
Сделал бранчу и добавил в нее jenkinsfile
2022-09-27 14:17:17 +04:00
228 changed files with 7858 additions and 1412 deletions

58
.gitignore vendored
View File

@ -1,42 +1,42 @@
# See http://help.github.com/ignore-files/ for more about ignoring files. # See http://help.github.com/ignore-files/ for more about ignoring files.
# Compiled output # Compiled output
/dist angular/dist
/tmp angular/tmp
/out-tsc angular/out-tsc
/bazel-out angular/bazel-out
# Node # Node
/node_modules angular/node_modules
npm-debug.log angular/npm-debug.log
yarn-error.log angular/yarn-error.log
# IDEs and editors # IDEs and editors
.idea/ angular/.idea/
.project angular/.project
.classpath angular/.classpath
.c9/ angular/.c9/
*.launch angular/*.launch
.settings/ angular/.settings/
*.sublime-workspace angular/*.sublime-workspace
# Visual Studio Code # Visual Studio Code
.vscode/* angular/.vscode/*
!.vscode/settings.json angular/!.vscode/settings.json
!.vscode/tasks.json angular/!.vscode/tasks.json
!.vscode/launch.json angular/!.vscode/launch.json
!.vscode/extensions.json angular/!.vscode/extensions.json
.history/* angular/.history/*
# Miscellaneous # Miscellaneous
/.angular/cache angular/.angular/cache
.sass-cache/ angular/.sass-cache/
/connect.lock angular/connect.lock
/coverage angular/coverage
/libpeerconnection.log angular/libpeerconnection.log
testem.log angular/testem.log
/typings angular/typings
# System files # System files
.DS_Store angular/.DS_Store
Thumbs.db angular/Thumbs.db

View File

@ -1,4 +0,0 @@
{
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=827846
"recommendations": ["angular.ng-template"]
}

20
.vscode/launch.json vendored
View File

@ -1,20 +0,0 @@
{
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
"version": "0.2.0",
"configurations": [
{
"name": "ng serve",
"type": "pwa-chrome",
"request": "launch",
"preLaunchTask": "npm: start",
"url": "http://localhost:4200/"
},
{
"name": "ng test",
"type": "chrome",
"request": "launch",
"preLaunchTask": "npm: test",
"url": "http://localhost:9876/debug.html"
}
]
}

42
.vscode/tasks.json vendored
View File

@ -1,42 +0,0 @@
{
// For more information, visit: https://go.microsoft.com/fwlink/?LinkId=733558
"version": "2.0.0",
"tasks": [
{
"type": "npm",
"script": "start",
"isBackground": true,
"problemMatcher": {
"owner": "typescript",
"pattern": "$tsc",
"background": {
"activeOnStart": true,
"beginsPattern": {
"regexp": "(.*?)"
},
"endsPattern": {
"regexp": "bundle generation complete"
}
}
}
},
{
"type": "npm",
"script": "test",
"isBackground": true,
"problemMatcher": {
"owner": "typescript",
"pattern": "$tsc",
"background": {
"activeOnStart": true,
"beginsPattern": {
"regexp": "(.*?)"
},
"endsPattern": {
"regexp": "bundle generation complete"
}
}
}
}
]
}

33
Jenkinsfile vendored Normal file
View File

@ -0,0 +1,33 @@
env.HL_BUILD_MODE = "jenkins"
node('Lithium'){
stage('get new version to repo') {
checkout scm
if (lastCommitIsBumpCommit()) {
currentBuild.result = 'ABORTED'
error('Последний коммит - результат сборки jenkins')
}
sh "git checkout ${env.BRANCH_NAME}"
sh "git checkout -- ."
sh "git pull"
//sh "git submodule update --init --recursive"
//sh "git submodule update --remote --merge"
}
stage("build and publish"){
dir('angular'){
sh label: '', script: 'npm i'
sh label: '', script: 'npm run build'
}
}
}
private boolean lastCommitIsBumpCommit() {
lastCommit = sh([script: 'git log -1', returnStdout: true])
if (lastCommit.contains("Author: jenkins")) {
return true
} else {
return false
}
}

View File

@ -1,4 +1,4 @@
# FashionLogica # Sakura
This project was generated with [Angular CLI](https://github.com/angular/angular-cli) version 14.0.6. This project was generated with [Angular CLI](https://github.com/angular/angular-cli) version 14.0.6.

View File

@ -1,15 +1,17 @@
{ {
"$schema": "./node_modules/@angular/cli/lib/config/schema.json", "$schema": "./node_modules/@angular/cli/lib/config/schema.json",
"version": 1, "version": 1,
"newProjectRoot": "projects", "newProjectRoot": "projects",
"projects": { "projects": {
"fashion-logica": { "sakura": {
"projectType": "application", "projectType": "application",
"schematics": { "schematics": {
"@schematics/angular:component": { "@schematics/angular:component": {
"style": "scss" "style": "scss"
} }
}, },
"root": "", "root": "",
"sourceRoot": "src", "sourceRoot": "src",
"prefix": "app", "prefix": "app",
@ -17,8 +19,9 @@
"build": { "build": {
"builder": "@angular-devkit/build-angular:browser", "builder": "@angular-devkit/build-angular:browser",
"options": { "options": {
"outputPath": "/var/www/lk/fashion-logica", "outputPath": "/var/www/html/lk.crm4retail.ru/sakura",
"baseHref": "/fashion-logica/", "deleteOutputPath": false,
"baseHref": "/",
"index": "src/index.html", "index": "src/index.html",
"main": "src/main.ts", "main": "src/main.ts",
"polyfills": "src/polyfills.ts", "polyfills": "src/polyfills.ts",
@ -31,7 +34,9 @@
"src/firebase-messaging-sw.js" "src/firebase-messaging-sw.js"
], ],
"styles": [ "styles": [
"node_modules/primeng/resources/themes/bootstrap4-light-blue/theme.css", "./node_modules/@angular/material/prebuilt-themes/indigo-pink.css",
"node_modules/mdb-angular-ui-kit/assets/scss/mdb.scss",
"node_modules/primeng/resources/themes/saga-blue/theme.css",
"node_modules/primeicons/primeicons.css", "node_modules/primeicons/primeicons.css",
"node_modules/primeng/resources/primeng.min.css", "node_modules/primeng/resources/primeng.min.css",
"node_modules/ngx-sharebuttons/themes/modern.scss", "node_modules/ngx-sharebuttons/themes/modern.scss",
@ -53,7 +58,7 @@
{ {
"type": "anyComponentStyle", "type": "anyComponentStyle",
"maximumWarning": "2kb", "maximumWarning": "2kb",
"maximumError": "4kb" "maximumError": "4.5kb"
} }
], ],
"fileReplacements": [ "fileReplacements": [
@ -79,10 +84,11 @@
"builder": "@angular-devkit/build-angular:dev-server", "builder": "@angular-devkit/build-angular:dev-server",
"configurations": { "configurations": {
"production": { "production": {
"browserTarget": "fashion-logica:build:production" "browserTarget": "sakura:build:production"
}, },
"development": { "development": {
"browserTarget": "fashion-logica:build:development" "browserTarget": "sakura:build:development",
"proxyConfig": "proxy.confi.json"
} }
}, },
"defaultConfiguration": "development" "defaultConfiguration": "development"
@ -90,7 +96,7 @@
"extract-i18n": { "extract-i18n": {
"builder": "@angular-devkit/build-angular:extract-i18n", "builder": "@angular-devkit/build-angular:extract-i18n",
"options": { "options": {
"browserTarget": "fashion-logica:build" "browserTarget": "sakura:build"
} }
}, },
"test": { "test": {
@ -108,6 +114,7 @@
"src/manifest.webmanifest" "src/manifest.webmanifest"
], ],
"styles": [ "styles": [
"./node_modules/@angular/material/prebuilt-themes/indigo-pink.css",
"src/styles.scss" "src/styles.scss"
], ],
"scripts": [] "scripts": []

View File

@ -1,12 +1,12 @@
{ {
"name": "fashion-logica", "name": "sakura",
"version": "0.0.0", "version": "0.0.1",
"lockfileVersion": 2, "lockfileVersion": 2,
"requires": true, "requires": true,
"packages": { "packages": {
"": { "": {
"name": "fashion-logica", "name": "sakura",
"version": "0.0.0", "version": "0.0.1",
"dependencies": { "dependencies": {
"@angular/animations": "^14.0.0", "@angular/animations": "^14.0.0",
"@angular/cdk": "^14.2.1", "@angular/cdk": "^14.2.1",
@ -15,14 +15,18 @@
"@angular/core": "^14.0.0", "@angular/core": "^14.0.0",
"@angular/fire": "^7.4.1", "@angular/fire": "^7.4.1",
"@angular/forms": "^14.0.0", "@angular/forms": "^14.0.0",
"@angular/material": "^14.2.1",
"@angular/platform-browser": "^14.0.0", "@angular/platform-browser": "^14.0.0",
"@angular/platform-browser-dynamic": "^14.0.0", "@angular/platform-browser-dynamic": "^14.0.0",
"@angular/router": "^14.0.0", "@angular/router": "^14.0.0",
"@angular/service-worker": "^14.0.0", "@angular/service-worker": "^14.0.0",
"@fortawesome/angular-fontawesome": "^0.11.1", "@fortawesome/angular-fontawesome": "^0.11.1",
"@fortawesome/fontawesome-free": "^6.0.0",
"@fortawesome/fontawesome-svg-core": "^6.2.0", "@fortawesome/fontawesome-svg-core": "^6.2.0",
"@fortawesome/free-brands-svg-icons": "^6.2.0", "@fortawesome/free-brands-svg-icons": "^6.2.0",
"@fortawesome/free-solid-svg-icons": "^6.2.0", "@fortawesome/free-solid-svg-icons": "^6.2.0",
"@ngrx/effects": "^14.3.2",
"@ngrx/store": "^14.3.2",
"@types/uuid": "^8.3.4", "@types/uuid": "^8.3.4",
"angular-moment-timezone": "^1.7.1", "angular-moment-timezone": "^1.7.1",
"angularx-qrcode": "^14.0.0", "angularx-qrcode": "^14.0.0",
@ -30,6 +34,7 @@
"firebase": "^9.9.3", "firebase": "^9.9.3",
"google-libphonenumber": "^3.2.30", "google-libphonenumber": "^3.2.30",
"jsbarcode": "^3.11.5", "jsbarcode": "^3.11.5",
"mdb-angular-ui-kit": "^3.0.0",
"ngx-sharebuttons": "^11.0.0", "ngx-sharebuttons": "^11.0.0",
"primeicons": "^5.0.0", "primeicons": "^5.0.0",
"primeng": "^14.0.1", "primeng": "^14.0.1",
@ -44,6 +49,7 @@
"@angular/compiler-cli": "^14.0.0", "@angular/compiler-cli": "^14.0.0",
"@types/google-libphonenumber": "^7.4.23", "@types/google-libphonenumber": "^7.4.23",
"@types/jasmine": "~4.0.0", "@types/jasmine": "~4.0.0",
"@types/lodash": "^4.14.186",
"jasmine-core": "~4.1.0", "jasmine-core": "~4.1.0",
"karma": "~6.3.0", "karma": "~6.3.0",
"karma-chrome-launcher": "~3.1.0", "karma-chrome-launcher": "~3.1.0",
@ -665,6 +671,23 @@
"rxjs": "^6.5.3 || ^7.4.0" "rxjs": "^6.5.3 || ^7.4.0"
} }
}, },
"node_modules/@angular/material": {
"version": "14.2.1",
"resolved": "https://registry.npmjs.org/@angular/material/-/material-14.2.1.tgz",
"integrity": "sha512-e7DkKJTuqrSpKPhxahrqkZt6AeU5ld5/aSeCamq2dcdqfZ8otmgiajzN0cXZGwSCT2Lth6c+QV3yn8ufTJQpTw==",
"dependencies": {
"tslib": "^2.3.0"
},
"peerDependencies": {
"@angular/animations": "^14.0.0 || ^15.0.0",
"@angular/cdk": "14.2.1",
"@angular/common": "^14.0.0 || ^15.0.0",
"@angular/core": "^14.0.0 || ^15.0.0",
"@angular/forms": "^14.0.0 || ^15.0.0",
"@angular/platform-browser": "^14.0.0 || ^15.0.0",
"rxjs": "^6.5.3 || ^7.4.0"
}
},
"node_modules/@angular/platform-browser": { "node_modules/@angular/platform-browser": {
"version": "14.1.3", "version": "14.1.3",
"resolved": "https://registry.npmjs.org/@angular/platform-browser/-/platform-browser-14.1.3.tgz", "resolved": "https://registry.npmjs.org/@angular/platform-browser/-/platform-browser-14.1.3.tgz",
@ -3225,6 +3248,15 @@
"node": ">=6" "node": ">=6"
} }
}, },
"node_modules/@fortawesome/fontawesome-free": {
"version": "6.2.1",
"resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-free/-/fontawesome-free-6.2.1.tgz",
"integrity": "sha512-viouXhegu/TjkvYQoiRZK3aax69dGXxgEjpvZW81wIJdxm5Fnvp3VVIP4VHKqX4SvFw6qpmkILkD4RJWAdrt7A==",
"hasInstallScript": true,
"engines": {
"node": ">=6"
}
},
"node_modules/@fortawesome/fontawesome-svg-core": { "node_modules/@fortawesome/fontawesome-svg-core": {
"version": "6.2.0", "version": "6.2.0",
"resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-svg-core/-/fontawesome-svg-core-6.2.0.tgz", "resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-svg-core/-/fontawesome-svg-core-6.2.0.tgz",
@ -3490,6 +3522,31 @@
"integrity": "sha512-Hcv+nVC0kZnQ3tD9GVu5xSMR4VVYOteQIr/hwFPVEvPdlXqgGEuRjiheChHgdM+JyqdgNcmzZOX/tnl0JOiI7A==", "integrity": "sha512-Hcv+nVC0kZnQ3tD9GVu5xSMR4VVYOteQIr/hwFPVEvPdlXqgGEuRjiheChHgdM+JyqdgNcmzZOX/tnl0JOiI7A==",
"dev": true "dev": true
}, },
"node_modules/@ngrx/effects": {
"version": "14.3.2",
"resolved": "https://registry.npmjs.org/@ngrx/effects/-/effects-14.3.2.tgz",
"integrity": "sha512-6bpGfA44jzwhBcmNaTwVgnFmYOX9iKPFpXyetDe41tVESo1CsNhUBPTmISDXKN9Mx2mwGbsMxrn6QFRypSsKAQ==",
"dependencies": {
"tslib": "^2.0.0"
},
"peerDependencies": {
"@angular/core": "^14.0.0",
"@ngrx/store": "14.3.2",
"rxjs": "^6.5.3 || ^7.5.0"
}
},
"node_modules/@ngrx/store": {
"version": "14.3.2",
"resolved": "https://registry.npmjs.org/@ngrx/store/-/store-14.3.2.tgz",
"integrity": "sha512-XGHjr0arh6gClo8Ce+xqJLvW9PkeXPW2tCo9Z5qMtHFI/z5dUppLVKGmMgD/fQBDyoqWTK5xu+89tDkdeZfRjQ==",
"dependencies": {
"tslib": "^2.0.0"
},
"peerDependencies": {
"@angular/core": "^14.0.0",
"rxjs": "^6.5.3 || ^7.5.0"
}
},
"node_modules/@ngtools/webpack": { "node_modules/@ngtools/webpack": {
"version": "14.1.3", "version": "14.1.3",
"resolved": "https://registry.npmjs.org/@ngtools/webpack/-/webpack-14.1.3.tgz", "resolved": "https://registry.npmjs.org/@ngtools/webpack/-/webpack-14.1.3.tgz",
@ -3914,6 +3971,12 @@
"resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.11.tgz", "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.11.tgz",
"integrity": "sha512-wOuvG1SN4Us4rez+tylwwwCV1psiNVOkJeM3AUWUNWg/jDQY2+HE/444y5gc+jBmRqASOm2Oeh5c1axHobwRKQ==" "integrity": "sha512-wOuvG1SN4Us4rez+tylwwwCV1psiNVOkJeM3AUWUNWg/jDQY2+HE/444y5gc+jBmRqASOm2Oeh5c1axHobwRKQ=="
}, },
"node_modules/@types/lodash": {
"version": "4.14.186",
"resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.14.186.tgz",
"integrity": "sha512-eHcVlLXP0c2FlMPm56ITode2AgLMSa6aJ05JTTbYbI+7EMkCEE5qk2E41d5g2lCVTqRe0GnnRFurmlCsDODrPw==",
"dev": true
},
"node_modules/@types/long": { "node_modules/@types/long": {
"version": "4.0.2", "version": "4.0.2",
"resolved": "https://registry.npmjs.org/@types/long/-/long-4.0.2.tgz", "resolved": "https://registry.npmjs.org/@types/long/-/long-4.0.2.tgz",
@ -9241,6 +9304,21 @@
"node": ">= 8" "node": ">= 8"
} }
}, },
"node_modules/mdb-angular-ui-kit": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/mdb-angular-ui-kit/-/mdb-angular-ui-kit-3.0.0.tgz",
"integrity": "sha512-D1gP9pC6CwYHg1O6WJeXTr6k78FNU/A55Gsez5ICTj6rM2sn3A3FoxrIYz8OGgaAVhm5NjBxX7N+2R0EF7dVwQ==",
"dependencies": {
"tslib": "^2.0.0"
},
"peerDependencies": {
"@angular/animations": "^14.0.0",
"@angular/cdk": "^14.0.0",
"@angular/common": "^14.0.0",
"@angular/core": "^14.0.0",
"@angular/forms": "^14.0.0"
}
},
"node_modules/media-typer": { "node_modules/media-typer": {
"version": "0.3.0", "version": "0.3.0",
"resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz",
@ -14157,6 +14235,14 @@
"tslib": "^2.3.0" "tslib": "^2.3.0"
} }
}, },
"@angular/material": {
"version": "14.2.1",
"resolved": "https://registry.npmjs.org/@angular/material/-/material-14.2.1.tgz",
"integrity": "sha512-e7DkKJTuqrSpKPhxahrqkZt6AeU5ld5/aSeCamq2dcdqfZ8otmgiajzN0cXZGwSCT2Lth6c+QV3yn8ufTJQpTw==",
"requires": {
"tslib": "^2.3.0"
}
},
"@angular/platform-browser": { "@angular/platform-browser": {
"version": "14.1.3", "version": "14.1.3",
"resolved": "https://registry.npmjs.org/@angular/platform-browser/-/platform-browser-14.1.3.tgz", "resolved": "https://registry.npmjs.org/@angular/platform-browser/-/platform-browser-14.1.3.tgz",
@ -15956,6 +16042,11 @@
"resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-common-types/-/fontawesome-common-types-6.2.0.tgz", "resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-common-types/-/fontawesome-common-types-6.2.0.tgz",
"integrity": "sha512-rBevIsj2nclStJ7AxTdfsa3ovHb1H+qApwrxcTVo+NNdeJiB9V75hsKfrkG5AwNcRUNxrPPiScGYCNmLMoh8pg==" "integrity": "sha512-rBevIsj2nclStJ7AxTdfsa3ovHb1H+qApwrxcTVo+NNdeJiB9V75hsKfrkG5AwNcRUNxrPPiScGYCNmLMoh8pg=="
}, },
"@fortawesome/fontawesome-free": {
"version": "6.2.1",
"resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-free/-/fontawesome-free-6.2.1.tgz",
"integrity": "sha512-viouXhegu/TjkvYQoiRZK3aax69dGXxgEjpvZW81wIJdxm5Fnvp3VVIP4VHKqX4SvFw6qpmkILkD4RJWAdrt7A=="
},
"@fortawesome/fontawesome-svg-core": { "@fortawesome/fontawesome-svg-core": {
"version": "6.2.0", "version": "6.2.0",
"resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-svg-core/-/fontawesome-svg-core-6.2.0.tgz", "resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-svg-core/-/fontawesome-svg-core-6.2.0.tgz",
@ -16168,6 +16259,22 @@
"integrity": "sha512-Hcv+nVC0kZnQ3tD9GVu5xSMR4VVYOteQIr/hwFPVEvPdlXqgGEuRjiheChHgdM+JyqdgNcmzZOX/tnl0JOiI7A==", "integrity": "sha512-Hcv+nVC0kZnQ3tD9GVu5xSMR4VVYOteQIr/hwFPVEvPdlXqgGEuRjiheChHgdM+JyqdgNcmzZOX/tnl0JOiI7A==",
"dev": true "dev": true
}, },
"@ngrx/effects": {
"version": "14.3.2",
"resolved": "https://registry.npmjs.org/@ngrx/effects/-/effects-14.3.2.tgz",
"integrity": "sha512-6bpGfA44jzwhBcmNaTwVgnFmYOX9iKPFpXyetDe41tVESo1CsNhUBPTmISDXKN9Mx2mwGbsMxrn6QFRypSsKAQ==",
"requires": {
"tslib": "^2.0.0"
}
},
"@ngrx/store": {
"version": "14.3.2",
"resolved": "https://registry.npmjs.org/@ngrx/store/-/store-14.3.2.tgz",
"integrity": "sha512-XGHjr0arh6gClo8Ce+xqJLvW9PkeXPW2tCo9Z5qMtHFI/z5dUppLVKGmMgD/fQBDyoqWTK5xu+89tDkdeZfRjQ==",
"requires": {
"tslib": "^2.0.0"
}
},
"@ngtools/webpack": { "@ngtools/webpack": {
"version": "14.1.3", "version": "14.1.3",
"resolved": "https://registry.npmjs.org/@ngtools/webpack/-/webpack-14.1.3.tgz", "resolved": "https://registry.npmjs.org/@ngtools/webpack/-/webpack-14.1.3.tgz",
@ -16521,6 +16628,12 @@
"resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.11.tgz", "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.11.tgz",
"integrity": "sha512-wOuvG1SN4Us4rez+tylwwwCV1psiNVOkJeM3AUWUNWg/jDQY2+HE/444y5gc+jBmRqASOm2Oeh5c1axHobwRKQ==" "integrity": "sha512-wOuvG1SN4Us4rez+tylwwwCV1psiNVOkJeM3AUWUNWg/jDQY2+HE/444y5gc+jBmRqASOm2Oeh5c1axHobwRKQ=="
}, },
"@types/lodash": {
"version": "4.14.186",
"resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.14.186.tgz",
"integrity": "sha512-eHcVlLXP0c2FlMPm56ITode2AgLMSa6aJ05JTTbYbI+7EMkCEE5qk2E41d5g2lCVTqRe0GnnRFurmlCsDODrPw==",
"dev": true
},
"@types/long": { "@types/long": {
"version": "4.0.2", "version": "4.0.2",
"resolved": "https://registry.npmjs.org/@types/long/-/long-4.0.2.tgz", "resolved": "https://registry.npmjs.org/@types/long/-/long-4.0.2.tgz",
@ -20455,6 +20568,14 @@
} }
} }
}, },
"mdb-angular-ui-kit": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/mdb-angular-ui-kit/-/mdb-angular-ui-kit-3.0.0.tgz",
"integrity": "sha512-D1gP9pC6CwYHg1O6WJeXTr6k78FNU/A55Gsez5ICTj6rM2sn3A3FoxrIYz8OGgaAVhm5NjBxX7N+2R0EF7dVwQ==",
"requires": {
"tslib": "^2.0.0"
}
},
"media-typer": { "media-typer": {
"version": "0.3.0", "version": "0.3.0",
"resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz",

View File

@ -1,9 +1,9 @@
{ {
"name": "fashion-logica", "name": "sakura",
"version": "0.0.1", "version": "0.0.1",
"scripts": { "scripts": {
"ng": "ng", "ng": "ng",
"start": "ng serve --host 192.168.0.179", "start": "ng serve --host 192.168.0.14 --verbose",
"build": "ng build", "build": "ng build",
"watch": "ng build --watch --configuration development", "watch": "ng build --watch --configuration development",
"test": "ng test" "test": "ng test"
@ -17,14 +17,18 @@
"@angular/core": "^14.0.0", "@angular/core": "^14.0.0",
"@angular/fire": "^7.4.1", "@angular/fire": "^7.4.1",
"@angular/forms": "^14.0.0", "@angular/forms": "^14.0.0",
"@angular/material": "^14.2.1",
"@angular/platform-browser": "^14.0.0", "@angular/platform-browser": "^14.0.0",
"@angular/platform-browser-dynamic": "^14.0.0", "@angular/platform-browser-dynamic": "^14.0.0",
"@angular/router": "^14.0.0", "@angular/router": "^14.0.0",
"@angular/service-worker": "^14.0.0", "@angular/service-worker": "^14.0.0",
"@fortawesome/angular-fontawesome": "^0.11.1", "@fortawesome/angular-fontawesome": "^0.11.1",
"@fortawesome/fontawesome-free": "^6.0.0",
"@fortawesome/fontawesome-svg-core": "^6.2.0", "@fortawesome/fontawesome-svg-core": "^6.2.0",
"@fortawesome/free-brands-svg-icons": "^6.2.0", "@fortawesome/free-brands-svg-icons": "^6.2.0",
"@fortawesome/free-solid-svg-icons": "^6.2.0", "@fortawesome/free-solid-svg-icons": "^6.2.0",
"@ngrx/effects": "^14.3.2",
"@ngrx/store": "^14.3.2",
"@types/uuid": "^8.3.4", "@types/uuid": "^8.3.4",
"angular-moment-timezone": "^1.7.1", "angular-moment-timezone": "^1.7.1",
"angularx-qrcode": "^14.0.0", "angularx-qrcode": "^14.0.0",
@ -32,6 +36,7 @@
"firebase": "^9.9.3", "firebase": "^9.9.3",
"google-libphonenumber": "^3.2.30", "google-libphonenumber": "^3.2.30",
"jsbarcode": "^3.11.5", "jsbarcode": "^3.11.5",
"mdb-angular-ui-kit": "^3.0.0",
"ngx-sharebuttons": "^11.0.0", "ngx-sharebuttons": "^11.0.0",
"primeicons": "^5.0.0", "primeicons": "^5.0.0",
"primeng": "^14.0.1", "primeng": "^14.0.1",
@ -46,6 +51,7 @@
"@angular/compiler-cli": "^14.0.0", "@angular/compiler-cli": "^14.0.0",
"@types/google-libphonenumber": "^7.4.23", "@types/google-libphonenumber": "^7.4.23",
"@types/jasmine": "~4.0.0", "@types/jasmine": "~4.0.0",
"@types/lodash": "^4.14.186",
"jasmine-core": "~4.1.0", "jasmine-core": "~4.1.0",
"karma": "~6.3.0", "karma": "~6.3.0",
"karma-chrome-launcher": "~3.1.0", "karma-chrome-launcher": "~3.1.0",

29
angular/proxy.confi.json Normal file
View File

@ -0,0 +1,29 @@
{
"/icard-proxy": {
"target": "https://sakura.lk.crm4retail.ru/api/icard-proxy",
"secure": false,
"pathRewrite": {
"^/icard-proxy": ""
},
"changeOrigin": true,
"logLevel": "debug"
},
"/static": {
"target": "https://sakura.lk.crm4retail.ru/static",
"secure": false,
"pathRewrite": {
"^/static": ""
},
"changeOrigin": true,
"logLevel": "debug"
},
"/api/orders": {
"target": "https://sakura.lk.crm4retail.ru/api/orders",
"secure": false,
"pathRewrite": {
"^/api/orders": ""
},
"changeOrigin": true,
"logLevel": "debug"
}
}

View File

@ -0,0 +1,5 @@
<app-navbar></app-navbar>
<p-toast position="top-center"></p-toast>
<div class="layout">
<router-outlet></router-outlet>
</div>

View File

@ -0,0 +1,5 @@
:host {
.layout {
padding: 0 16px 16px;
}
}

View File

@ -20,16 +20,16 @@ describe('AppComponent', () => {
expect(app).toBeTruthy(); expect(app).toBeTruthy();
}); });
it(`should have as title 'fashion-logica'`, () => { it(`should have as title 'sakura'`, () => {
const fixture = TestBed.createComponent(AppComponent); const fixture = TestBed.createComponent(AppComponent);
const app = fixture.componentInstance; const app = fixture.componentInstance;
expect(app.title).toEqual('fashion-logica'); expect(app.title).toEqual('sakura');
}); });
it('should render title', () => { it('should render title', () => {
const fixture = TestBed.createComponent(AppComponent); const fixture = TestBed.createComponent(AppComponent);
fixture.detectChanges(); fixture.detectChanges();
const compiled = fixture.nativeElement as HTMLElement; const compiled = fixture.nativeElement as HTMLElement;
expect(compiled.querySelector('.content span')?.textContent).toContain('fashion-logica app is running!'); expect(compiled.querySelector('.content span')?.textContent).toContain('sakura app is running!');
}); });
}); });

View File

@ -0,0 +1,22 @@
import { Component, OnInit } from '@angular/core';
import { Store } from '@ngrx/store';
import { PrimeNGConfig } from 'primeng/api';
import * as ConfigActions from './state/config/config.actions';
import * as ProfileActions from './state/profile/profile.actions';
@Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.scss'],
})
export class AppComponent implements OnInit {
title = 'Sakura';
constructor(private primengConfig: PrimeNGConfig, private store: Store) {}
ngOnInit() {
this.primengConfig.ripple = false;
this.store.dispatch(ConfigActions.getConfig());
this.store.dispatch(ProfileActions.getProfile());
}
}

View File

@ -0,0 +1,158 @@
import { MatDateFormats } from '@angular/material/core';
import {
IOptionDateFilter,
MainPageCode,
OrderStatus,
Page,
PageCode,
PaymentMethod,
} from './interface/data';
export const PageList: Page[] = [
// {
// code: PageCode.Auth,
// name: 'Вход',
// resName: 'auth',
// onSideBar: false,
// },
{
code: PageCode.Orders,
name: 'Заказы',
resName: 'orders',
onSideBar: true,
},
];
export const PageListWithBonus: Page[] = [
// {
// code: PageCode.Auth,
// name: 'Вход',
// resName: 'auth',
// onSideBar: false,
// },
{
code: PageCode.BonusProgram,
name: 'Ваша карта лояльности',
description: '',
resName: 'bonus-program',
onSideBar: false,
},
{
code: PageCode.Transactions,
name: 'Чеки',
description: '',
resName: 'transactions',
onSideBar: true,
},
{
code: PageCode.Orders,
name: 'Заказы',
description: '',
resName: 'orders',
onSideBar: true,
},
{
code: PageCode.Logout,
name: 'Выйти',
description: '',
resName: 'logout',
onSideBar: true,
},
// {
// code: PageCode.UserData,
// name: 'Заполнить анкету',
// description: '',
// resName: 'user-data',
// onSideBar: true
// },
// {
// code: PageCode.RefSystem,
// name: 'Пригласить друга',
// description: '',
// resName: 'ref-system',
// onSideBar: true,
// },
];
export const PageListMain: Page[] = [
{
code: MainPageCode.Account,
name: 'Аккаунт',
resName: 'account',
onSideBar: true,
icon: 'person',
},
{
code: MainPageCode.Products,
name: 'Товары',
resName: 'products',
onSideBar: true,
icon: 'manage_search',
},
{
code: MainPageCode.Cart,
name: 'Корзина',
resName: 'cart',
onSideBar: true,
icon: 'shopping_bag',
},
// {
// code: MainPageCode.Info,
// name: 'О нас',
// resName: 'info',
// onSideBar: true,
// icon: 'info'
// },
];
export const orderStatuses: OrderStatus = {
Cancelled: 'Отменен',
InProcessing: 'В обработке',
Unconfirmed: 'Принят',
WaitCooking: 'Принят',
ReadyForCooking: 'Принят',
CookingStarted: 'Готовится',
CookingCompleted: 'Приготовлен',
Waiting: 'В пути',
OnWay: 'В пути',
Delivered: 'Выполнен',
Closed: 'Выполнен',
};
export const paymentMethods: PaymentMethod[] = [
{
type: 'Card',
label: 'Безналичный расчет',
},
{
type: 'Cash',
label: 'Наличными',
},
];
export const dateFilterOptions: IOptionDateFilter[] = [
{
name: 'Текущий месяц',
value: 'currentMonth',
},
{
name: 'Прошлый месяц',
value: 'lastMonth',
},
{
name: 'Между',
value: 'between',
},
];
export const APP_DATE_FORMATS: MatDateFormats = {
parse: {
dateInput: { month: 'short', year: 'numeric', day: 'numeric' },
},
display: {
dateInput: 'input',
monthYearLabel: { year: 'numeric', month: 'numeric' },
dateA11yLabel: { year: 'numeric', month: 'long', day: 'numeric' },
monthYearA11yLabel: { year: 'numeric', month: 'long' },
},
};

View File

@ -0,0 +1,194 @@
import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { RouterModule, Routes } from '@angular/router';
import { AppRoutingModule } from './app-routing.module';
import { AppComponent } from './app.component';
import { MainComponent } from './pages/main/main.component';
import { NavbarComponent } from './components/navbar/navbar.component';
import { CardComponent } from './components/card/card.component';
import { InputMaskModule } from 'primeng/inputmask';
import { AuthComponent } from './pages/account/auth/auth.component';
import { ProgressSpinnerModule } from 'primeng/progressspinner';
import { FormsModule, ReactiveFormsModule } from '@angular/forms';
import { AccountComponent } from './pages/account/account.component';
import { ExitComponent } from './components/exit/exit.component';
import { HttpClientModule, HTTP_INTERCEPTORS } from '@angular/common/http';
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
import { DialogService } from 'primeng/dynamicdialog';
import { BonusProgramComponent } from './pages/account/bonus-program/bonus-program.component';
import { OrdersComponent } from './pages/account/orders/orders.component';
import { OrderInfoComponent } from './components/order-info/order-info.component';
import { ServiceWorkerModule } from '@angular/service-worker';
import { environment } from '../environments/environment';
import { AngularFireModule } from '@angular/fire/compat';
import { AngularFireMessagingModule } from '@angular/fire/compat/messaging';
import { ToastModule } from 'primeng/toast';
import { MessageService } from 'primeng/api';
import { FooterButtonsComponent } from './components/footer-buttons/footer-buttons.component';
import { UserDataComponent } from './pages/account/user-data/user-data.component';
import { RefSystemComponent } from './pages/account/ref-system/ref-system.component';
import { QRCodeModule } from 'angularx-qrcode';
import { ShareButtonsModule } from 'ngx-sharebuttons/buttons';
import { ShareIconsModule } from 'ngx-sharebuttons/icons';
import { MessagingService } from './services/messaging.service';
import { NotFoundComponent } from './pages/not-found/not-found.component';
import { ProductsComponent } from './pages/products/products.component';
import { CartComponent } from './pages/cart/cart.component';
import { ListboxModule } from 'primeng/listbox';
import { ProductModalComponent } from './components/product-modal/product-modal.component';
import { CheckboxGroupComponent } from './components/checkbox-group/checkbox-group.component';
import { TreeSelectModule } from 'primeng/treeselect';
import { UserDataOrderComponent } from './components/user-data-order/user-data-order.component';
import { DropdownModule } from 'primeng/dropdown';
import { SelectButtonModule } from 'primeng/selectbutton';
import { CalendarModule } from 'primeng/calendar';
import { MatIconModule } from '@angular/material/icon';
import { InfoComponent } from './pages/info/info.component';
import { MdbCarouselModule } from 'mdb-angular-ui-kit/carousel';
import { StoreModule } from '@ngrx/store';
import { configReducer } from './state/config/config.reducer';
import { EffectsModule } from '@ngrx/effects';
import { ConfigEffects } from './state/config/config.effects';
import { PaginatorModule } from 'primeng/paginator';
import { InputTextModule } from 'primeng/inputtext';
import { ChangeQuantityComponent } from './components/change-quantity/change-quantity.component';
import { MenuComponent } from './components/menu/menu.component';
import { SidebarModule } from 'primeng/sidebar';
import { RippleModule } from 'primeng/ripple';
import { MatTabsModule } from '@angular/material/tabs';
import { ModifierComponent } from './components/modifier/modifier.component';
import { OptionComponent } from './components/option/option.component';
import { ChangeQuantityOptionDirective } from './directives/change-quantity-option.directive';
import { MatSelectModule } from '@angular/material/select';
import {
MatFormFieldModule,
MAT_FORM_FIELD_DEFAULT_OPTIONS,
} from '@angular/material/form-field';
import { MatInputModule } from '@angular/material/input';
import { MatBottomSheetModule } from '@angular/material/bottom-sheet';
import { TerminalListComponent } from './components/terminal-list/terminal-list.component';
import { MatButtonModule } from '@angular/material/button';
import { MatListModule } from '@angular/material/list';
import { MatSnackBarModule } from '@angular/material/snack-bar';
import { SnackBarComponent } from './components/snack-bar/snack-bar.component';
import {MatDialogModule} from '@angular/material/dialog';
import { PurchaseInfoComponent } from './components/purchase-info/purchase-info.component';
import { DateFilterComponent } from './components/date-filter/date-filter.component';
import {MatDatepickerModule} from '@angular/material/datepicker';
import {MatNativeDateModule} from '@angular/material/core';
import { profileReducer } from './state/profile/profile.reducer';
import { ProfileEffects } from './state/profile/profile.effects';
import { CustomerInfoInterceptor } from './interceptors/customer-info.interceptor';
import { PurcahsesComponent } from './pages/account/purcahses/purcahses.component';
const routes: Routes = [
{ path: '', redirectTo: 'products', pathMatch: 'full' },
{ path: 'products', component: ProductsComponent },
// { path: 'cart', component: CartComponent },
{ path: 'account', component: AccountComponent },
{ path: '**', component: NotFoundComponent },
];
@NgModule({
declarations: [
AppComponent,
NavbarComponent,
MainComponent,
CardComponent,
AuthComponent,
AccountComponent,
ExitComponent,
BonusProgramComponent,
OrdersComponent,
OrderInfoComponent,
FooterButtonsComponent,
UserDataComponent,
RefSystemComponent,
NotFoundComponent,
ProductsComponent,
CartComponent,
ProductModalComponent,
CheckboxGroupComponent,
UserDataOrderComponent,
InfoComponent,
ChangeQuantityComponent,
MenuComponent,
ModifierComponent,
OptionComponent,
ChangeQuantityOptionDirective,
TerminalListComponent,
SnackBarComponent,
PurchaseInfoComponent,
DateFilterComponent,
PurcahsesComponent,
],
imports: [
BrowserModule,
AppRoutingModule,
RouterModule.forRoot(routes, {
scrollPositionRestoration: 'enabled',
}),
InputMaskModule,
ProgressSpinnerModule,
FormsModule,
HttpClientModule,
BrowserAnimationsModule,
BrowserModule,
ServiceWorkerModule.register('ngsw-worker.js', {
enabled: environment.production,
// Register the ServiceWorker as soon as the application is stable
// or after 30 seconds (whichever comes first).
registrationStrategy: 'registerWhenStable:30000',
}),
AngularFireModule.initializeApp(environment.firebase),
AngularFireMessagingModule,
ToastModule,
ReactiveFormsModule,
QRCodeModule,
ShareButtonsModule.withConfig({
debug: true,
}),
ShareIconsModule,
ListboxModule,
TreeSelectModule,
DropdownModule,
SelectButtonModule,
CalendarModule,
MatIconModule,
MdbCarouselModule,
StoreModule.forRoot({ config: configReducer, profile: profileReducer }),
EffectsModule.forRoot([ConfigEffects, ProfileEffects]),
PaginatorModule,
InputTextModule,
SidebarModule,
RippleModule,
MatTabsModule,
MatSelectModule,
MatFormFieldModule,
MatInputModule,
MatBottomSheetModule,
MatButtonModule,
MatListModule,
MatSnackBarModule,
MatDialogModule,
MatDatepickerModule,
MatNativeDateModule
],
providers: [
DialogService,
MessageService,
MessagingService,
{
provide: MAT_FORM_FIELD_DEFAULT_OPTIONS,
useValue: { appearance: 'outline' },
},
{
provide: HTTP_INTERCEPTORS,
useClass: CustomerInfoInterceptor,
multi: true,
},
],
bootstrap: [AppComponent],
})
export class AppModule {}

View File

@ -0,0 +1,19 @@
<div class="change-quantity">
<button (click)="changeValue({type: 'minus', variableQuantity: variableQuantity})"
[disabled]="disabledButton.includes('minus')"
[ngClass]="{
'is-active': !disabledButton.includes('minus')
}">
-
</button>
<span>
{{value}}
</span>
<button (click)="changeValue({type: 'plus', variableQuantity: variableQuantity})"
[disabled]="disabledButton.includes('plus')"
[ngClass]="{
'is-active': !disabledButton.includes('plus')
}">
+
</button>
</div>

View File

@ -0,0 +1,23 @@
:host {
.change-quantity {
border: 1px solid #dfdee2;
border-radius: 1.125rem;
display: flex;
align-items: center;
justify-content: space-between;
margin: 0;
height: 100%;
min-width: 100px;
padding: 0 8px;
button {
background: #ffffff;
border: 0;
font-size: 28px;
font-weight: 300;
&:disabled {
opacity: 0.3;
}
}
}
}

View File

@ -0,0 +1,23 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { ChangeQuantityComponent } from './change-quantity.component';
describe('ChangeQuantityComponent', () => {
let component: ChangeQuantityComponent;
let fixture: ComponentFixture<ChangeQuantityComponent>;
beforeEach(async () => {
await TestBed.configureTestingModule({
declarations: [ ChangeQuantityComponent ]
})
.compileComponents();
fixture = TestBed.createComponent(ChangeQuantityComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});

View File

@ -0,0 +1,28 @@
import { Component, EventEmitter, Input, OnInit, Output } from '@angular/core';
export interface ChangeValue {
type: 'plus' | 'minus',
variableQuantity: number
}
@Component({
selector: 'app-change-quantity',
templateUrl: './change-quantity.component.html',
styleUrls: ['./change-quantity.component.scss']
})
export class ChangeQuantityComponent implements OnInit {
@Output() onChangeValue = new EventEmitter<ChangeValue>();
@Input() variableQuantity: number = 1
@Input() value: number = 1
@Input() disabledButton: 'minus' | 'plus' | 'none' | string[] = 'none'
constructor() { }
ngOnInit(): void {
}
changeValue(changeValue: ChangeValue) {
this.onChangeValue.emit(changeValue)
}
}

View File

@ -0,0 +1,34 @@
<!-- <div *ngFor="let option of options; let index = index;" class="extra_options_checkbox">
<div class="extra_options_label"><label>{{option.name}}</label></div>
<div class="extra_options_value">
<div class="woofood-cbx-wrapper">
<input class="inp-woofood-cbx"
[disabled]="!optionIsSelected(option) && selectedOptions.length >= modifier.restrictions.maxQuantity && (modifier.restrictions.maxQuantity > 1 || !modifier.restrictions.minQuantity)"
[checked]="optionIsSelected(option)"
[type]="modifier.restrictions.maxQuantity > 1 || !modifier.restrictions.minQuantity ? 'checkbox' : 'radio'"
style="display: none" name="add_extra_option" value="{{option.id}}" id="{{option.id}}"
(change)="onToggle(option)">
<label class="woofood-cbx" for="{{option.id}}">
<span>
<svg width="12px" height="10px" viewBox="0 0 12 10">
<polyline points="1.5 6 4.5 9 10.5 1"></polyline>
</svg>
</span>
<span>{{currencySymbol}}{{option.price}}</span>
</label>
</div>
</div>
</div> -->
<span *ngIf="modifier.restrictions.minQuantity > allQuantity" class="validator-text">
Минимальное количество продуктов: {{modifier.restrictions.minQuantity}}
</span>
<div *ngFor="let option of options; let index = index;" class="extra_options_checkbox">
<div class="extra_options_label"><label>{{option.name}}</label></div>
<div class="extra_options_value">
<app-change-quantity (onChangeValue)="changeQuantity($event, option)"
[value]="(option.quantity || option.quantity === 0) ? option.quantity : option.restrictions.byDefault"
[disabledButton]="getDisabledButton(option)">
</app-change-quantity>
</div>
</div>

View File

@ -0,0 +1,94 @@
:host {
.extra_options_checkbox {
width: 100%;
display: block;
overflow: hidden;
display: flex;
align-items: center;
.extra_options_label {
width: 70%;
float: left;
font-size: 14px;
label {
margin: 5px;
font-weight: normal;
}
}
.extra_options_value {
width: 30%;
float: left;
.woofood-cbx-wrapper {
width: 100%;
float: left;
.woofood-cbx {
margin: auto;
-webkit-user-select: none;
user-select: none;
cursor: pointer;
line-height: 30px;
margin-bottom: 0px;
span:first-child {
display: inline-block;
position: relative;
width: 18px;
height: 18px;
margin-right: 8px;
border-radius: 3px;
transform: scale(1);
vertical-align: middle;
border: 1px solid #9098A9;
transition: all 0.2s ease;
&::before {
content: "";
width: 100%;
height: 100%;
background: #cc0000;
display: block;
transform: scale(0);
opacity: 1;
border-radius: 50%;
}
svg {
position: absolute;
top: 3px;
left: 2px;
fill: none;
stroke: #FFFFFF;
stroke-width: 2;
stroke-linecap: round;
stroke-linejoin: round;
stroke-dasharray: 16px;
stroke-dashoffset: 16px;
}
}
}
}
}
.inp-woofood-cbx:checked+.woofood-cbx span:first-child {
background: #cc0000;
border-color: #cc0000;
animation: wave 0.4s ease;
svg {
stroke-dashoffset: 0 !important;
}
}
.inp-woofood-cbx:disabled+.woofood-cbx span:first-child {
border-color: #dfdfdf !important;
}
}
.validator-text {
color: #cc0000;
font-size: 12px;
}
}

View File

@ -0,0 +1,23 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { CheckboxGroupComponent } from './checkbox-group.component';
describe('CheckboxGroupComponent', () => {
let component: CheckboxGroupComponent;
let fixture: ComponentFixture<CheckboxGroupComponent>;
beforeEach(async () => {
await TestBed.configureTestingModule({
declarations: [ CheckboxGroupComponent ]
})
.compileComponents();
fixture = TestBed.createComponent(CheckboxGroupComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});

View File

@ -0,0 +1,65 @@
import {Component, EventEmitter, Input, OnInit, Output} from '@angular/core';
import {CartModifier, Modifier, ModifiersGroup, Option} from 'src/app/interface/data';
import { cloneDeep } from 'lodash/fp';
import { ChangeValue } from '../change-quantity/change-quantity.component';
@Component({
selector: 'app-checkbox-group',
templateUrl: './checkbox-group.component.html',
styleUrls: ['./checkbox-group.component.scss']
})
export class CheckboxGroupComponent implements OnInit {
constructor() { }
@Input() modifier!: CartModifier;
@Input() options!: Modifier[];
@Input() currencySymbol!: string;
@Input() selectedOptions: Modifier[] = [];
@Output() toggle = new EventEmitter<Modifier>();
@Output() onChangeQuantity = new EventEmitter<any>();
public allQuantity!: number
ngOnInit() {
this.allQuantity = this.getAllQuantity(this.options, 'quantity')
}
getAllQuantity(array: Array<any>, key: string) {
return array.reduce((a, b) => a + (b[key] || 0), 0);
}
optionIsSelected(option: Modifier): boolean{
return !!this.selectedOptions.find(selected => selected.id === option.id);
}
onToggle(option: Modifier){
this.toggle.emit(option);
}
changeQuantity(value: ChangeValue, option: Modifier) {
this.onChangeQuantity.emit({
value,
option,
modifierGroup: this.modifier
})
this.allQuantity = this.getAllQuantity(this.options, 'quantity')
}
getDisabledButton(option: Modifier) {
if (!option.quantity && option.quantity !== 0) option.quantity = option.restrictions.byDefault
const minusCondition = option.quantity === 0 || option.quantity === option.restrictions.minQuantity
const plusCondition = option.quantity === option.restrictions.maxQuantity && option.restrictions.maxQuantity !== 0
const maxQuantityCondition = this.allQuantity === this.modifier.restrictions.maxQuantity && this.modifier.restrictions.maxQuantity !== 0
const minQuantityCondition = this.allQuantity === this.modifier.restrictions.minQuantity
let result: any = 'none'
if (minusCondition || (minQuantityCondition && !minusCondition)) result = 'minus'
if (plusCondition || (maxQuantityCondition && !minusCondition)) result = 'plus'
if ((maxQuantityCondition && minusCondition) || (minQuantityCondition && plusCondition)) result = ['plus', 'minus']
return result
}
}

View File

@ -0,0 +1,43 @@
<div class="date-filter-container">
<form [formGroup]="dateFilterForm" (ngSubmit)="submitFilter()">
<mat-form-field appearance="fill">
<mat-select formControlName="filterType">
<mat-option *ngFor="let option of options" [value]="option.value">{{
option.name
}}</mat-option>
</mat-select>
<mat-label>Фильтр</mat-label>
<mat-hint>Тип</mat-hint>
</mat-form-field>
<div
*ngIf="currentFilterType === 'between'"
class="date-filter-container__date-inputs"
>
<mat-form-field class="date-range-input-field" appearance="fill">
<mat-label>Интервал времени</mat-label>
<mat-date-range-input
[rangePicker]="campaignTwoPicker"
[comparisonStart]="dateFilterForm.value.from"
[comparisonEnd]="dateFilterForm.value.to"
>
<input matStartDate placeholder="От: " formControlName="from" />
<input matEndDate placeholder="До: " formControlName="to" />
</mat-date-range-input>
<mat-hint>дд.мм.гггг дд.мм.гггг</mat-hint>
<mat-datepicker-toggle
matIconSuffix
[for]="campaignTwoPicker"
></mat-datepicker-toggle>
<mat-date-range-picker #campaignTwoPicker>
<mat-date-range-picker-actions>
<button mat-button matDateRangePickerCancel>Назад</button>
<button mat-raised-button color="primary" matDateRangePickerApply>
Подтвердить
</button>
</mat-date-range-picker-actions>
</mat-date-range-picker>
</mat-form-field>
</div>
<button mat-stroked-button class="apply" type="submit">Применить</button>
</form>
</div>

View File

@ -0,0 +1,28 @@
:host {
.date-filter-container {
& > form {
width: 100%;
display: flex;
flex-direction: row;
gap: 8px;
}
}
}
@media screen and (max-width: 1065px) {
:host {
.date-filter-container {
& > form {
flex-direction: column;
}
}
}
.date-range-input-field {
width: 100%;
}
}
.apply {
height: 51.67px;
}

View File

@ -0,0 +1,23 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { DateFilterComponent } from './date-filter.component';
describe('DateFilterComponent', () => {
let component: DateFilterComponent;
let fixture: ComponentFixture<DateFilterComponent>;
beforeEach(async () => {
await TestBed.configureTestingModule({
declarations: [ DateFilterComponent ]
})
.compileComponents();
fixture = TestBed.createComponent(DateFilterComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});

View File

@ -0,0 +1,70 @@
import { Component, EventEmitter, Input, OnInit, Output } from '@angular/core';
import { FormControl, FormGroup, Validators } from '@angular/forms';
import { DateAdapter, MAT_DATE_FORMATS, MAT_DATE_LOCALE } from '@angular/material/core';
import { MatSnackBar } from '@angular/material/snack-bar';
import moment from 'moment';
import { APP_DATE_FORMATS } from 'src/app/app.constants';
import { IDateFilter, IOptionDateFilter } from 'src/app/interface/data';
import { DateAdapterService } from 'src/app/services/date-adapter.service';
@Component({
selector: 'app-date-filter[options]',
templateUrl: './date-filter.component.html',
styleUrls: ['./date-filter.component.scss'],
providers: [
{ provide: DateAdapter, useClass: DateAdapterService },
{ provide: MAT_DATE_FORMATS, useValue: APP_DATE_FORMATS },
{ provide: MAT_DATE_LOCALE, useValue: 'ru-RU' }
],
})
export class DateFilterComponent implements OnInit {
@Input() defaultFilterType!: string;
@Output() onSubmitFilter = new EventEmitter<any>();
@Input() options!: IOptionDateFilter[];
public currentFilterType!: string;
public dateFilterForm = new FormGroup({
filterType: new FormControl<string>(''),
from: new FormControl<Date | null>(null),
to: new FormControl<Date | null>(null),
});
constructor(private _snackBar: MatSnackBar) {}
ngOnInit(): void {
this.currentFilterType = this.defaultFilterType
? this.defaultFilterType
: this.options[0].value;
const filterType = this.dateFilterForm.get('filterType');
filterType?.setValue(this.currentFilterType);
filterType?.valueChanges.subscribe({
next: (value) => {
this.currentFilterType = value ?? '';
const fromControl = this.dateFilterForm.get('from')
const toControl = this.dateFilterForm.get('to')
if (this.currentFilterType === 'between') {
this.dateFilterForm.controls.from.addValidators([
Validators.required,
]);
this.dateFilterForm.controls.to.addValidators([Validators.required]);
} else {
fromControl?.clearValidators()
toControl?.clearValidators()
}
fromControl?.updateValueAndValidity()
toControl?.updateValueAndValidity()
},
});
}
submitFilter() {
if (!this.dateFilterForm.valid) {
this._snackBar.open('Заполните все поля!', 'Ок', {
duration: 2000,
});
}
this.onSubmitFilter.emit(this.dateFilterForm.value)
}
}

View File

@ -2,7 +2,7 @@ button {
margin-right: 1rem; margin-right: 1rem;
padding: 4px 21px; padding: 4px 21px;
margin-top: 8px; margin-top: 8px;
background-color: #09467f; background-color: var(--orange-main);
color: #fff; color: #fff;
border-radius: 3px; border-radius: 3px;
border: none; border: none;

View File

@ -15,7 +15,7 @@
align-items: center; align-items: center;
width: 60px; width: 60px;
height: 60px; height: 60px;
background: #09467f; background: var(--orange-main);
border: solid #fff 1px !important; border: solid #fff 1px !important;
color: #fff; color: #fff;
border-radius: 100%; border-radius: 100%;

View File

@ -0,0 +1,54 @@
<nav class="main-menu-container" isShow="false" #menu>
<ul>
<ng-container
*ngFor="
let page of mainPageList;
let index = index;
let last = last;
let first = first
"
>
<li
*ngIf="page.onSideBar && !last"
class="main-menu-container__item"
[ngClass]="{
cart: page.resName === 'cart'
}"
[attr.data-counter]="page.resName === 'cart' ? cartCount : null"
[routerLink]="page.resName"
routerLinkActive="active"
>
<mat-icon
[ngClass]="{
'mat-icon': true
}"
>
{{ page.icon }}
</mat-icon>
<a>
{{ page.name }}
</a>
</li>
</ng-container>
<li
*ngIf="mainPageList[mainPageList.length - 1].onSideBar"
class="main-menu-container__item"
[ngClass]="{
cart: true
}"
[attr.data-counter]="cartCount"
(click)="changeMainPage(mainPageList[mainPageList.length - 1])"
>
<mat-icon
[ngClass]="{
'mat-icon': true
}"
>
{{ mainPageList[mainPageList.length - 1].icon }}
</mat-icon>
<a>
{{ mainPageList[mainPageList.length - 1].name }}
</a>
</li>
</ul>
</nav>

View File

@ -0,0 +1,122 @@
@import "src/sass/mixins";
:host {
.main-menu-container {
// position: fixed;
// width: 100%;
// bottom: 0;
// left: 0;
// margin: 0;
// z-index: 777;
// height: 57px;
// border-top: solid 1px #dfdfdf;
// background-color: #fff;
ul {
display: flex;
height: 100%;
font-size: 14px;
flex-wrap: nowrap;
flex-direction: row;
gap: 32px;
li {
width: 100%;
height: 100%;
display: flex;
align-items: center;
justify-content: center;
flex-direction: column;
gap: 0px;
font-size: 12px;
font-weight: 600;
letter-spacing: 2px;
color: #b5b5b9;
transition: color 0.3s ease;
cursor: pointer;
&.active {
color: #252525;
a {
border-bottom: 2px solid #e16f38;
}
}
@include hover-supported() {
color: #252525;
a {
border-bottom: 2px solid #e16f38;
}
}
a {
border-bottom: 2px solid #e16f3800;
}
& .mat-icon {
display: none;
&.is-active {
color: #000;
}
}
&.is-active {
color: #000;
}
&.cart {
position: relative;
&::before {
content: attr(data-counter);
color: #fff;
position: absolute;
right: -18px;
top: -8px;
background: #d7120b;
border-radius: 50px;
min-width: 1.2rem;
line-height: 1.2rem;
font-size: 0.8rem;
text-align: center;
display: flex;
align-items: center;
justify-content: center;
}
}
}
}
}
@media screen and (max-width: 600px) {
.main-menu-container {
position: fixed;
width: 100%;
bottom: 0;
left: 0;
margin: 0;
z-index: 777;
border-top: solid 1px #dfdfdf;
background-color: #fff;
padding-bottom: 16px;
padding-top: 8px;
ul {
li {
&.cart {
&::before {
right: 34px;
top: 5px;
}
}
& .mat-icon {
display: block;
}
}
}
}
}
}

View File

@ -0,0 +1,23 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { MenuComponent } from './menu.component';
describe('MenuComponent', () => {
let component: MenuComponent;
let fixture: ComponentFixture<MenuComponent>;
beforeEach(async () => {
await TestBed.configureTestingModule({
declarations: [ MenuComponent ]
})
.compileComponents();
fixture = TestBed.createComponent(MenuComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});

View File

@ -0,0 +1,48 @@
import { Component, EventEmitter, OnInit, Output } from '@angular/core';
import { ActivatedRoute, Router } from '@angular/router';
import { PageListMain } from 'src/app/app.constants';
import { Page } from 'src/app/interface/data';
import { CartService } from 'src/app/services/cart.service';
@Component({
selector: 'app-menu',
templateUrl: './menu.component.html',
styleUrls: ['./menu.component.scss'],
})
export class MenuComponent implements OnInit {
@Output() toggleMenu = new EventEmitter();
readonly mainPageList = PageListMain;
public cartCount = 0;
constructor(
private router: Router,
private route: ActivatedRoute,
private cartService: CartService
) {}
ngOnInit(): void {
this.cartCount = this.cartService.cartCount;
this.cartService.cartCount$.subscribe({
next: (count) => {
this.cartCount = count;
document
.querySelectorAll('.cart')[0]
.setAttribute('data-counter', this.cartCount.toString());
},
});
}
changeMainPage(page: Page, event?: MouseEvent): void {
if (event) {
event.preventDefault();
}
if (page.resName === 'cart') {
this.toggleMenu.emit()
return
}
this.router.navigate([page.resName], {
// relativeTo: this.route,
// queryParamsHandling: 'merge',
});
}
}

View File

@ -0,0 +1,12 @@
<div class="modifier-container">
<ng-content select="[counter]"></ng-content>
<h3>{{ modifier.name }}</h3>
<div class="modifier-container__options">
<app-option
*ngFor="let option of modifier.options"
[option]="option"
[modifier]="modifier"
[product]="product"
></app-option>
</div>
</div>

View File

@ -0,0 +1,15 @@
:host {
.modifier-container {
margin-bottom: 32px;
&__options {
display: flex;
flex-direction: row;
flex-wrap: wrap;
gap: 12px;
}
h3 {
font-weight: 500;
}
}
}

View File

@ -0,0 +1,23 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { ModifierComponent } from './modifier.component';
describe('ModifierComponent', () => {
let component: ModifierComponent;
let fixture: ComponentFixture<ModifierComponent>;
beforeEach(async () => {
await TestBed.configureTestingModule({
declarations: [ ModifierComponent ]
})
.compileComponents();
fixture = TestBed.createComponent(ModifierComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});

View File

@ -0,0 +1,19 @@
import { Component, EventEmitter, Input, OnInit, Output } from '@angular/core';
import { CartModifier, Modifier } from 'src/app/interface/data';
import { CartProduct } from 'src/app/models/cart-product';
@Component({
selector: 'app-modifier[modifier][product]',
templateUrl: './modifier.component.html',
styleUrls: ['./modifier.component.scss']
})
export class ModifierComponent implements OnInit {
@Input() product!: CartProduct
@Input() modifier!: CartModifier
constructor() { }
ngOnInit(): void {
}
}

View File

@ -0,0 +1,6 @@
<div class="container-navbar">
<img src="./assets/sakura-logo.png" alt="Логотип">
<!-- <span class="menu" (click)="showMenu()">Меню</span> -->
<app-cart #cart></app-cart>
<app-menu (toggleMenu)="cart.toggleSideBar()"></app-menu>
</div>

View File

@ -0,0 +1,44 @@
.container-navbar {
box-sizing: border-box;
padding: 16px 54px;
width: 100%;
height: fit-content;
color: #fff;
display: flex;
align-items: center;
justify-content: space-between;
img {
height: 68px;
border-radius: 12px;
}
.menu {
color: #252525;
font-weight: 600;
font-size: 14px;
letter-spacing: 2px;
border-bottom: 2px solid #e16f38;
cursor: pointer;
}
}
.title {
font-weight: 400;
font-size: 18px;
margin-left: 12px;
}
@media screen and (max-width: 600px) {
.container-navbar {
justify-content: center;
}
}
@media screen and (max-width: 549px) {
.container-navbar {
.menu {
display: none;
}
}
}

View File

@ -12,4 +12,13 @@ export class NavbarComponent implements OnInit {
ngOnInit(): void { ngOnInit(): void {
} }
showMenu() {
const menu = document.getElementsByClassName('main-menu-container')
if (menu[0].getAttribute('isShow') === 'true') {
menu[0].setAttribute('isShow', 'false')
} else {
menu[0].setAttribute('isShow', 'true')
}
}
} }

View File

@ -0,0 +1,17 @@
<div
appChangeQuantityOption
[option]="option"
[modifier]="modifier"
[product]="product"
[ngClass]="{
'option-container': true,
'selected': option.quantity
}"
[attr.quantity]="option.quantity"
>
<img
src="{{ option.image?.length ? option.image : './assets/no-image.png' }}"
alt="{{ option.name }}"
/>
<h4>{{ option.name }}</h4>
</div>

View File

@ -0,0 +1,67 @@
:host {
width: 100%;
max-width: 170px;
.option-container {
align-items: center;
border: 1px solid #d8d8d8;
border-radius: 1.125rem;
display: flex;
flex-direction: column;
gap: 8px;
padding: 0;
padding-bottom: 12px;
position: relative;
-webkit-user-select: none;
user-select: none;
width: 100%;
height: 100%;
overflow: hidden;
text-align: center;
cursor: pointer;
&.selected {
border-color: var(--orange-main);
}
&[quantity] {
&::before {
content: attr(quantity);
display: block;
width: 35px;
height: 35px;
background-color: var(--orange-main);
position: absolute;
border-radius: 100%;
display: flex;
justify-content: center;
align-items: center;
color: #fff;
right: 10px;
top: 6px;
opacity: 0.8;
}
}
&[quantity="0"] {
&::before {
display: none;
}
}
img {
width: 100%;
object-fit: cover;
}
h4 {
font-size: 14px;
margin: 0 10px;
}
}
}
@media screen and (max-width: 500px) {
:host {
max-width: calc(50% - 6px);
}
}

View File

@ -0,0 +1,23 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { OptionComponent } from './option.component';
describe('OptionComponent', () => {
let component: OptionComponent;
let fixture: ComponentFixture<OptionComponent>;
beforeEach(async () => {
await TestBed.configureTestingModule({
declarations: [ OptionComponent ]
})
.compileComponents();
fixture = TestBed.createComponent(OptionComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});

View File

@ -0,0 +1,20 @@
import { Component, EventEmitter, Input, OnInit, Output } from '@angular/core';
import { CartModifier, Modifier } from 'src/app/interface/data';
import { CartProduct } from 'src/app/models/cart-product';
@Component({
selector: 'app-option[option][modifier][product]',
templateUrl: './option.component.html',
styleUrls: ['./option.component.scss']
})
export class OptionComponent implements OnInit {
@Input() option!: Modifier
@Input() modifier!: CartModifier;
@Input() product!: CartProduct;
constructor() { }
ngOnInit(): void {
}
}

View File

@ -0,0 +1,94 @@
<div class="product-modal">
<div class="product-modal__information-container" [ngStyle]="{
'max-width': product.modifiers_group.length ? 'calc(50% - 16px)' : '100%'
}">
<img
src="{{
product.image?.length ? product.image : './assets/no-image.png'
}}"
alt="{{ product.name }}"
/>
<p class="product-modal__description">{{product.description}}</p>
</div>
<div
*ngIf="product.modifiers_group.length"
class="product-modal__modifiers-container"
>
<!-- <ng-container *ngIf="product.modifiers_group.length">
<div *ngFor="let modifierGroup of cartProduct.modifiers" [attr.isShow]="false"
[ngClass]="{'product-modal__modifier': true, 'no-valid': isValidate && modifierGroup.allQuantity < modifierGroup.restrictions.minQuantity}" #modifierContainer>
<a (click)="setOptionsView($event, modifierContainer)">
{{modifierGroup.name}}
<span [ngClass]="{
'product-modal__modifier-icon': true,
'isShow': getIsShow(modifierContainer)
}"></span>
</a>
<div [ngClass]="{
'options-container': true,
'isShow': getIsShow(modifierContainer)
}">
<app-checkbox-group [modifier]="modifierGroup" [options]="modifierGroup.options"
currencySymbol="₽" (onChangeQuantity)="changeQuantity($event)">
</app-checkbox-group>
</div>
</div>
</ng-container> -->
<app-modifier
*ngFor="let modifier of cartProduct.modifiers; let index = index"
[modifier]="modifier"
[product]="cartProduct"
>
<span counter>{{ index + 1 }} из {{ cartProduct.modifiers.length }}</span>
</app-modifier>
</div>
<div class="product-modal__footer">
<!-- <app-change-quantity (onChangeValue)="changeProductAmount()"></app-change-quantity> -->
<span class="product-modal__footer-price">{{ cartProduct.finalPrice }}₽</span>
<div class="product-modal__footer-buttons">
<app-change-quantity [disabledButton]="cartProduct.amount === 1 ? 'minus' : 'none'" [value]="cartProduct.amount" (onChangeValue)="changeProductAmount($event)"></app-change-quantity>
<button class="product-modal__add-to-cart" (click)="addToCart($event)">
Добавить
</button>
</div>
</div>
</div>
<p-toast
position="bottom-center"
key="cC"
(onClose)="onReject()"
[baseZIndex]="5000"
>
<ng-template let-message pTemplate="message">
<div class="flex flex-column" style="flex: 1">
<div class="text-center">
<i class="pi pi-exclamation-triangle" style="font-size: 3rem"></i>
<h4>{{ message.summary }}</h4>
<p style="font-weight: 600">{{ message.detail }}</p>
</div>
<div class="grid p-fluid">
<div class="col-6">
<button
type="button"
pButton
(click)="onConfirm()"
label="Да"
class="p-button-success"
></button>
</div>
<div class="col-6">
<button
type="button"
pButton
(click)="onReject()"
label="Нет"
class="p-button-secondary"
></button>
</div>
</div>
</div>
</ng-template>
</p-toast>

View File

@ -0,0 +1,139 @@
:host {
.product-modal {
position: relative;
padding-bottom: 39px;
display: flex;
flex-direction: row;
gap: 32px;
&__information-container {
img {
width: 100%;
border-radius: 1.125rem;
object-fit: contain;
max-height: 50vh;
}
}
&__description {
margin-top: 8px;
font-size: 14px;
}
// & > img {
// width: 50%;
// border-radius: 1.125rem;
// height: fit-content;
// object-fit: cover;
// }
&__footer {
width: 100%;
height: auto;
display: flex;
position: absolute;
bottom: -14px;
padding-top: 16px;
border-top: solid #d1d1d1 1px;
flex-direction: row;
justify-content: space-between;
align-items: center;
}
&__footer-price {
font-size: 18px;
font-weight: 600;
}
&__footer-buttons {
display: flex;
flex-direction: row;
gap: 8px;
}
&__add-to-cart {
padding: 8px 26px;
background: var(--orange-main);
border: none;
border-radius: 1.125rem;
color: #fff;
}
&__modifier {
border: 1px solid rgba(128, 128, 128, 0.23);
margin: 0.5em 0;
padding-bottom: 6px;
border-radius: 1.125rem;
a {
width: 100%;
display: block;
padding: 0.75em;
border-radius: 0.15em;
transition: background 0.3s ease;
}
.options-container {
transition: opacity 0.5s ease;
padding-left: 16px;
display: none;
opacity: 0;
&.isShow {
display: block;
opacity: 1;
}
}
&.no-valid {
border-color: #cc0000;
}
}
&__modifiers-container {
height: 400px;
overflow-y: auto;
}
&__modifier-icon {
float: right;
&::before,
&::after {
display: inline-block;
font-size: 14px;
transition: transform 0.5s ease;
}
&::before {
content: "\\";
transform: rotate(346deg);
}
&::after {
content: "/";
transform: rotate(14deg);
}
&.isShow {
&::before {
transform: rotate(76deg);
}
&::after {
transform: rotate(104deg);
}
}
}
}
@media screen and (max-width: 1070px) {
.product-modal {
flex-direction: column;
&__information-container {
max-width: 100% !important;
}
}
}
}

View File

@ -0,0 +1,23 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { ProductModalComponent } from './product-modal.component';
describe('ProductModalComponent', () => {
let component: ProductModalComponent;
let fixture: ComponentFixture<ProductModalComponent>;
beforeEach(async () => {
await TestBed.configureTestingModule({
declarations: [ ProductModalComponent ]
})
.compileComponents();
fixture = TestBed.createComponent(ProductModalComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});

View File

@ -0,0 +1,137 @@
import { Component, OnInit } from '@angular/core';
import { MessageService } from 'primeng/api';
import { DynamicDialogConfig, DynamicDialogRef } from 'primeng/dynamicdialog';
import { AllData, CartModifier, Modifier, ModifiersGroup, Option, Product } from 'src/app/interface/data';
import { CartProduct } from 'src/app/models/cart-product';
import { CartService } from 'src/app/services/cart.service';
import { WpJsonService } from 'src/app/services/wp-json.service';
import { ChangeValue } from '../change-quantity/change-quantity.component';
import { CookiesService } from 'src/app/services/cookies.service';
@Component({
selector: 'app-product-modal',
templateUrl: './product-modal.component.html',
styleUrls: ['./product-modal.component.scss']
})
export class ProductModalComponent implements OnInit {
public product!: Product;
public allData!: AllData;
public modifiersGroups!: ModifiersGroup[];
public modifiers!: Modifier[];
public cartProduct!: CartProduct;
public isValidate: boolean = false;
private selectedTerminal: any;
constructor(
public dialogRef: DynamicDialogRef,
public config: DynamicDialogConfig,
private wpJsonService: WpJsonService,
private cartService: CartService,
private messageService: MessageService,
private cookiesService: CookiesService
) { }
ngOnInit(): void {
this.product = this.config.data.product
this.modifiersGroups = this.config.data.modifiersGroups
this.modifiers = this.config.data.modifiers
this.selectedTerminal = this.config.data.selectedTerminal
this.cartProduct = new CartProduct(this.product.id, this.product.name, this.modifiersFilter(), this.modifiers, this.product.price);
}
modifiersFilter(): ModifiersGroup[] {
return this.modifiersGroups.filter((value) => this.product.modifiers_group.includes(value.id))
}
optionsFilter(modifierGroup: ModifiersGroup): Modifier[] {
return this.modifiers.filter((modifier) => modifier.groupId === modifierGroup.id)
}
selectedOptions(modifier: ModifiersGroup): Modifier[] {
const cartModifier = this.cartProduct.modifiers.find(cartModifier => cartModifier.id === modifier.id)
if (modifier.restrictions.maxQuantity === 1 && modifier.restrictions.minQuantity === 1) {
cartModifier?.options.push(this.optionsFilter(modifier)[0])
}
return cartModifier?.options ?? [];
}
addOption(modifier: ModifiersGroup, option: Modifier) {
const modif = this.cartProduct.modifiers.find((modif) => modif.id === modifier.id);
const optionSelectedCartIndex = modif?.options.findIndex((modif) => modif.id === option.id)
if (modifier.restrictions.maxQuantity === 1 && modifier.restrictions.minQuantity === 1) {
if (modif?.options) {
modif.options.length = 0
}
} else {
if ((optionSelectedCartIndex || optionSelectedCartIndex === 0) && optionSelectedCartIndex !== -1) {
modif?.options.splice(optionSelectedCartIndex, 1)
return
}
}
modif?.options.push(option)
}
getIsShow(element: HTMLDivElement) {
const isShow = Object.values(element.attributes).find((value) => value.name === 'isshow')?.value
return isShow === 'true'
}
setOptionsView(event: Event, element: HTMLDivElement) {
if (event) {
event.preventDefault()
}
const isShow = this.getIsShow(element)
if (isShow) {
element.setAttribute('isShow', 'false')
return
}
element.setAttribute('isShow', 'true')
}
changeProductAmount({type, variableQuantity}: ChangeValue) {
if (type === 'plus') {
this.cartProduct.increment()
} else {
this.cartProduct.decrement()
}
}
onReject() {
this.messageService.clear('cC');
}
onConfirm() {
this.cartService.clearCart()
this.cartService.changeTerminal(this.config.data.selectedTerminal)
this.cartService.addToCart(this.cartProduct);
this.messageService.clear('cC');
this.dialogRef.close();
}
addToCart(event: Event) {
if (event) {
event.preventDefault()
}
const cookiesTerminal = JSON.parse(this.cookiesService.getItem('selectedTerminal') || '')
for (let modifiersGroup of this.cartProduct.modifiers) {
const isValidModifier = modifiersGroup.allQuantity < modifiersGroup.restrictions.minQuantity
if (isValidModifier) {
this.messageService.add({
severity: 'error',
summary: 'Заполните все модификаторы!'
});
this.isValidate = true
return
}
}
if (this.selectedTerminal.id !== cookiesTerminal?.id) {
this.messageService.add({ key: 'cC', sticky: true, severity: 'warn', summary: 'В заказе могут быть товары только из одного магазина, очистить корзину для создания нового заказа?' });
return
}
this.cartService.changeTerminal(this.config.data.selectedTerminal)
this.cartService.addToCart(this.cartProduct);
this.dialogRef.close();
}
}

View File

@ -0,0 +1,34 @@
<h2 mat-dialog-title>{{ moment(purchaseInfo.date).format("DD.MM.YYYY") }}</h2>
<mat-dialog-content>
<table>
<tbody>
<tr
*ngFor="let good of purchaseInfo.goods_list"
class="woocommerce-orders-table__row woocommerce-orders-table__row--status-processing order"
>
<td
class="woocommerce-orders-table__cell woocommerce-orders-table__cell-order-name"
data-title="Название"
>
{{ good.name }}
</td>
<td
class="woocommerce-orders-table__cell woocommerce-orders-table__cell-order-price"
data-title="Цена"
>
{{ good.price }}
</td>
<td
class="woocommerce-orders-table__cell woocommerce-orders-table__cell-order-quantity"
data-title="Количество"
>
{{ good.quantity }}
</td>
</tr>
</tbody>
</table>
</mat-dialog-content>
<mat-dialog-actions>
<span class="amount">Сумма: {{ purchaseInfo.sum }}</span>
<button mat-stroked-button mat-dialog-close>Закрыть</button>
</mat-dialog-actions>

View File

@ -0,0 +1,45 @@
:host {
.woocommerce-orders-table {
&__cell {
padding: 8px 4px;
border-bottom: 2px solid #dee2e6;
text-align: right !important;
display: block;
&::before {
content: attr(data-title) ": ";
font-weight: 700;
float: left;
margin-right: 20px;
}
}
&__row {
display: block;
&:nth-child(even) {
background: #ebebeb;
}
}
// &__cell-order-actions::before {
// display: none;
// }
&__cell-order-actions {
&.red-color {
color: #ed0000;
}
&.green-color {
color: #00a700;
}
}
&__cell-order-number a {
text-decoration: none;
color: #009688;
}
}
}
.amount {
font-weight: 600;
}
.mat-dialog-actions {
justify-content: space-between;
}

View File

@ -0,0 +1,23 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { PurchaseInfoComponent } from './purchase-info.component';
describe('PurchaseInfoComponent', () => {
let component: PurchaseInfoComponent;
let fixture: ComponentFixture<PurchaseInfoComponent>;
beforeEach(async () => {
await TestBed.configureTestingModule({
declarations: [ PurchaseInfoComponent ]
})
.compileComponents();
fixture = TestBed.createComponent(PurchaseInfoComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});

View File

@ -0,0 +1,24 @@
import { Component, OnInit, Inject } from '@angular/core';
import { MatDialogRef, MAT_DIALOG_DATA } from '@angular/material/dialog';
import moment from 'moment';
import { PurchaseInfo } from 'src/app/interface/data';
@Component({
selector: 'app-purchase-info',
templateUrl: './purchase-info.component.html',
styleUrls: ['./purchase-info.component.scss']
})
export class PurchaseInfoComponent implements OnInit {
public purchaseInfo!: PurchaseInfo;
readonly moment = moment;
constructor(
public dialogRef: MatDialogRef<PurchaseInfoComponent>,
@Inject(MAT_DIALOG_DATA) public data: {purchaseInfo: PurchaseInfo},
) { }
ngOnInit(): void {
this.purchaseInfo = this.data.purchaseInfo
}
}

View File

@ -0,0 +1,16 @@
<div class="snack-bar">
<span>{{ data.text }}</span>
<div class="buttons">
<button mat-stroked-button matSnackBarAction (click)="buttonClick('no')">
Нет
</button>
<button
mat-stroked-button
matSnackBarAction
color="error"
(click)="buttonClick('yes')"
>
Да
</button>
</div>
</div>

View File

@ -0,0 +1,18 @@
:host {
.snack-bar {
display: flex;
align-items: center;
justify-content: space-between;
span {
color: #323232;
}
.buttons {
display: flex;
align-items: center;
justify-content: space-between;
flex-wrap: nowrap;
flex-direction: row;
gap: 8px;
}
}
}

View File

@ -0,0 +1,23 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { SnackBarComponent } from './snack-bar.component';
describe('SnackBarComponent', () => {
let component: SnackBarComponent;
let fixture: ComponentFixture<SnackBarComponent>;
beforeEach(async () => {
await TestBed.configureTestingModule({
declarations: [ SnackBarComponent ]
})
.compileComponents();
fixture = TestBed.createComponent(SnackBarComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});

View File

@ -0,0 +1,23 @@
import { Component, Inject, OnInit } from '@angular/core';
import { MatSnackBarRef, MAT_SNACK_BAR_DATA } from '@angular/material/snack-bar';
@Component({
selector: 'app-snack-bar',
templateUrl: './snack-bar.component.html',
styleUrls: ['./snack-bar.component.scss']
})
export class SnackBarComponent implements OnInit {
constructor(@Inject(MAT_SNACK_BAR_DATA) public data: {text: string}, private _matSnackBarRef: MatSnackBarRef<SnackBarComponent>) { }
ngOnInit(): void {
}
buttonClick(result: 'yes' | 'no') {
if (result === 'yes') {
this._matSnackBarRef.dismissWithAction()
} else {
this._matSnackBarRef.dismiss()
}
}
}

View File

@ -0,0 +1,13 @@
<mat-nav-list>
<div
mat-list-item
*ngFor="let item of data.list"
[ngClass]="{
'list-item': true,
active: item.id === data.active.id
}"
(click)="selectItem(item)"
>
<span matListItemTitle>{{ item.label }}</span>
</div>
</mat-nav-list>

View File

@ -0,0 +1,21 @@
@import 'src/sass/mixins';
:host {
.list-item {
padding: 12px;
font-size: 14px;
border-radius: 6px;
transition: all .4s ease;
text-align: center;
font-weight: 600;
cursor: pointer;
&.active {
background-color: var(--orange-main);
color: #fff;
}
@include hover-supported() {
background-color: #eee;
color: #000;
}
}
}

View File

@ -0,0 +1,23 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { TerminalListComponent } from './terminal-list.component';
describe('TerminalListComponent', () => {
let component: TerminalListComponent;
let fixture: ComponentFixture<TerminalListComponent>;
beforeEach(async () => {
await TestBed.configureTestingModule({
declarations: [ TerminalListComponent ]
})
.compileComponents();
fixture = TestBed.createComponent(TerminalListComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});

View File

@ -0,0 +1,25 @@
import { Component, Inject, OnInit } from '@angular/core';
import { MatBottomSheetRef, MAT_BOTTOM_SHEET_DATA } from '@angular/material/bottom-sheet';
export interface IListData {
list: Array<any>;
active: any;
}
@Component({
selector: 'app-terminal-list',
templateUrl: './terminal-list.component.html',
styleUrls: ['./terminal-list.component.scss']
})
export class TerminalListComponent implements OnInit {
constructor(@Inject(MAT_BOTTOM_SHEET_DATA) public data: IListData, private _bottomSheetRef: MatBottomSheetRef<TerminalListComponent>) { }
ngOnInit(): void {
}
selectItem(item: any) {
this._bottomSheetRef.dismiss(item)
}
}

View File

@ -0,0 +1,159 @@
<div
*ngIf="mainFormGroup && !loading; else loadingEl"
class="woocommerce-shipping-fields__field-wrapper"
>
<form
*ngIf="!showAuthoriztion; else authEl"
(ngSubmit)="submit()"
[formGroup]="mainFormGroup"
action="false"
autocomplete="on"
>
<h2 class="order_form__title">Оформление заказа</h2>
<p *ngIf="hasError" class="request-error-message">
Произошла ошибка. Попробуйте позже.
</p>
<div class="order_form" formGroupName="userDataForm">
<p сlass="form-row form-row-wide">
<input
formControlName="first_name"
id="first_name"
pInputText
placeholder="Ваше имя"
type="text"
/>
</p>
<div *ngIf="deliverData.deliveryType?.name === 'Доставка'">
<p сlass="form-row form-row-last">
<input
formControlName="flat"
id="flat"
pInputText
placeholder="Квартира"
type="number"
min="1"
/>
</p>
<p сlass="form-row form-row-wide">
<input
formControlName="street"
id="street"
pInputText
placeholder="Улица"
type="text"
/>
</p>
<p сlass="form-row form-row-first">
<input
formControlName="house"
id="house"
pInputText
placeholder="Номер дома"
type="text"
/>
</p>
</div>
<p сlass="form-row form-row-wide">
<label class="terminal-list-label">Пункты самовывоза</label>
<p-dropdown
*ngIf="deliverData.deliveryType?.name === 'Самовывоз'"
[options]="terminalList"
formControlName="selectedTerminal"
placeholder="Пункты выдачи"
optionLabel="label"
></p-dropdown>
</p>
<!-- <div *ngIf="deliverData.deliveryType?.name === 'Самовывоз'" class="terminal-list-container">
<div *ngFor="let terminal of terminalList" [ngClass]="{
'terminal-container': true,
'selected': terminal.label === selectedTerminal.label
}">
<img src="{{terminal.image}}" alt="{{terminal.address}}">
<span class="terminal-container__name">{{terminal.label}}</span>
<span class="terminal-container__address">{{terminal.address}}</span>
</div>
</div> -->
</div>
<div formGroupName="deliveryDataForm">
<p сlass="form-row form-row-wide">
<label class="terminal-list-label">Способ получения</label>
<p-dropdown
[options]="deliveryTypes"
formControlName="deliveryType"
placeholder="Доставка"
optionLabel="name"
(onChange)="changeDeliveryType($event)"
></p-dropdown>
</p>
<p сlass="form-row form-row-wide">
<label id="deliveryDate">Время выдачи</label>
<p-calendar
formControlName="deliveryDate"
[showTime]="true"
[showSeconds]="false"
[touchUI]="true"
inputId="time"
placeholder="Время выдачи"
dateFormat="dd.mm.yy"
></p-calendar>
</p>
<p сlass="form-row form-row-wide">
<p-dropdown
[options]="paymentMethods"
formControlName="paymentMethod"
optionLabel="label"
placeholder="Тип оплаты"
>
</p-dropdown>
<!-- *Оплата бонусами -->
</p>
<!-- <p сlass="form-row form-row-last">
<input [maxLength]="255" id="promo-code" pInputText placeholder="Промокод" type="text">
</p> -->
<p сlass="form-row form-row-wide">
<textarea
[maxLength]="255"
cols="30"
formControlName="comment"
pInputTextarea
placeholder="Комментарий"
rows="1"
></textarea>
</p>
</div>
<div class="bootom-info">
<div class="subtotal">
<span class="products-count">Товаров: {{ order.products.length }}</span>
<span class="woocommerce-Price-amount amount"
><bdi
><span class="woocommerce-Price-currencySymbol">{{
order.products[0].currency_symbol
}}</span
>{{ order.price }}</bdi
></span
>
</div>
<button
class="elementor-button elementor-button--checkout elementor-size-md"
>
<span class="elementor-button-text">Оформить заказ</span>
</button>
</div>
<p *ngIf="showMyMessage" style="color: red; font-size: 20px">
Такой адрес не найден! Введите правильный адрес
</p>
</form>
</div>
<ng-template #loadingEl>
<div
class="angular-spinner-container"
style="width: fit-content; height: 100%; margin: 16px auto"
>
<p-progressSpinner styleClass="angular-spinner"></p-progressSpinner>
</div>
</ng-template>
<ng-template #authEl>
<app-auth (phoneConfirmed)="phoneConfirmed()"></app-auth>
</ng-template>

View File

@ -0,0 +1,159 @@
:host {
.woocommerce-shipping-fields__field-wrapper {
margin: 8px auto 100px auto;
max-width: 400px;
}
.order_form__title {
font-weight: 700;
font-size: 18px;
margin-bottom: 12px;
}
input {
width: 100%;
color: #000000;
border: 1px solid #000000;
font-family: -apple-system, BlinkMacSystemFont, Segoe UI, Roboto, Helvetica,
Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", Segoe UI Symbol;
font-size: 1rem;
color: #495057;
background: #ffffff;
padding: 0.5rem 0.75rem;
border: 1px solid #ced4da;
transition: background-color 0.15s, border-color 0.15s, box-shadow 0.15s;
-webkit-appearance: none;
appearance: none;
border-radius: 4px;
&.ng-dirty.ng-invalid {
border-color: red;
}
&.ng-invalid.ng-touched {
border-color: red;
}
}
p {
margin-top: 0;
margin-bottom: 1rem;
}
textarea {
width: 100%;
height: 52px;
color: #000000;
border: 1px solid #000000;
font-family: -apple-system, BlinkMacSystemFont, Segoe UI, Roboto, Helvetica,
Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", Segoe UI Symbol;
font-size: 1rem;
color: #495057;
background: #ffffff;
padding: 0.5rem 0.75rem;
border: 1px solid #ced4da;
transition: background-color 0.15s, border-color 0.15s, box-shadow 0.15s;
-webkit-appearance: none;
appearance: none;
border-radius: 4px;
}
.terminal-list-container {
display: flex;
flex-wrap: wrap;
gap: 16px;
margin: 8px 0 16px;
.terminal-container {
width: calc(50% - 8px);
display: flex;
flex-direction: column;
align-items: center;
gap: 8px;
border: solid var(--orange-main) 1px;
border-radius: 16px;
overflow: hidden;
padding-bottom: 16px;
opacity: 0.5;
text-align: center;
&.selected {
opacity: 1;
}
img {
width: 100%;
height: 120px;
object-fit: cover;
}
&__name {
}
&__address {
font-size: 14px;
}
}
}
.terminal-list-label {
margin-left: 8px;
font-size: 14px;
}
.form-row-wide {
display: flex;
flex-direction: column;
}
#deliveryDate {
margin-left: 8px;
margin-bottom: 4px;
display: block;
font-size: 14px;
}
.bootom-info {
position: fixed;
width: 100%;
left: 0;
display: flex;
justify-content: space-between;
align-items: center;
bottom: 0;
padding: 18px;
background: #fff;
z-index: 3;
border-top: solid 1px #e7e7e7;
.subtotal {
font-weight: 600;
font-size: 20px;
display: flex;
flex-direction: column;
.products-count {
font-size: 12px;
font-weight: 400;
}
}
.elementor-button--checkout {
background-color: var(--orange-main);
color: #fff;
border-radius: 6px;
display: flex;
justify-content: center;
align-items: center;
border: none;
padding: 12px;
font-size: 12px;
cursor: pointer;
}
}
@media screen and (min-width: 650px) {
.bootom-info {
width: 450px;
right: 0;
left: auto;
}
}
}

View File

@ -0,0 +1,23 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { UserDataOrderComponent } from './user-data-order.component';
describe('UserDataOrderComponent', () => {
let component: UserDataOrderComponent;
let fixture: ComponentFixture<UserDataOrderComponent>;
beforeEach(async () => {
await TestBed.configureTestingModule({
declarations: [ UserDataOrderComponent ]
})
.compileComponents();
fixture = TestBed.createComponent(UserDataOrderComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});

View File

@ -0,0 +1,269 @@
import { Component, EventEmitter, OnDestroy, OnInit, Output } from '@angular/core';
import { FormBuilder, FormGroup, Validators } from '@angular/forms';
import { DeliveryData, DeliveryType, PaymentMethod, UserData } from 'src/app/interface/data';
import { paymentMethods } from "../../app.constants";
import { OrderService } from "../../services/order.service";
import { AutocompleteService } from "../../services/autocomplete.service";
import { StreetValidator } from "../../validators/street.validator";
import { CartService } from 'src/app/services/cart.service';
import { environment } from "../../../environments/environment";
import { MessageService } from "primeng/api";
import { WpJsonService } from "../../services/wp-json.service";
import { HttpClient, HttpClientModule } from '@angular/common/http';
import { CookiesService } from 'src/app/services/cookies.service';
import moment from 'moment';
import { Order } from 'src/app/models/order';
import { Store } from '@ngrx/store';
import * as fromConfig from '../../state/config/config.reducer'
import { lastValueFrom } from 'rxjs';
import { GetTerminalsService } from 'src/app/services/get-terminals.service';
@Component({
selector: 'app-user-data-order',
templateUrl: './user-data-order.component.html',
styleUrls: ['./user-data-order.component.scss']
})
export class UserDataOrderComponent implements OnInit, OnDestroy {
@Output() orderSubmitted = new EventEmitter<number>();
@Output() userNotFound = new EventEmitter<null>();
readonly cities = environment.cities;
public paymentMethods!: PaymentMethod[];
public loading = false;
public hasError = false;
public mainFormGroup!: FormGroup;
public deliveryTypes: DeliveryType[] = [];
public new_street!: string | null;
public street!: string;
public new_house!: string | null;
public checkAddress: boolean = true;
public showMyMessage: boolean = false;
public order!: Order;
public showAuthoriztion = false;
private intervalTimeDelivery!: any
public userData: UserData = {
name: '',
first_name: null,
last_name: null,
street: null,
house: null,
flat: null,
city: this.cities[0],
phone: null,
selectedTerminal: null
};
public deliverData: DeliveryData = {
deliveryDate: null,
deliveryType: null,
paymentMethod: null,
comment: '',
persons: 1,
orderid: 0
};
public terminalList!: any;
public selectedTerminal!: any;
checkoutConfig$ = this.store.select(fromConfig.selectCheckout);
checkoutConfig!: any;
constructor(
private fb: FormBuilder,
private orderService: OrderService,
private autoCompleteService: AutocompleteService,
private streetValidator: StreetValidator,
private cartService: CartService,
private messageService: MessageService,
private wpJsonService: WpJsonService,
private http: HttpClient,
private cookiesService: CookiesService,
private store: Store,
private _getTerminals: GetTerminalsService
) { }
async ngOnInit() {
this.checkAuthorization(true)
this._createMainForm();
this.getTerminalList();
this.checkoutConfig$.subscribe({
next: (value: any) => {
this.checkoutConfig = value
}
})
this.deliverData.deliveryDate = moment().add(this.checkoutConfig?.timeDelivery?.changeTime?.defaultValue || 0, 'minutes').toDate()
this.intervalTimeDelivery = setInterval(() => {
this.mainFormGroup.controls['deliveryDataForm'].patchValue({
deliveryDate: moment().add(this.checkoutConfig?.timeDelivery?.changeTime?.defaultValue || 0, 'minutes').toDate()
})
}, 60_000)
this.paymentMethods = this.checkoutConfig.payments.values
this.deliverData.paymentMethod = this.paymentMethods[this.checkoutConfig.payments.default]
}
async getTerminalList() {
// this.http.get('./assets/terminal_list1.json').subscribe({
// next: (value) => {
// this.terminalList = value
// },
// error: (err) => {
// console.error(err);
// }
// })
const _getTerminals = await this._getTerminals.getTerminalList()
this.terminalList = _getTerminals.list
this.selectedTerminal = _getTerminals.active
}
checkAuthorization(showAuthoriztion: boolean, forced = false) {
if (!this.getToken() || forced) {
this.showAuthoriztion = showAuthoriztion
}
}
getToken(): string | void {
return this.cookiesService.getItem('token');
}
phoneConfirmed() {
this._createMainForm();
this.showAuthoriztion = false
this.checkAuthorization(true)
}
changeDeliveryType(event: any) {
this.deliverData.deliveryType = event.value;
if (this.deliverData.deliveryType?.name) {
this.changeValidators(this.deliverData.deliveryType.name)
}
}
changeValidators(name: string) {
const comment = this.mainFormGroup.controls['deliveryDataForm'].value.comment;
const streetValidators = name === 'Доставка' ? [Validators.required, Validators.minLength(2), Validators.maxLength(255),] : []
const houseValidators = name === 'Доставка' ? [Validators.required, Validators.maxLength(10),] : []
const userDataForm = this.fb.group({
phone: [this.userData.phone],
first_name: [this.userData.first_name, [Validators.required, Validators.minLength(2), Validators.maxLength(255)]],
// last_name: [this.userData.last_name, [Validators.required, Validators.minLength(2), Validators.maxLength(255),]],
street: [this.userData.street, streetValidators],
house: [this.userData.house, houseValidators],
flat: [this.userData.flat, []],
// city: [this.userData.city, [Validators.required]],
});
const deliveryDataForm = this.fb.group({
deliveryDate: [this.deliverData.deliveryDate, []],
deliveryType: [this.deliverData.deliveryType, [Validators.required]],
paymentMethod: [this.deliverData.paymentMethod, [Validators.required]],
// persons: [this.deliverData.persons, [Validators.required, Validators.minLength(2), Validators.maxLength(255),]],
comment: [comment, [Validators.maxLength(255),]],
});
this.mainFormGroup = this.fb.group({
userDataForm,
deliveryDataForm,
});
}
submit(): void {
const mainControls = this.mainFormGroup.controls;
if (this.mainFormGroup.invalid) {
Object.keys(mainControls).forEach(groupName => {
const childGroupControls = (mainControls[groupName] as FormGroup).controls;
Object.keys(childGroupControls).forEach(controlName => {
childGroupControls[controlName].markAsTouched();
});
});
this.messageService.add({
severity: 'error',
summary: 'Заполните обязательные поля',
})
return;
}
this.submitOrder();
}
async submitOrder() {
this.loading = true;
const userData: UserData = this.mainFormGroup.controls['userDataForm'].getRawValue();
userData.phone = this.userData.phone;
const deliveryData = this.mainFormGroup.controls['deliveryDataForm'].getRawValue()
deliveryData.orderid = Math.floor(Math.random() * (10 ** 12))
this.orderService.setUserData(userData);
this.orderService.setDeliveryData(deliveryData);
this.orderService.submit().subscribe({
next: (_) => {
this.loading = false;
this.cartService.clearCart();
this.orderSubmitted.next(deliveryData.orderid);
},
error: () => {
this.loading = false;
this.hasError = true;
}
})
}
private async _createMainForm(): Promise<void> {
try {
this.loading = true;
const userDataForm = await this._createUserDataForm();
const deliveryDataForm = await this._createDeliveryDataForm();
this.mainFormGroup = this.fb.group({
userDataForm,
deliveryDataForm,
});
this.loading = false;
}
catch (e) {
console.error('Error: ', e);
this.messageService.add({
severity: 'error',
summary: 'Произошла ошибка',
})
}
}
private async _createUserDataForm(): Promise<FormGroup> {
this.order = await this.orderService.getOrder(true);
if (this.order.userData?.errorCode === "Customer_CustomerNotFound") {
this.userNotFound.emit(null)
}
this.userData = Object.assign({}, this.userData, this.order.userData);
this.userData.city = this.cities[0];
this.userData.phone = this.order.phone;
// await this.autoCompleteService.setCity(this.userData.city);
const isSelfDelivery = this.deliverData.deliveryType?.name === "Самовывоз"
return this.fb.group({
selectedTerminal: [{ value: this.selectedTerminal, disabled: true }, []],
phone: [this.userData.phone],
first_name: [{value: this.userData.name, disabled: true}, [Validators.required, Validators.minLength(2), Validators.maxLength(255),]],
// last_name: [this.userData.last_name, [Validators.required, Validators.minLength(2), Validators.maxLength(255),]],
street: [{ value: this.userData.street, disabled: isSelfDelivery }, isSelfDelivery ?? [Validators.required, Validators.minLength(2), Validators.maxLength(255),]],
house: [{ value: this.userData.house, disabled: isSelfDelivery }, isSelfDelivery ?? [Validators.required, Validators.maxLength(10), Validators.pattern('^\\d+[-|\\d]+\\d+$|^\\d*$')]],
flat: [this.userData.flat, []],
// city: [this.userData.city, [Validators.required]],
});
}
private async _createDeliveryDataForm(): Promise<FormGroup> {
this.deliveryTypes = this.checkoutConfig.delivery.filter((value: any) => value.isPickUp);
this.deliverData.deliveryType = this.deliveryTypes[0];
return this.fb.group({
deliveryDate: [{ value: this.deliverData.deliveryDate, disabled: this.checkoutConfig.timeDelivery.changeTime.disabled }, []],
deliveryType: [{ value: this.deliverData.deliveryType, disabled: this.checkoutConfig.delivery.disabled || this.deliveryTypes.length < 2 }, [Validators.required]],
paymentMethod: [{ value: this.deliverData.paymentMethod, disabled: this.checkoutConfig.payments.disabled }, [Validators.required]],
persons: [this.deliverData.persons, [Validators.required, Validators.minLength(2), Validators.maxLength(255),]],
comment: [this.deliverData.comment, [Validators.maxLength(255),]]
});
}
ngOnDestroy(): void {
clearInterval(this.intervalTimeDelivery)
}
}

View File

@ -0,0 +1,8 @@
import { ChangeQuantityOptionDirective } from './change-quantity-option.directive';
describe('ChangeQuantityOptionDirective', () => {
it('should create an instance', () => {
const directive = new ChangeQuantityOptionDirective();
expect(directive).toBeTruthy();
});
});

View File

@ -0,0 +1,65 @@
import {
Directive,
ElementRef,
HostListener,
Input,
OnInit,
} from '@angular/core';
import { CartModifier, Modifier } from '../interface/data';
import { CartProduct } from '../models/cart-product';
@Directive({
selector: '[appChangeQuantityOption]',
})
export class ChangeQuantityOptionDirective implements OnInit {
@Input() option!: Modifier;
@Input() modifier!: CartModifier;
@Input() product!: CartProduct;
constructor(private el: ElementRef) {}
ngOnInit(): void {
this.option.quantity = this.option.restrictions.byDefault;
if (!this.modifier.lastChangeOption && this.option.restrictions.byDefault) {
this.modifier.lastChangeOption = this.option.idLocal;
}
}
@HostListener('click') onOptionClick() {
this.changeQuantity({ option: this.option, modifier: this.modifier });
}
changeQuantity({
option,
modifier,
}: {
option: Modifier;
modifier: CartModifier;
}): void {
if (!option.quantity && option.quantity !== 0) {
return;
}
const quantity = option.quantity;
const allQuantity = modifier.allQuantity;
const maxOptionQ = option.restrictions.maxQuantity;
const minOptionQ = option.restrictions.minQuantity;
const maxModifierQ = modifier.restrictions.maxQuantity;
const minModifierQ = modifier.restrictions.minQuantity;
const plusCondition =
(quantity < maxOptionQ && allQuantity < maxModifierQ) ||
(maxOptionQ === 0 && (allQuantity < maxModifierQ || maxModifierQ === 0));
if (!plusCondition && quantity > minOptionQ && allQuantity > minModifierQ) {
this.product.decrementOption(modifier.idLocal, option.idLocal);
}
if (allQuantity === maxModifierQ && modifier.lastChangeOption) {
this.product.decrementOption(modifier.idLocal, modifier.lastChangeOption);
this.changeQuantity({ option, modifier });
}
if (plusCondition) {
this.product.incrementOption(modifier.idLocal, option.idLocal);
modifier.lastChangeOption = option.idLocal;
}
}
}

View File

@ -0,0 +1,50 @@
import { Injectable } from '@angular/core';
import {
HttpRequest,
HttpHandler,
HttpEvent,
HttpInterceptor,
HttpResponse,
HttpErrorResponse,
} from '@angular/common/http';
import { Observable, tap } from 'rxjs';
import { MatSnackBar } from '@angular/material/snack-bar';
@Injectable()
export class CustomerInfoInterceptor implements HttpInterceptor {
constructor(private _snackBar: MatSnackBar) {}
intercept(
request: HttpRequest<unknown>,
next: HttpHandler
): Observable<HttpEvent<unknown>> {
return next.handle(request).pipe(
tap(
(event) => {
if (event instanceof HttpResponse) {
const body = event.body as any;
const url = new URL(event.url || '');
if (url.pathname.includes('/icard-proxy/customer_info')) {
if (
body.customer_info?.errorCode === 'Customer_CustomerNotFound'
) {
this._snackBar.open(
'Пользователь не найден в системе! Обратитесь к руководству',
'Ок'
);
} else if ('Error' in body) {
this._snackBar.open('Произошла ошибка! Попробуйте снова', 'Ок');
}
}
}
if (event instanceof HttpErrorResponse) {
this._snackBar.open('Произошла ошибка! Попробуйте снова', 'Ок');
}
},
(err) => {
console.error('ERROR FROM INTERCEPTOR: ', err);
}
)
);
}
}

View File

@ -0,0 +1,353 @@
import { CartProduct } from '../models/cart-product';
export enum PageCode {
// Auth,
BonusProgram,
Orders,
RefSystem,
UserData,
Transactions,
Logout
}
export enum MainPageCode {
Account,
Products,
Cart,
Info,
}
export interface Page {
code: PageCode | MainPageCode;
component?: any;
name: string;
description?: string;
getMethod?: string;
resName?: string;
onSideBar: boolean;
icon?: string;
}
export interface UserDataForm {
first_name: string;
birthdate: string;
gender: string;
}
export interface BonusProgramAccount {
// BonusProgramName: string;
// BonusProgramTypeID: string;
CardNumber: string;
Bonuses: number;
// HoldedBonuses: number;
// BonusProgramAccounts: BonusProgramAccount[];
// DateBonusBurn: string;
// _links: any[];
// _embedded: any;
}
export interface Purchase {
// PurchaseId?: string;
// CustomerId?: string;
// PurchaseDate: string;
// PurchaseState?: number;
// CardNumber?: number;
// Address?: string
// CheckSummary?: number
// BonusSummary?: number
// ID: string;
// Transactions: Transaction[];
// IsSingleTransaction?: boolean;
orderNumber: number;
transactionCreateDate: string;
orderSum: number;
transactionSum: number;
transactionType: 'CancelPayFromWallet' | 'PayFromWallet' | 'RefillWallet';
more_info: PurchaseInfo | null;
}
export interface PurchaseInfo {
date: string;
goods_list: [
{
name: string;
price: number;
quantity: number;
}
];
number: string;
sum: number;
}
export interface Transaction {
User: string;
Purchase: string;
Date: string;
Value: number;
TransactionType: number;
UserBonusesSnapshot: number;
BonusPercent: number;
DateActiveBonus: string;
AccountBonus: string;
Bonus: string;
ID: string;
HasPurchase?: boolean;
}
export interface OrderStatus {
[key: string]: string;
}
export interface DeliveryType {
// cost: number;
// title: string;
// id: number;
// type: string;
name: string;
iikoId: string;
iikoName: string;
isPickUp: boolean;
}
export interface AcceptedOrder {
id: number;
status: string;
currency_symbol: string;
total: number;
address: {
city: string;
street: string;
house: number;
flat: number;
};
payment_method: string;
shipping: {
name: string;
total: number;
};
date_created: string;
items: OrderProduct[];
}
export interface Product {
id: string;
name: string;
price: number;
image: string;
image_gallery: string[];
category_id: number;
description: string;
stock_status: string;
currency_symbol: string;
modifier_data: CartModifier[];
short_description: string;
guid: string;
groupId: string;
modifiers_group: string[];
}
export interface AllData {
datetime: string;
groups: Group[];
products: Product[];
modifiers_groups: ModifiersGroup[];
modifiers: Modifier[];
categories: any[];
}
export interface Group {
id: string;
label: string;
}
export interface ModifiersGroup {
name: string;
id: string;
restrictions: {
minQuantity: number;
maxQuantity: number;
freeQuantity: number;
byDefault: number;
};
}
export interface Modifier {
id: string;
idLocal: string;
name: string;
groupId: string;
price?: number;
quantity?: number;
image?: string;
restrictions: {
minQuantity: number;
maxQuantity: number;
freeQuantity: number;
byDefault: number;
};
}
export interface CartModifier {
lastChangeOption?: string;
id: string;
idLocal: string;
name: string;
options: Modifier[];
allQuantity: number;
restrictions: {
minQuantity: number;
maxQuantity: number;
freeQuantity: number;
byDefault: number;
};
}
export interface Cart {
products: CartProduct[];
}
// export interface Modifier {
// id: number;
// name: string;
// category_type: string;
// minimum_options: number;
// maximum_options: number;
// global_categories: string;
// required: number;
// options: Option[];
// allOptions?: Option[];
// }
export interface Option {
id: number;
name: string;
groupId: string;
restrictions: {
minQuantity: number;
maxQuantity: number;
freeQuantity: number;
byDefault: number;
};
}
export interface OrderProduct {
id: number;
amount: number;
name: string;
price: number;
modifiers: {
[name: string]: OrderModifier[];
};
}
export interface OrderModifier {
name: string;
id: number;
price: number;
}
export interface DeliveryData {
paymentMethod: PaymentMethod | null;
deliveryDate: Date | null;
deliveryType: DeliveryType | null;
persons: number;
comment: string;
orderid: number;
}
export interface PaymentMethod {
type: string;
label: string;
}
export interface UserData {
first_name: string | null;
last_name: string | null;
street: string | null;
house: string | null;
flat: string | null;
city: string;
phone: string | null;
selectedTerminal: ITerminal | null;
name: string;
errorCode?: string;
}
export interface ITerminal {
label: string;
id: string;
}
export interface IOptionDateFilter {
name: string;
value: string;
}
export interface IDateFilter {
filterType: string;
from?: Date;
to?: Date;
}
export interface ICustomerInfo {
anonymized: boolean;
birthday: string | Date;
cards: ICardCustomer[];
categories: ICategoriesCustomer[];
comment: string | null;
consentStatus: number;
cultureName: string;
email: string | null;
id: string;
iikoCardOrdersSum: number;
isBlocked: boolean;
isDeleted: boolean;
middleName: string | null;
name: string;
personalDataConsentFrom: string | null;
personalDataConsentTo: string | null;
personalDataProcessingFrom: string | null;
personalDataProcessingTo: string | null;
phone: string;
rank: string | null;
referrerId: string | null;
sex: number;
shouldReceiveLoyaltyInfo: boolean;
shouldReceiveOrderStatusInfo: boolean;
shouldReceivePromoActionsInfo: boolean;
surname: string;
userData: any;
walletBalances: IWalletBalance[];
errorCode?: string;
first_name: string | null;
last_name: string | null;
street: string | null;
house: string | null;
flat: string | null;
city: string;
selectedTerminal: ITerminal | null;
}
export interface ICardCustomer {
Id: string;
IsActivated: boolean;
NetworkId: string | null;
Number: string;
OrganizationId: string;
OrganizationName: string | null;
Track: string;
ValidToDate: string | Date;
}
export interface ICategoriesCustomer {}
export interface IWalletBalance {
balance: number;
wallet: {
id: string;
name: string;
programType: string;
type: string;
};
}

View File

@ -0,0 +1,85 @@
import { CartModifier, Modifier, ModifiersGroup } from "../interface/data";
import { v4 as uuidv4 } from 'uuid';
export class CartProduct {
constructor(id: string, name: string, modifiers: ModifiersGroup[] = [], options: Modifier[], price: number, amount: number = 1) {
this.id = id;
this.guid = uuidv4();
this.amount = amount;
this.name = name;
this.price = price * amount
this.modifiers = modifiers.map(modifier => ({
name: modifier.name,
id: modifier.id,
idLocal: uuidv4(),
options: JSON.parse(JSON.stringify(options)).filter((option: any) => option.groupId === modifier.id),
restrictions: modifier.restrictions,
get allQuantity() {
return this.options.reduce((a: any, b: any) => a + (b['quantity'] || 0), 0)
}
}));
this.modifiers.forEach((modifier) => {
modifier.options.forEach((option) => {
option.idLocal = uuidv4()
if (!option.quantity && option.quantity !== 0) {
option.quantity = option.restrictions.byDefault
}
})
})
}
id: string;
guid: string;
amount: number;
name: string;
price: number;
modifiers: CartModifier[];
increment(): void {
this.amount++;
}
decrement(): void {
if (this.amount > 0) {
this.amount--;
}
}
incrementOption(modifierId: string, optionId: string): void {
const modifier = this.modifiers.find((modifier) => modifier.idLocal === modifierId)
const option = modifier?.options.find((option) => option.idLocal === optionId)
if (!option?.quantity && option?.quantity !== 0) return
option.quantity++
}
decrementOption(modifierId: string, optionId: string): void {
const modifier = this.modifiers.find((modifier) => modifier.idLocal === modifierId)
const option = modifier?.options.find((option) => option.idLocal === optionId)
if (!option?.quantity && option?.quantity !== 0) return
option.quantity--
}
get finalPrice(): number{
const modifiersPrice = this.modifiers.reduce<number>((previousValue, currentValue) => {
return previousValue + currentValue.options.reduce<number>((previousOptionValue, currentOptionValue) => {
return previousOptionValue + Number(currentOptionValue.price ? currentOptionValue.price * (currentOptionValue.quantity ?? 0) : 0);
}, 0);
}, 0);
return (Number(this.price) + modifiersPrice) * this.amount;
}
// addOption(modifier: ModifiersGroup, option: Modifier): void {
// const productModifier = this.modifiers.find(value => value.id === modifier.id);
// if (productModifier) {
// const optionIndex = productModifier.options.findIndex(value => value.id === option.id);
// if (optionIndex === -1) {
// productModifier.options.push(option);
// }
// else {
// productModifier.options.splice(optionIndex, 1)
// }
// }
// }
}

View File

@ -0,0 +1,111 @@
import { CartModifier, Modifier, Product } from '../interface/data';
export interface OrderProductToJson {
id: string;
amount: number;
price: number;
quantity: number;
name: string;
options?: OrderProductOption[];
}
export interface OrderProductOption {
option: string;
variants: {
variant: string;
id: string;
quantity: number | undefined;
}[];
id: string;
}
export class OrderProduct implements Product {
constructor(product: Product, guid: string, amount: number = 1) {
this.category_id = product.category_id;
this.currency_symbol = product.currency_symbol;
this.description = product.description;
this.id = product.id;
this.image_gallery = product.image_gallery;
this.image = product.image;
this.modifier_data = product.modifier_data;
this.name = product.name;
this.price = product.price;
this.stock_status = product.stock_status;
this.amount = amount;
this.guid = guid;
this.short_description = product.short_description;
this.groupId = product.groupId;
this.modifiers_group = product.modifiers_group;
}
public amount: number;
public category_id: number;
public currency_symbol: string;
public description: string;
public short_description: string;
public id: string;
public image_gallery: string[];
public image: string;
public modifier_data: CartModifier[];
public name: string;
public price: number;
public stock_status: string;
public guid: string;
public groupId: string;
public modifiers_group: string[];
get finalPrice(): number {
const modifiersPrice = this.modifier_data.reduce<number>(
(previousValue, currentValue) => {
return (
previousValue +
currentValue.options.reduce<number>(
(previousOptionValue, currentOptionValue) => {
return (
previousOptionValue +
Number(
currentOptionValue.price
? currentOptionValue.price *
(currentOptionValue.quantity ?? 0)
: 0
)
);
},
0
)
);
},
0
);
return (Number(this.price) + modifiersPrice) * this.amount;
}
toJson(): OrderProductToJson {
const product: OrderProductToJson = {
id: this.id,
amount: this.amount * this.price,
price: this.price,
quantity: this.amount,
name: this.name,
};
const options = this.modifier_data
?.map((modifier) => {
return {
option: modifier.name,
variants:
modifier.options
.map((option) => ({
variant: option.name,
id: option.id,
quantity: option.quantity,
}))
.filter((option) => option.quantity) || null,
id: modifier.id,
};
})
.filter((modifier) => modifier.variants.length);
if (options.length) product.options = options;
return product;
}
}

View File

@ -2,22 +2,25 @@ import {DeliveryData, UserData} from "../interface/data";
import {OrderProduct} from "./order-product"; import {OrderProduct} from "./order-product";
import * as moment from 'moment'; import * as moment from 'moment';
import { CookiesService } from "../services/cookies.service"; import { CookiesService } from "../services/cookies.service";
import { environment } from "src/environments/environment";
export interface OrderInfo { export interface OrderInfo {
products: OrderProduct[]; products: OrderProduct[];
userData?: UserData; userData: UserData | null;
deliveryData?: DeliveryData; deliveryData?: DeliveryData;
phone: string; phone: string;
token: string | undefined; token: string | undefined;
terminal_id: string;
} }
export class Order { export class Order {
public products: OrderProduct[]; public products: OrderProduct[];
public userData?: UserData; public userData!: UserData | null;
public deliveryData?: DeliveryData; public deliveryData?: DeliveryData;
public phone: string; public phone: string;
public token: string | undefined; public token: string | undefined;
public terminal_id: string;
constructor( constructor(
orderInfo: OrderInfo orderInfo: OrderInfo
@ -27,6 +30,7 @@ export class Order {
this.deliveryData = orderInfo.deliveryData; this.deliveryData = orderInfo.deliveryData;
this.phone = orderInfo.phone; this.phone = orderInfo.phone;
this.token = orderInfo.token; this.token = orderInfo.token;
this.terminal_id = orderInfo.terminal_id;
} }
get price(): number { get price(): number {
@ -36,33 +40,30 @@ export class Order {
toJson(): any { toJson(): any {
const date = moment(this.deliveryData?.deliveryDate ?? Date.now()); const date = moment(this.deliveryData?.deliveryDate ?? Date.now());
return { return {
items: this.products.map(product => { formname: "Cart",
paymentsystem: this.deliveryData?.paymentMethod?.type,
phone: this.phone,
persons: 1,
name: this.userData?.first_name,
payment: {
orderid: this.deliveryData?.orderid,
delivery_price: 0,
products: this.products.map(product => {
return product.toJson(); return product.toJson();
}), }),
user_data: { delivery_fio: this.userData?.first_name,
phone: this.phone, subtotal: this.price,
...this.userData delivery_comment: this.deliveryData?.comment,
delivery: this.deliveryData?.deliveryType?.name,
delivery_address: {
street: this.userData?.street || '',
house: this.userData?.house || '',
flat: '',
city: ''
}, },
payment_method: this.deliveryData?.paymentMethod.type, amount: this.price,
delivery_time: date.format('HH:mm'), terminal_id: this.userData?.selectedTerminal?.id || this.terminal_id
delivery_date: date.format("YYYY-MM-DD"),
delivery_instance_id: this.deliveryData?.deliveryType?.id,
persons: 1,
payments: [
{
type: this.deliveryData?.paymentMethod.type,
summ: this.price,
}, },
{
type: "crm4retail",
summ: 0,
payload: {
id: "c07a10d8-ba7e-43b0-92aa-ae470060bc7d"
}
}
],
comment: this.deliveryData?.comment,
token: this.token
} }
} }
} }

View File

@ -0,0 +1,81 @@
<div
[ngClass]="{
woocommerce: true,
'auth-page': showAuthoriztion
}"
>
<!-- <button *ngIf="!showAuthoriztion" class="logout" color="warn" mat-stroked-button (click)="logout()">Выйти</button> -->
<div *ngIf="!showAuthoriztion; else authEl" class="account-page">
<div *ngIf="showAuthoriztion" class="top-left-attribute"></div>
<app-bonus-program
[currentPage]="currentPage"
(deauthorization)="changePage(pageList[0])"
></app-bonus-program>
<mat-tab-group mat-stretch-tabs="false" mat-align-tabs="center" (selectedTabChange)="changeTabPage($event)" [(selectedIndex)]="selectedTabPage">
<ng-container *ngFor="let page of pageList">
<mat-tab *ngIf="page.onSideBar" label="{{ page.name }}">
<div [ngSwitch]="page.resName" class="">
<ng-container *ngSwitchCase="'transactions'">
<app-orders
[currentPage]="currentPage"
(deauthorization)="changePage(pageList[0])"
></app-orders>
</ng-container>
<ng-container *ngSwitchCase="'orders'">
<app-purcahses
></app-purcahses>
</ng-container>
<ng-container *ngSwitchCase="'user-data'">
<app-user-data></app-user-data>
</ng-container>
<ng-container *ngSwitchCase="'ref-system'">
<app-ref-system></app-ref-system>
</ng-container>
</div>
</mat-tab>
</ng-container>
</mat-tab-group>
</div>
<p-toast
position="bottom-center"
key="c"
(onClose)="onReject()"
[baseZIndex]="5000"
>
<ng-template let-message pTemplate="message">
<div class="flex flex-column" style="flex: 1">
<div class="text-center">
<i class="pi pi-exclamation-triangle" style="font-size: 3rem"></i>
<h4>{{ message.summary }}</h4>
<p style="font-weight: 600">{{ message.detail }}</p>
</div>
<div class="grid p-fluid">
<div class="col-6">
<button
type="button"
pButton
(click)="onConfirm()"
label="Да"
class="p-button-success"
></button>
</div>
<div class="col-6">
<button
type="button"
pButton
(click)="onReject()"
label="Нет"
class="p-button-secondary"
></button>
</div>
</div>
</div>
</ng-template>
</p-toast>
</div>
<ng-template #authEl>
<app-auth (phoneConfirmed)="phoneConfirmed()"></app-auth>
</ng-template>

View File

@ -0,0 +1,218 @@
:host ::ng-deep .mat-tab-body {
padding: 16px 0;
}
:host {
.woocommerce {
min-height: calc(100vh - 39px);
padding: 20px 18px 65px;
position: relative;
.logout {
float: right;
}
&.auth-page {
}
.main-menu-container {
position: fixed;
width: 100%;
bottom: 0;
left: 0;
margin: 0;
z-index: 777;
height: 57px;
border-top: solid 1px #dfdfdf;
background-color: #fff;
ul {
display: flex;
height: 100%;
font-size: 14px;
flex-wrap: nowrap;
flex-direction: row;
li {
width: 100%;
height: 100%;
display: flex;
align-items: center;
justify-content: center;
flex-direction: column;
gap: 4px;
font-size: 12px;
color: #b5b5b9;
cursor: pointer;
&.mat-icon {
color: #b5b5b9;
&.is-active {
color: #000;
}
}
&.is-active {
color: #000;
}
&.cart {
position: relative;
&::before {
content: attr(data-counter);
color: #fff;
position: absolute;
right: calc(50% - 24px);
top: 3px;
background: #d7120b;
border-radius: 50px;
min-width: 1.2rem;
line-height: 1.2rem;
font-size: 0.8rem;
text-align: center;
}
}
}
}
}
.account-page {
max-width: 1208px;
display: flex;
margin: 0 auto;
gap: 32px;
width: 98%;
flex-direction: row;
nav {
margin-bottom: 24px;
margin-top: 26px;
display: flex;
justify-content: center;
ul {
max-width: 400px;
width: 100%;
border-radius: 6px;
display: flex;
justify-content: space-between;
flex-direction: column;
li {
padding: 12px;
width: 100%;
text-align: center;
cursor: pointer;
height: 81px;
margin-bottom: 10px;
background: #ffffff;
box-shadow: 0px 0px 5px rgb(0 0 0 / 15%);
color: #000;
border-radius: 15px;
&.is-active {
border: solid var(--orange-main) 1px;
// display: none;
}
&.first {
// border-radius: 7px 0 0 7px;
}
.container {
display: flex;
align-items: center;
height: 100%;
.menu-item-info {
margin-left: 16px;
& > a {
font-style: normal;
font-weight: 700;
font-size: 18px;
line-height: 22px;
text-decoration: none;
color: #000;
display: block;
text-align-last: left;
}
& > p {
font-style: normal;
font-weight: 400;
font-size: 12px;
line-height: 15px;
text-align: left;
}
}
}
}
li:nth-child(odd) {
background-color: #ebebeb;
}
}
}
.mat-tab-group {
width: calc(100% - 432px);
}
}
.top-left-attribute {
width: 10px;
height: 5px;
background: var(--orange-main);
left: 0;
position: absolute;
top: 69px;
}
.version {
opacity: 0.5;
position: absolute;
bottom: 60px;
}
}
@media screen and (min-width: 550px) {
.woocommerce {
padding-bottom: 78px;
.main-menu-container {
width: 500px;
left: 50%;
transform: translate(-50%, 0);
border: solid 1px #dfdfdf;
border-radius: 12px;
box-shadow: 0 0 6px 2px rgb(223 223 223);
transition: top 0.4s cubic-bezier(0.57, -0.43, 0.41, 1.3);
top: -60px;
&[isShow="true"] {
top: 8px;
}
}
.version {
bottom: 20px;
}
}
}
@media screen and (max-width: 895px) {
.woocommerce {
.account-page {
flex-direction: column;
width: 100%;
.mat-tab-group {
width: 100%;
}
}
}
}
}

View File

@ -0,0 +1,243 @@
import { Component, ElementRef, EventEmitter, Input, OnInit, Output, ViewChild } from '@angular/core';
import { CookiesService } from '../../services/cookies.service';
import { ActivatedRoute, Router } from '@angular/router';
import { MainPageCode, Page, PageCode } from '../../interface/data';
import { environment } from '../../../environments/environment';
import { PageList, PageListMain, PageListWithBonus } from '../../app.constants';
import { HttpErrorResponse, HttpParams } from '@angular/common/http';
import { ExitComponent } from '../../components/exit/exit.component';
import { DialogService, DynamicDialogRef } from 'primeng/dynamicdialog';
import { JsonrpcService, RpcService } from 'src/app/services/jsonrpc.service';
import { MessageService } from 'primeng/api';
import { lastValueFrom } from 'rxjs';
import { CartService } from 'src/app/services/cart.service';
import { Store } from '@ngrx/store';
import { getProfile, getTransactions, getTransactionsInfo } from 'src/app/state/profile/profile.actions';
import { MatTabChangeEvent } from '@angular/material/tabs';
@Component({
selector: 'app-account',
templateUrl: './account.component.html',
styleUrls: ['./account.component.scss'],
providers: [DialogService],
})
export class AccountComponent implements OnInit {
@Output() setUserDataOrderPage = new EventEmitter<null>();
@ViewChild('menu', { static: true}) menu!: ElementRef;
public refSystemId: string = '';
public selectedTabPage: number = 1
constructor(
private cookiesService: CookiesService,
private router: Router,
private route: ActivatedRoute,
private dialogService: DialogService,
private jsonRpcService: JsonrpcService,
private messageService: MessageService,
private cartService: CartService,
private store: Store
) { }
public currentPage!: Page;
public handleHttpErrorFunc = this.handleHttpError.bind(this);
private ref!: DynamicDialogRef;
public version: string = environment.version;
readonly PageCode = PageCode;
readonly pageList = environment.hasBonusProgram
? PageListWithBonus
: PageList;
readonly MainPageCode = MainPageCode;
readonly mainPageList = PageListMain;
public currentPageMain: Page = this.mainPageList[environment.production ? 1 : 1];
public cartCount = 0;
public showAuthoriztion = false;
ngOnInit(): void {
this.checkAuthorization(true)
this.store.dispatch(getProfile())
this.store.dispatch(getTransactions())
this.store.dispatch(getTransactionsInfo())
this.currentPage = this.pageList[0];
document.body.classList.add(
'woocommerce-account',
'woocommerce-page',
'woocommerce-orders'
);
this.route.queryParams.subscribe((params) => {
if (!params['activeAccountPage']) {
this.currentPage = this.pageList[0];
return;
}
const currentPage = this.pageList.find((page) => {
return page.code === Number(params['activeAccountPage']);
});
if (!currentPage) {
this.currentPage = this.pageList[0];
} else {
this.currentPage = currentPage;
}
});
this.cartCount = this.cartService.cartCount;
this.cartService.cartCount$.subscribe({
next: (count) => {
this.cartCount = count;
document.querySelectorAll('.cart')[0].setAttribute("data-counter", this.cartCount.toString())
}
});
}
phoneConfirmed(): void {
this.refSystem();
this.showAuthoriztion = false
this.checkAuthorization(true)
}
async refSystem() {
const additionalInfo = (await lastValueFrom(
this.jsonRpcService.rpc({
method: 'getAdditionalInfo',
params: []
}, RpcService.authService, true)
)).data
this.route.queryParams.subscribe((params) => {
if (params['refUserId']) {
if (additionalInfo.refSystem?.code.length) {
this.messageService.add({
severity: 'custom',
summary:
'Вы уже зарегестрированы в реферальной программе!',
});
return;
}
this.refSystemId = params['refUserId'];
this.jsonRpcService
.rpc(
{
method: 'updateAdditionalInfo',
params: [
{
refSystem: {
code: this.refSystemId,
},
},
],
},
RpcService.authService,
true
)
.subscribe({
next: () => {
this.router.navigate([], {
queryParams: {
refUserId: null,
},
queryParamsHandling: 'merge',
});
this.messageService.add({
severity: 'custom',
summary:
'Регистрация прошла успешна! Бонусы скоро будут начислены',
});
},
error: (err) => {
console.error('Error: ', err);
this.messageService.add({
severity: 'error',
summary: 'Произошла ошибка, попробуйте позже',
});
},
});
}
});
}
changeMainPage(page: Page, event?: MouseEvent): void {
if (event) {
event.preventDefault();
}
this.currentPageMain = page;
this.router.navigate(['.'], {
relativeTo: this.route,
// queryParams: {
// activePage: this.currentPage.code,
// },
queryParamsHandling: 'merge',
});
this.checkAuthorization(true)
if (this.currentPageMain === this.mainPageList[2]) {
this.checkAuthorization(false)
}
this.menu.nativeElement.setAttribute('isShow', 'false')
}
changePage(page: Page, event?: MouseEvent): void {
if (event) {
event.preventDefault();
}
this.currentPage = page;
this.router.navigate(['.'], {
relativeTo: this.route,
queryParams: {
activeAccountPage: this.currentPage.code,
},
queryParamsHandling: 'merge',
});
}
checkAuthorization(showAuthoriztion: boolean, forced = false) {
if (!this.getToken() || forced) {
this.showAuthoriztion = showAuthoriztion
}
}
handleHttpError(error: HttpErrorResponse): void {
if (error.status === 500) {
this.logout();
}
}
setToken(token: string): void {
this.cookiesService.setCookie('token', token);
}
getToken(): string | void {
return this.cookiesService.getItem('token');
}
deleteToken(): void {
this.cookiesService.deleteCookie('token');
// this.router.navigate(['.'], {
// queryParams: {},
// });
}
onReject() {
this.messageService.clear('c');
}
onConfirm() {
this.deleteToken();
this.showAuthoriztion = true;
this.messageService.clear('c')
}
changeTabPage(event: MatTabChangeEvent) {
if (event.index === 2) {
this.selectedTabPage = 1
this.logout()
}
}
logout(event?: MouseEvent) {
if (event) {
event.preventDefault()
}
this.messageService.add({ key: 'c', sticky: true, severity: 'warn', summary: 'Вы уверены, что хотите выйти?' });
}
}

View File

@ -40,7 +40,7 @@
></p-progressSpinner> ></p-progressSpinner>
</button> </button>
</p> </p>
<div class="decoration-pattern" style="background: url('./assets/card-decorative-pattern.png') no-repeat;"></div> <!-- <div class="decoration-pattern" style="background: url('./assets/card-decorative-pattern.png') no-repeat;"></div> -->
</form> </form>
</ng-container> </ng-container>
<ng-template #confirmPhoneField> <ng-template #confirmPhoneField>
@ -54,8 +54,7 @@
class="woocommerce-form-row woocommerce-form-row--wide form-row form-row-wide" class="woocommerce-form-row woocommerce-form-row--wide form-row form-row-wide"
> >
<label for="code"> <label for="code">
Введите 4 последних цифры позвонившего вам номера телефона. На звонок Введите код из смс, которое пришло на Ваш номер телефона
отвечать не нужно.
</label> </label>
<input <input
pInputText pInputText
@ -94,6 +93,6 @@
<p style="color: red; margin: 0" *ngIf="errorConfirmCode"> <p style="color: red; margin: 0" *ngIf="errorConfirmCode">
Пароль введен неверно Пароль введен неверно
</p> </p>
<div class="decoration-pattern" style="background: url('./assets/card-decorative-pattern.png') no-repeat;"></div> <!-- <div class="decoration-pattern" style="background: url('./assets/card-decorative-pattern.png') no-repeat;"></div> -->
</form> </form>
</ng-template> </ng-template>

View File

@ -39,7 +39,7 @@
background: #ffffff; background: #ffffff;
box-shadow: 0px 0px 10px rgb(0 0 0 / 15%); box-shadow: 0px 0px 10px rgb(0 0 0 / 15%);
border-radius: 15px; border-radius: 15px;
min-height: 262px; // min-height: 262px;
max-width: 400px; max-width: 400px;
overflow: auto; overflow: auto;
position: relative; position: relative;

View File

@ -83,11 +83,11 @@ export class AuthComponent {
next: (result) => { next: (result) => {
if (result.code === 0) { if (result.code === 0) {
this.cookiesService.setCookie('token', result?.data?.token); this.cookiesService.setCookie('token', result?.data?.token);
this.router.navigate(['/auth'], { // this.router.navigate(['/auth'], {
queryParams: { // queryParams: {
token: result?.data?.token // token: result?.data?.token
}, // },
}); // });
this.phoneConfirmed.emit(null); this.phoneConfirmed.emit(null);
} else { } else {
this.errorConfirmCode = true; this.errorConfirmCode = true;

View File

@ -4,27 +4,30 @@
<div style="display: flex; justify-content: center"> <div style="display: flex; justify-content: center">
<div class="card-container"> <div class="card-container">
<div class="card-container__header"> <div class="card-container__header">
<img src="./assets/logo.svg" alt="Логотип карта" /> <img src="./assets/logo.png" alt="Логотип карта" />
<span *ngIf="accountData">#{{ accountData.CardNumber }}</span> <span *ngIf="accountData">#{{ accountData.CardNumber }}</span>
</div> </div>
<div class="card-container__decorative-pattern"> <div class="card-container__decorative-pattern">
<!-- <div class="imgs-row"></div> <!-- <div class="imgs-row"></div>
<div class="imgs-row" style="background-position-x: -22px;"></div> <div class="imgs-row" style="background-position-x: -22px;"></div>
<div class="imgs-row"></div> --> <div class="imgs-row"></div> -->
<img src="./assets/card-decorative-pattern.svg" alt="" /> <!-- <img src="./assets/card-decorative-pattern.svg" alt="" /> -->
</div> </div>
<div class="card-container__content"> <div class="card-container__content">
<div class="info"> <div class="info">
<div *ngIf="accountData" class="row"> <div *ngIf="!accountData && !loadingBonuses" class="info__row">
Пользователь не найден, обратитесь к руководству
</div>
<div *ngIf="accountData" class="info__row">
<span class="key">Имя</span> <span class="key">Имя</span>
<span class="value" *ngIf="userName.length">{{ <span class="value" *ngIf="userName?.length">{{
userName userName
}}</span> }}</span>
<span class="value" *ngIf="!userName.length" <span class="value" *ngIf="!userName?.length"
>Не задано</span >Не задано</span
> >
</div> </div>
<div *ngIf="accountData" class="row"> <div *ngIf="accountData" class="info__row">
<span class="key">Баланс карты</span> <span class="key">Баланс карты</span>
<span class="value">{{ accountData.Bonuses }}</span> <span class="value">{{ accountData.Bonuses }}</span>
</div> </div>
@ -47,4 +50,5 @@
</div> </div>
</div> </div>
</div> </div>
<!-- <button *ngIf="deviceType == 'ios'" class="add-to-wallet" (click)="addCardToWallet($event)">Добавить в Apple Wallet</button> -->
</div> </div>

Some files were not shown because too many files have changed in this diff Show More