Compare commits

...

197 Commits

Author SHA1 Message Date
gofnnp
c51b47955d test 2025-12-19 13:02:46 +04:00
a8240ed0db dev #15189 фикс мерджа 2023-09-12 15:10:58 +04:00
188f93e2b3 Merge branch 'tasty-coffee' of https://git.hlcompany.ru/git/usersite into tasty-coffee
# Conflicts:
#	angular/angular.json
#	angular/src/app/app.component.ts
#	angular/src/app/interface/data.ts
#	angular/src/app/presentation-options/default-option/components/navbar/navbar.component.html
#	angular/src/app/presentation-options/default-option/components/navbar/navbar.component.scss
#	angular/src/app/presentation-options/default-option/components/navbar/navbar.component.ts
#	angular/src/app/presentation-options/default-option/default-option.module.ts
#	angular/src/app/presentation-options/default-option/pages/guest-card/guest-card.component.html
#	angular/src/app/presentation-options/default-option/pages/guest-card/guest-card.component.scss
#	angular/src/app/presentation-options/default-option/pages/guest-card/guest-card.component.ts
#	angular/src/app/presentation-options/default-option/pages/login/login.component.html
#	angular/src/app/presentation-options/default-option/pages/login/login.component.scss
#	angular/src/app/services/auth.service.ts
#	angular/src/environments/environment.prod.ts
#	angular/src/environments/environment.ts
#	angular/src/manifest.webmanifest
#	angular/src/styles.scss
2023-09-12 14:42:41 +04:00
af56582aca dev #15189
сделал копию море для tasty coffee
2023-09-12 14:36:51 +04:00
f75d727c2d dev #15189
изменил на tasty coffee
2023-09-12 12:09:02 +04:00
756a033504 исправил кр код при авторизации 2023-09-05 15:04:50 +04:00
nikolay
29f8ca5230 dev #14384 Правка ошибок отображения WPA КофеЛайк: remove asterisk from name input 2023-08-21 09:46:05 +04:00
nikolay
a62f60df40 dev #14384 Правка ошибок отображения WPA КофеЛайк: remove banner 2023-08-18 09:06:12 +04:00
nikolay
ea66abfcd1 dev #14607 Море. Правки по сайту: add href to doc on word "условия" on login page 2023-08-11 10:06:12 +04:00
nikolay
172a21b7f7 dev #14384 Правка ошибок отображения WPA КофеЛайк: fix description 2023-07-26 14:00:13 +04:00
nikolay
a1668de544 dev #14384 Правка ошибок отображения WPA КофеЛайк: fix error handling 2023-07-26 10:30:07 +04:00
nikolay
3e9544ff96 dev #14384 Правка ошибок отображения WPA КофеЛайк: add "О программе лояльности" menu item and hide "О приложении" menu item 2023-07-24 12:08:26 +04:00
nikolay
86c913b4c1 dev #14384 Правка ошибок отображения WPA КофеЛайк: fix app install button 2023-07-24 10:41:41 +04:00
nikolay
68dea47a27 dev #14384 Правка ошибок отображения WPA КофеЛайк: fix level description 2023-07-21 14:21:51 +04:00
nikolay
2d3fc2f4ca dev #14384 Правка ошибок отображения WPA КофеЛайк: fix text styles 2023-07-20 13:32:05 +04:00
nikolay
59edb5939b dev #14384 Правка ошибок отображения WPA КофеЛайк: add exclamation mark 2023-07-19 16:58:10 +04:00
nikolay
9a6cecad00 dev #14384 Правка ошибок отображения WPA КофеЛайк: fix description 2023-07-19 11:24:30 +04:00
nikolay
34447c0d8b dev #14384 Правка ошибок отображения WPA КофеЛайк: fix styles and level description 2023-07-19 11:23:31 +04:00
nikolay
1e62141a45 dev #14384 Правка ошибок отображения WPA КофеЛайк:
complete:
1. поменять банер на закругленный
2. добавить значок лупы для куар - как в приложении x5
3. Зеленый цвет для текста взять такой же как для вернхей панели
4. кнопку оценить так же цвет из верхней панели
5. в тексте процент бонусов и сумму покупок выделить тем же цветом что и Уровень и Кешбек около куара
6. текст сделать пошире, что бы на телефоне было 2-3 строки
2023-07-19 10:33:18 +04:00
nikolay
94b34e020e dev #14384 Правка ошибок отображения WPA КофеЛайк: fix qr code description 2023-07-17 12:01:12 +04:00
nikolay
56b1249e3d dev #14760 Кофелайк WPA динамическое сообщение в зависимости от уровня: add description table 2023-07-17 11:33:26 +04:00
nikolay
db666df676 dev #14384 Правка ошибок отображения WPA КофеЛайк: change background to white 2023-07-17 11:07:31 +04:00
nikolay
5cb88250c5 change colors to old version 2023-07-14 15:00:37 +04:00
7c48e1dc1e #14726
изменил фоновую картинку в карте валлет
2023-07-12 16:03:19 +04:00
nikolay
9d00a32d4f dev #14717 Добавить меню в андрод WPAL: remove back arrow from guest card 2023-07-11 15:48:02 +04:00
feeed2c640 Merge branch 'more' of https://git.hlcompany.ru/git/usersite into more 2023-07-11 14:24:09 +04:00
46059461db #14726
изменил картинки в карте валлет
2023-07-11 14:23:55 +04:00
nikolay
19577e272f dev #14717 Добавить меню в андрод WPA 2023-07-11 13:58:33 +04:00
nikolay
ea5b07964d dev #14716 Отображение в интерфейсе ошибок с бека 2023-07-11 12:18:15 +04:00
nikolay
d2669236ec return ruble sign in last order price 2023-07-06 13:22:43 +04:00
nikolay
14c2af863b dev #14607 Море. Правки по сайту: change size of agree info on login page 2023-07-06 11:16:58 +04:00
nikolay
50333514af dev #14384 Правка ошибок отображения WPA КофеЛайк: revert banner 2023-07-06 10:40:28 +04:00
nikolay
cc192a6b0f dev #14607 Море. Правки по сайту: fix input border color on hover 2023-07-05 16:17:48 +04:00
nikolay
1b8cb2b185 dev #14607 Море. Правки по сайту: remove date input blur 2023-07-05 15:38:42 +04:00
nikolay
eb15e47328 dev #14607 Море. Правки по сайту: set cursor position at beginning of date input on focus 2023-07-05 15:25:58 +04:00
nikolay
73de9ea6b7 dev #14607 Море. Правки по сайту: fix mask 2023-07-05 14:50:43 +04:00
nikolay
c1219e1637 dev #14607 Море. Правки по сайту: remove datepicker and add date mask 2023-07-05 14:21:19 +04:00
nikolay
5f522bad47 dev #14384 Правка ошибок отображения WPA КофеЛайк: some fixes 2023-07-04 15:53:49 +04:00
nikolay
9fd031f423 dev #14607 Море. Правки по сайту:
change agree info;
remove add to apple wallet notification;
fix apple wallet generation;
2023-07-04 15:45:43 +04:00
nikolay
7fe4b6de52 dev #14607 Море. Правки по сайту:
add agree information;
remove uncessary user update;
2023-07-04 15:03:57 +04:00
nikolay
55c98f5aa1 dev #14607 Море. Правки по сайту: remove menu item "about app" 2023-07-04 11:27:29 +04:00
nikolay
75ddd847f3 dev #14607 Море. Правки по сайту: add app to apple wallet on login and register 2023-07-04 10:41:12 +04:00
nikolay
252433e197 dev #14384 Правка ошибок отображения WPA КофеЛайк: fix last purchase date format 2023-07-03 15:56:08 +04:00
nikolay
46bd1e6d2a dev #14384 Правка ошибок отображения WPA КофеЛайк: fix last purchase date format 2023-07-03 12:24:38 +04:00
nikolay
e5c2e0efe4 dev #14607 Море. Правки по сайту: add icons and fix some styles 2023-07-03 12:18:09 +04:00
nikolay
656e41b8cf dev #14384 Правка ошибок отображения WPA КофеЛайк: change user info data 2023-07-03 11:52:56 +04:00
nikolay
54795be75a dev #14607 Море. Правки по сайту: add registration form 2023-07-03 09:46:22 +04:00
nikolay
0e5e7e1d84 dev #14607 Море. Правки по сайту: fixes 2023-06-30 13:38:43 +04:00
nikolay
4c7b5437df dev #14607 Море. Правки по сайту: show all bonuses 2023-06-29 16:09:56 +04:00
nikolay
e640539d96 dev #14607 Море. Правки по сайту: fix notification and auth new_customer 2023-06-29 15:35:34 +04:00
nikolay
7808270bb4 dev #14607 Море. Правки по сайту: some fixes 2023-06-29 14:22:29 +04:00
nikolay
e12eb7a542 dev #14607 Море. Правки по сайту: add apple wallet and logo 2023-06-29 12:53:32 +04:00
nikolay
0abdb264f5 dev #14607 Море. Правки по сайту: change interface and api 2023-06-29 11:53:38 +04:00
nikolay
b7cc0ed72d dev #14384 Правка ошибок отображения WPA КофеЛайк:
fix header;
add link to banner;
fix last purchase loading;
2023-06-29 09:11:08 +04:00
tosandriy
06f668cd3d changed system id 2023-06-28 15:17:11 +04:00
b6483f78a7 dev #14600
изменил путь сборки
2023-06-28 13:17:36 +04:00
9b3488775c dev #14600
изменил названия для новой ветки
2023-06-28 11:56:16 +04:00
nikolay
e5ae004a29 dev #14384 Правка ошибок отображения WPA КофеЛайк: show only one message at a time and fix header 2023-06-27 16:01:12 +04:00
nikolay
142ece15de dev #14384 Правка ошибок отображения WPA КофеЛайк: add banner 2023-06-27 15:37:52 +04:00
nikolay
c9bcf3fc15 dev #14384 Правка ошибок отображения WPA КофеЛайк: fix login form 2023-06-27 14:22:52 +04:00
nikolay
9e7394e848 dev #14384 Правка ошибок отображения WPA КофеЛайк: fix last purchase 2023-06-26 10:40:10 +04:00
nikolay
8ea4a41786 dev #14384 Правка ошибок отображения WPA КофеЛайк: fix last purchase 2023-06-26 10:24:18 +04:00
nikolay
7fa882e423 dev #14384 Правка ошибок отображения WPA КофеЛайк: fix mistake 2023-06-23 14:36:24 +04:00
nikolay
03771d2012 dev #14384 Правка ошибок отображения WPA КофеЛайк: show info when wallet balance is negative 2023-06-23 13:33:02 +04:00
nikolay
3ff34525ed dev #14384 Правка ошибок отображения WPA КофеЛайк: fix logout and login, change ios header 2023-06-23 11:06:28 +04:00
nikolay
4e0a41b644 dev #14384 Правка ошибок отображения WPA КофеЛайк: add auth service to store information about user and fix getUserInfo 2023-06-23 09:32:20 +04:00
nikolay
e20cd30a54 dev #14562 Добавить меню для iOS WPA: fix z index of menu 2023-06-22 12:48:08 +04:00
nikolay
abdb5b790c dev #14562 Добавить меню для iOS WPA: remove back arrow on ios on guest card 2023-06-22 12:32:02 +04:00
nikolay
10e180613f dev #14562 Добавить меню для iOS WPA: remove install app 2023-06-22 11:56:00 +04:00
nikolay
6d55ca2b14 dev #14384 Правка ошибок отображения WPA КофеЛайк: check if app is in installed apps 2023-06-22 11:48:12 +04:00
nikolay
fcdb3d8fc7 dev #14384 Правка ошибок отображения WPA КофеЛайк: add getInstalledrelatedApps log 2023-06-22 11:35:06 +04:00
nikolay
0ae4e31cfd dev #14562 Добавить меню для iOS WPA 2023-06-22 11:09:52 +04:00
nikolay
50ae2b601e dev #14384 Правка ошибок отображения WPA КофеЛайк: fix install button 2023-06-21 11:41:50 +04:00
nikolay
798ec5e21a dev #14384 Правка ошибок отображения WPA КофеЛайк: change keydown to input on code enter listener 2023-06-21 10:27:05 +04:00
nikolay
15e9fe7ec4 dev #14384 Правка ошибок отображения WPA КофеЛайк: fix sms enter code in safari 2023-06-21 10:06:11 +04:00
nikolay
26b2e8eb13 dev #14384 Правка ошибок отображения WPA КофеЛайк: fix sms enter code on ios 2023-06-20 12:28:35 +04:00
nikolay
7b5200d9f8 auto fill sms 2023-06-20 10:12:32 +04:00
nikolay
d66f72dc2e dev #14537 Добавить кнопку подписки на уведомления CL 2023-06-19 10:04:45 +04:00
nikolay
0ca219c7fa dev #14525 Заменить текст WPA CoffeeLike 2023-06-15 17:09:03 +04:00
nikolay
1a6345431d change firebase config 2023-06-15 16:49:41 +04:00
nikolay
bef4ed1a48 dev #14494 WPA CoffeeLike изменить апи для получения последней транзакции 2023-06-15 15:49:33 +04:00
nikolay
eef97fd35f dev #14494 WPA CoffeeLike изменить апи для получения последней транзакции 2023-06-15 15:40:19 +04:00
nikolay
a8062667df fix jenkinsfile 2023-06-15 15:07:28 +04:00
nikolay
46d10e50ea change jenkins node 2023-06-15 14:35:43 +04:00
nikolay
ccaa41ddf9 dev #14490 WPA CoffeeLike добавление в wallet вместо установки для iOS: fix url 2023-06-15 11:44:35 +04:00
nikolay
0e38321526 dev #14507 Авто подписка на уведомления для андроид WPA Кофелайк 2023-06-14 15:36:02 +04:00
nikolay
4e52e180d8 add logging 2023-06-14 09:18:49 +04:00
nikolay
30854f4cb9 dev #14490 WPA CoffeeLike добавление в wallet вместо установки для iOS 2023-06-13 15:31:04 +04:00
nikolay
425ef99af2 dev #14495 WPA CoffeeLike добавить ссылку на Правила 2023-06-13 13:33:35 +04:00
nikolay
b29f2e1fdf dev #14490 WPA CoffeeLike добавление в wallet вместо установки для iOS 2023-06-09 12:18:09 +04:00
nikolay
5c3e4a0368 dev #14384 Правка ошибок отображения WPA КофеЛайк: fix dependencies 2023-05-31 16:07:16 +04:00
nikolay
077b60fc87 dev #14384 Правка ошибок отображения WPA КофеЛайк: upgrade angular to 15 2023-05-31 15:57:54 +04:00
nikolay
e06d703b69 dev #14384 Правка ошибок отображения WPA КофеЛайк: change build command 2023-05-31 09:40:59 +04:00
nikolay
cfe370e3e3 Merge branch 'coffee-like-test' of https://git.hlcompany.ru/git/usersite into coffee-like-test 2023-05-29 16:16:39 +04:00
nikolay
e3fd36b881 dev #14384 Правка ошибок отображения WPA КофеЛайк: fix issue 2023-05-29 16:16:12 +04:00
1b1dcd9b50 Merge branch 'coffee-like-test' of https://git.hlcompany.ru/git/usersite into coffee-like-test 2023-05-26 10:42:13 +04:00
bb7e39f885 Поменял логотип для светлой темы 2023-05-26 10:41:49 +04:00
nikolay
b31bd30161 dev #14305 Изменение дизайна WPA Кофе-лайка: change styles 2023-05-25 16:57:04 +04:00
nikolay
7d098d1798 dev #14305 Изменение дизайна WPA Кофе-лайка: change styles 2023-05-25 16:43:27 +04:00
nikolay
7bade7caea dev #14305 Изменение дизайна WPA Кофе-лайка: fix infinity loading when login (typescript error) 2023-05-23 12:36:10 +04:00
238b70e95b dev #14338
Правка стилей
2023-05-20 21:51:07 +04:00
877cf018af dev #14338
добавил кнопку установки pwa
2023-05-20 16:09:36 +04:00
nikolay
24edd456db dev #14328 Добавить префикс в формирование QR: remove + 2023-05-18 15:33:22 +04:00
nikolay
cff512c561 Merge branch 'coffee-like-test' of https://git.hlcompany.ru/git/usersite into coffee-like-test 2023-05-18 09:55:11 +04:00
nikolay
6b60c4d8c4 dev #14328 Добавить префикс в формирование QR: change phone value in qr 2023-05-18 09:50:20 +04:00
04884bd0d9 Merge branch 'coffee-like-test' of https://git.hlcompany.ru/git/usersite into coffee-like-test 2023-05-17 12:12:41 +04:00
6c283812e7 dev #14318
добавил регистрацию юзера в айкокард, если его нет
2023-05-17 12:12:25 +04:00
nikolay
c5e4f3c229 dev #14305 Изменение дизайна WPA Кофе-лайка: block user with no info 2023-05-17 11:42:50 +04:00
nikolay
2fa05b391e dev #14305 Изменение дизайна WPA Кофе-лайка: change loyality program page 2023-05-16 15:38:21 +04:00
nikolay
ff9e04ecbd dev #14305 Изменение дизайна WPA Кофе-лайка: fix manifest 2023-05-16 11:53:23 +04:00
nikolay
28ab33248d dev #14305 Изменение дизайна WPA Кофе-лайка: fix manifest 2023-05-16 11:51:02 +04:00
nikolay
659e9d50bf dev #14305 Изменение дизайна WPA Кофе-лайка: fix manifest 2023-05-16 11:45:31 +04:00
nikolay
43980516ff dev #14305 Изменение дизайна WPA Кофе-лайка: change icons 2023-05-16 11:40:12 +04:00
nikolay
ca0fa2bfa6 dev #14305 Изменение дизайна WPA Кофе-лайка: change loading 2023-05-16 11:04:25 +04:00
nikolay
b81ee7f713 dev #14305 Изменение дизайна WPA Кофе-лайка: change qr code color to white 2023-05-16 09:11:28 +04:00
nikolay
044144735d dev #14305 Изменение дизайна WPA Кофе-лайка: change qr code color 2023-05-15 14:32:32 +04:00
nikolay
6feeec80f2 dev #14305 Изменение дизайна WPA Кофе-лайка: round bonuses count 2023-05-15 13:45:49 +04:00
e062f05f36 Merge remote-tracking branch 'remotes/origin/coffee-like' into coffee-like-test 2023-05-15 12:20:24 +04:00
bf3fd7e0a4 dev #13995
убарл очепятку
2023-05-15 12:10:26 +04:00
37ad910e75 dev #13995
добавил обновление имени и даты рождения(заглшка, чтобы работал костыль в СА) при авторизации
2023-05-15 12:08:22 +04:00
4a492f16e8 dev #13995
перенес изменения Николая из основной директории в дефолтный вариант представления
2023-05-15 11:52:27 +04:00
1e7175e3b5 dev #13995
сделал тестовую ветку , поменял в ней названия
2023-05-15 10:43:44 +04:00
be7c62a12b dev #13995
поменял иконки
2023-05-15 10:30:20 +04:00
6a075ec773 Merge branch 'coffee-like' of https://git.hlcompany.ru/git/usersite into coffee-like 2023-05-12 20:18:37 +04:00
86ac20f5e5 dev #13995
изменил структуру под аб тестирование, сделал 2 различных вариант (один дефолтный, другой с красным текстом)
модули загружаются лениво, для оптимизации загрузки приложения
пофиксил подсчет суммы транзакций за периоды
2023-05-12 20:10:06 +04:00
nikolay
2131e052c6 issue 14305: change styles 2023-05-12 16:21:46 +04:00
nikolay
787bc65691 change styles 2023-05-12 15:10:44 +04:00
04ecbb837c dev #13995
Исправил баги, доработал таймер для отправки повторного кода и сделал обработку ошибок при авторизации
2023-05-10 13:11:41 +04:00
77ecb32411 dev #13995
правка
2023-05-10 11:07:00 +04:00
9e1cad31f3 dev #13995
поменял версию
2023-05-10 02:40:47 +04:00
7c3a4168c4 dev #13995
поменял фавикон и другие доработки
2023-05-10 02:39:33 +04:00
76f1799b22 dev #14249
Информация по транзакциям
2023-05-10 02:38:17 +04:00
42b46bc478 dev #13995 2023-05-02 11:39:19 +04:00
af60b55e94 dev #13995
припилил СА, iikocard (но пока только customer_info), доработки
2023-04-25 19:10:14 +04:00
1da7556aed dev #13995
первоначальная верстка
2023-04-18 15:05:02 +04:00
f460954bc8 initial 2023-04-06 23:48:00 +04:00
7a9f9fc872 dev #13951
Исправил ошибки у новых пользователей
2023-03-30 12:43:42 +04:00
7bb05324a9 dev #13951
сделал суммирование бонусов со всех бонусных программ
2023-03-20 23:30:20 +04:00
5e3b211778 dev #13882
правки
2023-03-08 02:04:54 +04:00
e75999a3c1 dev #13882
правки
2023-03-08 02:02:21 +04:00
f8f500f612 dev #13882
правки
2023-03-08 01:42:44 +04:00
72808c3c7c dev #13882
правки
2023-03-08 01:36:27 +04:00
efb8095e2f dev #13882
правки
2023-03-08 01:17:48 +04:00
699db43b39 dev #13882
правки
2023-03-08 01:11:58 +04:00
5152d9ad30 dev #13882
правки
2023-03-08 00:29:44 +04:00
da340475e0 support #13704
поменял картинки
2023-02-19 23:46:28 +04:00
ef8ac5d24d support #13704
поменял картинку в apple wallet
2023-02-05 21:01:20 +04:00
fa5d29e517 support #13704
правка
2023-02-05 20:55:56 +04:00
1a5fa44abc support #13704
поменял картинки
2023-02-05 20:47:41 +04:00
Kataev Denis
bedda578c8 support #13704
поменял фавикон
2023-02-01 03:35:31 +04:00
Kataev Denis
44cf8d5074 support #13704
поменял картинку бабочки в манифесте
2023-02-01 03:29:49 +04:00
Kataev Denis
ecdd4f103e support #13704
поменял картинку бабочки
2023-02-01 03:11:59 +04:00
c0387d967d dev #13538
правка
2023-01-13 03:22:36 +04:00
b33ed15666 dev #13538
правка
2023-01-13 03:13:06 +04:00
c7ade7515c dev #13538
правка
2023-01-13 03:07:39 +04:00
e4bb1d8947 dev #13538
правка
2023-01-13 03:05:50 +04:00
c71e2d4c48 dev #13538
правка
2023-01-13 03:02:49 +04:00
88b0d92300 dev #13538
правка
2023-01-12 19:37:31 +04:00
e570e82f90 dev #13538
правка
2023-01-12 19:27:34 +04:00
85174c1fab dev #13538
правка
2023-01-12 19:22:10 +04:00
7a895c76a6 dev #13538
правка
2023-01-12 02:41:31 +04:00
dbf01e4fc5 dev #13538
правка
2023-01-12 02:38:24 +04:00
227392d7f6 dev #13538
правка
2023-01-12 02:35:56 +04:00
f8f2fe4673 dev #13538
правка
2023-01-12 02:27:14 +04:00
4d16fa4184 dev #13538
правка
2023-01-12 02:04:56 +04:00
5e69d6e5c5 dev #13538
перенес кнопку, вынес функционал в директиву
2023-01-12 01:57:32 +04:00
pumpurumer
a0de9cbb61 bump version 2022-10-27 12:34:25 +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
299 changed files with 38405 additions and 29358 deletions

View File

@ -1,16 +0,0 @@
# This file is used by the build system to adjust CSS and JS output to support the specified browsers below.
# For additional information regarding the format and rule options, please see:
# https://github.com/browserslist/browserslist#queries
# For the full list of supported browsers by the Angular framework, please see:
# https://angular.io/guide/browser-support
# You can see what browsers were selected by your queries by running:
# npx browserslist
last 1 Chrome version
last 1 Firefox version
last 2 Edge major versions
last 2 Safari major versions
last 2 iOS major versions
Firefox ESR

84
.gitignore vendored
View File

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

30
Jenkinsfile vendored Normal file
View File

@ -0,0 +1,30 @@
env.HL_BUILD_MODE = "jenkins"
node('Palladium'){
stage('get new version to repo') {
checkout scm
if (lastCommitIsBumpCommit()) {
currentBuild.result = 'ABORTED'
error('Последний коммит - результат сборки jenkins')
}
//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,122 +0,0 @@
{
"$schema": "./node_modules/@angular/cli/lib/config/schema.json",
"version": 1,
"newProjectRoot": "projects",
"projects": {
"fashion-logica": {
"projectType": "application",
"schematics": {
"@schematics/angular:component": {
"style": "scss"
}
},
"root": "",
"sourceRoot": "src",
"prefix": "app",
"architect": {
"build": {
"builder": "@angular-devkit/build-angular:browser",
"options": {
"outputPath": "/var/www/lk/fashion-logica",
"baseHref": "/fashion-logica/",
"index": "src/index.html",
"main": "src/main.ts",
"polyfills": "src/polyfills.ts",
"tsConfig": "tsconfig.app.json",
"inlineStyleLanguage": "scss",
"assets": [
"src/favicon.ico",
"src/assets",
"src/manifest.webmanifest",
"src/firebase-messaging-sw.js"
],
"styles": [
"node_modules/primeng/resources/themes/bootstrap4-light-blue/theme.css",
"node_modules/primeicons/primeicons.css",
"node_modules/primeng/resources/primeng.min.css",
"node_modules/ngx-sharebuttons/themes/modern.scss",
"node_modules/ngx-sharebuttons/themes/material.scss",
"src/styles.scss"
],
"scripts": [],
"serviceWorker": true,
"ngswConfigPath": "ngsw-config.json"
},
"configurations": {
"production": {
"budgets": [
{
"type": "initial",
"maximumWarning": "4mb",
"maximumError": "5mb"
},
{
"type": "anyComponentStyle",
"maximumWarning": "2kb",
"maximumError": "4kb"
}
],
"fileReplacements": [
{
"replace": "src/environments/environment.ts",
"with": "src/environments/environment.prod.ts"
}
],
"outputHashing": "all"
},
"development": {
"buildOptimizer": false,
"optimization": false,
"vendorChunk": true,
"extractLicenses": false,
"sourceMap": true,
"namedChunks": true
}
},
"defaultConfiguration": "production"
},
"serve": {
"builder": "@angular-devkit/build-angular:dev-server",
"configurations": {
"production": {
"browserTarget": "fashion-logica:build:production"
},
"development": {
"browserTarget": "fashion-logica:build:development"
}
},
"defaultConfiguration": "development"
},
"extract-i18n": {
"builder": "@angular-devkit/build-angular:extract-i18n",
"options": {
"browserTarget": "fashion-logica:build"
}
},
"test": {
"builder": "@angular-devkit/build-angular:karma",
"options": {
"main": "src/test.ts",
"polyfills": "src/polyfills.ts",
"tsConfig": "tsconfig.spec.json",
"karmaConfig": "karma.conf.js",
"inlineStyleLanguage": "scss",
"assets": [
"src/favicon.ico",
"src/firebase-messaging-sw.js",
"src/assets",
"src/manifest.webmanifest"
],
"styles": [
"src/styles.scss"
],
"scripts": []
}
}
}
}
},
"cli": {
"analytics": false
}
}

View File

@ -1,16 +1,16 @@
# Editor configuration, see https://editorconfig.org
root = true
[*]
charset = utf-8
indent_style = space
indent_size = 2
insert_final_newline = true
trim_trailing_whitespace = true
[*.ts]
quote_type = single
[*.md]
max_line_length = off
trim_trailing_whitespace = false
# Editor configuration, see https://editorconfig.org
root = true
[*]
charset = utf-8
indent_style = space
indent_size = 2
insert_final_newline = true
trim_trailing_whitespace = true
[*.ts]
quote_type = single
[*.md]
max_line_length = off
trim_trailing_whitespace = false

View File

@ -1,27 +1,27 @@
# FashionLogica
This project was generated with [Angular CLI](https://github.com/angular/angular-cli) version 14.0.6.
## Development server
Run `ng serve` for a dev server. Navigate to `http://localhost:4200/`. The application will automatically reload if you change any of the source files.
## Code scaffolding
Run `ng generate component component-name` to generate a new component. You can also use `ng generate directive|pipe|service|class|guard|interface|enum|module`.
## Build
Run `ng build` to build the project. The build artifacts will be stored in the `dist/` directory.
## Running unit tests
Run `ng test` to execute the unit tests via [Karma](https://karma-runner.github.io).
## Running end-to-end tests
Run `ng e2e` to execute the end-to-end tests via a platform of your choice. To use this command, you need to first add a package that implements end-to-end testing capabilities.
## Further help
To get more help on the Angular CLI use `ng help` or go check out the [Angular CLI Overview and Command Reference](https://angular.io/cli) page.
# TastyCoffee
This project was generated with [Angular CLI](https://github.com/angular/angular-cli) version 14.0.6.
## Development server
Run `ng serve` for a dev server. Navigate to `http://localhost:4200/`. The application will automatically reload if you change any of the source files.
## Code scaffolding
Run `ng generate component component-name` to generate a new component. You can also use `ng generate directive|pipe|service|class|guard|interface|enum|module`.
## Build
Run `ng build` to build the project. The build artifacts will be stored in the `dist/` directory.
## Running unit tests
Run `ng test` to execute the unit tests via [Karma](https://karma-runner.github.io).
## Running end-to-end tests
Run `ng e2e` to execute the end-to-end tests via a platform of your choice. To use this command, you need to first add a package that implements end-to-end testing capabilities.
## Further help
To get more help on the Angular CLI use `ng help` or go check out the [Angular CLI Overview and Command Reference](https://angular.io/cli) page.

131
angular/angular.json Normal file
View File

@ -0,0 +1,131 @@
{
"$schema": "./node_modules/@angular/cli/lib/config/schema.json",
"version": 1,
"newProjectRoot": "projects",
"projects": {
"tasty-coffee": {
"projectType": "application",
"schematics": {
"@schematics/angular:component": {
"style": "scss"
},
"i18n": {
"sourceLocale": "ru-RU"
}
},
"root": "",
"sourceRoot": "src",
"prefix": "app",
"architect": {
"build": {
"builder": "@angular-devkit/build-angular:browser",
"options": {
"outputPath": "/var/www/html/lk.crm4retail.ru/tasty-coffee",
"baseHref": "/",
"index": "src/index.html",
"main": "src/main.ts",
"polyfills": "src/polyfills.ts",
"tsConfig": "tsconfig.app.json",
"inlineStyleLanguage": "scss",
"assets": [
"src/favicon.ico",
"src/assets",
"src/manifest.webmanifest",
"src/firebase-messaging-sw.js",
"src/sw-master.js",
"src/sw-custom.js"
],
"styles": [
"./node_modules/@angular/material/prebuilt-themes/indigo-pink.css",
"node_modules/primeng/resources/themes/bootstrap4-light-blue/theme.css",
"node_modules/primeicons/primeicons.css",
"node_modules/primeng/resources/primeng.min.css",
"node_modules/ngx-sharebuttons/themes/modern.scss",
"node_modules/ngx-sharebuttons/themes/material.scss",
"src/styles.scss"
],
"scripts": [],
"serviceWorker": true,
"ngswConfigPath": "ngsw-config.json"
},
"configurations": {
"production": {
"budgets": [
{
"type": "initial",
"maximumWarning": "4mb",
"maximumError": "5mb"
},
{
"type": "anyComponentStyle",
"maximumWarning": "2kb",
"maximumError": "4kb"
}
],
"fileReplacements": [
{
"replace": "src/environments/environment.ts",
"with": "src/environments/environment.prod.ts"
}
],
"outputHashing": "all"
},
"development": {
"buildOptimizer": false,
"optimization": false,
"vendorChunk": true,
"extractLicenses": false,
"sourceMap": true,
"namedChunks": true
}
},
"defaultConfiguration": "production"
},
"serve": {
"builder": "@angular-devkit/build-angular:dev-server",
"configurations": {
"production": {
"browserTarget": "tasty-coffee:build:production"
},
"development": {
"browserTarget": "tasty-coffee:build:development",
"proxyConfig": "proxy.confi.json"
}
},
"defaultConfiguration": "development"
},
"extract-i18n": {
"builder": "@angular-devkit/build-angular:extract-i18n",
"options": {
"browserTarget": "tasty-coffee:build"
}
},
"test": {
"builder": "@angular-devkit/build-angular:karma",
"options": {
"main": "src/test.ts",
"polyfills": "src/polyfills.ts",
"tsConfig": "tsconfig.spec.json",
"karmaConfig": "karma.conf.js",
"inlineStyleLanguage": "scss",
"assets": [
"src/favicon.ico",
"src/firebase-messaging-sw.js",
"src/assets",
"src/manifest.webmanifest"
],
"styles": [
"./node_modules/@angular/material/prebuilt-themes/purple-green.css",
"src/styles.scss"
],
"scripts": []
}
}
}
}
},
"cli": {
"analytics": false
}
}

View File

@ -1,44 +1,44 @@
// Karma configuration file, see link for more information
// https://karma-runner.github.io/1.0/config/configuration-file.html
module.exports = function (config) {
config.set({
basePath: '',
frameworks: ['jasmine', '@angular-devkit/build-angular'],
plugins: [
require('karma-jasmine'),
require('karma-chrome-launcher'),
require('karma-jasmine-html-reporter'),
require('karma-coverage'),
require('@angular-devkit/build-angular/plugins/karma')
],
client: {
jasmine: {
// you can add configuration options for Jasmine here
// the possible options are listed at https://jasmine.github.io/api/edge/Configuration.html
// for example, you can disable the random execution with `random: false`
// or set a specific seed with `seed: 4321`
},
clearContext: false // leave Jasmine Spec Runner output visible in browser
},
jasmineHtmlReporter: {
suppressAll: true // removes the duplicated traces
},
coverageReporter: {
dir: require('path').join(__dirname, './coverage/card-project'),
subdir: '.',
reporters: [
{ type: 'html' },
{ type: 'text-summary' }
]
},
reporters: ['progress', 'kjhtml'],
port: 9876,
colors: true,
logLevel: config.LOG_INFO,
autoWatch: true,
browsers: ['Chrome'],
singleRun: false,
restartOnFileChange: true
});
};
// Karma configuration file, see link for more information
// https://karma-runner.github.io/1.0/config/configuration-file.html
module.exports = function (config) {
config.set({
basePath: '',
frameworks: ['jasmine', '@angular-devkit/build-angular'],
plugins: [
require('karma-jasmine'),
require('karma-chrome-launcher'),
require('karma-jasmine-html-reporter'),
require('karma-coverage'),
require('@angular-devkit/build-angular/plugins/karma')
],
client: {
jasmine: {
// you can add configuration options for Jasmine here
// the possible options are listed at https://jasmine.github.io/api/edge/Configuration.html
// for example, you can disable the random execution with `random: false`
// or set a specific seed with `seed: 4321`
},
clearContext: false // leave Jasmine Spec Runner output visible in browser
},
jasmineHtmlReporter: {
suppressAll: true // removes the duplicated traces
},
coverageReporter: {
dir: require('path').join(__dirname, './coverage/card-project'),
subdir: '.',
reporters: [
{ type: 'html' },
{ type: 'text-summary' }
]
},
reporters: ['progress', 'kjhtml'],
port: 9876,
colors: true,
logLevel: config.LOG_INFO,
autoWatch: true,
browsers: ['Chrome'],
singleRun: false,
restartOnFileChange: true
});
};

View File

@ -1,30 +1,30 @@
{
"$schema": "./node_modules/@angular/service-worker/config/schema.json",
"index": "/index.html",
"assetGroups": [
{
"name": "app",
"installMode": "prefetch",
"resources": {
"files": [
"/favicon.ico",
"/index.html",
"/manifest.webmanifest",
"/*.css",
"/*.js"
]
}
},
{
"name": "assets",
"installMode": "lazy",
"updateMode": "prefetch",
"resources": {
"files": [
"/assets/**",
"/*.(svg|cur|jpg|jpeg|png|apng|webp|avif|gif|otf|ttf|woff|woff2)"
]
}
}
]
}
{
"$schema": "./node_modules/@angular/service-worker/config/schema.json",
"index": "/index.html",
"assetGroups": [
{
"name": "app",
"installMode": "prefetch",
"resources": {
"files": [
"/favicon.ico",
"/index.html",
"/manifest.webmanifest",
"/*.css",
"/*.js"
]
}
},
{
"name": "assets",
"installMode": "lazy",
"updateMode": "prefetch",
"resources": {
"files": [
"/assets/**",
"/*.(svg|cur|jpg|jpeg|png|apng|webp|avif|gif|otf|ttf|woff|woff2)"
]
}
}
]
}

File diff suppressed because it is too large Load Diff

63
angular/package.json Normal file
View File

@ -0,0 +1,63 @@
{
"name": "tasty-coffee",
"version": "0.0.2",
"scripts": {
"ng": "ng",
"start": "ng serve --host 192.168.0.58",
"build": "ng build --aot --configuration production --output-hashing none",
"watch": "ng build --watch --configuration development",
"test": "ng test"
},
"private": true,
"dependencies": {
"@angular/animations": "^15.2.9",
"@angular/cdk": "^15.2.9",
"@angular/common": "^15.2.9",
"@angular/compiler": "^15.2.9",
"@angular/core": "^15.2.9",
"@angular/fire": "^7.5.0",
"@angular/forms": "^15.2.9",
"@angular/material": "^15.2.9",
"@angular/material-moment-adapter": "^15.2.9",
"@angular/platform-browser": "^15.2.9",
"@angular/platform-browser-dynamic": "^15.2.9",
"@angular/router": "^15.2.9",
"@angular/service-worker": "^15.2.9",
"@fortawesome/angular-fontawesome": "^0.12.1",
"@fortawesome/fontawesome-svg-core": "^6.2.0",
"@fortawesome/free-brands-svg-icons": "^6.2.0",
"@fortawesome/free-solid-svg-icons": "^6.2.0",
"@types/uuid": "^8.3.4",
"@types/web": "^0.0.99",
"angular-moment-timezone": "^1.7.1",
"angular2-text-mask": "^9.0.0",
"barcode-2-svg": "^0.3.3",
"firebase": "^9.9.3",
"google-libphonenumber": "^3.2.30",
"jsbarcode": "^3.11.5",
"libphonenumber-js": "^1.10.28",
"ng-qrcode": "^8.0.1",
"ngx-mat-intl-tel-input": "^5.0.0",
"ngx-sharebuttons": "^11.0.0",
"primeicons": "^5.0.0",
"primeng": "^15.4.1",
"rxjs": "~7.5.0",
"tslib": "^2.3.0",
"uuid": "^8.3.2",
"zone.js": "~0.11.4"
},
"devDependencies": {
"@angular-devkit/build-angular": "^15.2.8",
"@angular/cli": "~15.2.8",
"@angular/compiler-cli": "^15.2.9",
"@types/google-libphonenumber": "^7.4.23",
"@types/jasmine": "~4.0.0",
"jasmine-core": "~4.1.0",
"karma": "~6.3.0",
"karma-chrome-launcher": "~3.1.0",
"karma-coverage": "~2.2.0",
"karma-jasmine": "~5.0.0",
"karma-jasmine-html-reporter": "~1.7.0",
"typescript": "4.8"
}
}

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

@ -0,0 +1,38 @@
{
"/api": {
"target": "https://apple-push-notifications.it-retail.tech/apns/api",
"secure": false,
"pathRewrite": {
"^/api": ""
},
"changeOrigin": true,
"logLevel": "debug"
},
"/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"
},
"/it-retail": {
"target": "https://sakura.lk.crm4retail.ru/it-retail",
"secure": false,
"pathRewrite": {
"^/it-retail": ""
},
"changeOrigin": true,
"logLevel": "debug"
}
}

View File

@ -0,0 +1,20 @@
import { NgModule } from '@angular/core';
import { RouterModule, Routes } from '@angular/router';
import { MainComponent } from './pages/main/main.component';
import { AuthGuard } from './guards/auth-guard.guard';
const routes: Routes = [
{
path: '',
loadChildren: () => null as any,
// component: GuestCardComponent,
// canActivate: [AuthGuard]
},
// { path: '**', component: NotFoundComponent }
];
@NgModule({
imports: [RouterModule.forRoot(routes)],
exports: [RouterModule]
})
export class AppRoutingModule { }

View File

@ -0,0 +1,2 @@
<router-outlet></router-outlet>
<p-toast position="top-center"></p-toast>

View File

@ -1,35 +1,35 @@
import { TestBed } from '@angular/core/testing';
import { RouterTestingModule } from '@angular/router/testing';
import { AppComponent } from './app.component';
describe('AppComponent', () => {
beforeEach(async () => {
await TestBed.configureTestingModule({
imports: [
RouterTestingModule
],
declarations: [
AppComponent
],
}).compileComponents();
});
it('should create the app', () => {
const fixture = TestBed.createComponent(AppComponent);
const app = fixture.componentInstance;
expect(app).toBeTruthy();
});
it(`should have as title 'fashion-logica'`, () => {
const fixture = TestBed.createComponent(AppComponent);
const app = fixture.componentInstance;
expect(app.title).toEqual('fashion-logica');
});
it('should render title', () => {
const fixture = TestBed.createComponent(AppComponent);
fixture.detectChanges();
const compiled = fixture.nativeElement as HTMLElement;
expect(compiled.querySelector('.content span')?.textContent).toContain('fashion-logica app is running!');
});
});
import { TestBed } from '@angular/core/testing';
import { RouterTestingModule } from '@angular/router/testing';
import { AppComponent } from './app.component';
describe('AppComponent', () => {
beforeEach(async () => {
await TestBed.configureTestingModule({
imports: [
RouterTestingModule
],
declarations: [
AppComponent
],
}).compileComponents();
});
it('should create the app', () => {
const fixture = TestBed.createComponent(AppComponent);
const app = fixture.componentInstance;
expect(app).toBeTruthy();
});
it(`should have as title 'tasty-coffee'`, () => {
const fixture = TestBed.createComponent(AppComponent);
const app = fixture.componentInstance;
expect(app.title).toEqual('tasty-coffee');
});
it('should render title', () => {
const fixture = TestBed.createComponent(AppComponent);
fixture.detectChanges();
const compiled = fixture.nativeElement as HTMLElement;
expect(compiled.querySelector('.content span')?.textContent).toContain('tasty-coffee app is running!');
});
});

View File

@ -0,0 +1,64 @@
import { Component, OnInit } from '@angular/core';
import { RouteConfigLoadStart, Router } from '@angular/router';
import { AuthService } from './services/auth.service';
import { CookiesService } from './services/cookies.service';
import { JsonrpcService, RpcService } from './services/jsonrpc.service';
@Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.scss'],
})
export class AppComponent implements OnInit {
title = 'Tasty Coffee';
constructor(
private router: Router,
private cookiesService: CookiesService,
private jsonRpcService: JsonrpcService,
private authService: AuthService,
) {
this.router.events.subscribe((x) => {
if (x instanceof RouteConfigLoadStart && x.route.path === '') {
x.route.loadChildren = () => {
switch (this.cookiesService.getItem('presentation-option')) {
case 'first':
return import(
'./presentation-options/first-option/first-option.module'
).then((mod) => mod.FirstOptionModule);
default:
return import(
'./presentation-options/default-option/default-option.module'
).then((mod) => mod.DefaultOptionModule);
}
};
}
});
}
getAdditionalInfo() {
return this.jsonRpcService.rpc(
{
method: 'getAdditionalInfo',
params: [],
},
RpcService.authService,
true
);
}
ngOnInit(): void {
if (this.authService.authorized) {
this.authService.getUserInfo();
}
this.getAdditionalInfo().subscribe({
next: (value) => {
this.cookiesService.setCookie('presentation-option', value?.data?.interface_id || 'default')
},
error: (err) => {
console.error(err);
}
})
}
}

View File

@ -0,0 +1,93 @@
import { OrderStatus, Page, PageCode, lvlPeriod } 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: true,
},
{
code: PageCode.Orders,
name: 'Ваши чеки',
description: '',
resName: 'orders',
onSideBar: true,
},
{
code: PageCode.UserData,
name: 'Заполнить анкету',
description: '',
resName: 'user-data',
onSideBar: true
},
{
code: PageCode.RefSystem,
name: 'Пригласить друга',
description: '',
resName: 'ref-system',
onSideBar: true,
}
];
export const orderStatuses: OrderStatus = {
'Cancelled': 'Отменен',
'InProcessing': 'В обработке',
'Unconfirmed': 'Принят',
'WaitCooking': 'Принят',
'ReadyForCooking': 'Принят',
'CookingStarted': 'Готовится',
'CookingCompleted': 'Приготовлен',
'Waiting': 'В пути',
'OnWay': 'В пути',
'Delivered': 'Выполнен',
'Closed': 'Выполнен',
};
export const lvlPeriods: lvlPeriod[] = [
{
percent: 3,
start: 0,
end: 1600,
color: '#f2c94c'
},
{
percent: 6,
start: 1601,
end: 3600,
color: '#f2994a'
},
{
percent: 10,
start: 3601,
end: 8600,
color: '#6fcf97'
},
{
percent: 15,
start: 8601,
color: '#6fcf97'
},
]

View File

@ -0,0 +1,121 @@
import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { LoyalityProgramComponent } from './pages/loyality-program/loyality-program.component';
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 } 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 { 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 { 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 { MatIconModule } from '@angular/material/icon';
import { GuestCardComponent } from './pages/guest-card/guest-card.component';
import { QrCodeModule } from 'ng-qrcode';
import { AccordionComponent } from './components/accordion/accordion.component';
import { LastOrderComponent } from './components/last-order/last-order.component';
import { InviteFriendsComponent } from './components/invite-friends/invite-friends.component';
import { FooterComponent } from './components/footer/footer.component';
import { SocialMediaButtonsComponent } from './components/social-media-buttons/social-media-buttons.component';
import { LoginComponent } from './pages/login/login.component';
import { NgxMatIntlTelInputComponent } from 'ngx-mat-intl-tel-input';
import { MatFormFieldModule } from '@angular/material/form-field';
import { MatInputModule } from '@angular/material/input';
import { MatSnackBarModule } from '@angular/material/snack-bar';
import {
MAT_BOTTOM_SHEET_DATA,
MatBottomSheetModule,
MatBottomSheetRef,
} from '@angular/material/bottom-sheet';
import { MatProgressSpinnerModule } from '@angular/material/progress-spinner';
import { DirectivesModule } from './directives/directives.module';
@NgModule({
declarations: [
AppComponent,
NavbarComponent,
MainComponent,
CardComponent,
AuthComponent,
AccountComponent,
ExitComponent,
BonusProgramComponent,
OrderInfoComponent,
FooterButtonsComponent,
UserDataComponent,
RefSystemComponent,
NotFoundComponent,
GuestCardComponent,
AccordionComponent,
LastOrderComponent,
InviteFriendsComponent,
FooterComponent,
SocialMediaButtonsComponent,
LoginComponent,
// FocusNextInputDirective,
LoyalityProgramComponent,
],
imports: [
BrowserModule,
AppRoutingModule,
InputMaskModule,
ProgressSpinnerModule,
FormsModule,
HttpClientModule,
BrowserAnimationsModule,
BrowserModule,
ServiceWorkerModule.register('/sw-master.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,
ShareButtonsModule.withConfig({
debug: true,
}),
ShareIconsModule,
MatIconModule,
QrCodeModule,
NgxMatIntlTelInputComponent,
MatFormFieldModule,
MatInputModule,
MatSnackBarModule,
MatBottomSheetModule,
MatProgressSpinnerModule,
DirectivesModule,
],
providers: [
DialogService,
MessageService,
MessagingService,
{ provide: MatBottomSheetRef, useValue: {} },
{ provide: MAT_BOTTOM_SHEET_DATA, useValue: {} },
],
bootstrap: [AppComponent],
})
export class AppModule {}

View File

@ -0,0 +1,7 @@
<div class="tab">
<input [id]="'tab-' + guid" type="checkbox" name="tabs" />
<label [for]="'tab-' + guid">{{ header }}</label>
<div class="tab-content">
<ng-content></ng-content>
</div>
</div>

View File

@ -0,0 +1,85 @@
:host {
width: 100%;
}
.tab {
position: relative;
margin-bottom: 1px;
width: 100%;
color: var(--text-color);
overflow: hidden;
border-bottom: solid 1px #bdbdbd;
}
.tab input {
position: absolute;
opacity: 0;
z-index: -1;
}
.tab label {
display: block;
padding: 20px 26px;
position: relative;
background: #ffffff00;
font-weight: 400;
font-size: 12px;
line-height: 14px;
letter-spacing: -0.5px;
cursor: pointer;
}
// .tab label:before {
// display: block;
// content: " ";
// padding-top: 5px;
// }
.tab-content {
max-height: 0;
overflow: hidden;
background: #292929;
-webkit-transition: all 0.35s;
-o-transition: all 0.35s;
transition: all 0.35s;
border-top: solid 1px #bdbdbd;
font-style: normal;
font-weight: 400;
font-size: 12px;
p {
margin: 0;
}
}
.tab-content p {
margin: 1em;
}
/* :checked */
.tab input:checked ~ .tab-content {
padding: 16px 26px;
max-height: 100vh;
}
/* Icon */
.tab label::after {
position: absolute;
right: 0;
top: 0;
line-height: 3;
text-align: center;
-webkit-transition: all 0.35s;
-o-transition: all 0.35s;
transition: all 0.35s;
height: 100%;
margin-right: 12px;
display: flex;
align-items: center;
}
.tab input[type="checkbox"] + label::after {
content: "+";
}
.tab input[type="radio"] + label::after {
content: "\25BC";
}
.tab input[type="checkbox"]:checked + label::after {
transform: rotate(315deg);
}
.tab input[type="radio"]:checked + label::after {
transform: rotateX(180deg);
}

View File

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

View File

@ -0,0 +1,23 @@
import { Component, Input, OnInit } from '@angular/core';
import { v4 as uuidv4 } from 'uuid';
export interface IAccordionData {
header: string;
body: string;
}
@Component({
selector: 'app-accordion[header]',
templateUrl: './accordion.component.html',
styleUrls: ['./accordion.component.scss']
})
export class AccordionComponent implements OnInit {
@Input() header!: string
public guid: string = uuidv4()
constructor() { }
ngOnInit(): void {
}
}

View File

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

View File

@ -1,15 +1,15 @@
import { Component, OnInit } from '@angular/core';
@Component({
selector: 'app-card',
templateUrl: './card.component.html',
styleUrls: ['./card.component.scss']
})
export class CardComponent implements OnInit {
constructor() { }
ngOnInit(): void {
}
}
import { Component, OnInit } from '@angular/core';
@Component({
selector: 'app-card',
templateUrl: './card.component.html',
styleUrls: ['./card.component.scss']
})
export class CardComponent implements OnInit {
constructor() { }
ngOnInit(): void {
}
}

View File

@ -0,0 +1,7 @@
<span class="example-pizza-party" matSnackBarLabel>
Вы действительно хотите выйти?
</span>
<div class="buttons-container">
<button mat-button (click)="rejection()" style="background: red;">Нет</button>
<button mat-button (click)="logout()">Да</button>
</div>

View File

@ -0,0 +1,22 @@
:host {
display: flex;
flex-direction: column;
align-items: center;
.buttons-container {
width: 100%;
display: flex;
flex-direction: row;
justify-content: space-between;
}
}
button {
padding: 10px 62px;
margin-top: 8px;
background-color: var(--button-color);
color: var(--button-text-color);
border-radius: 3px;
border: none;
width: calc(50% - 12px);
}

View File

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

View File

@ -0,0 +1,21 @@
import { Component } from '@angular/core';
import { MatBottomSheetRef } from '@angular/material/bottom-sheet';
@Component({
selector: 'app-exit',
templateUrl: './exit.component.html',
styleUrls: ['./exit.component.scss'],
})
export class ExitComponent {
constructor(
private _bottomSheetRef: MatBottomSheetRef<ExitComponent>
) {}
rejection() {
this._bottomSheetRef.dismiss(false)
}
logout() {
this._bottomSheetRef.dismiss(true)
}
}

View File

@ -1,18 +1,17 @@
<div class="footer-buttons-container">
<!-- *ngIf="deferredPrompt && token?.length" -->
<button
*ngIf="((deviceType == 'android' && deferredPrompt) || deviceType == 'ios') && token?.length"
class="footer-buttons-container__button download"
(click)="downloadPWA()"
>
<img src="./assets/download.svg" alt="download" />
</button>
<button
*ngIf="!isPermissionNotifications && token?.length"
class="footer-buttons-container__button"
(click)="requestPermission()"
value="click to copy"
>
<img src="./assets/notification.svg" alt="notification" />
</button>
</div>
<div class="footer-buttons-container">
<!-- <button
*ngIf="((deviceType == 'android' && deferredPrompt) || deviceType == 'ios') && token?.length"
class="footer-buttons-container__button download"
(click)="downloadPWA()"
>
<img src="./assets/download.svg" alt="download" />
</button> -->
<button
*ngIf="!isPermissionNotifications && token?.length"
class="footer-buttons-container__button"
(click)="requestPermission()"
value="click to copy"
>
<img src="./assets/notification.svg" alt="notification" />
</button>
</div>

View File

@ -1,39 +1,39 @@
:host {
.footer-buttons-container {
position: fixed;
bottom: 16px;
right: 0;
display: flex;
justify-content: flex-end;
z-index: 9999;
flex-direction: column;
align-items: flex-end;
padding: 0 8px;
&__button {
display: flex;
justify-content: center;
align-items: center;
width: 60px;
height: 60px;
background: #09467f;
border: solid #fff 1px !important;
color: #fff;
border-radius: 100%;
font-weight: 600;
letter-spacing: 2px;
border: none;
margin-top: 8px;
margin-right: 0;
cursor: pointer;
img {
width: 60%;
}
}
}
@media (display-mode: standalone) {
.download {
display: none;
}
}
}
:host {
.footer-buttons-container {
position: fixed;
bottom: 16px;
right: 0;
display: flex;
justify-content: flex-end;
z-index: 9999;
flex-direction: column;
align-items: flex-end;
padding: 0 8px;
&__button {
display: flex;
justify-content: center;
align-items: center;
width: 60px;
height: 60px;
background: var(--text-color);
border: solid var(--text-color) 1px !important;
color: var(--text-color);
border-radius: 100%;
font-weight: 600;
letter-spacing: 2px;
border: none;
margin-top: 8px;
margin-right: 0;
cursor: pointer;
img {
width: 60%;
}
}
}
@media (display-mode: standalone) {
.download {
display: none;
}
}
}

View File

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

View File

@ -0,0 +1,24 @@
import { Component, EventEmitter, Input, OnInit, Output } from '@angular/core';
import { MessageService } from 'primeng/api';
@Component({
selector: 'app-footer-buttons',
templateUrl: './footer-buttons.component.html',
styleUrls: ['./footer-buttons.component.scss']
})
export class FooterButtonsComponent implements OnInit {
@Input() token!: string;
@Input() isPermissionNotifications!: boolean;
@Output() requestingPermission = new EventEmitter<null>();
public deviceType: 'ios' | 'android' | null = null;
constructor(
) { }
ngOnInit(): void {
}
requestPermission() {
this.requestingPermission.emit(null)
}
}

View File

@ -0,0 +1,4 @@
<img src="/assets/logo.svg" alt="Логотип">
<h3>Горячая линия</h3>
<a class="phone-number" href="tel:88003334130">8 (800) 333-41-30</a>
<app-social-media-buttons></app-social-media-buttons>

View File

@ -0,0 +1,28 @@
:host {
display: flex;
flex-direction: column;
align-items: center;
gap: 20px;
padding-bottom: 100px;
h3 {
margin: 0;
font-family: Montserrat;
font-style: normal;
font-weight: 400;
font-size: 15px;
line-height: 20px;
text-align: center;
letter-spacing: -0.24px;
color: #6a737c;
}
.phone-number {
font-style: normal;
font-weight: 700;
font-size: 17px;
line-height: 22px;
text-align: center;
letter-spacing: -0.408px;
color: #d9d9d9;
text-decoration: none;
}
}

View File

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

View File

@ -0,0 +1,15 @@
import { Component, OnInit } from '@angular/core';
@Component({
selector: 'app-footer',
templateUrl: './footer.component.html',
styleUrls: ['./footer.component.scss']
})
export class FooterComponent implements OnInit {
constructor() { }
ngOnInit(): void {
}
}

View File

@ -0,0 +1,25 @@
<h2>Пригласи друзей!</h2>
<div class="container">
<p>
Пригласи друзей зарегистрироваться в приложении по твоему уникальному коду и
получи бонусы, когда они совершат первую покупку.
</p>
<button class="share" (click)="share()" [disabled]="loading">
<ng-container *ngIf="loading">
<ng-container
*ngTemplateOutlet="spinner; context: { $implicit: 24 }"
></ng-container>
</ng-container>
<mat-icon
*ngIf="!loading"
aria-hidden="false"
aria-label="Share"
fontIcon="share"
style="color: #fff"
></mat-icon>
</button>
</div>
<ng-template #spinner let-diameter>
<mat-spinner [strokeWidth]="3" [diameter]="diameter"></mat-spinner>
</ng-template>

View File

@ -0,0 +1,34 @@
:host {
padding: 16px;
h2 {
font-family: Montserrat;
font-style: normal;
font-weight: 700;
font-size: 17px;
line-height: 22px;
letter-spacing: -0.408px;
}
& .container {
width: 100%;
display: flex;
flex-direction: row;
gap: 16px;
p {
font-style: normal;
font-weight: 400;
font-size: 12px;
line-height: 16px;
color: var(--text-color);
}
.share {
width: 72px;
height: 48px;
background: var(--button-color);
padding: 8px 26px;
border: none;
}
}
}

View File

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

View File

@ -0,0 +1,54 @@
import { Component, OnInit } from '@angular/core';
import { MessageService } from 'primeng/api';
import { lastValueFrom } from 'rxjs';
import { JsonrpcService, RpcService } from 'src/app/services/jsonrpc.service';
import { environment } from 'src/environments/environment';
@Component({
selector: 'app-invite-friends',
templateUrl: './invite-friends.component.html',
styleUrls: ['./invite-friends.component.scss']
})
export class InviteFriendsComponent implements OnInit {
public refUrl: string = `${environment.defaultUrl}/?refUserId=`
public loading: boolean = true;
private shareData: ShareData = {
title: ''
}
constructor(
private jsonrpc: JsonrpcService,
private messageService: MessageService
) { }
async ngOnInit() {
const accountData = (await lastValueFrom(
this.jsonrpc
.rpc(
{
method: 'getTokenData',
params: [],
},
RpcService.authService,
true
)
)).data
this.refUrl += accountData.user_id
this.loading = false
}
share() {
if (navigator.share) {
navigator.share({
title: document.title,
text: "Tasty Coffee",
url: this.refUrl
})
.then(() => console.log('Successful share'))
.catch((error) => {
console.log('Error sharing:', error)
});
}
}
}

View File

@ -0,0 +1,31 @@
<h2>Ваш предыдущий заказ</h2>
<div class="info-order">
<p class="flex"><span>Дата: </span>
<span *ngIf="!loading">{{(lastOrder?.transactionCreateDate | date:'dd.MM.yyyyг.') || 'Данные не найдены'}}</span>
<ng-container *ngIf="loading">
<ng-container
*ngTemplateOutlet="spinner; context: { $implicit: 24 }"
></ng-container>
</ng-container>
</p>
<p class="flex"><span>На сумму: </span>
<span *ngIf="!loading">{{lastOrder?.orderSum ? lastOrder?.orderSum + ' ₽' : 'Данные не найдены'}}</span>
<ng-container *ngIf="loading">
<ng-container
*ngTemplateOutlet="spinner; context: { $implicit: 24 }"
></ng-container>
</ng-container>
</p>
</div>
<a href="https://yandex.ru/profile/151770398186" target="_blank">
<button class="evaluate-order">Оценить заказ</button>
</a>
<p class="info">
Списание бонусов возможно на любые категории. Бонусами можно оплатить 100%
суммы покупки. Бонусы начисляются только на напитки с учётом добавок.
Неиспользованные бонусы сгорают в течение 90 дней.
</p>
<ng-template #spinner let-diameter>
<mat-spinner [strokeWidth]="3" [diameter]="diameter"></mat-spinner>
</ng-template>

View File

@ -0,0 +1,52 @@
:host {
padding: 24px 16px 0px;
& > h2 {
font-family: Montserrat;
font-style: normal;
font-weight: 700;
font-size: 15px;
line-height: 20px;
letter-spacing: -0.24px;
}
& > .info-order {
font-style: normal;
font-weight: 400;
font-size: 12px;
line-height: 16px;
span {
color: #828282;
}
.flex {
display: flex;
align-items: center;
flex-direction: row;
gap: 8px;
}
}
.evaluate-order {
margin: 24px 0;
width: 100%;
padding: 12px;
text-align: center;
border: 2px solid #28af49;
border-radius: 6px;
font-style: normal;
font-weight: 700;
font-size: 17px;
line-height: 22px;
letter-spacing: -0.408px;
background-color: transparent;
color: #28af49;
}
.info {
font-style: normal;
font-weight: 400;
font-size: 12px;
line-height: 16px;
color: rgba(255, 255, 255, 0.5);
}
}

View File

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

View File

@ -0,0 +1,18 @@
import { Component, Input, OnInit } from '@angular/core';
import { Purchase } from 'src/app/interface/data';
@Component({
selector: 'app-last-order[lastOrder]',
templateUrl: './last-order.component.html',
styleUrls: ['./last-order.component.scss']
})
export class LastOrderComponent implements OnInit {
@Input() lastOrder!: Purchase;
@Input() loading!: boolean;
constructor() { }
ngOnInit(): void {
}
}

View File

@ -0,0 +1,5 @@
<div class="container">
<mat-icon style="cursor: pointer;" aria-hidden="false" aria-label="Назад" fontIcon="arrow_back_ios" class="back-arrow" (click)="back()"></mat-icon>
<h1 class="title">{{title}}</h1>
<div class="plug"></div>
</div>

View File

@ -0,0 +1,33 @@
:host {
width: 100%;
}
.container {
box-sizing: border-box;
padding: 12px 16px;
width: 100%;
background: #231f20;
box-shadow: 0px 8px 16px rgba(17, 17, 17, 0.5);
color: var(--text-color);
display: flex;
align-items: center;
justify-content: space-between;
.back-arrow {
font-size: 16px;
display: flex;
align-items: center;
justify-content: center;
}
.plug {
height: 24px;
width: 24px;
visibility: hidden;
}
.title {
font-family: "Montserrat", sans-serif;
font-weight: 700;
font-size: 17px;
line-height: 22px;
margin: 0;
}
}

View File

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

View File

@ -0,0 +1,21 @@
import { Component, EventEmitter, Input, OnInit, Output } from '@angular/core';
@Component({
selector: 'app-navbar[title]',
templateUrl: './navbar.component.html',
styleUrls: ['./navbar.component.scss']
})
export class NavbarComponent implements OnInit {
@Input() title: string = 'Название не задано'
@Output() backEvent = new EventEmitter<null>();
constructor() { }
ngOnInit(): void {
}
back() {
this.backEvent.emit(null)
}
}

View File

@ -1,98 +1,97 @@
.woocommerce-MyAccount-content {
max-width: 400px;
margin-left: calc(50vw - 200px);
}
.container-progressbar {
width: 80%;
transform: translate(0, 0);
margin: 18px 10% 90px;
}
.steps {
position: relative;
display: flex;
justify-content: space-between;
width: 100%;
}
.step {
width: 20px;
height: 20px;
background: #fff;
border: 2px solid #acaca6;
border-radius: 50%;
transition: background 1s;
position: relative;
.status-image {
width: 80px;
height: 80px;
position: absolute;
top: 25px;
left: -32px;
}
}
.step.selected {
border: 2px solid #f9b004;
}
.step.completed {
border: 2px solid #f9b004;
background: #f9b004;
}
.step.cancelled {
background: #d7120b;
border: 2px solid #d7120b;
}
.progress {
position: absolute;
width: 100%;
height: 50%;
border-bottom: 2px solid #acaca6;
z-index: -1;
}
.percent {
position: absolute;
width: 0;
top: 2px;
height: 100%;
border-bottom: 2px solid #f9b004;
z-index: 1;
transition: width 1s;
}
p {
margin: 16px 0;
}
.woocommerce-order-details {
&__title {
font-weight: 600;
font-size: 18px;
}
}
.woocommerce-table {
border: 2px solid #dee2e6;
border-radius: 0.25rem;
border-collapse: separate;
text-align: left;
margin-top: 8px;
th {
font-weight: 600;
}
th, td {
padding: 0.7rem 1.5rem;
border-bottom: 2px solid #dee2e6;
}
}
.woocommerce-column {
&__title {
margin-top: 16px;
font-weight: 600;
font-size: 18px;
}
}
address {
border: 1px solid #dee2e6;
border-bottom-width: 2px;
border-right-width: 2px;
text-align: left;
width: 100%;
border-radius: 5px;
padding: 10px 12px;
margin-top: 8px;
}
.woocommerce-MyAccount-content {
max-width: 400px;
margin-left: calc(50vw - 200px);
}
.container-progressbar {
width: 80%;
transform: translate(0, 0);
margin: 18px 10% 90px;
}
.steps {
position: relative;
display: flex;
justify-content: space-between;
width: 100%;
}
.step {
width: 20px;
height: 20px;
border: 2px solid #acaca6;
border-radius: 50%;
transition: background 1s;
position: relative;
.status-image {
width: 80px;
height: 80px;
position: absolute;
top: 25px;
left: -32px;
}
}
.step.selected {
border: 2px solid #f9b004;
}
.step.completed {
border: 2px solid #f9b004;
background: #f9b004;
}
.step.cancelled {
background: #d7120b;
border: 2px solid #d7120b;
}
.progress {
position: absolute;
width: 100%;
height: 50%;
border-bottom: 2px solid #acaca6;
z-index: -1;
}
.percent {
position: absolute;
width: 0;
top: 2px;
height: 100%;
border-bottom: 2px solid #f9b004;
z-index: 1;
transition: width 1s;
}
p {
margin: 16px 0;
}
.woocommerce-order-details {
&__title {
font-weight: 600;
font-size: 18px;
}
}
.woocommerce-table {
border: 2px solid #dee2e6;
border-radius: 0.25rem;
border-collapse: separate;
text-align: left;
margin-top: 8px;
th {
font-weight: 600;
}
th, td {
padding: 0.7rem 1.5rem;
border-bottom: 2px solid #dee2e6;
}
}
.woocommerce-column {
&__title {
margin-top: 16px;
font-weight: 600;
font-size: 18px;
}
}
address {
border: 1px solid #dee2e6;
border-bottom-width: 2px;
border-right-width: 2px;
text-align: left;
width: 100%;
border-radius: 5px;
padding: 10px 12px;
margin-top: 8px;
}

View File

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

View File

@ -1,43 +1,43 @@
import { Component, Input, OnInit } from '@angular/core';
import { orderStatuses } from 'src/app/app.constants';
import { AcceptedOrder, OrderProduct } from 'src/app/interface/data';
import * as moment from 'moment-timezone';
@Component({
selector: 'app-order-info',
templateUrl: './order-info.component.html',
styleUrls: ['./order-info.component.scss']
})
export class OrderInfoComponent implements OnInit {
@Input() order!: AcceptedOrder;
@Input() loadingStatus: boolean = false;
percent: number = 0;
readonly moment = moment;
constructor() { }
ngOnInit(): void {
this.setPercent();
}
setPercent() {
const statusIndex = this.getStatusIndex(this.order.status);
if (statusIndex !== -1 && statusIndex !== 0) {
this.percent = (statusIndex - 1) * 20;
}
}
formatStatus(status: string): string|undefined{
const key = Object.keys(orderStatuses).find((el) => status === el) ?? '';
return orderStatuses[key];
}
getStatusIndex(status: string) {
return [...new Set(Object.values(orderStatuses))].findIndex((value) => value === this.formatStatus(status));
}
getItemModifiersName(item: OrderProduct): string[]{
return Object.keys(item.modifiers);
}
}
import { Component, Input, OnInit } from '@angular/core';
import { orderStatuses } from 'src/app/app.constants';
import { AcceptedOrder, OrderProduct } from 'src/app/interface/data';
import * as moment from 'moment-timezone';
@Component({
selector: 'app-order-info',
templateUrl: './order-info.component.html',
styleUrls: ['./order-info.component.scss']
})
export class OrderInfoComponent implements OnInit {
@Input() order!: AcceptedOrder;
@Input() loadingStatus: boolean = false;
percent: number = 0;
readonly moment = moment;
constructor() { }
ngOnInit(): void {
this.setPercent();
}
setPercent() {
const statusIndex = this.getStatusIndex(this.order.status);
if (statusIndex !== -1 && statusIndex !== 0) {
this.percent = (statusIndex - 1) * 20;
}
}
formatStatus(status: string): string|undefined{
const key = Object.keys(orderStatuses).find((el) => status === el) ?? '';
return orderStatuses[key];
}
getStatusIndex(status: string) {
return [...new Set(Object.values(orderStatuses))].findIndex((value) => value === this.formatStatus(status));
}
getItemModifiersName(item: OrderProduct): string[]{
return Object.keys(item.modifiers);
}
}

View File

@ -0,0 +1,3 @@
<a *ngFor="let link of links" [href]="link.url" target="_blank">
<img [src]="link.imgUrl" [alt]="link.label" />
</a>

View File

@ -0,0 +1,15 @@
:host {
display: flex;
flex-direction: row;
gap: 16px;
justify-content: space-between;
a {
width: 48px;
height: 48px;
border-radius: 100%;
background: #333333;
display: flex;
align-items: center;
justify-content: center;
}
}

View File

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

View File

@ -0,0 +1,38 @@
import { Component, OnInit } from '@angular/core';
export interface ISocialMediaLink {
url: string;
imgUrl: string;
label: string;
}
@Component({
selector: 'app-social-media-buttons',
templateUrl: './social-media-buttons.component.html',
styleUrls: ['./social-media-buttons.component.scss']
})
export class SocialMediaButtonsComponent implements OnInit {
public links: ISocialMediaLink[] = [
{
label: 'Инстаграм',
url: 'https://www.instagram.com/',
imgUrl: '/assets/social-media-icons/instagram.svg'
},
{
label: 'ВК',
url: 'https://vk.com/coffeelike_com',
imgUrl: '/assets/social-media-icons/vk.svg'
},
{
label: 'Youtube',
url: 'https://www.youtube.com/c/coffeelikeru',
imgUrl: '/assets/social-media-icons/youtube.svg'
}
]
constructor() { }
ngOnInit(): void {
}
}

View File

@ -0,0 +1,13 @@
import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { FocusNextInputDirective } from './focus-next-input.directive';
import { DownloadAppDirective } from './download-app.directive';
@NgModule({
imports: [
CommonModule
],
declarations: [FocusNextInputDirective, DownloadAppDirective],
exports: [FocusNextInputDirective, DownloadAppDirective]
})
export class DirectivesModule { }

View File

@ -0,0 +1,81 @@
import {
Directive,
ElementRef,
HostListener,
OnInit,
Renderer2,
} from '@angular/core';
import { MessageService } from 'primeng/api';
import { getTypeDevice, pwaInstalled } from 'src/app/utils';
@Directive({
selector: '[appDownloadApp]',
})
export class DownloadAppDirective implements OnInit {
public deviceType: 'ios' | 'android' | null = null;
public deferredPrompt: any;
constructor(
private messageService: MessageService,
public renderer: Renderer2,
private el: ElementRef,
) { }
ngOnInit(): void {
getTypeDevice();
if (this.deviceType === 'ios') {
this.el.nativeElement.style.display = 'block';
}
this.checkInstalled();
}
async checkInstalled(): Promise<void> {
if (window.matchMedia('(display-mode: standalone)').matches
|| await pwaInstalled()) {
this.el.nativeElement.style.display = 'none';
}
}
@HostListener('window:beforeinstallprompt', ['$event'])
onBeforeInstallPrompt(e: any) {
e.preventDefault();
this.deferredPrompt = e;
if (this.deferredPrompt) {
this.el.nativeElement.style.display = 'block';
}
}
@HostListener('window:appinstalled', ['$event'])
onAppInstalled(e: any) {
// Prevent Chrome 67 and earlier from automatically showing the prompt
e.preventDefault();
// Stash the event so it can be triggered later.
this.deferredPrompt = e;
this.el.nativeElement.style.display = 'none';
}
@HostListener('click', ['$event'])
async downloadApp(event: MouseEvent) {
if (!this.deferredPrompt) {
this.messageService.clear();
this.messageService.add({
severity: 'error',
summary: 'Не поддерживается в Вашем браузере!',
});
return;
}
this.deferredPrompt.prompt();
this.deferredPrompt.userChoice.then((res: any) => {
if (res.outcome === 'accepted') {
this.messageService.clear();
this.messageService.add({
severity: 'success',
summary: 'Спасибо за установку!',
});
}
this.deferredPrompt = null;
});
}
}

View File

@ -0,0 +1,29 @@
import { Directive, EventEmitter, Input, Renderer2 } from '@angular/core';
@Directive({
selector: '[appFocusNextInput]'
})
export class FocusNextInputDirective {
@Input('appFocusNextInput') eventEmitter!: EventEmitter<string>;
constructor(private renderer: Renderer2) { }
ngOnInit() {
// TODO:
// 1: don't need to listen all events
// 2: not working in safari
this.eventEmitter.subscribe(elementId => {
try {
const element = this.renderer.selectRootElement(elementId)
setTimeout(() => {
element.focus();
element.click();
}, 0);
} catch (ex) {
(document.activeElement as HTMLElement).blur();
// If the element doesn't exist or if the element disappears when this called then no need to do anything
}
});
}
}

View File

@ -0,0 +1,16 @@
import { TestBed } from '@angular/core/testing';
import { AuthGuard } from './auth-guard.guard';
describe('AuthGuardGuard', () => {
let guard: AuthGuard;
beforeEach(() => {
TestBed.configureTestingModule({});
guard = TestBed.inject(AuthGuard);
});
it('should be created', () => {
expect(guard).toBeTruthy();
});
});

View File

@ -0,0 +1,47 @@
import { Injectable } from '@angular/core';
import {
ActivatedRouteSnapshot,
CanActivate,
CanActivateChild,
Router,
RouterStateSnapshot,
UrlTree,
} from '@angular/router';
import { Observable, of } from 'rxjs';
import { CookiesService } from '../services/cookies.service';
@Injectable({
providedIn: 'root',
})
export class AuthGuard implements CanActivate, CanActivateChild {
constructor(private cookiesService: CookiesService, private router: Router) {}
canActivate(
route: ActivatedRouteSnapshot,
state: RouterStateSnapshot
):
| Observable<boolean | UrlTree>
| Promise<boolean | UrlTree>
| boolean
| UrlTree
{
if (this.cookiesService.getItem('token')) {
return of(true);
} else {
this.router.navigate(['/login']);
return of(false);
}
}
canActivateChild(
childRoute: ActivatedRouteSnapshot,
state: RouterStateSnapshot
):
| boolean
| UrlTree
| Observable<boolean | UrlTree>
| Promise<boolean | UrlTree>
{
return this.canActivate(childRoute, state);
}
}

View File

@ -0,0 +1,222 @@
export enum PageCode {
Auth,
Orders,
BonusProgram,
RefSystem,
UserData,
}
export interface Moment extends moment.Moment { }
export interface Page {
code: PageCode;
component?: any;
name: string;
description?: string;
getMethod?: string;
resName?: string;
onSideBar: boolean;
}
export interface UserDataForm {
first_name: string;
birthdate: string;
gender: string;
}
export interface BonusProgramAccount {
BonusProgramName: string;
BonusProgramTypeID: string;
CardNumber: number;
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;
transactionCreateDate?: string;
transactionType?: 'CancelPayFromWallet' | 'PayFromWallet' | 'RefillWallet';
transactionSum: number;
orderSum?: 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 lvlPeriod {
percent: number;
start: number;
end?: number;
color: string;
}
export interface DeliveryType {
cost: number;
title: string;
id: number;
}
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: number;
name: string;
price: string;
image_url: string;
image_gallery: string[];
category_id: number;
description?: string;
stock_status: string;
currency_symbol: string;
modifier_data: Modifier[];
short_description?: string;
guid?: string;
}
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;
price: string;
prechecked: string;
active?: boolean;
}
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;
deliveryDate: Date | null;
deliveryType: DeliveryType | null;
persons: number;
comment: string;
}
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;
}
export interface UserInfoCategory {
id: string;
isActive: boolean;
isDefaultForNewGuests: boolean;
name: string;
}
export interface UserInfoWallet {
id: string;
name: string;
programType: string;
type: string;
}
export interface UserInfoWalletBalance {
balance: number;
wallet: UserInfoWallet;
}
export interface UserInfo {
OrdersSum: number;
categories: UserInfoCategory[];
customer_level: number;
birthday: string;
id: string;
name: string | null;
phone: string;
walletBalances: UserInfoWalletBalance[] | { error: ResponseError };
}
export interface ResponseError {
code: number;
msg: string;
}
export interface UpdateNewCustomerRequest {
name: string;
sex: string;
birthday: string;
}

View File

@ -1,62 +1,62 @@
import {Modifier, Product} from "../interface/data";
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_url = product.image_url;
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;
}
public amount: number;
public category_id: number;
public currency_symbol: string;
public description?: string;
public short_description?: string;
public id: number;
public image_gallery: string[];
public image_url: string;
public modifier_data: Modifier[];
public name: string;
public price: string;
public stock_status: string;
public guid: 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);
}, 0);
}, 0);
return (Number(this.price) + modifiersPrice) * this.amount;
new Date()
}
toJson(){
return {
id: this.id,
amount: this.amount,
name: this.name,
modifiers: this.modifier_data?.map(modifier => {
return {
id: modifier.id,
options: modifier.options,
}
}),
}
}
}
import {Modifier, Product} from "../interface/data";
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_url = product.image_url;
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;
}
public amount: number;
public category_id: number;
public currency_symbol: string;
public description?: string;
public short_description?: string;
public id: number;
public image_gallery: string[];
public image_url: string;
public modifier_data: Modifier[];
public name: string;
public price: string;
public stock_status: string;
public guid: 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);
}, 0);
}, 0);
return (Number(this.price) + modifiersPrice) * this.amount;
new Date()
}
toJson(){
return {
id: this.id,
amount: this.amount,
name: this.name,
modifiers: this.modifier_data?.map(modifier => {
return {
id: modifier.id,
options: modifier.options,
}
}),
}
}
}

View File

@ -1,68 +1,68 @@
import {DeliveryData, UserData} from "../interface/data";
import {OrderProduct} from "./order-product";
import * as moment from 'moment';
import { CookiesService } from "../services/cookies.service";
export interface OrderInfo {
products: OrderProduct[];
userData?: UserData;
deliveryData?: DeliveryData;
phone: string;
token: string | undefined;
}
export class Order {
public products: OrderProduct[];
public userData?: UserData;
public deliveryData?: DeliveryData;
public phone: string;
public token: string | undefined;
constructor(
orderInfo: OrderInfo
) {
this.products = orderInfo.products;
this.userData = orderInfo.userData;
this.deliveryData = orderInfo.deliveryData;
this.phone = orderInfo.phone;
this.token = orderInfo.token;
}
get price(): number {
return this.products.reduce<number>((previousValue, currentValue) => previousValue + currentValue.finalPrice, 0);
}
toJson(): any {
const date = moment(this.deliveryData?.deliveryDate ?? Date.now());
return {
items: this.products.map(product => {
return product.toJson();
}),
user_data: {
phone: this.phone,
...this.userData
},
payment_method: this.deliveryData?.paymentMethod.type,
delivery_time: date.format('HH:mm'),
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
}
}
}
import {DeliveryData, UserData} from "../interface/data";
import {OrderProduct} from "./order-product";
import * as moment from 'moment';
import { CookiesService } from "../services/cookies.service";
export interface OrderInfo {
products: OrderProduct[];
userData?: UserData;
deliveryData?: DeliveryData;
phone: string;
token: string | undefined;
}
export class Order {
public products: OrderProduct[];
public userData?: UserData;
public deliveryData?: DeliveryData;
public phone: string;
public token: string | undefined;
constructor(
orderInfo: OrderInfo
) {
this.products = orderInfo.products;
this.userData = orderInfo.userData;
this.deliveryData = orderInfo.deliveryData;
this.phone = orderInfo.phone;
this.token = orderInfo.token;
}
get price(): number {
return this.products.reduce<number>((previousValue, currentValue) => previousValue + currentValue.finalPrice, 0);
}
toJson(): any {
const date = moment(this.deliveryData?.deliveryDate ?? Date.now());
return {
items: this.products.map(product => {
return product.toJson();
}),
user_data: {
phone: this.phone,
...this.userData
},
payment_method: this.deliveryData?.paymentMethod.type,
delivery_time: date.format('HH:mm'),
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

@ -1,75 +1,96 @@
<div class="woocommerce">
<div
*ngIf="currentPage.code !== PageCode.Auth"
class="top-left-attribute"
></div>
<div [ngSwitch]="currentPage.code" class="">
<ng-container *ngSwitchCase="PageCode.Auth">
<app-auth
[handleHttpError]="handleHttpErrorFunc"
(phoneConfirmed)="phoneConfirmed()"
></app-auth>
</ng-container>
<ng-container *ngSwitchCase="PageCode.Orders">
<app-orders></app-orders>
</ng-container>
<ng-container *ngSwitchCase="PageCode.BonusProgram">
<app-bonus-program></app-bonus-program>
</ng-container>
<ng-container *ngSwitchCase="PageCode.UserData">
<app-user-data></app-user-data>
</ng-container>
<ng-container *ngSwitchCase="PageCode.RefSystem">
<app-ref-system></app-ref-system>
</ng-container>
</div>
<nav
*ngIf="currentPage.code !== PageCode.Auth"
class="woocommerce-MyAccount-navigation"
>
<ul>
<ng-container *ngFor="let page of pageList; let index = index">
<li
*ngIf="page.onSideBar"
class="woocommerce-MyAccount-navigation-link"
[ngClass]="{
'is-active': page === currentPage,
first: index === 1
}"
(click)="changePage($event, page)"
>
<div class="container">
<img
src="{{ './assets/menu-icons/' + page.resName + '.png' }}"
alt="Иконка меню"
/>
<div class="menu-item-info">
<a href="#">{{ page.name }}</a>
<p>{{ page.description }}</p>
</div>
</div>
</li>
</ng-container>
<li
class="woocommerce-MyAccount-navigation-link"
(click)="logout($event)"
>
<div class="container">
<img src="./assets/menu-icons/exit.png" alt="Иконка меню" />
<div class="menu-item-info">
<a href="#">Выход</a>
</div>
</div>
</li>
</ul>
</nav>
<span
class="version"
[ngClass]="{
version: true,
bottom: currentPage.code === PageCode.Auth
}"
>
v{{ version }}
</span>
</div>
<div
[ngClass]="{
woocommerce: true,
'auth-page': currentPage.code === PageCode.Auth
}"
>
<!-- <div
*ngIf="currentPage.code !== PageCode.Auth"
class="top-left-attribute"
></div>
<div [ngSwitch]="currentPage.code" class="">
<ng-container *ngSwitchCase="PageCode.Auth">
<app-auth
[handleHttpError]="handleHttpErrorFunc"
(phoneConfirmed)="phoneConfirmed()"
></app-auth>
</ng-container>
<ng-container *ngSwitchCase="PageCode.Orders">
<app-orders></app-orders>
</ng-container>
<ng-container *ngSwitchCase="PageCode.BonusProgram">
<app-bonus-program></app-bonus-program>
</ng-container>
<ng-container *ngSwitchCase="PageCode.UserData">
<app-user-data></app-user-data>
</ng-container>
<ng-container *ngSwitchCase="PageCode.RefSystem">
<app-ref-system></app-ref-system>
</ng-container>
</div>
<nav
*ngIf="currentPage.code !== PageCode.Auth"
class="woocommerce-MyAccount-navigation"
>
<ul>
<ng-container *ngFor="let page of pageList; let index = index">
<li
*ngIf="page.onSideBar"
class="woocommerce-MyAccount-navigation-link"
[ngClass]="{
'is-active': page === currentPage,
first: index === 1
}"
(click)="changePage($event, page)"
>
<div class="container">
<img
style="width: 36px"
src="{{ './assets/menu-icons/' + page.resName + '.png' }}"
alt="Иконка меню"
/>
<div class="menu-item-info">
<a href="#">{{ page.name }}</a>
<p>{{ page.description }}</p>
</div>
</div>
</li>
</ng-container>
<li
class="woocommerce-MyAccount-navigation-link download-app"
appDownloadApp
>
<div class="container">
<img
style="width: 36px"
src="./assets/menu-icons/download-app.png"
alt="Иконка меню"
/>
<div class="menu-item-info">
<a href="#">Установить приложение</a>
<p>{{ page.description }}</p>
</div>
</div>
</li>
<li
class="woocommerce-MyAccount-navigation-link"
(click)="logout($event)"
>
<div class="container">
<img src="./assets/menu-icons/exit.png" alt="Иконка меню" />
<div class="menu-item-info">
<a href="#">Выход</a>
</div>
</div>
</li>
</ul>
</nav> -->
<span
class="version"
[ngClass]="{
version: true
}"
>
v{{ version }}
</span>
</div>

View File

@ -1,82 +1,100 @@
:host {
.woocommerce {
min-height: calc(100vh - 39px);
padding: 20px 18px;
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 #09467f 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;
}
}
}
.top-left-attribute {
width: 10px;
height: 5px;
background: #09467f;
left: 0;
position: absolute;
top: 69px;
}
.version {
opacity: 0.5;
&.bottom {
position: absolute;
bottom: 8px;
}
}
}
}
:host {
.woocommerce {
min-height: calc(100vh - 39px);
padding: 20px 18px;
&.auth-page {
display: flex;
flex-direction: column;
justify-content: space-between;
}
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(--main-color) 1px;
// display: none;
}
&.first {
// border-radius: 7px 0 0 7px;
}
&.download-app {
display: none;
}
.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;
}
}
}
.top-left-attribute {
width: 10px;
height: 5px;
background: var(--main-color);
left: 0;
position: absolute;
top: 103px;
}
.version {
opacity: 0.5;
}
}
}

View File

@ -1,186 +1,205 @@
import { Component, EventEmitter, Input, OnInit, Output } from '@angular/core';
import { CookiesService } from '../../services/cookies.service';
import { ActivatedRoute, Router } from '@angular/router';
import { Page, PageCode } from '../../interface/data';
import { environment } from '../../../environments/environment';
import { PageList, 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';
@Component({
selector: 'app-account',
templateUrl: './account.component.html',
styleUrls: ['./account.component.scss'],
providers: [DialogService],
})
export class AccountComponent implements OnInit {
@Output() setUserDataOrderPage = new EventEmitter<null>();
public refSystemId: string = '';
constructor(
private cookiesService: CookiesService,
private router: Router,
private route: ActivatedRoute,
private dialogService: DialogService,
private jsonRpcService: JsonrpcService,
private messageService: MessageService
) {}
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;
ngOnInit(): void {
if (!this.getToken()) {
this.currentPage = this.pageList[0];
} else {
this.route.queryParams.subscribe((params) => {
if (!params['activePage']) {
this.currentPage = this.pageList[1];
return;
}
const currentPage = this.pageList.find((page) => {
return page.code === Number(params['activePage']);
});
if (!currentPage) {
this.currentPage = this.pageList[1];
} else {
this.currentPage = currentPage;
}
});
}
document.body.classList.add(
'woocommerce-account',
'woocommerce-page',
'woocommerce-orders'
);
}
phoneConfirmed(): void {
this.refSystem();
this.currentPage = this.pageList[1];
}
refSystem() {
this.route.queryParams.subscribe((params) => {
if (params['refCardNumber']) {
this.refSystemId = params['refCardNumber'];
this.jsonRpcService
.rpc(
{
method: 'updateAdditionalInfo',
params: [
{
refSystem: {
code: this.refSystemId,
},
},
],
},
RpcService.authService,
true
)
.subscribe({
next: () => {
this.router.navigate([], {
queryParams: {
refCardNumber: null,
},
queryParamsHandling: 'merge',
});
this.messageService.add({
severity: 'custom',
summary:
'Регистрация прошла успешна! Бонусы скоро будут начислены',
});
},
error: (err) => {
console.error('Error: ', err);
this.messageService.add({
severity: 'error',
summary: 'Произошла ошибка, попробуйте позже',
});
},
});
}
});
}
changePage(event: MouseEvent, page: Page): void {
event.preventDefault();
this.currentPage = page;
// let params = new HttpParams();
// params = params.append('activePage', this.currentPage.code);
this.router.navigate(['.'], {
relativeTo: this.route,
queryParams: {
activePage: this.currentPage.code,
},
queryParamsHandling: 'merge',
// preserve the existing query params in the route
// skipLocationChange: true
// do not trigger navigation
});
}
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: {},
// });
}
logout(event?: MouseEvent) {
if (event) {
event.preventDefault();
}
this.ref = this.dialogService.open(ExitComponent, {
width: 'auto',
style: {
'max-width': '90vw',
'max-height': '90vh',
},
contentStyle: {
'max-height': '90vh',
height: 'auto',
'max-width': '90vw',
overflow: 'auto',
'border-radius': '4px',
},
baseZIndex: 10000,
autoZIndex: true,
dismissableMask: true,
closeOnEscape: true,
showHeader: false,
});
this.ref.onClose.subscribe((result) => {
if (result) {
this.deleteToken();
this.currentPage = this.pageList[0];
}
});
}
}
import { Component, EventEmitter, Input, OnInit, Output } from '@angular/core';
import { CookiesService } from '../../services/cookies.service';
import { ActivatedRoute, Router } from '@angular/router';
import { Page, PageCode } from '../../interface/data';
import { environment } from '../../../environments/environment';
import { PageList, 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';
@Component({
selector: 'app-account',
templateUrl: './account.component.html',
styleUrls: ['./account.component.scss'],
providers: [DialogService],
})
export class AccountComponent implements OnInit {
@Output() setUserDataOrderPage = new EventEmitter<null>();
public refSystemId: string = '';
constructor(
private cookiesService: CookiesService,
private router: Router,
private route: ActivatedRoute,
private dialogService: DialogService,
private jsonRpcService: JsonrpcService,
private messageService: MessageService
) {}
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;
ngOnInit(): void {
if (!this.getToken()) {
this.currentPage = this.pageList[0];
} else {
this.route.queryParams.subscribe((params) => {
if (!params['activePage']) {
this.currentPage = this.pageList[1];
return;
}
const currentPage = this.pageList.find((page) => {
return page.code === Number(params['activePage']);
});
if (!currentPage) {
this.currentPage = this.pageList[1];
} else {
this.currentPage = currentPage;
}
});
}
document.body.classList.add(
'woocommerce-account',
'woocommerce-page',
'woocommerce-orders'
);
}
phoneConfirmed(): void {
this.refSystem();
this.currentPage = this.pageList[1];
}
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: 'Произошла ошибка, попробуйте позже',
});
},
});
}
});
}
changePage(event: MouseEvent, page: Page): void {
event.preventDefault();
if (page.resName === 'download-app') {
return
}
this.currentPage = page;
// let params = new HttpParams();
// params = params.append('activePage', this.currentPage.code);
this.router.navigate(['.'], {
relativeTo: this.route,
queryParams: {
activePage: this.currentPage.code,
},
queryParamsHandling: 'merge',
// preserve the existing query params in the route
// skipLocationChange: true
// do not trigger navigation
});
}
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.logout();
// this.router.navigate(['.'], {
// queryParams: {},
// });
}
logout(event?: MouseEvent) {
if (event) {
event.preventDefault();
}
this.ref = this.dialogService.open(ExitComponent, {
width: 'auto',
style: {
'max-width': '90vw',
'max-height': '90vh',
},
contentStyle: {
'max-height': '90vh',
height: 'auto',
'max-width': '90vw',
overflow: 'auto',
'border-radius': '4px',
},
baseZIndex: 10000,
autoZIndex: true,
dismissableMask: true,
closeOnEscape: true,
showHeader: false,
});
this.ref.onClose.subscribe((result) => {
if (result) {
this.deleteToken();
this.currentPage = this.pageList[0];
}
});
}
}

View File

@ -1,99 +1,98 @@
<div class="woocommerce-notices-wrapper"></div>
<ng-container *ngIf="!isCodeConfirm; else confirmPhoneField">
<form
class="woocommerce-form woocommerce-form-login login"
(submit)="getCode($event)"
>
<h2>Вход</h2>
<p
class="woocommerce-form-row woocommerce-form-row--wide form-row form-row-wide"
>
<label for="phone">Введите Ваш номер телефона</label>
<p-inputMask
mask="+7 (999) 999-99-99"
characterPattern="[0-9]"
styleClass="woocommerce-Input woocommerce-Input--text input-text"
name="phone"
type="tel"
inputId="phone"
autocomplete="phone"
placeholder="+7 (000) 000-00-00"
mask="+7 (999) 999-9999"
[unmask]="false"
[(ngModel)]="phone"
></p-inputMask>
</p>
<p class="form-row">
<button
type="submit"
class="woocommerce-button button woocommerce-form-login__submit"
name="login"
value="Получить код"
[disabled]="loading || phone.length < 10"
>
{{ loading ? "" : "Получить код" }}
<p-progressSpinner
*ngIf="loading"
[style]="{ width: '16px', height: '16px' }"
strokeWidth="2"
styleClass="angular-spinner"
></p-progressSpinner>
</button>
</p>
<div class="decoration-pattern" style="background: url('./assets/card-decorative-pattern.png') no-repeat;"></div>
</form>
</ng-container>
<ng-template #confirmPhoneField>
<form
class="woocommerce-form woocommerce-form-login login"
action="false"
(submit)="confirmCode($event)"
>
<h2>Вход</h2>
<p
class="woocommerce-form-row woocommerce-form-row--wide form-row form-row-wide"
>
<label for="code">
Введите 4 последних цифры позвонившего вам номера телефона. На звонок
отвечать не нужно.
</label>
<input
pInputText
type="number"
pattern="\d*"
class="woocommerce-Input woocommerce-Input--text input-text"
name="code"
id="code"
autocomplete="code"
placeholder="00-00"
[(ngModel)]="code"
/>
</p>
<p class="form-row" style="margin-bottom: 6px;">
<button
type="submit"
class="woocommerce-button button woocommerce-form-login__submit"
name="login"
value="Войти"
[disabled]="loading"
>
{{ loading ? "" : "Войти" }}
<p-progressSpinner
*ngIf="loading"
[style]="{ width: '16px', height: '16px' }"
strokeWidth="2"
styleClass="angular-spinner"
></p-progressSpinner>
</button>
</p>
<p class="form-row" style="margin-top: 6px">
Не пришел код?
<span class="resend-code" (click)="getCode($event)">Отправить повторно </span>
<span *ngIf="timeLeft">через {{timeLeft}}с</span>
</p>
<p style="color: red; margin: 0" *ngIf="errorConfirmCode">
Пароль введен неверно
</p>
<div class="decoration-pattern" style="background: url('./assets/card-decorative-pattern.png') no-repeat;"></div>
</form>
</ng-template>
<div class="woocommerce-notices-wrapper"></div>
<ng-container *ngIf="!isCodeConfirm; else confirmPhoneField">
<form
class="woocommerce-form woocommerce-form-login login"
(submit)="getCode($event)"
>
<h2>Вход</h2>
<p
class="woocommerce-form-row woocommerce-form-row--wide form-row form-row-wide"
>
<label for="phone">Введите Ваш номер телефона</label>
<p-inputMask
mask="+7 (999) 999-99-99"
characterPattern="[0-9]"
styleClass="woocommerce-Input woocommerce-Input--text input-text"
name="phone"
type="tel"
inputId="phone"
autocomplete="phone"
placeholder="+7 (000) 000-00-00"
mask="+7 (999) 999-9999"
[unmask]="false"
[(ngModel)]="phone"
></p-inputMask>
</p>
<p class="form-row">
<button
type="submit"
class="woocommerce-button button woocommerce-form-login__submit"
name="login"
value="Получить код"
[disabled]="loading || phone.length < 10"
>
{{ loading ? "" : "Получить код" }}
<p-progressSpinner
*ngIf="loading"
[style]="{ width: '16px', height: '16px' }"
strokeWidth="2"
styleClass="angular-spinner"
></p-progressSpinner>
</button>
</p>
<div class="decoration-pattern"></div>
</form>
</ng-container>
<ng-template #confirmPhoneField>
<form
class="woocommerce-form woocommerce-form-login login"
action="false"
(submit)="confirmCode($event)"
>
<h2>Вход</h2>
<p
class="woocommerce-form-row woocommerce-form-row--wide form-row form-row-wide"
>
<label for="code">
Введите 4 цифры из смс, которое пришло на Ваш номер телефона
</label>
<input
pInputText
type="number"
pattern="\d*"
class="woocommerce-Input woocommerce-Input--text input-text"
name="code"
id="code"
autocomplete="code"
placeholder="00-00"
[(ngModel)]="code"
/>
</p>
<p class="form-row" style="margin-bottom: 6px;">
<button
type="submit"
class="woocommerce-button button woocommerce-form-login__submit"
name="login"
value="Войти"
[disabled]="loading"
>
{{ loading ? "" : "Войти" }}
<p-progressSpinner
*ngIf="loading"
[style]="{ width: '16px', height: '16px' }"
strokeWidth="2"
styleClass="angular-spinner"
></p-progressSpinner>
</button>
</p>
<p class="form-row" style="margin-top: 6px">
Не пришел код?
<span class="resend-code" (click)="getCode($event)">Отправить повторно </span>
<span *ngIf="timeLeft">через {{timeLeft}}с</span>
</p>
<p style="color: red; margin: 0" *ngIf="errorConfirmCode">
Пароль введен неверно
</p>
<div class="decoration-pattern"></div>
</form>
</ng-template>

View File

@ -1,106 +1,109 @@
:host {
display: flex;
justify-content: center;
.woocommerce-form-login__submit {
width: 150px;
}
h2 {
color: #fff;
width: 100%;
height: 49px;
text-align: left;
padding-top: 13px;
padding-left: 32px;
background: #0d457e;
position: relative;
font-style: normal;
font-weight: 700;
font-size: 20px;
line-height: 24px;
&::before {
content: "";
width: 19px;
height: 4px;
background: #fff;
display: block;
position: absolute;
left: 0;
top: 24px;
}
}
.woocommerce-form {
display: flex;
flex-direction: column;
align-items: center;
background: #ffffff;
box-shadow: 0px 0px 10px rgb(0 0 0 / 15%);
border-radius: 15px;
min-height: 262px;
max-width: 400px;
overflow: auto;
position: relative;
padding-bottom: 10px;
}
.form-row {
width: 80%;
margin-bottom: 16px;
max-width: 400px;
.button {
width: 100%;
font-weight: 600;
letter-spacing: 2px;
border: none;
height: 45px;
background: #b8deff;
border-radius: 15px;
color: #000;
font-style: normal;
font-weight: 700;
font-size: 16px;
line-height: 20px;
}
label {
display: inline-block;
margin-bottom: 8px;
margin-top: 15px;
color: #000;
font-style: normal;
font-weight: 400;
font-size: 18px;
line-height: 22px;
}
.input-text {
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;
width: 100%;
}
.resend-code {
color: #0d457e;
font-weight: 600;
}
}
.decoration-pattern {
width: calc(100% - 24px);
height: 34px;
}
:host {
display: flex;
justify-content: center;
.woocommerce-form-login__submit {
width: 150px;
}
h2 {
color: #fff;
width: 100%;
height: 49px;
text-align: left;
padding-top: 13px;
padding-left: 32px;
background: #0d457e;
position: relative;
font-style: normal;
font-weight: 700;
font-size: 20px;
line-height: 24px;
&::before {
content: "";
width: 19px;
height: 4px;
background: #fff;
display: block;
position: absolute;
left: 0;
top: 24px;
}
}
.woocommerce-form {
display: flex;
flex-direction: column;
align-items: center;
background: #ffffff;
box-shadow: 0px 0px 10px rgb(0 0 0 / 15%);
border-radius: 15px;
min-height: 262px;
max-width: 400px;
overflow: auto;
position: relative;
padding-bottom: 10px;
}
.form-row {
width: 80%;
margin-bottom: 16px;
max-width: 400px;
.button {
width: 100%;
font-weight: 600;
letter-spacing: 2px;
border: none;
height: 45px;
background: #b8deff;
border-radius: 15px;
color: #000;
font-style: normal;
font-weight: 700;
font-size: 16px;
line-height: 20px;
}
label {
display: inline-block;
margin-bottom: 8px;
margin-top: 15px;
color: #000;
font-style: normal;
font-weight: 400;
font-size: 18px;
line-height: 22px;
}
.input-text {
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;
width: 100%;
}
.resend-code {
color: #0d457e;
font-weight: 600;
}
}
.decoration-pattern {
width: calc(100% - 24px);
height: 34px;
background-image: url('../../../../assets/card-decorative-pattern.png');
background-repeat: no-repeat;
background-size: contain;
}
}

View File

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

View File

@ -1,101 +1,101 @@
import { HttpErrorResponse } from '@angular/common/http';
import { Component, EventEmitter, Input, OnInit, Output } from '@angular/core';
import { ActivatedRoute, Router } from '@angular/router';
import { MessageService } from 'primeng/api';
import { CookiesService } from 'src/app/services/cookies.service';
import { FormatPhoneService } from 'src/app/services/format-phone.service';
import { JsonrpcService, RpcService } from 'src/app/services/jsonrpc.service';
@Component({
selector: 'app-auth',
templateUrl: './auth.component.html',
styleUrls: ['./auth.component.scss']
})
export class AuthComponent {
@Input() handleHttpError!: (error: HttpErrorResponse) => void;
@Output() phoneConfirmed = new EventEmitter<null>();
public phone: string = '';
public isCodeConfirm = false;
public code: string = '';
public phoneToConfirm!: string;
public loading = false;
public errorConfirmCode: boolean = false;
timeLeft: number = 0;
constructor(
private phoneFormatter: FormatPhoneService,
private jsonrpc: JsonrpcService,
private cookiesService: CookiesService,
private router: Router,
private route: ActivatedRoute,
private messageService: MessageService,
) {
}
getCode(event: any): void {
event.preventDefault();
this.loading = true;
this.phoneToConfirm = '+' + this.phone.replace(/\D/g, '');
if (this.timeLeft) {
this.messageService.add({
severity: 'custom',
summary: `Отправить повторно можно через ${this.timeLeft}с`,
});
this.loading = false;
return;
}
this.jsonrpc.rpc({
method: 'sendVerifyByPhone',
params: [this.phoneToConfirm]
}, RpcService.authService, false).subscribe({
next: (result) => {
if (result.code === -1) {
this.messageService.add({
severity: 'error',
summary: 'Произошла ошибка, попробуйте позже!',
});
}
if (result.code === 0) {
this.isCodeConfirm = true;
this.timeLeft = 60;
const interval = setInterval(() => {
if(this.timeLeft > 0) {
this.timeLeft--;
} else {
clearInterval(interval);
}
},1000)
}
this.loading = false;
},
error: this.handleHttpError
}
);
}
confirmCode(event: SubmitEvent): void {
event.preventDefault();
this.jsonrpc.rpc({
method: 'getTokenByPhone',
params: [this.phoneToConfirm, String(this.code)]
}, RpcService.authService, false).subscribe({
next: (result) => {
if (result.code === 0) {
this.cookiesService.setCookie('token', result?.data?.token);
this.router.navigate(['/auth'], {
queryParams: {
token: result?.data?.token
},
});
this.phoneConfirmed.emit(null);
} else {
this.errorConfirmCode = true;
}
},
error: this.handleHttpError
}
);
}
}
import { HttpErrorResponse } from '@angular/common/http';
import { Component, EventEmitter, Input, OnInit, Output } from '@angular/core';
import { ActivatedRoute, Router } from '@angular/router';
import { MessageService } from 'primeng/api';
import { CookiesService } from 'src/app/services/cookies.service';
import { FormatPhoneService } from 'src/app/services/format-phone.service';
import { JsonrpcService, RpcService } from 'src/app/services/jsonrpc.service';
@Component({
selector: 'app-auth',
templateUrl: './auth.component.html',
styleUrls: ['./auth.component.scss']
})
export class AuthComponent {
@Input() handleHttpError!: (error: HttpErrorResponse) => void;
@Output() phoneConfirmed = new EventEmitter<null>();
public phone: string = '';
public isCodeConfirm = false;
public code: string = '';
public phoneToConfirm!: string;
public loading = false;
public errorConfirmCode: boolean = false;
timeLeft: number = 0;
constructor(
private phoneFormatter: FormatPhoneService,
private jsonrpc: JsonrpcService,
private cookiesService: CookiesService,
private router: Router,
private route: ActivatedRoute,
private messageService: MessageService,
) {
}
getCode(event: any): void {
event.preventDefault();
this.loading = true;
this.phoneToConfirm = '+' + this.phone.replace(/\D/g, '');
if (this.timeLeft) {
this.messageService.add({
severity: 'custom',
summary: `Отправить повторно можно через ${this.timeLeft}с`,
});
this.loading = false;
return;
}
this.jsonrpc.rpc({
method: 'sendVerifyByPhone',
params: [this.phoneToConfirm]
}, RpcService.authService, false).subscribe({
next: (result) => {
if (result.code === -1) {
this.messageService.add({
severity: 'error',
summary: 'Произошла ошибка, попробуйте позже!',
});
}
if (result.code === 0) {
this.isCodeConfirm = true;
this.timeLeft = 60;
const interval = setInterval(() => {
if(this.timeLeft > 0) {
this.timeLeft--;
} else {
clearInterval(interval);
}
},1000)
}
this.loading = false;
},
error: this.handleHttpError
}
);
}
confirmCode(event: SubmitEvent): void {
event.preventDefault();
this.jsonrpc.rpc({
method: 'getTokenByPhone',
params: [this.phoneToConfirm, String(this.code)]
}, RpcService.authService, false).subscribe({
next: (result) => {
if (result.code === 0) {
this.cookiesService.setCookie('token', result?.data?.token);
this.router.navigate(['/auth'], {
queryParams: {
token: result?.data?.token
},
});
this.phoneConfirmed.emit(null);
} else {
this.errorConfirmCode = true;
}
},
error: this.handleHttpError
}
);
}
}

View File

@ -1,50 +1,48 @@
<div class="woocommerce-MyAccount-content">
<h2>{{ currentPage.name }}</h2>
<!-- <p>{{currentPage.description}}</p> -->
<div style="display: flex; justify-content: center">
<div class="card-container">
<div class="card-container__header">
<img src="./assets/logo.svg" alt="Логотип карта" />
<span *ngIf="accountData">#{{ accountData.CardNumber }}</span>
</div>
<div class="card-container__decorative-pattern">
<!-- <div class="imgs-row"></div>
<div class="imgs-row" style="background-position-x: -22px;"></div>
<div class="imgs-row"></div> -->
<img src="./assets/card-decorative-pattern.svg" alt="" />
</div>
<div class="card-container__content">
<div class="info">
<div *ngIf="accountData" class="row">
<span class="key">Имя</span>
<span class="value" *ngIf="userName.length">{{
userName
}}</span>
<span class="value" *ngIf="!userName.length"
>Не задано</span
>
</div>
<div *ngIf="accountData" class="row">
<span class="key">Баланс карты</span>
<span class="value">{{ accountData.Bonuses }}</span>
</div>
</div>
<div class="card-container__barcode-container">
<svg
id="barcode"
[ngClass]="{
hidden: loadingBonuses
}"
></svg>
<p-progressSpinner
*ngIf="loadingBonuses"
[style]="{ width: '100%' }"
[ngStyle]="{width: '90px'}"
strokeWidth="2"
styleClass="angular-spinner"
></p-progressSpinner>
</div>
</div>
</div>
</div>
</div>
<div class="woocommerce-MyAccount-content">
<h2>{{ currentPage.name }}</h2>
<!-- <p>{{currentPage.description}}</p> -->
<div style="display: flex; justify-content: center">
<div class="card-container">
<div class="card-container__header">
<img src="./assets/logo.svg" alt="Логотип карта" />
<span *ngIf="accountData">#{{ accountData.CardNumber }}</span>
</div>
<div class="card-container__decorative-pattern">
<!-- <div class="imgs-row"></div>
<div class="imgs-row" style="background-position-x: -22px;"></div>
<div class="imgs-row"></div> -->
<img src="./assets/card-decorative-pattern.png" alt="" />
</div>
<div class="card-container__content">
<div class="info">
<div *ngIf="accountData" class="row">
<span class="key">Имя</span>
<span class="value">{{
userName && userName.length ? userName : 'Не задано'
}}</span>
</div>
<div *ngIf="accountData" class="row">
<span class="key">Баланс карты</span>
<span class="value">{{ bonuses }}</span>
</div>
</div>
<div class="card-container__barcode-container">
<svg
id="barcode"
[ngClass]="{
hidden: loadingBonuses
}"
></svg>
<p-progressSpinner
*ngIf="loadingBonuses"
[style]="{ width: '100%' }"
[ngStyle]="{width: '90px'}"
strokeWidth="2"
styleClass="angular-spinner"
></p-progressSpinner>
</div>
</div>
</div>
</div>
<button *ngIf="deviceType == 'ios'" class="add-to-wallet" (click)="addCardToWallet($event)">Добавить в Apple Wallet</button>
</div>

View File

@ -1,200 +1,215 @@
:host {
.woocommerce-MyAccount-content {
display: flex;
flex-direction: column;
&>h2 {
font-style: normal;
font-weight: 700;
font-size: 20px;
line-height: 24px;
}
&>p {
font-style: normal;
font-weight: 400;
font-size: 15px;
line-height: 18px;
margin-top: 8px;
}
.card-container {
background: #09467f;
box-shadow: 0px 0px 5px rgb(0 0 0 / 15%);
border-radius: 15px;
height: 466px;
margin-top: 11px;
overflow: hidden;
max-width: 400px;
width: 100%;
&__header {
height: 62px;
color: #fff;
display: flex;
justify-content: space-between;
img {
height: 38px;
margin-top: 15px;
}
span {
font-style: normal;
font-weight: 400;
font-size: 20px;
line-height: 24px;
margin-top: 19px;
margin-right: 18px;
}
}
&__decorative-pattern {
height: 122px;
background-color: #fff;
// padding-top: 9px;
display: flex;
justify-content: center;
align-items: center;
img {
height: 90px;
}
// .imgs-row {
// background: url("./assets/card-decorative-pattern.png") repeat-x;
// height: 32%;
// }
}
&__content {
background-color: #09467f;
color: #fff;
.info {
height: 79px;
border-bottom: solid #fff 1px;
padding: 14px 18px;
.row {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 7px;
.key {
font-size: 14px;
line-height: 17px;
font-weight: 400;
}
.value {
font-size: 18px;
font-weight: 600;
}
}
}
}
&__barcode-container {
display: flex;
justify-content: center;
align-items: center;
padding: 33px 0;
#barcode {
border-radius: 16px;
&.hidden {
display: none;
width: 0;
}
}
}
}
.card-content {
position: relative;
height: 200px;
width: 100%;
max-width: 400px;
-moz-user-select: none;
-webkit-user-select: none;
-ms-user-select: none;
-o-user-select: none;
user-select: none;
&__front,
&__back {
display: block;
transition-timing-function: cubic-bezier(0.175, 0.885, 0.32, 1.275);
transition-duration: 0.7s;
transition-property: transform, opacity;
color: #fff;
width: 100%;
height: 100%;
background: #fdfdfd;
border-radius: 12px;
border: solid #a3a3a3 1px;
box-shadow: 0 0 3px 1px #fff;
padding: 12px;
background-size: cover;
}
// &__front {
// transform: rotateY(0deg);
// background-image: url(./assets/background.svg);
// }
&__back {
position: absolute;
opacity: 0;
top: 0px;
left: 0px;
transform: rotateY(-180deg);
background: #000;
}
&.active_back {
>.card-content__front {
transform: rotateY(180deg);
opacity: 0;
}
>.card-content__back {
opacity: 1;
transform: rotateY(0deg);
}
}
&__logo {
height: 30px;
display: flex;
align-items: center;
justify-content: space-between;
.title {
color: #03d1be;
font-weight: 600;
letter-spacing: 2px;
}
}
&__footer {
display: flex;
justify-content: space-between;
align-items: flex-end;
position: absolute;
bottom: 12px;
width: calc(100% - 24px);
.count {
font-size: 22px;
}
}
}
.explanation {
font-size: 12px;
margin-bottom: 8px;
}
}
}
:host {
.woocommerce-MyAccount-content {
display: flex;
flex-direction: column;
& > h2 {
font-style: normal;
font-weight: 700;
font-size: 20px;
line-height: 24px;
max-width: 400px;
width: 100%;
margin: 0 auto;
text-align: center;
}
& > p {
font-style: normal;
font-weight: 400;
font-size: 15px;
line-height: 18px;
margin-top: 8px;
}
.card-container {
background: var(--main-color);
box-shadow: 0px 0px 5px rgb(0 0 0 / 15%);
border-radius: 15px;
height: 466px;
margin-top: 11px;
overflow: hidden;
max-width: 400px;
width: 100%;
&__header {
height: 62px;
color: #fff;
display: flex;
justify-content: space-between;
img {
height: 38px;
margin-top: 15px;
}
span {
font-style: normal;
font-weight: 400;
font-size: 20px;
line-height: 24px;
margin-top: 19px;
margin-right: 18px;
}
}
&__decorative-pattern {
height: 122px;
background-color: #fff;
// padding-top: 9px;
display: flex;
justify-content: center;
align-items: center;
img {
height: 90px;
}
// .imgs-row {
// background: url("./assets/card-decorative-pattern.png") repeat-x;
// height: 32%;
// }
}
&__content {
background-color: var(--main-color);
color: #fff;
.info {
height: 79px;
border-bottom: solid #fff 1px;
padding: 14px 18px;
.row {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 7px;
.key {
font-size: 14px;
line-height: 17px;
font-weight: 400;
}
.value {
font-size: 18px;
font-weight: 600;
}
}
}
}
&__barcode-container {
display: flex;
justify-content: center;
align-items: center;
padding: 33px 0;
#barcode {
border-radius: 16px;
&.hidden {
display: none;
width: 0;
}
}
}
}
.card-content {
position: relative;
height: 200px;
width: 100%;
max-width: 400px;
-moz-user-select: none;
-webkit-user-select: none;
-ms-user-select: none;
-o-user-select: none;
user-select: none;
&__front,
&__back {
display: block;
transition-timing-function: cubic-bezier(0.175, 0.885, 0.32, 1.275);
transition-duration: 0.7s;
transition-property: transform, opacity;
color: #fff;
width: 100%;
height: 100%;
background: #fdfdfd;
border-radius: 12px;
border: solid #a3a3a3 1px;
box-shadow: 0 0 3px 1px #fff;
padding: 12px;
background-size: cover;
}
// &__front {
// transform: rotateY(0deg);
// background-image: url(./assets/background.svg);
// }
&__back {
position: absolute;
opacity: 0;
top: 0px;
left: 0px;
transform: rotateY(-180deg);
background: #000;
}
&.active_back {
> .card-content__front {
transform: rotateY(180deg);
opacity: 0;
}
> .card-content__back {
opacity: 1;
transform: rotateY(0deg);
}
}
&__logo {
height: 30px;
display: flex;
align-items: center;
justify-content: space-between;
.title {
color: #03d1be;
font-weight: 600;
letter-spacing: 2px;
}
}
&__footer {
display: flex;
justify-content: space-between;
align-items: flex-end;
position: absolute;
bottom: 12px;
width: calc(100% - 24px);
.count {
font-size: 22px;
}
}
}
.explanation {
font-size: 12px;
margin-bottom: 8px;
}
.add-to-wallet {
height: 36px;
border-radius: 8px;
background: var(--main-color);
border-color: var(--main-color);
color: #fff;
max-width: 400px;
width: 100%;
margin: 12px auto 0;
}
}
}

View File

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

View File

@ -0,0 +1,193 @@
import { Component, Inject, OnInit } from '@angular/core';
import { lastValueFrom } from 'rxjs';
import {
orderStatuses,
PageList,
PageListWithBonus,
} from 'src/app/app.constants';
import {
BonusProgramAccount,
Page,
Purchase,
Transaction,
} from 'src/app/interface/data';
import { JsonrpcService, RpcService } from 'src/app/services/jsonrpc.service';
import * as moment from 'moment-timezone';
import * as barcode from 'jsbarcode';
import { environment } from 'src/environments/environment';
import { AppleWalletService } from 'src/app/services/apple-wallet.service';
import { CookiesService } from 'src/app/services/cookies.service';
import { DOCUMENT } from '@angular/common';
import { HttpClient, HttpHeaders } from '@angular/common/http';
import { MessageService } from 'primeng/api';
@Component({
selector: 'app-bonus-program',
templateUrl: './bonus-program.component.html',
styleUrls: ['./bonus-program.component.scss'],
})
export class BonusProgramComponent implements OnInit {
public accountData!: BonusProgramAccount;
public purchases: Purchase[] = [];
public loadingBonuses: boolean = false;
readonly orderStatuses = orderStatuses;
readonly moment = moment;
readonly pageList = environment.hasBonusProgram
? PageListWithBonus
: PageList;
public currentPage: Page = this.pageList[1];
public userName: string = '';
public deviceType: 'ios' | 'android' | null = null;
public bonuses: number = 0
constructor(
private jsonrpc: JsonrpcService,
private appleWallet: AppleWalletService,
private cookiesService: CookiesService,
@Inject(DOCUMENT) private document: Document,
private http: HttpClient,
private messageService: MessageService,
) {}
ngOnInit(): void {
this.getAccountData();
this.getTypeDevice();
}
getTypeDevice() {
const userAgent = window.navigator.userAgent.toLowerCase();
const ios = /iphone|ipod|ipad/.test(userAgent);
this.deviceType = ios ? 'ios' : 'android';
}
async getAccountData(): Promise<void> {
this.loadingBonuses = true;
this.jsonrpc
.rpc(
{
method: 'getAdditionalInfo',
params: [],
},
RpcService.authService,
true
)
.subscribe({
next: (res) => {
this.userName = res?.data?.first_name;
},
error: (err) => {
console.error('Error: ', err);
},
});
const getAccount = await lastValueFrom(
this.jsonrpc.rpc(
{
method: 'GetAccounts',
params: [],
},
RpcService.bonusService
)
);
this.accountData = getAccount && getAccount['Cards'][0];
this.bonuses = this.accountData.BonusProgramAccounts?.reduce((previous, {Bonuses}) => {
return previous + Bonuses
}, 0) || this.accountData.Bonuses
this.loadingBonuses = false;
if (this.accountData) {
barcode('#barcode')
.options({ font: 'OCR-B' }) // Will affect all barcodes
.EAN13(`${this.accountData.CardNumber}`.padStart(12, '0'), {
fontSize: 18,
textMargin: 0,
})
.render();
}
const getTransactions = (
await lastValueFrom(
this.jsonrpc.rpc(
{
method: 'GetAccountTransactions',
params: [],
},
RpcService.bonusService
)
)
)
if (!getTransactions) {
this.messageService.add({severity:'error', summary:'Произошла ошибка, попробуйте позже'});
return
}
const transactions: Transaction[] = getTransactions && getTransactions['Transactions'];
const getPurchases = (
await lastValueFrom(
this.jsonrpc.rpc(
{
method: 'GetAccountPurchase',
params: [],
},
RpcService.bonusService
)
)
)
if (!getPurchases) {
this.messageService.add({severity:'error', summary:'Произошла ошибка, попробуйте позже'});
return
}
const purchases: Purchase[] = getPurchases && getPurchases['Purchases'];
this.purchases = purchases && purchases.map<Purchase>((purchase) => {
const id = purchase.ID.slice(0, 36).toLowerCase();
purchase.Transactions = transactions.filter((transaction) => {
const same = transaction.Purchase === id;
transaction.HasPurchase = same;
return same;
});
return purchase;
});
transactions && transactions.forEach((transaction) => {
if (!transaction.HasPurchase) {
this.purchases.push({
ID: transaction.ID,
PurchaseDate: transaction.Date,
Transactions: [transaction],
IsSingleTransaction: true,
transactionSum: 0
});
}
});
this.purchases = this.purchases && this.purchases.sort((a, b) => {
return moment(a.PurchaseDate).date() - moment(b.PurchaseDate).date();
});
}
async addCardToWallet(e: any) {
e.preventDefault();
const token = this.cookiesService.getItem('token');
const accountData = (
await lastValueFrom(
this.jsonrpc.rpc(
{
method: 'getTokenData',
params: [],
},
RpcService.authService,
true
)
)
).data;
if (token && accountData.user_id) {
this.appleWallet.generateCard(token, accountData.user_id).subscribe({
next: (res: any) => {
this.document.location.href = res.url;
},
error: (err) => {
console.log('Error: ', err);
},
});
}
}
}

View File

@ -1,88 +1,89 @@
<div class="orders">
<table
class="woocommerce-orders-table woocommerce-MyAccount-orders shop_table shop_table_responsive my_account_orders account-orders-table"
>
<thead>
<tr>
<th
class="woocommerce-orders-table__header woocommerce-orders-table__header-order-date"
>
<span class="nobr">Дата</span>
</th>
<th
class="woocommerce-orders-table__header woocommerce-orders-table__header-order-status"
>
<span class="nobr">Место покупки</span>
</th>
<th
class="woocommerce-orders-table__header woocommerce-orders-table__header-order-total"
>
<span class="nobr">Сумма чека</span>
</th>
<th
class="woocommerce-orders-table__header woocommerce-orders-table__header-order-actions"
>
<span class="nobr">Бонусы</span>
</th>
</tr>
</thead>
<tbody>
<tr
*ngFor="let purchase of purchasesShortArray"
class="woocommerce-orders-table__row woocommerce-orders-table__row--status-processing order"
>
<td
class="woocommerce-orders-table__cell woocommerce-orders-table__cell-order-date"
data-title="Дата"
>
<time [dateTime]="purchase.PurchaseDate">{{
moment(purchase.PurchaseDate).format("DD.MM.YYYY")
}}</time>
</td>
<td
class="woocommerce-orders-table__cell woocommerce-orders-table__cell-order-status"
data-title="Место покупки"
>
{{ purchase.Address }}
</td>
<td
class="woocommerce-orders-table__cell woocommerce-orders-table__cell-order-total"
data-title="Сумма чека"
>
<span class="woocommerce-Price-amount amount">
{{ purchase.CheckSummary }}
</span>
</td>
<td
class="woocommerce-orders-table__cell woocommerce-orders-table__cell-order-actions"
data-title="Бонусы"
[ngClass]="{
'red-color': purchase.BonusSummary ? purchase.BonusSummary < 0 : false,
'green-color': purchase.BonusSummary ? purchase.BonusSummary > 0 : false
}"
>
{{ purchase.BonusSummary }}
</td>
</tr>
</tbody>
</table>
<p
*ngIf="purchases.length !== purchasesShortArray.length"
class="show-more-orders"
(click)="getMoreOrders()"
>
Показать больше
</p>
<p-progressSpinner
*ngIf="ordersLoadingStatus"
[style]="{ width: '100%' }"
strokeWidth="2"
styleClass="angular-spinner"
></p-progressSpinner>
<p
*ngIf="purchases.length === 0 && !ordersLoadingStatus"
style="width: 100%; text-align: center"
>
Нет заказов
</p>
</div>
<div class="orders">
<table
class="woocommerce-orders-table woocommerce-MyAccount-orders shop_table shop_table_responsive my_account_orders account-orders-table"
>
<thead>
<tr>
<th
class="woocommerce-orders-table__header woocommerce-orders-table__header-order-date"
>
<span class="nobr">Дата</span>
</th>
<th
class="woocommerce-orders-table__header woocommerce-orders-table__header-order-status"
>
<span class="nobr">Место покупки</span>
</th>
<th
class="woocommerce-orders-table__header woocommerce-orders-table__header-order-total"
>
<span class="nobr">Сумма чека</span>
</th>
<th
class="woocommerce-orders-table__header woocommerce-orders-table__header-order-actions"
>
<span class="nobr">Бонусы</span>
</th>
</tr>
</thead>
<tbody>
<tr
*ngFor="let purchase of purchasesShortArray"
class="woocommerce-orders-table__row woocommerce-orders-table__row--status-processing order"
>
<td
class="woocommerce-orders-table__cell woocommerce-orders-table__cell-order-date"
data-title="Дата"
>
<time [dateTime]="purchase.PurchaseDate">{{
moment(purchase.PurchaseDate).format("DD.MM.YYYY")
}}</time>
</td>
<td
class="woocommerce-orders-table__cell woocommerce-orders-table__cell-order-status"
data-title="Место покупки"
>
{{ purchase.Address }}
</td>
<td
class="woocommerce-orders-table__cell woocommerce-orders-table__cell-order-total"
data-title="Сумма чека"
>
<span class="woocommerce-Price-amount amount">
{{ purchase.CheckSummary }}
</span>
</td>
<td
class="woocommerce-orders-table__cell woocommerce-orders-table__cell-order-actions"
data-title="Бонусы"
[ngClass]="{
'red-color': purchase.BonusSummary ? purchase.BonusSummary < 0 : false,
'green-color': purchase.BonusSummary ? purchase.BonusSummary > 0 : false
}"
>
{{ purchase.BonusSummary }}
</td>
</tr>
</tbody>
</table>
<p
*ngIf="purchases?.length !== purchasesShortArray?.length"
class="show-more-orders"
(click)="getMoreOrders()"
>
Показать больше
</p>
<p-progressSpinner
*ngIf="ordersLoadingStatus"
[style]="{ display: 'block' }"
strokeWidth="2"
styleClass="angular-spinner"
></p-progressSpinner>
<p
*ngIf="purchases?.length === 0 && !ordersLoadingStatus"
class="no-orders"
style="width: 100%; text-align: center"
>
Нет заказов
</p>
</div>

View File

@ -0,0 +1,76 @@
.show-more-orders {
text-align: center;
cursor: pointer;
color: var(--main-color);
margin-top: 16px;
}
.woocommerce-MyAccount-content {
max-width: 400px;
margin-left: calc(50vw - 200px);
}
.woocommerce-orders-table {
border: 2px solid #dee2e6;
border-bottom: 0;
border-radius: 0.25rem;
border-collapse: separate;
text-align: left;
margin-top: 8px;
width: 100%;
max-width: 400px;
margin-left: auto;
margin-right: auto;
thead {
display: none;
}
.woocommerce-orders-table {
&__cell {
padding: 0.7rem 1.5rem;
border-bottom: 2px solid #dee2e6;
text-align: right !important;
display: block;
&::before {
content: attr(data-title) ": ";
font-weight: 700;
float: left;
}
}
&__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;
}
}
.woocommerce-button {
display: inline-block;
color: #ffffff;
background-color: #009688;
padding: 13px;
border-radius: 4px;
text-decoration: none;
width: 100%;
text-align: center;
}
}
.no-orders {
width: 100%;
text-align: center;
margin-top: 16px;
}

View File

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

View File

@ -1,59 +1,67 @@
import { HttpErrorResponse } from '@angular/common/http';
import { Component, Input, OnInit } from '@angular/core';
import { ActivatedRoute, Router } from '@angular/router';
import { Purchase } from 'src/app/interface/data';
import * as moment from 'moment-timezone';
import { lastValueFrom } from 'rxjs';
import { JsonrpcService, RpcService } from 'src/app/services/jsonrpc.service';
@Component({
selector: 'app-orders',
templateUrl: './orders.component.html',
styleUrls: ['./orders.component.scss']
})
export class OrdersComponent implements OnInit {
@Input() handleHttpError!: (error: HttpErrorResponse) => void;
public lastViewOrder: number = 3;
public ordersLoadingStatus: boolean = true;
readonly moment = moment;
public purchases: Purchase[] = [];
public purchasesShortArray: Purchase[] = [];
constructor(
private route: ActivatedRoute,
private router: Router,
private jsonrpc: JsonrpcService,
) { }
ngOnInit(): void {
this.getOrders()
}
async getOrders(){
const purchases: Purchase[] = (await lastValueFrom(
this.jsonrpc.rpc(
{
method: 'GetAccountPurchase',
params: []
},
RpcService.bonusService
)))['Purchases'];
this.purchases = purchases.map<Purchase>((purchase) => {
const id = purchase.ID.slice(0,36).toLowerCase();
// purchase.Transactions = transactions.filter((transaction) => {
// const same = transaction.Purchase === id;
// transaction.HasPurchase = same;
// return same;
// });
return purchase;
});
this.purchasesShortArray = this.purchases.slice(0, this.lastViewOrder)
this.ordersLoadingStatus = false;
}
getMoreOrders() {
this.lastViewOrder += 4;
this.purchasesShortArray = this.purchases.slice(0, this.lastViewOrder);
}
}
import { HttpErrorResponse } from '@angular/common/http';
import { Component, Input, OnInit } from '@angular/core';
import { ActivatedRoute, Router } from '@angular/router';
import { Purchase } from 'src/app/interface/data';
import * as moment from 'moment-timezone';
import { lastValueFrom } from 'rxjs';
import { JsonrpcService, RpcService } from 'src/app/services/jsonrpc.service';
import { MessageService } from 'primeng/api';
@Component({
selector: 'app-orders',
templateUrl: './orders.component.html',
styleUrls: ['./orders.component.scss'],
})
export class OrdersComponent implements OnInit {
@Input() handleHttpError!: (error: HttpErrorResponse) => void;
public lastViewOrder: number = 3;
public ordersLoadingStatus: boolean = true;
readonly moment = moment;
public purchases: Purchase[] = [];
public purchasesShortArray: Purchase[] = [];
constructor(
private route: ActivatedRoute,
private router: Router,
private jsonrpc: JsonrpcService,
private messageService: MessageService,
) {}
ngOnInit(): void {
this.getOrders();
}
async getOrders() {
const getPurchases = await lastValueFrom(
this.jsonrpc.rpc(
{
method: 'GetAccountPurchase',
params: [],
},
RpcService.bonusService
)
);
if (!getPurchases) {
this.messageService.add({severity:'error', summary:'Произошла ошибка, попробуйте позже'});
return
}
const purchases: Purchase[] = getPurchases && getPurchases['Purchases'];
this.purchases = purchases && purchases.map<Purchase>((purchase) => {
const id = purchase.ID.slice(0, 36).toLowerCase();
// purchase.Transactions = transactions.filter((transaction) => {
// const same = transaction.Purchase === id;
// transaction.HasPurchase = same;
// return same;
// });
return purchase;
});
this.purchasesShortArray = this.purchases && this.purchases.slice(0, this.lastViewOrder);
this.ordersLoadingStatus = false;
}
getMoreOrders() {
this.lastViewOrder += 4;
this.purchasesShortArray = this.purchases.slice(0, this.lastViewOrder);
}
}

View File

@ -1,25 +1,25 @@
<div class="ref-system">
<qrcode
*ngIf="!loading"
[qrdata]="refUrl"
[width]="256"
[errorCorrectionLevel]="'M'"
></qrcode>
<p-progressSpinner
*ngIf="loading"
[style]="{ width: '100%' }"
strokeWidth="2"
styleClass="angular-spinner"
></p-progressSpinner>
<!-- <share-buttons
[theme]="'modern-dark'"
[include]="['telegram', 'whatsapp', 'vk', 'copy', 'facebook', 'twitter']"
[show]="4"
[url]="refUrl"
style="display: flex; justify-content: center;"
></share-buttons> -->
<div class="share-container" *ngIf="!loading">
<div class="copy" type="button" (click)="copyUrl()">Скопировать ссылку</div>
<div class="share" type="button" (click)="share()"><img src="./assets/share.svg" alt="share"></div>
</div>
</div>
<div class="ref-system">
<!-- <qrcode
*ngIf="!loading"
[qrdata]="refUrl"
[width]="256"
[errorCorrectionLevel]="'M'"
></qrcode> -->
<p-progressSpinner
*ngIf="loading"
[style]="{ width: '100%' }"
strokeWidth="2"
styleClass="angular-spinner"
></p-progressSpinner>
<!-- <share-buttons
[theme]="'modern-dark'"
[include]="['telegram', 'whatsapp', 'vk', 'copy', 'facebook', 'twitter']"
[show]="4"
[url]="refUrl"
style="display: flex; justify-content: center;"
></share-buttons> -->
<div class="share-container" *ngIf="!loading">
<div class="copy" type="button" (click)="copyUrl()">Скопировать ссылку</div>
<div class="share" type="button" (click)="share()"><img src="./assets/share.svg" alt="share"></div>
</div>
</div>

View File

@ -1,31 +1,33 @@
:host {
.ref-system {
.share-container {
display: flex;
justify-content: space-between;
height: 50px;
max-width: 400px;
margin: 0 auto;
.share {
width: 50px;
background-color: #09467f;
border-radius: 8px;
display: flex;
justify-content: center;
align-items: center;
img {
height: 26px;
}
}
.copy {
background-color: #09467f;
color: #fff;
border-radius: 8px;
width: calc(100% - 66px);
display: flex;
justify-content: center;
align-items: center;
}
}
}
:host {
.ref-system {
.share-container {
display: flex;
justify-content: space-between;
height: 50px;
max-width: 400px;
margin: 0 auto;
.share {
width: 50px;
background-color: var(--main-color);
border-radius: 8px;
display: flex;
justify-content: center;
align-items: center;
cursor: pointer;
img {
height: 26px;
}
}
.copy {
background-color: var(--main-color);
color: #fff;
border-radius: 8px;
width: calc(100% - 66px);
display: flex;
justify-content: center;
align-items: center;
cursor: pointer;
}
}
}
}

View File

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

View File

@ -1,64 +1,71 @@
import { Component, OnInit } from '@angular/core';
import * as barcode from 'jsbarcode';
import { MessageService } from 'primeng/api';
import { lastValueFrom } from 'rxjs';
import { JsonrpcService, RpcService } from 'src/app/services/jsonrpc.service';
import { environment } from 'src/environments/environment';
@Component({
selector: 'app-ref-system',
templateUrl: './ref-system.component.html',
styleUrls: ['./ref-system.component.scss']
})
export class RefSystemComponent implements OnInit {
public refUrl: string = `${environment.defaultUrl}/?refCardNumber=`
public loading: boolean = true;
constructor(
private jsonrpc: JsonrpcService,
private messageService: MessageService
) { }
async ngOnInit() {
const accountData = (await lastValueFrom(
this.jsonrpc.rpc({
method: 'GetAccounts',
params: []
},
RpcService.bonusService
)))['Cards'][0]
this.refUrl += accountData.CardNumber
this.loading = false
}
share() {
if (navigator.share) {
navigator.share({
title: document.title,
text: "Fashion Logica",
url: this.refUrl
})
.then(() => console.log('Successful share'))
.catch((error) => {
console.log('Error sharing:', error)
});
}
}
copyUrl() {
navigator.clipboard.writeText(this.refUrl)
.then(() => {
this.messageService.add({
severity: 'custom',
summary: 'Ссылка скопирована!',
});
})
.catch(err => {
this.messageService.add({
severity: 'error',
summary: 'Произошла ошибка!',
});
});
}
}
import { Component, OnInit } from '@angular/core';
import * as barcode from 'jsbarcode';
import { MessageService } from 'primeng/api';
import { lastValueFrom } from 'rxjs';
import { JsonrpcService, RpcService } from 'src/app/services/jsonrpc.service';
import { environment } from 'src/environments/environment';
@Component({
selector: 'app-ref-system',
templateUrl: './ref-system.component.html',
styleUrls: ['./ref-system.component.scss']
})
export class RefSystemComponent implements OnInit {
public refUrl: string = `${environment.defaultUrl}/?refUserId=`
public loading: boolean = true;
constructor(
private jsonrpc: JsonrpcService,
private messageService: MessageService
) { }
async ngOnInit() {
const accountData = (await lastValueFrom(
this.jsonrpc
.rpc(
{
method: 'getTokenData',
params: [],
},
RpcService.authService,
true
)
)).data
this.refUrl += accountData.user_id
this.loading = false
}
share() {
if (navigator.share) {
navigator.share({
title: document.title,
text: "Tasty Coffee",
url: this.refUrl
})
.then(() => console.log('Successful share'))
.catch((error) => {
console.log('Error sharing:', error)
});
}
}
copyUrl() {
navigator.clipboard.writeText(this.refUrl)
.then(() => {
this.messageService.clear();
this.messageService.add({
severity: 'custom',
summary: 'Ссылка скопирована!',
});
})
.catch(err => {
this.messageService.clear();
this.messageService.add({
severity: 'error',
summary: 'Произошла ошибка!',
});
});
}
}

View File

@ -1,87 +1,87 @@
<form
class="woocommerce-form woocommerce-form-login login"
action="false"
(ngSubmit)="saveData()"
[formGroup]="userDataFormGroup"
>
<h2>Личные данные</h2>
<p
class="woocommerce-form-row woocommerce-form-row--wide form-row form-row-wide"
>
<label for="phone">Заполните поля анкеты</label>
<input
formControlName="first_name"
pInputText
type="text"
class="woocommerce-Input woocommerce-Input--text input-text"
name="first_name"
id="first_name"
placeholder="Имя"
/>
</p>
<p
class="woocommerce-form-row woocommerce-form-row--wide form-row form-row-wide"
>
<!-- <p-inputMask
type="text"
class="woocommerce-Input woocommerce-Input--text"
name="birthdate"
inputId="birthdate"
placeholder="Дата рождения (00.00.00)"
mask="99.99.99"
[styleClass]="datevalid ? '' : 'invalid'"
[(ngModel)]="birthdate"
[ngModelOptions]="{standalone: true}"
></p-inputMask> -->
<input
type="date"
formControlName="birthdate"
class="woocommerce-Input woocommerce-Input--text input-text"
pInputText
name="birthdate"
id="birthdate"
placeholder="Дата рождения"
/>
</p>
<p
class="woocommerce-form-row woocommerce-form-row--wide form-row form-row-wide"
>
<span class="select-wrapper" [ngClass]="{
'select-wrapper': true,
'hidden': userDataFormGroup.value.gender.length
}">
<select formControlName="gender" name="gender" id="gender" required>
<!-- <option hidden value="" disabled selected>Выберите пол</option> -->
<option value="Мужской">Мужской</option>
<option value="Женский">Женский</option>
</select>
</span>
</p>
<p class="form-row" style="margin-bottom: 6px">
<button
type="submit"
class="woocommerce-button button woocommerce-form-login__submit"
name="save"
value="Сохранить"
[disabled]="false"
>
Сохранить
</button>
</p>
<span class="policy"
>Нажимая кнопку, Вы соглашаетесь с Политикой обработки персональных
данных.</span
>
<div class="decoration-pattern">
<img
style="height: 34px"
src="./assets/card-decorative-pattern.svg"
alt=""
/>
<img
style="height: 16px; margin-left: 2px"
src="./assets/card-decorative-pattern.svg"
alt=""
/>
</div>
</form>
<form
class="woocommerce-form woocommerce-form-login login"
action="false"
(ngSubmit)="saveData()"
[formGroup]="userDataFormGroup"
>
<h2>Личные данные</h2>
<p
class="woocommerce-form-row woocommerce-form-row--wide form-row form-row-wide"
>
<label for="phone">Заполните поля анкеты</label>
<input
formControlName="first_name"
pInputText
type="text"
class="woocommerce-Input woocommerce-Input--text input-text"
name="first_name"
id="first_name"
placeholder="Имя"
/>
</p>
<p
class="woocommerce-form-row woocommerce-form-row--wide form-row form-row-wide"
>
<!-- <p-inputMask
type="text"
class="woocommerce-Input woocommerce-Input--text"
name="birthdate"
inputId="birthdate"
placeholder="Дата рождения (00.00.00)"
mask="99.99.99"
[styleClass]="datevalid ? '' : 'invalid'"
[(ngModel)]="birthdate"
[ngModelOptions]="{standalone: true}"
></p-inputMask> -->
<input
type="date"
formControlName="birthdate"
class="woocommerce-Input woocommerce-Input--text input-text"
pInputText
name="birthdate"
id="birthdate"
placeholder="Дата рождения"
/>
</p>
<p
class="woocommerce-form-row woocommerce-form-row--wide form-row form-row-wide"
>
<span class="select-wrapper" [ngClass]="{
'select-wrapper': true,
'hidden': userDataFormGroup.value.gender.length
}">
<select formControlName="gender" name="gender" id="gender" required>
<!-- <option hidden value="" disabled selected>Выберите пол</option> -->
<option value="Мужской">Мужской</option>
<option value="Женский">Женский</option>
</select>
</span>
</p>
<p class="form-row" style="margin-bottom: 6px">
<button
type="submit"
class="woocommerce-button button woocommerce-form-login__submit"
name="save"
value="Сохранить"
[disabled]="false"
>
Сохранить
</button>
</p>
<span class="policy"
>Нажимая кнопку, Вы соглашаетесь с Политикой обработки персональных
данных.</span
>
<div class="decoration-pattern">
<img
style="height: 34px"
src="./assets/card-decorative-pattern.png"
alt=""
/>
<img
style="height: 16px; margin-left: 2px"
src="./assets/card-decorative-pattern.png"
alt=""
/>
</div>
</form>

View File

@ -1,189 +1,189 @@
:host {
display: flex;
justify-content: center;
.woocommerce-form-login__submit {
width: 150px;
}
h2 {
color: #fff;
width: 100%;
height: 49px;
text-align: left;
padding-top: 13px;
padding-left: 32px;
background: #0d457e;
position: relative;
font-style: normal;
font-weight: 700;
font-size: 20px;
line-height: 24px;
&::before {
content: "";
width: 19px;
height: 4px;
background: #fff;
display: block;
position: absolute;
left: 0;
top: 24px;
}
}
.woocommerce-form {
display: flex;
flex-direction: column;
align-items: center;
background: #ffffff;
box-shadow: 0px 0px 10px rgb(0 0 0 / 15%);
border-radius: 15px;
min-height: 262px;
max-width: 400px;
overflow: auto;
position: relative;
padding-bottom: 10px;
}
.form-row {
width: 80%;
margin-bottom: 16px;
max-width: 400px;
.button {
width: 100%;
font-weight: 600;
letter-spacing: 2px;
border: none;
height: 45px;
background: #b8deff;
border-radius: 15px;
color: #000;
font-style: normal;
font-weight: 700;
font-size: 16px;
line-height: 20px;
}
label {
display: inline-block;
margin-bottom: 8px;
margin-top: 15px;
color: #000;
font-style: normal;
font-weight: 400;
font-size: 18px;
line-height: 22px;
}
.input-text {
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto,
Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji",
"Segoe UI Symbol";
color: #495057;
padding: 0.5rem 0.75rem;
padding-left: 13px;
transition: background-color 0.15s, border-color 0.15s, box-shadow 0.15s;
-webkit-appearance: none;
appearance: none;
border-radius: 4px;
width: 100%;
height: 45px;
background: #FFFFFF;
border: 1px solid #B8DEFF;
border-radius: 15px;
font-size: 14px;
text-align: left;
&.ng-dirty.ng-invalid {
border-color: red;
}
&.ng-invalid.ng-touched {
border-color: red;
}
}
select {
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto,
Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji",
"Segoe UI Symbol";
background: #FFFFFF;
border: 1px solid #B8DEFF;
border-radius: 15px;
width: 100%;
height: 45px;
padding: 0 10px;
font-size: 14px;
text-align: left;
color: #495057;
// option[value=""][disabled] {
// display: none;
// }
&.ng-dirty.ng-invalid {
border-color: red;
}
&.ng-invalid.ng-touched {
border-color: red;
}
}
select:required:invalid {
color: #6c757d;
}
}
.policy {
width: 80%;
text-align: center;
font-style: normal;
font-weight: 400;
font-size: 12px;
line-height: 15px;
color: #BDBDBD;
}
.decoration-pattern {
margin-top: 12px;
display: flex;
align-items: flex-end;
margin-left: 0;
width: 92%;
}
input[type="date"]::before {
content: attr(placeholder);
width: 100%;
color: #6c757d;
}
input[type="date"]:focus::before,
input[type="date"].ng-valid::before {
display: none;
}
.select-wrapper {
position: relative;
&::before {
content: 'Выберите пол';
position: absolute;
top: 3px;
left: 13px;
height: 100%;
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto,
Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji",
"Segoe UI Symbol" !important;
font-family: inherit;
color: #6c757d;
font-size: 14px;
}
&.hidden::before {
display: none;
}
}
/* hide our custom/fake placeholder text when in focus to show the default
* 'mm/dd/yyyy' value and when valid to show the users' date of birth value.
*/
// input[type="date"]:focus::before,
// input[type="date"]:valid::before { display: none }
}
:host {
display: flex;
justify-content: center;
.woocommerce-form-login__submit {
width: 150px;
}
h2 {
color: #fff;
width: 100%;
height: 49px;
text-align: left;
padding-top: 13px;
padding-left: 32px;
background: #0d457e;
position: relative;
font-style: normal;
font-weight: 700;
font-size: 20px;
line-height: 24px;
&::before {
content: "";
width: 19px;
height: 4px;
background: #fff;
display: block;
position: absolute;
left: 0;
top: 24px;
}
}
.woocommerce-form {
display: flex;
flex-direction: column;
align-items: center;
background: #ffffff;
box-shadow: 0px 0px 10px rgb(0 0 0 / 15%);
border-radius: 15px;
min-height: 262px;
max-width: 400px;
overflow: auto;
position: relative;
padding-bottom: 10px;
}
.form-row {
width: 80%;
margin-bottom: 16px;
max-width: 400px;
.button {
width: 100%;
font-weight: 600;
letter-spacing: 2px;
border: none;
height: 45px;
background: #b8deff;
border-radius: 15px;
color: #000;
font-style: normal;
font-weight: 700;
font-size: 16px;
line-height: 20px;
}
label {
display: inline-block;
margin-bottom: 8px;
margin-top: 15px;
color: #000;
font-style: normal;
font-weight: 400;
font-size: 18px;
line-height: 22px;
}
.input-text {
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto,
Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji",
"Segoe UI Symbol";
color: #495057;
padding: 0.5rem 0.75rem;
padding-left: 13px;
transition: background-color 0.15s, border-color 0.15s, box-shadow 0.15s;
-webkit-appearance: none;
appearance: none;
border-radius: 4px;
width: 100%;
height: 45px;
background: #FFFFFF;
border: 1px solid #B8DEFF;
border-radius: 15px;
font-size: 14px;
text-align: left;
&.ng-dirty.ng-invalid {
border-color: red;
}
&.ng-invalid.ng-touched {
border-color: red;
}
}
select {
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto,
Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji",
"Segoe UI Symbol";
background: #FFFFFF;
border: 1px solid #B8DEFF;
border-radius: 15px;
width: 100%;
height: 45px;
padding: 0 10px;
font-size: 14px;
text-align: left;
color: #495057;
// option[value=""][disabled] {
// display: none;
// }
&.ng-dirty.ng-invalid {
border-color: red;
}
&.ng-invalid.ng-touched {
border-color: red;
}
}
select:required:invalid {
color: #6c757d;
}
}
.policy {
width: 80%;
text-align: center;
font-style: normal;
font-weight: 400;
font-size: 12px;
line-height: 15px;
color: #BDBDBD;
}
.decoration-pattern {
margin-top: 12px;
display: flex;
align-items: flex-end;
margin-left: 0;
width: 92%;
}
input[type="date"]::before {
content: attr(placeholder);
width: 100%;
color: #6c757d;
}
input[type="date"]:focus::before,
input[type="date"].ng-valid::before {
display: none;
}
.select-wrapper {
position: relative;
&::before {
content: 'Выберите пол';
position: absolute;
top: 3px;
left: 13px;
height: 100%;
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto,
Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji",
"Segoe UI Symbol" !important;
font-family: inherit;
color: #6c757d;
font-size: 14px;
}
&.hidden::before {
display: none;
}
}
/* hide our custom/fake placeholder text when in focus to show the default
* 'mm/dd/yyyy' value and when valid to show the users' date of birth value.
*/
// input[type="date"]:focus::before,
// input[type="date"]:valid::before { display: none }
}

View File

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

View File

@ -1,77 +1,110 @@
import { Component, OnInit } from '@angular/core';
import { FormControl, FormGroup, Validators } from '@angular/forms';
import { MessageService } from 'primeng/api';
import { UserDataForm } from 'src/app/interface/data';
import { JsonrpcService, RpcService } from 'src/app/services/jsonrpc.service';
@Component({
selector: 'app-user-data',
templateUrl: './user-data.component.html',
styleUrls: ['./user-data.component.scss']
})
export class UserDataComponent implements OnInit {
public userDataFormGroup!: FormGroup;
public userData: UserDataForm = {
first_name: '',
birthdate: '',
gender: '',
}
constructor(
private jsonRpcService: JsonrpcService,
private messageService: MessageService,
) { }
ngOnInit(): void {
this.createUserDataForm({...this.userData})
this.jsonRpcService.rpc({
method: 'getAdditionalInfo',
params: []
}, RpcService.authService, true).subscribe({
next: (res) => {
const { first_name, birthdate, gender } = res.data
this.userData = { first_name, birthdate, gender }
this.createUserDataForm({...this.userData})
},
error: (err) => {
console.error('Error: ', err)
}
});
}
createUserDataForm(val: UserDataForm) {
this.userDataFormGroup = new FormGroup({
first_name: new FormControl(val.first_name, [Validators.required, Validators.minLength(2)]),
birthdate: new FormControl(val.birthdate, [Validators.required]),
gender: new FormControl(val.gender ? val.gender : '', [Validators.required]),
})
}
saveData() {
if (this.userDataFormGroup.invalid) {
this.markFormGroupTouched(this.userDataFormGroup)
return
}
this.userData = this.userDataFormGroup.value as UserDataForm
this.jsonRpcService.rpc({
method: 'updateAdditionalInfo',
params: [this.userData]
}, RpcService.authService, true).subscribe({
next: () => {
this.messageService.add({severity:'custom', summary:'Данные успешно обновлены!'});
},
error: (err) => {
console.error('Error: ', err)
this.messageService.add({severity:'error', summary:'Произошла ошибка, попробуйте позже'});
}
})
}
markFormGroupTouched(formGroup: FormGroup) {
(<any>Object).values(formGroup.controls).forEach((control: FormControl) => {
control.markAsDirty();
});
}
}
import { Component, OnInit } from '@angular/core';
import { FormControl, FormGroup, Validators } from '@angular/forms';
import { MessageService } from 'primeng/api';
import { lastValueFrom } from 'rxjs';
import { UserDataForm } from 'src/app/interface/data';
import { AppleWalletService } from 'src/app/services/apple-wallet.service';
import { JsonrpcService, RpcService } from 'src/app/services/jsonrpc.service';
@Component({
selector: 'app-user-data',
templateUrl: './user-data.component.html',
styleUrls: ['./user-data.component.scss']
})
export class UserDataComponent implements OnInit {
public userDataFormGroup!: FormGroup;
public userData: UserDataForm = {
first_name: '',
birthdate: '',
gender: '',
}
constructor(
private jsonRpcService: JsonrpcService,
private messageService: MessageService,
private appleWallet: AppleWalletService,
private jsonrpc: JsonrpcService,
) { }
ngOnInit(): void {
this.createUserDataForm({...this.userData})
this.jsonRpcService.rpc({
method: 'getAdditionalInfo',
params: []
}, RpcService.authService, true).subscribe({
next: (res) => {
if (res.code === 45) {
this.messageService.add({severity:'warn', summary:'Данные не найдены, заполните их на этой странице'});
return
}
if (!res.data) {
this.messageService.add({severity:'error', summary:'Произошла ошибка, попробуйте позже'});
return
}
const { first_name, birthdate, gender } = res.data
this.userData = { first_name, birthdate, gender }
this.createUserDataForm({...this.userData})
},
error: (err) => {
console.error('Error: ', err)
}
});
}
createUserDataForm(val: UserDataForm) {
this.userDataFormGroup = new FormGroup({
first_name: new FormControl(val.first_name, [Validators.required, Validators.minLength(2)]),
birthdate: new FormControl(val.birthdate, [Validators.required]),
gender: new FormControl(val.gender ? val.gender : '', [Validators.required]),
})
}
saveData() {
if (this.userDataFormGroup.invalid) {
this.markFormGroupTouched(this.userDataFormGroup)
return
}
this.userData = this.userDataFormGroup.value as UserDataForm
this.jsonRpcService.rpc({
method: 'updateAdditionalInfo',
params: [this.userData]
}, RpcService.authService, true).subscribe({
next: async () => {
this.messageService.add({severity:'custom', summary:'Данные успешно обновлены!'});
const accountData = (await lastValueFrom(
this.jsonrpc
.rpc(
{
method: 'getTokenData',
params: [],
},
RpcService.authService,
true
)
)).data
if (accountData.user_id) {
this.appleWallet.reloadCard(accountData.user_id).subscribe({
next: (value) => {
console.log(value);
},
error: (err) => {
console.error(err);
}
})
}
},
error: (err) => {
console.error('Error: ', err)
this.messageService.add({severity:'error', summary:'Произошла ошибка, попробуйте позже'});
}
})
}
markFormGroupTouched(formGroup: FormGroup) {
(<any>Object).values(formGroup.controls).forEach((control: FormControl) => {
control.markAsDirty();
});
}
}

View File

@ -0,0 +1,82 @@
<app-navbar title="Карта гостя" (backEvent)="logout()"></app-navbar>
<div class="guest-card">
<div class="top-info">
<div class="top-info__level">
<p id="level">Уровень {{ currentLvl }}</p>
<p id="level-percent">Кэшбек {{ currentLvlPeriod.percent }}%</p>
</div>
<p class="top-info__bonus">{{ getBalanceAmount(customerInfo?.walletBalances) }} бонусов</p>
</div>
<div class="guest-card__qr" (click)="qrCodeClick()">
<ng-container *ngIf="customerInfo">
<qr-code
[value]="customerInfo?.phone.substr(2) || 'Данные не найдены'"
[margin]="0"
[size]="qrCodeSize"
errorCorrectionLevel="M"
></qr-code>
</ng-container>
<ng-container *ngIf="!customerInfo">
<ng-container
*ngTemplateOutlet="spinner; context: { $implicit: 85 }"
></ng-container>
</ng-container>
</div>
<div class="guest-card__user-description">
<p>
<ng-container *ngIf="currentLvlPeriod.end">
Осталось купить на сумму <span class="price">{{ currentLvlPeriod.end - (purchaseData.currentAmount || 0) + 1 }}</span>
рублей, тогда кэшбек будет <span class="percent">{{ getNextLevel().percent }}%</span> с
{{ purchaseData.currentPeriod[1].locale('ru').format('D MMMM')}}
</ng-container>
<ng-container *ngIf="!currentLvlPeriod.end">
У Вас последний уровень бонусной программы. Процент начисляемых бонусов:
{{currentLvlPeriod.percent}}%
</ng-container>
</p>
</div>
<span id="bonuses-condition"></span>
<!-- <div class="guest-card__level-info"> -->
<!-- <ng-container *ngIf="!purchaseData.$loading"> -->
<!-- <ng-container *ngIf="currentLvlPeriod.end"> -->
<!-- <\!-- <input -\-> -->
<!-- <\!-- type="range" -\-> -->
<!-- <\!-- [value]="purchaseData.currentAmount" -\-> -->
<!-- <\!-- [min]="currentLvlPeriod.start" -\-> -->
<!-- <\!-- [max]="currentLvlPeriod.end" -\-> -->
<!-- <\!-- [step]="0.01" -\-> -->
<!-- <\!-- disabled="true" -\-> -->
<!-- <\!-- [ngStyle]="{ -\-> -->
<!-- <\!-- 'background-size': ((purchaseData.currentAmount! - currentLvlPeriod.start) / (currentLvlPeriod.end - currentLvlPeriod.start + 1)) * 100 + '% 100%' -\-> -->
<!-- <\!-- }" -\-> -->
<!-- <\!-- /> -\-> -->
<!-- </ng-container> -->
<!-- <ng-container *ngIf="!currentLvlPeriod.end"> -->
<!-- <h2> -->
<!-- У Вас последний уровень бонусной программы. Процент начисляемых бонусов: {{currentLvlPeriod.percent}}% -->
<!-- </h2> -->
<!-- </ng-container> -->
<!-- </ng-container> -->
<!-- <ng-container *ngIf="purchaseData.$loading"> -->
<!-- <ng-container -->
<!-- *ngTemplateOutlet="spinner; context: { $implicit: 48 }" -->
<!-- ></ng-container> -->
<!-- </ng-container> -->
<!-- </div> -->
<app-last-order
[lastOrder]="lastPurchase"
[loading]="purchaseData.$loading"
></app-last-order>
<a class="guest-card__loyalty-program" routerLink="loyality-program"
>Подробнее о правилах <br />
Программы лояльности</a
>
</div>
<ng-template #spinner let-diameter>
<mat-spinner [strokeWidth]="3" [diameter]="diameter"></mat-spinner>
</ng-template>

View File

@ -0,0 +1,222 @@
:host {
.guest-card {
display: flex;
flex-direction: column;
align-items: center;
max-width: 600px;
margin: 0 auto;
.top-info {
padding: 8px 0 0;
width: 100%;
display: flex;
flex-direction: column;
align-items: center;
background-color: #292929;
border-bottom-left-radius: 10px;
border-bottom-right-radius: 10px;
border: 1px solid #000000;
border-top: 1px solid #888888;
&__level {
width: 100%;
display: flex;
align-items: center;
justify-content: space-around;
font-size: 16px;
}
& p {
color: var(--button-color);
}
p.top-info__bonus{
font-size: 20px;
}
}
&__qr {
margin: 10px;
padding: 10px;
width: fit-content;
background-image: linear-gradient(
#fff 33%,
transparent 0px,
transparent 67%,
#fff 0px
),
linear-gradient(
90deg,
#ffe 33%,
transparent 0px,
transparent 66%,
#fff 0px
),
linear-gradient(#fff 33%, transparent 0px, transparent 67%, #fff 0),
linear-gradient(90deg, #fff 33%, transparent 0, transparent 66%, #fff 0);
background-size: 1px 100%, 100% 1px, 1px 100%, 100% 1px;
background-position: 0 0, 0 0, 100% 100%, 100% 100%;
background-repeat: no-repeat, no-repeat, no-repeat, no-repeat;
cursor: pointer;
}
&__user-description {
background-color: #292929;
padding: 14px 24px;
p {
width: 60%;
text-align: center;
margin: 0 auto;
font-style: normal;
font-weight: 400;
font-size: 16px;
line-height: 19px;
letter-spacing: -0.5px;
color: var(--text-color);
}
.price,
.percent {
font-weight: bold;
}
}
&__purchases-description {
margin: 0;
padding: 14px 24px;
width: 100%;
text-align: center;
font-style: normal;
font-weight: 400;
font-size: 16px;
line-height: 19px;
letter-spacing: -0.5px;
grid-template-columns: calc(100% - 24px) 24px;
span {
color: #219653;
}
}
&__level-info {
padding-top: 36px;
padding-left: 36px;
padding-right: 36px;
display: flex;
flex-direction: column;
align-items: center;
h2 {
font-style: normal;
font-family: Montserrat;
font-weight: 700;
font-size: 17px;
line-height: 22px;
text-align: center;
letter-spacing: -0.408px;
}
input[type="range"] {
-webkit-appearance: none;
width: 100%;
height: 6px;
border-radius: 5px;
display: block;
position: relative;
background: #231f20;
box-shadow: 0px 0px 3px #f2c94c59;
background-image: linear-gradient(#f2c94c, #f2c94c);
background-size: 70% 100%;
background-repeat: no-repeat;
&::before,
&::after {
content: " ";
display: block;
width: 16px;
height: 16px;
border-radius: 100%;
position: absolute;
top: -5px;
}
&::before {
background-color: #f2c94c;
left: 0px;
}
&::after {
background-color: #f2c94c;
right: 0px;
}
&::-webkit-slider-thumb {
-webkit-appearance: none;
background: #f2c94c;
width: 16px;
height: 16px;
border-radius: 100%;
display: none;
}
&::-ms-thumb {
-webkit-appearance: none;
height: 20px;
width: 20px;
border-radius: 50%;
background: #f2c94c;
cursor: ew-resize;
box-shadow: 0 0 2px 0 #555;
transition: background 0.3s ease-in-out;
}
&::-moz-range-thumb {
-webkit-appearance: none;
height: 20px;
width: 20px;
border-radius: 50%;
background: #f2c94c;
cursor: ew-resize;
box-shadow: 0 0 2px 0 #555;
transition: background 0.3s ease-in-out;
}
}
}
&__download-app {
width: 100%;
position: relative;
margin-top: 32px;
display: flex;
justify-content: flex-end;
img {
width: 100%;
max-width: calc(100% - 16px);
}
}
&__loyalty-program {
text-align: center;
color: #7F2061;
font-style: normal;
font-weight: bold;
font-size: 12px;
line-height: 16px;
text-decoration: none;
margin: 17px 0 22px;
}
}
}
app-accordion {
ul {
li {
list-style-type: disc;
margin-left: 16px;
}
}
}

View File

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

View File

@ -0,0 +1,193 @@
import { Component, OnInit } from '@angular/core';
import { MatBottomSheet } from '@angular/material/bottom-sheet';
import { Router } from '@angular/router';
import { Observable } from 'rxjs';
import { ExitComponent } from 'src/app/components/exit/exit.component';
import { CookiesService } from 'src/app/services/cookies.service';
import { WpJsonService } from 'src/app/services/wp-json.service';
import { environment } from 'src/environments/environment';
import moment from 'moment';
import { Purchase, lvlPeriod } from 'src/app/interface/data';
import { lvlPeriods } from 'src/app/app.constants';
interface Moment extends moment.Moment {}
export interface IPurchaseData {
currentPeriod: Moment[];
lastPeriod: Moment[];
lastPurchases: Purchase[];
currentPurchases: Purchase[];
lastAmount?: number;
currentAmount?: number;
$loading: boolean;
}
@Component({
selector: 'app-guest-card',
templateUrl: './guest-card.component.html',
styleUrls: ['./guest-card.component.scss'],
})
export class GuestCardComponent implements OnInit {
public qrCodeSize: number = 85;
private isQrCodeClicked: boolean = false;
public customerInfo!: any;
public purchases!: Purchase[];
public lastPurchase!: Purchase;
public purchaseData: IPurchaseData = {
currentPeriod: [],
lastPeriod: [],
lastPurchases: [],
currentPurchases: [],
$loading: true,
get currentAmount():number {
const amount = this.currentPurchases.reduce((accumulator, currentValue) => {
if (currentValue.transactionType !== 'PayFromWallet') return accumulator;
return accumulator + currentValue.orderSum!;
}, 0);
return amount * 1
},
get lastAmount():number {
const amount = this.lastPurchases.reduce((accumulator, currentValue) => {
if (currentValue.transactionType !== 'PayFromWallet') return accumulator;
return accumulator + currentValue.orderSum!;
}, 0);
return amount * 1
}
};
public discountLevel: number = 4.2;
public lvlPeriods: lvlPeriod[] = lvlPeriods;
public currentLvlPeriod!: lvlPeriod;
public currentLvl: number = 1;
constructor(
private _bottomSheet: MatBottomSheet,
private cookiesService: CookiesService,
private router: Router,
private wpJsonService: WpJsonService
) {}
ngOnInit(): void {
const token = this.cookiesService.getItem('token');
this.getCurrentQuarterOfYear();
this.wpJsonService
.getCustomerInfo(
environment.systemId,
token || '',
environment.icardProxy
)
.subscribe({
next: (value) => {
this.customerInfo = value.customer_info;
this.getPurchases().subscribe((value) => {
this.purchases = value[this.customerInfo?.id].filter(
(purchase: Purchase) =>
[
'PayFromWallet',
'CancelPayFromWallet',
'RefillWallet',
'RefillWalletFromOrder'
].includes(purchase.transactionType || '')
);
this.lastPurchase = this.purchases.filter(
(purchase: Purchase) =>
[
'PayFromWallet',
'RefillWalletFromOrder'
].includes(purchase.transactionType || '')
)[0]
this.purchaseData.lastPurchases = this.purchases.filter((value: Purchase) => {
return moment(value.transactionCreateDate).isBetween(this.purchaseData.lastPeriod[0], this.purchaseData.lastPeriod[1])
})
this.purchaseData.currentPurchases = this.purchases.filter((value: Purchase) => {
return moment(value.transactionCreateDate).isBetween(this.purchaseData.currentPeriod[0], this.purchaseData.currentPeriod[1])
})
const currentAmount = this.purchaseData.currentAmount || 0
const index = this.lvlPeriods.findIndex((item) => item.start <= currentAmount && currentAmount <= (item.end || Infinity))!
if(index != -1) {
this.currentLvlPeriod = this.lvlPeriods[index];
this.currentLvl = index + 1;
this.purchaseData.$loading = false;
}
});
},
});
}
getNextLevel(): lvlPeriod {
if(this.currentLvl == this.lvlPeriods.length) {
return lvlPeriods[this.currentLvl - 1];
}
return lvlPeriods[this.currentLvl];
}
qrCodeClick() {
this.isQrCodeClicked = !this.isQrCodeClicked;
this.qrCodeSize = this.isQrCodeClicked ? 180 : 85;
}
deleteToken(): void {
this.cookiesService.logout();
}
logout() {
const bottomSheet = this._bottomSheet.open(ExitComponent);
bottomSheet.afterDismissed().subscribe({
next: (val) => {
if (val) {
this.deleteToken();
this.router.navigate(['/login']);
}
},
});
}
getPurchases(
start: Date | Moment = moment().subtract(1, 'months').startOf('month'),
end: Date | Moment = moment()
): Observable<any> {
const token = this.cookiesService.getItem('token');
const delta = moment(end).diff(moment(start), 'days');
return this.wpJsonService.getTransactions(
environment.systemId,
token ?? '',
environment.icardProxy
);
}
getCurrentQuarterOfYear() {
const quarters = [
[
moment().subtract(1, 'years').endOf('year').subtract(3, 'months'),
moment().subtract(1, 'years').endOf('year'),
],
[moment().startOf('year'), moment().startOf('year').add(3, 'months')],
[
moment().startOf('year').add(3, 'months'),
moment().startOf('year').add(6, 'months'),
],
[
moment().startOf('year').add(6, 'months'),
moment().startOf('year').add(9, 'months'),
],
[
moment().startOf('year').add(9, 'months'),
moment().startOf('year').add(12, 'months'),
],
];
for (let i = 0; i < 4; i++) {
if (moment().isBetween(quarters[i][0], quarters[i][1])) {
this.purchaseData.lastPeriod = quarters[i - 1];
this.purchaseData.currentPeriod = quarters[i];
}
}
}
getBalanceAmount(loyaltyPrograms: any[]) {
return loyaltyPrograms.reduce((accumulator, currentValue) => {
return accumulator + currentValue.balance;
}, 0);
}
}

View File

@ -0,0 +1,107 @@
<app-navbar
*ngIf="phoneForm.value.phone && !isShowNumber"
[title]="phoneForm.value.phone"
(backEvent)="backToPhoneForm()"
[ngStyle]="{
position: 'absolute',
top: 0
}"
></app-navbar>
<h1>Участвуй в программе лояльности COFFEE LIKE</h1>
<p class="description">Начни получать бонусы прямо сейчас</p>
<form
*ngIf="isShowNumber; else smsCode"
(ngSubmit)="submitNumber()"
[formGroup]="phoneForm"
>
<!-- <div class="input-container"> -->
<mat-form-field appearance="outline">
<mat-label>Ваше имя</mat-label>
<input formControlName="name" matInput type="text" />
</mat-form-field>
<!-- </div> -->
<div class="input-container">
<!-- <label for="number">Номер телефона</label>
<input id="number" type="text" placeholder="Введите номер" /> -->
<mat-form-field appearance="outline">
<mat-label>Номер телефона</mat-label>
<ngx-mat-intl-tel-input
formControlName="phone"
[enablePlaceholder]="true"
[enableSearch]="false"
[onlyCountries]="['ru', 'kg', 'by', 'kz', 'fi', 'de']"
[preferredCountries]="['ru']"
name="phone"
#phone
>
</ngx-mat-intl-tel-input>
</mat-form-field>
</div>
<p class="offer">
Используя приложение, вы принимаете условия в <span>соглашениях</span> и
соглашаетесь на получение рекламно-информационных сообщений
</p>
<button [disabled]="phoneForm.invalid">Принять участие</button>
</form>
<ng-template #smsCode>
<h2>Введите код из SMS</h2>
<form class="code-form" [formGroup]="codeForm" (ngSubmit)="submitCode()">
<div class="inputs-container">
<label class="box"
><input
class="field"
id="field"
type="tel"
placeholder="•"
#field
[appFocusNextInput]="inputFocusEmitter"
formControlName="code"
maxlength="1"
/></label>
<label class="box"
><input
class="field"
id="field1"
type="tel"
placeholder="•"
#field1
[appFocusNextInput]="inputFocusEmitter"
formControlName="code1"
maxlength="1"
/></label>
<label class="box"
><input
class="field"
id="field2"
type="tel"
placeholder="•"
#field2
[appFocusNextInput]="inputFocusEmitter"
formControlName="code2"
maxlength="1"
/></label>
<label class="box"
><input
class="field"
id="field3"
type="tel"
placeholder="•"
#field3
[appFocusNextInput]="inputFocusEmitter"
formControlName="code3"
maxlength="1"
/></label>
</div>
<button>Войти</button>
</form>
<p class="resend-code">
Не пришло SMS?<br />
<ng-container *ngIf="timeLeft">
Отправим повторно через {{timeLeft}}с
</ng-container>
<ng-container *ngIf="!timeLeft">
<span class="resend" (click)="submitNumber()">Отправить повторно</span>
</ng-container>
</p>
</ng-template>

View File

@ -0,0 +1,214 @@
:host {
padding-top: 48px;
display: flex;
flex-direction: column;
align-items: center;
max-width: 600px;
margin: 0 auto 52px;
h1 {
margin-top: 20px;
width: 302px;
font-style: normal;
font-weight: 700;
font-size: 17px;
line-height: 22px;
text-align: center;
letter-spacing: -0.408px;
}
h2 {
margin-top: 44px;
font-style: normal;
font-weight: 700;
font-size: 20px;
line-height: 24px;
text-align: center;
letter-spacing: 0.38px;
}
.description {
width: 180px;
font-style: normal;
font-weight: 400;
font-size: 15px;
line-height: 20px;
text-align: center;
letter-spacing: -0.24px;
margin-top: 16px;
}
form {
display: flex;
flex-direction: column;
align-items: center;
margin-top: 35px;
.offer {
margin-top: 10px;
padding: 0 16px;
font-family: "Gowun Dodum";
font-style: normal;
font-weight: 400;
font-size: 12px;
line-height: 17px;
text-align: center;
span {
color: #13a538;
}
}
.input-container {
position: relative;
width: 100%;
label {
font-style: normal;
font-weight: 400;
font-size: 12px;
line-height: 16px;
color: #6a737c;
text-align: left;
position: absolute;
top: 10px;
left: 16px;
}
input {
width: 100%;
padding: 24px 16px 8px;
background-color: #252323;
margin-bottom: 16px;
border: none;
border-top: solid #6a737c 1px;
border-bottom: solid #6a737c 1px;
color: #6a737c;
font-style: normal;
font-weight: 400;
font-size: 22px;
line-height: 28px;
}
}
button {
height: 46px;
width: calc(100% - 32px);
padding: 10px 24px;
background: #28af49;
border-radius: 6px;
display: flex;
flex-direction: row;
justify-content: center;
align-items: center;
border: none;
letter-spacing: -0.408px;
color: #ffffff;
font-style: normal;
font-weight: 700;
font-size: 17px;
line-height: 22px;
&:disabled {
background-color: #344a3a;
}
}
}
.code-form {
width: 100%;
// input {
// width: 100%;
// padding: 24px 16px 8px;
// background-color: #252323;
// margin-bottom: 16px;
// border: none;
// border-top: solid #6a737c 1px;
// border-bottom: solid #6a737c 1px;
// color: #6a737c;
// font-style: normal;
// font-weight: 400;
// font-size: 22px;
// line-height: 28px;
// }
.inputs-container {
width: 102px;
display: flex;
flex-direction: row;
justify-content: space-between;
margin-bottom: 40px;
// code
.box {
position: relative;
display: flex;
align-items: center;
justify-content: center;
height: 42px;
width: 42px;
border-radius: 6px;
// box-shadow: 0 0 6px 1px hsla(240, 54%, 61%, 0.2);
overflow: hidden;
will-change: transform;
}
.box:focus-within {
box-shadow: 0 0 6px 1px rgba($color: #28af49, $alpha: 0.2),
0 0 0 2px rgba($color: #28af49, $alpha: 0.6);
}
.box::before,
.box::after {
content: "";
position: absolute;
height: 100%;
width: 100%;
top: 0;
left: 0;
border-radius: 6px;
overflow: hidden;
}
.box::before {
// background: hsl(240, 54%, 97%);
z-index: 1;
transition: background-color 450ms cubic-bezier(0.25, 0.01, 0.25, 1);
}
.box::after {
transform: translateY(100%);
background-color: hsl(145, 0%, 42%);
opacity: 0;
z-index: 10;
transition: transform 450ms cubic-bezier(0.25, 0.01, 0.25, 1),
opacity 450ms cubic-bezier(0.25, 0.01, 0.25, 1),
background-color 450ms cubic-bezier(0.25, 0.01, 0.25, 1);
}
.field {
position: relative;
border: 0;
outline: 0;
font-size: 25.21px;
line-height: 42px;
color: #fff;
background-color: transparent;
text-align: center;
z-index: 100;
}
.field::placeholder {
color: #fff;
}
}
}
.resend-code {
font-style: normal;
font-weight: 400;
font-size: 12px;
line-height: 16px;
text-align: center;
margin-top: 23px;
.resend {
cursor: pointer;
}
}
}
mat-form-field {
width: 100%;
}

View File

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

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