Merge branch 'coffee-like-test' into coffee-like

This commit is contained in:
nikolay 2023-07-06 10:58:20 +04:00
commit 97ce3c9e55
54 changed files with 8025 additions and 5978 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

View File

@ -4,7 +4,7 @@
"version": 1,
"newProjectRoot": "projects",
"projects": {
"coffee-like-test": {
"coffee-like": {
"projectType": "application",
"schematics": {
"@schematics/angular:component": {
@ -35,7 +35,7 @@
"src/sw-custom.js"
],
"styles": [
"./node_modules/@angular/material/prebuilt-themes/purple-green.css",
"./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",
@ -84,10 +84,10 @@
"builder": "@angular-devkit/build-angular:dev-server",
"configurations": {
"production": {
"browserTarget": "coffee-like-test:build:production"
"browserTarget": "coffee-like:build:production"
},
"development": {
"browserTarget": "coffee-like-test:build:development",
"browserTarget": "coffee-like:build:development",
"proxyConfig": "proxy.confi.json"
}
},
@ -96,7 +96,7 @@
"extract-i18n": {
"builder": "@angular-devkit/build-angular:extract-i18n",
"options": {
"browserTarget": "coffee-like-test:build"
"browserTarget": "coffee-like:build"
}
},
"test": {

11448
angular/package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -1,5 +1,5 @@
{
"name": "coffee-like-test",
"name": "coffee-like",
"version": "0.0.2",
"scripts": {
"ng": "ng",
@ -10,43 +10,44 @@
},
"private": true,
"dependencies": {
"@angular/animations": "^14.0.0",
"@angular/cdk": "^14.2.1",
"@angular/common": "^14.0.0",
"@angular/compiler": "^14.0.0",
"@angular/core": "^14.0.0",
"@angular/fire": "^7.4.1",
"@angular/forms": "^14.0.0",
"@angular/material": "^14.2.1",
"@angular/platform-browser": "^14.0.0",
"@angular/platform-browser-dynamic": "^14.0.0",
"@angular/router": "^14.0.0",
"@angular/service-worker": "^14.0.0",
"@fortawesome/angular-fontawesome": "^0.11.1",
"@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/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",
"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": "^7.0.0",
"ng-qrcode": "^8.0.1",
"ngx-mat-intl-tel-input": "^5.0.0",
"ngx-sharebuttons": "^11.0.0",
"primeicons": "^5.0.0",
"primeng": "^14.0.1",
"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": "^14.0.6",
"@angular/cli": "~14.0.6",
"@angular/compiler-cli": "^14.0.0",
"@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",
@ -55,6 +56,6 @@
"karma-coverage": "~2.2.0",
"karma-jasmine": "~5.0.0",
"karma-jasmine-html-reporter": "~1.7.0",
"typescript": "~4.7.2"
"typescript": "4.8"
}
}

View File

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

View File

@ -1,8 +1,8 @@
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';
import { lastValueFrom } from 'rxjs';
@Component({
selector: 'app-root',
@ -10,12 +10,13 @@ import { lastValueFrom } from 'rxjs';
styleUrls: ['./app.component.scss'],
})
export class AppComponent implements OnInit {
title = 'Coffee Like Test';
title = 'Coffee Like';
constructor(
private router: Router,
private cookiesService: CookiesService,
private jsonRpcService: JsonrpcService
private jsonRpcService: JsonrpcService,
private authService: AuthService,
) {
this.router.events.subscribe((x) => {
if (x instanceof RouteConfigLoadStart && x.route.path === '') {
@ -48,6 +49,9 @@ export class AppComponent implements OnInit {
}
ngOnInit(): void {
if (this.authService.authorized) {
this.authService.getUserInfo();
}
this.getAdditionalInfo().subscribe({
next: (value) => {
this.cookiesService.setCookie('presentation-option', value?.data?.interface_id || 'default')

View File

@ -1,4 +1,4 @@
import {OrderStatus, Page, PageCode, lvlPeriod} from "./interface/data";
import { OrderStatus, Page, PageCode, lvlPeriod } from "./interface/data";
export const PageList: Page[] = [
{

View File

@ -16,7 +16,6 @@ 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 { OrdersComponent } from './pages/account/orders/orders.component';
import { OrderInfoComponent } from './components/order-info/order-info.component';
import { ServiceWorkerModule } from '@angular/service-worker';
import { environment } from '../environments/environment';
@ -49,7 +48,7 @@ import {
MatBottomSheetModule,
MatBottomSheetRef,
} from '@angular/material/bottom-sheet';
import {MatProgressSpinnerModule} from '@angular/material/progress-spinner';
import { MatProgressSpinnerModule } from '@angular/material/progress-spinner';
import { DirectivesModule } from './directives/directives.module';
@NgModule({
@ -62,7 +61,6 @@ import { DirectivesModule } from './directives/directives.module';
AccountComponent,
ExitComponent,
BonusProgramComponent,
OrdersComponent,
OrderInfoComponent,
FooterButtonsComponent,
UserDataComponent,
@ -120,4 +118,4 @@ import { DirectivesModule } from './directives/directives.module';
],
bootstrap: [AppComponent],
})
export class AppModule {}
export class AppModule { }

View File

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

View File

@ -5,8 +5,9 @@ import {
OnInit,
Renderer2,
} from '@angular/core';
import { MatSnackBar } from '@angular/material/snack-bar';
import { MessageService } from 'primeng/api';
import { getTypeDevice, pwaInstalled } from 'src/app/utils';
@Directive({
selector: '[appDownloadApp]',
@ -19,14 +20,21 @@ export class DownloadAppDirective implements OnInit {
private messageService: MessageService,
public renderer: Renderer2,
private el: ElementRef,
private _snackBar: MatSnackBar,
) {}
) { }
ngOnInit(): void {
this.getTypeDevice();
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'])
@ -47,41 +55,27 @@ export class DownloadAppDirective implements OnInit {
this.el.nativeElement.style.display = 'none';
}
getTypeDevice() {
const userAgent = window.navigator.userAgent.toLowerCase();
const ios = /iphone|ipod|ipad/.test(userAgent);
this.deviceType = ios ? 'ios' : 'android';
}
@HostListener('click', ['$event'])
downloadApp(event: MouseEvent) {
if (event) {
event.preventDefault();
}
if (this.deviceType === 'ios') {
this._snackBar.open(`Для установки нажмите на кнопку поделиться в Вашем браузере и выберите пункт 'На экран «Домой»'`, '', {
duration: 5000,
});
return;
}
async downloadApp(event: MouseEvent) {
if (!this.deferredPrompt) {
this.messageService.clear();
this.messageService.add({
severity: 'error',
summary: 'Не поддерживается в Вашем браузере!',
});
this._snackBar.open('Не поддерживается в Вашем браузере!', '', {
duration: 3000,
});
return;
}
this.deferredPrompt.prompt();
this.deferredPrompt.userChoice.then((res: any) => {
if (res.outcome === 'accepted') {
this._snackBar.open('Спасибо за установку!', '', {
duration: 3000,
this.messageService.clear();
this.messageService.add({
severity: 'success',
summary: 'Спасибо за установку!',
});
}
this.deferredPrompt = null;
});
}
}

View File

@ -9,10 +9,16 @@ export class FocusNextInputDirective {
constructor(private renderer: Renderer2) { }
ngOnInit() {
// TODO:
// 1: don't need to listen all events
// 2: not working in safari
this.eventEmitter.subscribe(elementId => {
try {
this.renderer.selectRootElement(elementId).focus();
this.renderer.selectRootElement(elementId).click();
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

@ -1,17 +0,0 @@
import { AfterViewInit, Directive } from '@angular/core';
import { MatFormField } from '@angular/material/form-field';
// TODO: this is temporary workaround, upgrade angular to 15+, where input labels fork fine
@Directive({
selector: 'mat-form-field[appearance=outline]',
})
export class UpdateOutlineGapDirective implements AfterViewInit {
constructor(private formField: MatFormField) {
}
ngAfterViewInit() {
document.fonts.ready.then(() => {
this.formField.updateOutlineGap();
});
}
}

View File

@ -6,7 +6,7 @@ export enum PageCode {
UserData,
}
export interface Moment extends moment.Moment {}
export interface Moment extends moment.Moment { }
export interface Page {
code: PageCode;
@ -179,3 +179,52 @@ export interface UserData {
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 CurrentInfo {
current_cashback: number;
current_level: number;
}
export interface LastPurchase {
last_purchase_date: string;
last_purchase_sum: number;
}
export interface NextLevel {
cashback: number;
next_level: number;
sum_for_next_level: number;
}
export interface UserInfo {
current_level_and_cashback?: CurrentInfo;
last_purchase?: LastPurchase;
id: string;
phone: string;
walletBalances: number;
next_level: NextLevel;
}
export interface ResponseError {
code: number;
msg: string;
}

View File

@ -53,12 +53,14 @@ export class RefSystemComponent implements OnInit {
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

@ -38,9 +38,9 @@ export class LoginComponent implements OnInit, AfterViewInit {
private jsonrpc: JsonrpcService,
private messageService: MessageService,
private _snackBar: MatSnackBar
) {}
) { }
ngOnInit(): void {}
ngOnInit(): void { }
ngAfterViewInit() {
setTimeout(() => {
@ -85,6 +85,7 @@ export class LoginComponent implements OnInit, AfterViewInit {
const data = this.phoneForm.value;
this.isShowNumber = false;
if (this.timeLeft) {
this.messageService.clear();
this.messageService.add({
severity: 'custom',
summary: `Отправить повторно можно через ${this.timeLeft}с`,
@ -104,12 +105,12 @@ export class LoginComponent implements OnInit, AfterViewInit {
if (result.code === 0) {
this.timeLeft = 60;
const interval = setInterval(() => {
if(this.timeLeft > 0) {
if (this.timeLeft > 0) {
this.timeLeft--;
} else {
clearInterval(interval);
}
},1000)
}, 1000)
}
this.isShowNumber = false;
},

View File

@ -61,6 +61,7 @@ export class MainComponent implements OnInit {
const userAgent = window.navigator.userAgent.toLowerCase();
const ios = /iphone|ipod|ipad/.test(userAgent);
if (ios) {
this.messageService.clear();
this.messageService.add({
severity: 'custom',
summary: `Чтобы получать уведомления, добавьте карту в Apple Wallet`,

View File

@ -1,22 +1,22 @@
<h2>Ваш предыдущий заказ</h2>
<div class="info-order">
<ng-container *ngIf="lastOrder">
<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>
<span *ngIf="!loading">{{lastOrder!.last_purchase_date}}</span>
</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>
<span *ngIf="!loading">{{lastOrder?.last_purchase_sum}}</span>
</p>
</ng-container>
<ng-container *ngIf="!lastOrder">
<p class="flex">
<span>Данные не найдены</span>
</p>
</ng-container>
</div>
<a href="https://promo.coffee-like.com/?utm_medium=prilozhenie">
<img src="./assets/970х250_3.png" alt="" width="100%" />
</a>
<a href="https://yandex.ru/profile/151770398186" target="_blank">
<button class="evaluate-order">Оценить заказ</button>
</a>

View File

@ -2,7 +2,6 @@
padding: 24px 16px 0px;
& > h2 {
font-family: Montserrat;
font-style: normal;
font-weight: 700;
font-size: 15px;

View File

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

View File

@ -0,0 +1,3 @@
<li class="menu-item">
<ng-content></ng-content>
</li>

View File

@ -0,0 +1,20 @@
.menu-item {
display: flex;
gap: 10px;
align-items: center;
padding: 10px;
cursor: pointer;
transition: background-color 0.1s;
&:hover {
background-color: var(--main-color_hover);
}
.title {
color: var(--text-color);
}
.icon {
}
}

View File

@ -0,0 +1,18 @@
import { Component, Input, HostListener, } from "@angular/core";
@Component({
selector: 'menu-item',
templateUrl: './menu_item.component.html',
styleUrls: ['./menu_item.component.scss'],
})
export class MenuItemComponent {
@Input()
handler?: (e: any) => void;
@HostListener('click', ['$event'])
onClick(e: any) {
if (this.handler) {
this.handler(e);
}
}
}

View File

@ -1,6 +1,51 @@
<div class="container">
<mat-icon style="cursor: pointer;" aria-hidden="false" aria-label="Назад" fontIcon="arrow_back_ios" class="back-arrow" (click)="back()"></mat-icon>
<div class="block">
<h1 class="title">{{title}}</h1>
</div>
<div class="block">
<div class="back">
<mat-icon *ngIf="backEvent" style="cursor: pointer;" aria-hidden="false" aria-label="Назад" fontIcon="arrow_back_ios" class="back-arrow" (click)="back()"></mat-icon>
</div>
<!-- <div class="plug"></div> -->
<div *ngIf="showDropdown" class="backdrop" (click)="closeMenu()"></div>
<div *ngIf="showMenu" class="menu">
<button mat-button (click)="toggleMenu()">
<span class="material-icons" style="color: white !important">
more_horiz
</span>
</button>
<div class="menu__dropdown" *ngIf="showDropdown">
<ul>
<menu-item [handler]="handler(aboutApp)">
<span class="material-icons icon" style="color: var(--main-color)">
info
</span>
<span class="item_title">О приложении</span>
</menu-item>
<menu-item [handler]="handler(addToWallet)">
<img src="./assets/apple_wallet.svg" class="icon" width="24" height="24" />
<span class="item_title">Добавить карточку в Wallet</span>
</menu-item>
<menu-item [handler]="handler(logout)">
<span class="material-icons" style="color: var(--main-color)">
logout
</span>
<span class="item_title">Выйти</span>
</menu-item>
</ul>
</div>
</div>
<div class="wrapper" *ngIf="!showMenu">
<button
mat-button
*ngIf="!messagingService.checkRequestPermission()"
(click)="messagingService.requestPermission()"
class="notification"
>
<img src="./assets/notification.svg" alt="notification" />
</button>
<button mat-stroked-button appDownloadApp>Установить</button>
</div>
</div>
</div>

View File

@ -1,18 +1,33 @@
:host {
width: 100%;
.notification {
img {
width: 28px;
height: 28px;
}
}
}
.container {
max-width: 600px;
margin: 0 auto;
box-sizing: border-box;
padding: 12px 16px;
width: 100%;
background: var(--button-color);
color: var(--button-text-color);
display: grid;
display: flex;
flex-direction: column;
gap: 10px;
.block {
display: flex;
justify-content: space-between;
grid-template-columns: auto auto auto;
justify-items: center;
align-items: center;
}
.back {
width: 40px;
}
.back-arrow {
font-size: 16px;
display: flex;
@ -25,10 +40,44 @@
visibility: hidden;
}
.title {
font-family: "Montserrat", sans-serif;
font-weight: 700;
font-size: 17px;
line-height: 22px;
margin: 0;
margin: 0 auto;
}
.wrapper {
display: flex;
align-items: center;
gap: 10px;
min-width: 40px;
}
.menu {
position: relative;
.menu__dropdown {
z-index: 110;
position: fixed;
background-color: white;
width: fit-content;
height: fit-content;
right: 20px;
border-radius: 6px;
box-shadow: 0px 2px 5px -3px black;
.item_title {
color: var(--text-color);
font-size: 14px;
font-style: normal;
}
}
}
.backdrop {
z-index: 100;
position: absolute;
width: 100vw;
left: 0;
top: 0;
height: 100vh;
}
}

View File

@ -1,4 +1,11 @@
import { Component, EventEmitter, Input, OnInit, Output } from '@angular/core';
import { Component, Input, OnInit } from '@angular/core';
import { MessagingService } from 'src/app/services/messaging.service';
import { getTypeDevice, DeviceType } from 'src/app/utils';
import { AppleWalletService } from 'src/app/services/apple-wallet.service';
import { CookiesService } from 'src/app/services/cookies.service';
import { MatBottomSheet } from '@angular/material/bottom-sheet';
import { ExitComponent } from 'src/app/components/exit/exit.component';
import { AuthService } from 'src/app/services/auth.service';
@Component({
selector: 'app-navbar[title]',
@ -7,15 +14,69 @@ import { Component, EventEmitter, Input, OnInit, Output } from '@angular/core';
})
export class NavbarComponent implements OnInit {
@Input() title: string = 'Название не задано'
@Output() backEvent = new EventEmitter<null>();
@Input() backEvent?: () => void;
constructor() { }
showMenu: boolean = false;
showDropdown: boolean = false;
constructor(
public messagingService: MessagingService,
private appleWalletService: AppleWalletService,
private cookiesService: CookiesService,
private _bottomSheet: MatBottomSheet,
private authService: AuthService,
) { }
handler(cb: () => void): (e: any) => void {
return (e: any) => {
cb();
this.closeMenu();
}
}
aboutApp() { }
ngOnInit(): void {
const deviceType = getTypeDevice();
this.showMenu = deviceType === DeviceType.ios;
}
toggleMenu() {
this.showDropdown = !this.showDropdown;
}
openMenu() {
this.showDropdown = true;
}
closeMenu = () => {
this.showDropdown = false;
}
back() {
this.backEvent.emit(null)
if (this.backEvent) {
this.backEvent();
}
}
addToWallet = () => {
this.appleWalletService.addCardToWallet();
}
deleteToken = (): void => {
this.cookiesService.logout();
}
logout = () => {
const bottomSheet = this._bottomSheet.open(ExitComponent);
bottomSheet.afterDismissed().subscribe({
next: (val) => {
if (val) {
this.authService.logout();
}
},
});
}
}

View File

@ -15,7 +15,7 @@ export class SocialMediaButtonsComponent implements OnInit {
public links: ISocialMediaLink[] = [
{
label: 'Инстаграм',
url: 'https://www.instagram.com/',
url: 'https://instagram.com/coffeelike_com?igshid=MzRlODBiNWFlZA==',
imgUrl: '/assets/social-media-icons/instagram.svg'
},
{

View File

@ -1,6 +1,5 @@
import { NgModule } from '@angular/core';
import { RouterModule, Routes } from '@angular/router';
import { IndexComponent } from './components/index/index.component';
import { GuestCardComponent } from './pages/guest-card/guest-card.component';
import { AuthGuard } from 'src/app/guards/auth-guard.guard';
import { LoginComponent } from './pages/login/login.component';
@ -26,4 +25,4 @@ const routes: Routes = [
imports: [RouterModule.forChild(routes)],
exports: [RouterModule],
})
export class DefaultOptionRoutingModule {}
export class DefaultOptionRoutingModule { }

View File

@ -5,6 +5,7 @@ import { IndexComponent } from './components/index/index.component';
import { FooterComponent } from './components/footer/footer.component';
import { SocialMediaButtonsComponent } from './components/social-media-buttons/social-media-buttons.component';
import { NavbarComponent } from './components/navbar/navbar.component';
import { MenuItemComponent } from './components/navbar/menu_item.component';
import { MatIconModule } from '@angular/material/icon';
import { GuestCardComponent } from './pages/guest-card/guest-card.component';
import { QrCodeModule } from 'ng-qrcode';
@ -19,7 +20,9 @@ import { NgxMatIntlTelInputComponent } from 'ngx-mat-intl-tel-input';
import { MatInputModule } from '@angular/material/input';
import { DirectivesModule } from 'src/app/directives/directives.module';
import { LoyalityProgramComponent } from './pages/loyality-program/loyality-program.component';
import {MatButtonModule} from '@angular/material/button';
import { MatButtonModule } from '@angular/material/button';
import { ToastModule } from 'primeng/toast';
@NgModule({
declarations: [
@ -32,9 +35,11 @@ import {MatButtonModule} from '@angular/material/button';
LastOrderComponent,
InviteFriendsComponent,
LoginComponent,
LoyalityProgramComponent
LoyalityProgramComponent,
MenuItemComponent,
],
imports: [
ToastModule,
CommonModule,
DefaultOptionRoutingModule,
MatIconModule,
@ -50,4 +55,4 @@ import {MatButtonModule} from '@angular/material/button';
],
bootstrap: [IndexComponent],
})
export class DefaultOptionModule {}
export class DefaultOptionModule { }

View File

@ -1,92 +1,44 @@
<app-navbar title="Карта гостя" (backEvent)="logout()"></app-navbar>
<ng-container *ngIf="!this.loyaltyProgram.purchaseData.$loading && customerInfo">
<app-navbar title="Карта гостя" [backEvent]="showBack ? logout : undefined"></app-navbar>
<ng-container *ngIf="!authService.loading && authService.userInfo">
<div class="guest-card">
<div class="top-info">
<div class="top-info__level">
<p id="level">Уровень {{ loyaltyProgram.currentLvl }}</p>
<p id="level-percent">Кэшбек {{ loyaltyProgram.currentLvlPeriod.percent }}%</p>
<p id="level">Уровень {{ authService.userInfo.current_level_and_cashback?.current_level ?? '--' }}</p>
<p id="level-percent">Кэшбек {{ authService.currentLvlPeriod.percent }}%</p>
</div>
<p class="top-info__bonus">
{{ Math.floor(loyaltyProgram.getBalanceAmount(customerInfo?.walletBalances)) }}
{{ Math.floor(authService.userInfo.walletBalances) }}
бонусов
</p>
</div>
<div class="guest-card__qr" (click)="qrCodeClick()">
<ng-container *ngIf="customerInfo">
<qr-code
[value]="customerInfo?.phone?.substr(1) || 'Данные не найдены'"
[value]="authService.userInfo?.phone?.substr(1) || 'Данные не найдены'"
[margin]="0"
[size]="qrCodeSize"
errorCorrectionLevel="H"
></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="loyaltyProgram.currentLvlPeriod.end">
Осталось купить на сумму
<span class="price">{{
loyaltyProgram.currentLvlPeriod.end -
(loyaltyProgram.purchaseData.currentAmount || 0) +
1
authService.userInfo.next_level.sum_for_next_level
}}</span>
рублей, тогда кэшбек будет
<span class="percent"
>{{ loyaltyProgram.getNextLevel().percent }}%</span
>
<span class="percent">{{ authService.userInfo.next_level.cashback }}%</span>
с
{{
loyaltyProgram.purchaseData.currentPeriod[1]
authService.currentPeriod[1]
.locale("ru")
.format("D MMMM")
}}
</ng-container>
<ng-container *ngIf="!loyaltyProgram.currentLvlPeriod.end">
У Вас последний уровень бонусной программы. Процент начисляемых
бонусов:
{{ loyaltyProgram.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]="loyaltyProgram.purchaseData.$loading"
[lastOrder]="authService.userInfo?.last_purchase"
[loading]="authService.loading"
></app-last-order>
<a class="guest-card__loyalty-program" routerLink="loyality-program"
>Подробнее о правилах <br />
@ -95,12 +47,12 @@
</div>
<app-footer></app-footer>
</ng-container>
<ng-container *ngIf="!customerInfo && !loyaltyProgram.purchaseData.$loading">
<ng-container *ngIf="!authService.loading && !authService.userInfo">
<div class="not-found">
<p>Данные недоступны, попробуйте позже</p>
</div>
</ng-container>
<ng-container *ngIf="loyaltyProgram.purchaseData.$loading">
<ng-container *ngIf="authService.loading">
<ng-container
*ngTemplateOutlet="spinner; context: { $implicit: 85 }"
></ng-container>

View File

@ -104,7 +104,6 @@
h2 {
font-style: normal;
font-family: Montserrat;
font-weight: 700;
font-size: 17px;
line-height: 22px;

View File

@ -1,16 +1,11 @@
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 { Moment, Purchase, lvlPeriod } from 'src/app/interface/data';
import { lvlPeriods } from 'src/app/app.constants';
import { LoyaltyProgramService } from 'src/app/services/loyalty-program.service';
import { MessageService } from 'primeng/api';
import { MessagingService } from 'src/app/services/messaging.service';
import { DeviceType, getTypeDevice } from 'src/app/utils';
import { AuthService } from 'src/app/services/auth.service';
@Component({
selector: 'app-guest-card',
@ -20,46 +15,41 @@ import { LoyaltyProgramService } from 'src/app/services/loyalty-program.service'
export class GuestCardComponent implements OnInit {
public qrCodeSize: number = 85;
private isQrCodeClicked: boolean = false;
public customerInfo!: any;
public purchases!: Purchase[];
public lastPurchase!: Purchase;
public Math: Math = Math;
public showBack: boolean = false;
constructor(
private _bottomSheet: MatBottomSheet,
public cookiesService: CookiesService,
private router: Router,
private wpJsonService: WpJsonService,
public loyaltyProgram: LoyaltyProgramService
) {}
private messagingService: MessagingService,
private messageService: MessageService,
public authService: AuthService,
) { }
ngOnInit(): void {
const token = this.cookiesService.getItem('token');
this.loyaltyProgram.purchaseData.$loading = true;
this.wpJsonService
.getCustomerInfo(
environment.systemId,
token || '',
environment.icardProxy
)
.subscribe({
next: (value) => {
this.customerInfo = value.customer_info;
this.cookiesService.setCookie('phone-number', this.customerInfo?.phone?.substr(2))
this.getPurchases().subscribe((value) => {
this.purchases = this.loyaltyProgram.filterPurchases(value[this.customerInfo?.id])
this.lastPurchase = this.loyaltyProgram.getLastPurchase(this.purchases)
this.showBack = getTypeDevice() === DeviceType.android;
this.loyaltyProgram.setLastPurchases(this.purchases)
this.loyaltyProgram.setCurrentPurchases(this.purchases)
this.requestPermission();
}
const currentAmount = this.loyaltyProgram.purchaseData.currentAmount || 0
this.loyaltyProgram.setCurrentLvl(currentAmount)
// this.currentLvlPeriod = this.lvlPeriods.find((item) => item.start <= currentAmount && currentAmount <= (item.end || Infinity))!
this.loyaltyProgram.purchaseData.$loading = false;
});
},
requestPermission() {
const userAgent = window.navigator.userAgent.toLowerCase();
const ios = /iphone|ipod|ipad/.test(userAgent);
if (ios) {
this.messageService.clear();
this.messageService.add({
severity: 'custom',
summary: `Чтобы получать уведомления, добавьте карту в Apple Wallet`,
life: 5000,
});
// var permissionData = window.safari.pushNotification.permission('web.com.example.domain');
// $scope.checkRemotePermission(permissionData);
} else {
//FIREBASE HERE
this.messagingService.requestPermission();
// this.message = this.messagingService.currentMessage;
}
}
qrCodeClick() {
@ -71,29 +61,14 @@ export class GuestCardComponent implements OnInit {
this.cookiesService.logout();
}
logout() {
logout = () => {
const bottomSheet = this._bottomSheet.open(ExitComponent);
bottomSheet.afterDismissed().subscribe({
next: (val) => {
if (val) {
this.deleteToken();
this.router.navigate(['/login']);
this.authService.logout();
}
},
});
}
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
);
}
}

View File

@ -1,11 +1,7 @@
<app-navbar
*ngIf="phoneForm.value.phone && !isShowNumber"
[title]="phoneForm.value.phone"
(backEvent)="backToPhoneForm()"
[ngStyle]="{
position: 'absolute',
top: 0
}"
[backEvent]="backToPhoneForm"
></app-navbar>
<h1>Участвуй в программе лояльности COFFEE LIKE</h1>
<p class="description">Начни получать бонусы прямо сейчас</p>
@ -38,10 +34,10 @@
</mat-form-field>
</div>
<p class="offer">
Используя приложение, вы принимаете условия в <span>соглашениях</span> и
соглашаетесь на получение рекламно-информационных сообщений
Используя приложение, вы принимаете <a href="https://docs.google.com/document/d/1tOZyI9DKerQpMY_N-hMExMB15j2F98E3VDqwIVMkk4I">условия</a>
и соглашаетесь на получение рекламно-информационных сообщений
</p>
<button [disabled]="phoneForm.invalid">Принять участие</button>
<button>Принять участие</button>
</form>
<ng-template #smsCode>
@ -55,7 +51,6 @@
type="tel"
placeholder="•"
#field
[appFocusNextInput]="inputFocusEmitter"
formControlName="code"
maxlength="1"
/></label>
@ -66,7 +61,6 @@
type="tel"
placeholder="•"
#field1
[appFocusNextInput]="inputFocusEmitter"
formControlName="code1"
maxlength="1"
/></label>
@ -77,7 +71,6 @@
type="tel"
placeholder="•"
#field2
[appFocusNextInput]="inputFocusEmitter"
formControlName="code2"
maxlength="1"
/></label>
@ -88,7 +81,6 @@
type="tel"
placeholder="•"
#field3
[appFocusNextInput]="inputFocusEmitter"
formControlName="code3"
maxlength="1"
/></label>
@ -97,10 +89,10 @@
</form>
<p class="resend-code">
Не пришло SMS?<br />
<ng-container *ngIf="timeLeft">
Отправим повторно через {{timeLeft}}с
<ng-container *ngIf="authService.timeLeft">
Отправим повторно через {{authService.timeLeft}}с
</ng-container>
<ng-container *ngIf="!timeLeft">
<ng-container *ngIf="!authService.timeLeft">
<span class="resend" (click)="submitNumber()">Отправить повторно</span>
</ng-container>
</p>

View File

@ -1,10 +1,11 @@
:host {
padding-top: 48px;
display: flex;
flex-direction: column;
align-items: center;
max-width: 600px;
margin: 0 auto 52px;
padding-left: 20px;
padding-right: 20px;
h1 {
margin-top: 20px;
@ -47,13 +48,14 @@
.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 {
a {
text-decoration: none;
color: var(--button-color);
}
}

View File

@ -1,26 +1,24 @@
import {
AfterViewInit,
Component,
EventEmitter,
ElementRef,
HostListener,
OnInit,
ViewChild,
} from '@angular/core';
import { FormControl, FormGroup, Validators } from '@angular/forms';
import { MatSnackBar } from '@angular/material/snack-bar';
import { Router } from '@angular/router';
import { AuthService } from 'src/app/services/auth.service';
import { MessageService } from 'primeng/api';
import { CookiesService } from 'src/app/services/cookies.service';
import { JsonrpcService, RpcService } from 'src/app/services/jsonrpc.service';
@Component({
selector: 'app-login',
templateUrl: './login.component.html',
styleUrls: ['./login.component.scss'],
})
export class LoginComponent implements OnInit, AfterViewInit {
export class LoginComponent implements OnInit {
public isShowNumber: boolean = true;
public phoneForm = new FormGroup({
name: new FormControl('', []),
name: new FormControl('', [Validators.required]),
phone: new FormControl('', [Validators.required]),
});
public codeForm = new FormGroup({
@ -30,40 +28,34 @@ export class LoginComponent implements OnInit, AfterViewInit {
code3: new FormControl('', [Validators.required]),
});
private inputIds = ['field', 'field1', 'field2', 'field3'];
timeLeft: number = 0;
constructor(
private cookiesService: CookiesService,
public authService: AuthService,
private router: Router,
private jsonrpc: JsonrpcService,
private messageService: MessageService,
private _snackBar: MatSnackBar,
private jsonRpcService: JsonrpcService
) {}
) { }
ngOnInit(): void {}
ngOnInit(): void { }
ngAfterViewInit() {
setTimeout(() => {
this.inputFocusEmitter.emit(`#${this.inputIds[0]}`);
}, 1000);
}
@ViewChild('field', { static: false }) field!: ElementRef;
@ViewChild('field1', { static: false }) field1!: ElementRef;
@ViewChild('field2', { static: false }) field2!: ElementRef;
@ViewChild('field3', { static: false }) field3!: ElementRef;
public inputFocusEmitter = new EventEmitter<string>();
@HostListener('window:keyup', ['$event'])
@HostListener('window:input', ['$event'])
HandlKeyEvents(event: any) {
if (!event.target.classList.contains('field')) return;
const key = event.key.toLocaleLowerCase();
const key = event.data
let elementId = '';
switch (key) {
case 'backspace':
case null:
elementId = event.target.id;
event.target.value = '';
const prevInputIndex = this.inputIds.indexOf(elementId) - 1;
if (prevInputIndex >= 0) {
this.inputFocusEmitter.emit(`#${this.inputIds[prevInputIndex]}`);
// this.inputFocusEmitter.emit(`#${this.inputIds[prevInputIndex]}`);
this.focusFieldByIndex(prevInputIndex);
}
break;
@ -71,118 +63,72 @@ export class LoginComponent implements OnInit, AfterViewInit {
elementId = event.target.id;
const index = this.inputIds.indexOf(elementId);
const nextInputIndex = index + 1;
if (event.target.value.length > 1) {
event.target.value = event.target.value.slice(-1);
}
event.target.value = key;
if (nextInputIndex > 0 && nextInputIndex <= this.inputIds.length) {
this.inputFocusEmitter.emit(`#${this.inputIds[nextInputIndex]}`);
// this.inputFocusEmitter.emit(`#${this.inputIds[nextInputIndex]}`);
this.focusFieldByIndex(nextInputIndex);
}
if (nextInputIndex == this.inputIds.length) {
this.field3!.nativeElement.blur();
}
break;
}
}
focusFieldByIndex(i: number) {
setTimeout(() => {
switch (i) {
case 0:
this.field.nativeElement.focus();
this.field.nativeElement.click();
break;
case 1:
this.field1.nativeElement.focus();
this.field1.nativeElement.click();
break;
case 2:
this.field2.nativeElement.focus();
this.field2.nativeElement.click();
break;
case 3:
this.field3.nativeElement.focus();
this.field3.nativeElement.click();
break;
}
}, 0);
}
submitNumber() {
const data = this.phoneForm.value;
this.isShowNumber = false;
if (this.timeLeft) {
if (this.phoneForm.invalid) {
this.phoneForm.markAsTouched();
this.messageService.clear();
this.messageService.add({
severity: 'custom',
summary: `Отправить повторно можно через ${this.timeLeft}с`,
severity: 'error',
summary: 'Введите имя и телефон',
});
return;
}
this.jsonrpc
.rpc(
{
method: 'sendVerifyByPhone',
params: [data.phone],
},
RpcService.authService,
false
)
.subscribe({
next: (result) => {
if (result.code !== 0) {
this._snackBar.open('Произошла ошибка! Попробуйте позже', '', {
duration: 3000,
});
}
if (result.code === 0) {
this.timeLeft = 60;
const interval = setInterval(() => {
if (this.timeLeft > 0) {
this.timeLeft--;
} else {
clearInterval(interval);
}
}, 1000);
}
const data = this.phoneForm.value;
this.authService.sendVerifyByPhone(data.phone!);
this.isShowNumber = false;
},
error: (error) => {
console.error('Error: ', error);
},
});
setTimeout(() => {
this.inputFocusEmitter.emit(`#${this.inputIds[0]}`);
this.field.nativeElement.focus();
}, 0);
}
submitCode() {
const data = this.codeForm.value;
this.jsonrpc
.rpc(
{
method: 'getTokenByPhone',
params: [this.phoneForm.value.phone, Object.values(data).join('')],
},
RpcService.authService,
false
)
.subscribe({
next: (result) => {
if (result.code === 0) {
this.cookiesService.setCookie('token', result?.data?.token);
this.jsonRpcService.rpc(
{
method: 'updateAdditionalInfo',
params: [
{
first_name: this.phoneForm.value.name,
birth_day: '01.01.1999'
},
],
},
RpcService.authService,
true
).subscribe({
next: (value) => {
this.router.navigate(['/'], {
queryParams: {
token: result?.data?.token,
},
});
},
error: (err) => {
console.error(err);
const phoneData = this.phoneForm.value;
}
})
// this.phoneConfirmed.emit(null);
} else if (result.code === 230) {
this._snackBar.open('Неверный код!', '', {
duration: 3000,
});
// this.errorConfirmCode = true;
}
},
error: (error) => {
console.error(error);
},
});
this.authService.submitCode(
Object.values(data).join(''), phoneData.phone!, phoneData.name!);
}
backToPhoneForm() {
backToPhoneForm = () => {
this.codeForm.setValue({
code: '',
code1: '',

View File

@ -1,20 +1,20 @@
<app-navbar title="Программа лояльности" (backEvent)="goBack()"></app-navbar>
<app-navbar title="Программа лояльности" [backEvent]="goBack"></app-navbar>
<ng-container *ngIf="loyaltyProgram.currentLvlPeriod">
<div class="loyality-program">
<app-accordion header="Условия начисления бонусов">
<ng-container *ngIf="authService.currentLvlPeriod">
<div class="loyality-program">
<app-accordion header="Условия начисления бонусов">
<p>
Ваш текущий уровень {{ loyaltyProgram.currentLvl }},
поэтому вам начисляется {{ loyaltyProgram.currentLvlPeriod.percent }}% от суммы покупки.
Ваш текущий уровень {{ authService.userInfo?.current_level_and_cashback?.current_level ?? '--' }},
поэтому вам начисляется {{ authService.currentLvlPeriod.percent }}% от суммы покупки.
</p>
<p>
Смена уровня произойдет в начале следующего квартала,
{{ loyaltyProgram.purchaseData.currentPeriod[1]
{{ authService.currentPeriod[1]
.locale("ru")
.format("DD.MM.YY") }}.
</p>
</app-accordion>
<app-accordion header="Уровни бонусной программы">
</app-accordion>
<app-accordion header="Уровни бонусной программы">
<p>
Начисление Бонусных баллов происходит по дифференцированной шкале в
зависимости от уровня:
@ -42,9 +42,9 @@
</ul>
</ng-container>
</ng-container>
</app-accordion>
</app-accordion>
<app-accordion header="Условия «оплаты» покупки бонусами">
<app-accordion header="Условия «оплаты» покупки бонусами">
<p>
Участник может использовать Бонусы для «оплаты» до 100% стоимости любой
покупки.
@ -65,8 +65,8 @@
приложения.
</p>
<p>Полученные Бонусы не подлежат обмену на денежные средства.</p>
</app-accordion>
<app-accordion header="Особые условия">
</app-accordion>
<app-accordion header="Особые условия">
<p>
Начисленные на счет бонусы сгорают по прошествии 90 дней с момента
совершения последней покупки с начислением или списанием бонусов.
@ -97,9 +97,9 @@
товара при покупке, за вычетом суммы, оплаченной бонусами.
</li>
</ul>
</app-accordion>
</app-accordion>
</div>
</div>
<app-footer></app-footer>
<app-footer></app-footer>
</ng-container>

View File

@ -1,3 +1,4 @@
.loyality-program {
max-width: 600px;
margin: 0 auto;
}

View File

@ -1,29 +1,23 @@
import { Component, OnInit } from '@angular/core';
import { Component } from '@angular/core';
import { Location } from '@angular/common';
import { lvlPeriod } from 'src/app/interface/data';
import { lvlPeriods } from 'src/app/app.constants';
import { LoyaltyProgramService } from 'src/app/services/loyalty-program.service';
import { AuthService } from 'src/app/services/auth.service';
@Component({
selector: 'loyality-program',
templateUrl: './loyality-program.component.html',
styleUrls: ['./loyality-program.component.scss'],
})
export class LoyalityProgramComponent implements OnInit {
export class LoyalityProgramComponent {
constructor(
private _location: Location,
public loyaltyProgram: LoyaltyProgramService,
) {}
public authService: AuthService,
) { }
public lvlPeriods: lvlPeriod[] = lvlPeriods;
goBack() {
goBack = () => {
this._location.back();
}
ngOnInit(): void {
if(this.loyaltyProgram.currentLvlPeriod == null) {
this._location.back();
}
}
}

View File

@ -9,14 +9,14 @@ 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 { MatProgressSpinnerModule } from '@angular/material/progress-spinner';
import { MatLegacyProgressSpinnerModule as MatProgressSpinnerModule } from '@angular/material/legacy-progress-spinner';
import { LastOrderComponent } from './components/last-order/last-order.component';
import { InviteFriendsComponent } from './components/invite-friends/invite-friends.component';
import { LoginComponent } from './pages/login/login.component';
import { FormsModule, ReactiveFormsModule } from '@angular/forms';
import { MatFormFieldModule } from '@angular/material/form-field';
import { MatLegacyFormFieldModule as MatFormFieldModule } from '@angular/material/legacy-form-field';
import { NgxMatIntlTelInputComponent } from 'ngx-mat-intl-tel-input';
import { MatInputModule } from '@angular/material/input';
import { MatLegacyInputModule as MatInputModule } from '@angular/material/legacy-input';
import { DirectivesModule } from 'src/app/directives/directives.module';

View File

@ -6,7 +6,7 @@ import {
OnInit
} from '@angular/core';
import { FormControl, FormGroup, Validators } from '@angular/forms';
import { MatSnackBar } from '@angular/material/snack-bar';
import { MatLegacySnackBar as MatSnackBar } from '@angular/material/legacy-snack-bar';
import { Router } from '@angular/router';
import { MessageService } from 'primeng/api';
import { CookiesService } from 'src/app/services/cookies.service';

View File

@ -1,6 +1,10 @@
import { lastValueFrom } from 'rxjs';
import { HttpClient, HttpHeaders } from '@angular/common/http';
import { Injectable } from '@angular/core';
import { Injectable, Inject } from '@angular/core';
import { environment } from 'src/environments/environment';
import { CookiesService } from './cookies.service';
import { RpcService, JsonrpcService } from 'src/app/services/jsonrpc.service';
import { DOCUMENT } from '@angular/common';
@Injectable({
providedIn: 'root',
@ -9,7 +13,10 @@ export class AppleWalletService {
private url: string = environment.appleWalletEndpoint;
constructor(
private http: HttpClient,
) {}
private cookiesService: CookiesService,
private jsonrpc: JsonrpcService,
@Inject(DOCUMENT) private document: Document,
) { }
generateCard(token: string, user_id: string) {
let headers = new HttpHeaders();
@ -20,7 +27,7 @@ export class AppleWalletService {
return this.http.get(`${this.url}/client/${environment.clientName}/passUrl/${user_id}/token/${token}`, options)
}
reloadCard(user_id:string) {
reloadCard(user_id: string) {
let headers = new HttpHeaders();
headers = headers.set('Authorization', environment.appleWalletSecret);
const options = {
@ -32,4 +39,37 @@ export class AppleWalletService {
}
return this.http.post(`${this.url}/sendNotification/${user_id}`, body, options)
}
async addCardToWallet() {
const token = this.cookiesService.getItem('token');
try {
const accountData = (
await lastValueFrom(
this.jsonrpc.rpc(
{
method: 'getTokenData',
params: [],
},
RpcService.authService,
true
)
)
).data;
if (token && accountData.user_id) {
this.generateCard(token, accountData.user_id).subscribe({
next: (res: any) => {
this.document.location.href = res.url;
},
error: (err) => {
console.log('Error: ', err);
},
});
}
} catch (e) {
console.log(e);
}
}
}

View File

@ -0,0 +1,243 @@
import { Injectable } from '@angular/core';
import { CookiesService } from './cookies.service';
import { WpJsonService } from './wp-json.service';
import { environment } from 'src/environments/environment';
import { JsonrpcService, RpcService } from './jsonrpc.service';
import { MessageService } from 'primeng/api';
import { UserInfo, lvlPeriod, UserInfoWalletBalance, Moment } from '../interface/data';
import { lvlPeriods } from 'src/app/app.constants';
import moment from 'moment-timezone';
import { Router } from '@angular/router';
@Injectable({
providedIn: 'root',
})
export class AuthService {
lastPeriod: Moment[] = [];
currentPeriod: Moment[] = [];
userInfo?: UserInfo;
loading: boolean = false;
error: any;
timeLeft: number = 0;
get currentLvlPeriod(): lvlPeriod {
return lvlPeriods[
this.userInfo?.current_level_and_cashback ? this.userInfo?.current_level_and_cashback.current_level - 1 : 0];
}
constructor(
private cookiesService: CookiesService,
private wpJsonService: WpJsonService,
private jsonrpc: JsonrpcService,
private messageService: MessageService,
private router: Router,
) {
this.getCurrentQuarterOfYear();
}
get token(): string | undefined {
return this.cookiesService.getItem('token');
}
get authorized(): boolean {
return !!this.token;
}
getUserInfo() {
const token = this.cookiesService.getItem('token');
if (!token) {
return;
}
this.loading = true;
this.wpJsonService
.getCustomerInfo(
environment.systemId,
token,
environment.icardProxy,
)
.subscribe({
next: (value) => {
if (value && value.error && value.error.code === 11) {
this.wpJsonService.newCustomer(
environment.systemId,
token,
environment.icardProxy,
)
.subscribe({
next: () => {
this.getUserInfo();
}
})
} else if (value && value.error && value.error.code > 1) {
this.messageService.clear();
this.messageService.add({
severity: 'error',
summary: 'Произошла ошибка! Попробуйте позже',
});
} else if (value && value.customer_info) {
this.userInfo = value.customer_info;
this.cookiesService.setCookie('phone-number', this.userInfo!.phone?.slice(2));
}
},
error: (e) => {
this.error = e;
},
complete: () => {
this.loading = false;
}
});
}
logout() {
this.userInfo = undefined;
this.cookiesService.logout();
this.router.navigate(['/login']);
}
sendVerifyByPhone(phone: string) {
if (this.timeLeft) {
this.messageService.clear();
this.messageService.add({
severity: 'custom',
summary: `Отправить повторно можно через ${this.timeLeft}с`,
});
return;
}
this.loading = true;
this.jsonrpc
.rpc(
{
method: 'sendVerifyByPhone',
params: [phone],
},
RpcService.authService,
false
)
.subscribe({
next: (result) => {
if (result.code !== 0) {
this.messageService.clear();
this.messageService.add({
severity: 'error',
summary: 'Произошла ошибка! Попробуйте позже',
});
}
if (result.code === 0) {
this.timeLeft = 60;
const interval = setInterval(() => {
if (this.timeLeft > 0) {
this.timeLeft--;
} else {
clearInterval(interval);
}
}, 1000);
}
},
error: (error) => {
console.error('Error: ', error);
},
complete: () => {
this.loading = false;
},
});
}
submitCode(code: string, phone: string, name: string) {
this.loading = true;
this.jsonrpc
.rpc(
{
method: 'getTokenByPhone',
params: [phone, code],
},
RpcService.authService,
false
)
.subscribe({
next: (result) => {
if (result.code === 0) {
this.cookiesService.setCookie('token', result?.data?.token);
this.jsonrpc.rpc(
{
method: 'updateAdditionalInfo',
params: [
{
first_name: name,
birth_day: '01.01.1999'
},
],
},
RpcService.authService,
true
).subscribe({
next: () => {
this.router.navigate(['/']);
this.getUserInfo();
},
error: (err) => {
console.error(err);
},
})
} else if (result.code === 230) {
this.messageService.clear();
this.messageService.add({
severity: 'error',
summary: 'Неверный код!',
});
}
},
error: (error) => {
console.error(error);
},
complete: () => {
this.loading = false;
}
});
}
getCurrentQuarterOfYear() {
const quarters = [
[
moment().subtract(1, 'years').endOf('year').subtract(3, 'months'),
moment().startOf('year').add(10, 'days'),
],
[
moment().startOf('year').add(10, 'days'),
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.lastPeriod = quarters[i - 1];
this.currentPeriod = quarters[i];
}
}
}
getBalanceAmount(loyaltyPrograms: UserInfoWalletBalance[]) {
return (loyaltyPrograms || []).reduce((accumulator, currentValue) => {
if (currentValue.wallet.name !== 'Федеральная программа лояльности') {
return accumulator
}
return accumulator + currentValue.balance;
}, 0);
}
}

View File

@ -1,173 +0,0 @@
import { Injectable } from '@angular/core';
import moment from 'moment';
import { Moment, Purchase, lvlPeriod } from '../interface/data';
import { lvlPeriods } from '../app.constants';
export interface IPurchaseData {
currentPeriod: Moment[];
lastPeriod: Moment[];
lastPurchases: Purchase[];
currentPurchases: Purchase[];
lastAmount?: number;
currentAmount?: number;
$loading: boolean;
}
@Injectable({
providedIn: 'root',
})
export class LoyaltyProgramService {
public purchaseData: IPurchaseData = {
currentPeriod: [],
lastPeriod: [],
lastPurchases: [],
currentPurchases: [],
$loading: false,
get currentAmount(): number {
const amount = this.currentPurchases.reduce(
(accumulator, currentValue) => {
if (
['CancelPayFromWallet', 'CancelRefillWalletFromOrder'].includes(
currentValue.transactionType || ''
)
) {
return accumulator - currentValue.orderSum!;
} else if (
['PayFromWallet', 'RefillWalletFromOrder'].includes(
currentValue.transactionType || ''
)
) {
return accumulator + currentValue.orderSum!;
}
return accumulator;
},
0
);
return amount * 1;
},
get lastAmount(): number {
const amount = this.lastPurchases.reduce((accumulator, currentValue) => {
if (
['CancelPayFromWallet', 'CancelRefillWalletFromOrder'].includes(
currentValue.transactionType || ''
)
) {
return accumulator - currentValue.orderSum!;
} else if (
['PayFromWallet', 'RefillWalletFromOrder'].includes(
currentValue.transactionType || ''
)
) {
return accumulator + currentValue.orderSum!;
}
return accumulator;
}, 0);
return amount * 1;
},
};
public currentLvl: number = 1;
public currentLvlPeriod!: lvlPeriod;
public lvlPeriods: lvlPeriod[] = lvlPeriods;
constructor() {
this.getCurrentQuarterOfYear();
}
getCurrentQuarterOfYear() {
const quarters = [
[
moment().subtract(1, 'years').endOf('year').subtract(3, 'months'),
moment().startOf('year').add(10, 'days'),
],
[
moment().startOf('year').add(10, 'days'),
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) => {
if (currentValue.wallet.name !== 'Федеральная программа лояльности') {
return accumulator
}
return accumulator + currentValue.balance;
}, 0);
}
setLastPurchases(purchases: Purchase[]) {
this.purchaseData.lastPurchases = (purchases || []).filter((value: Purchase) => {
return moment(value.transactionCreateDate).isBetween(
this.purchaseData.lastPeriod[0],
this.purchaseData.lastPeriod[1]
);
});
}
setCurrentPurchases(purchases: Purchase[]) {
this.purchaseData.currentPurchases = (purchases || []).filter((value: Purchase) => {
return moment(value.transactionCreateDate).isBetween(
this.purchaseData.currentPeriod[0],
this.purchaseData.currentPeriod[1]
);
});
}
filterPurchases(purchases: Purchase[]) {
return (purchases || []).filter((purchase: Purchase) =>
[
'PayFromWallet',
'RefillWalletFromOrder',
'CancelPayFromWallet',
'CancelRefillWalletFromOrder',
].includes(purchase.transactionType || '')
);
}
getLastPurchase(purchases: Purchase[]) {
return purchases.filter((purchase: Purchase) =>
['PayFromWallet', 'RefillWalletFromOrder'].includes(
purchase.transactionType || ''
)
)[0];
}
setCurrentLvl(currentAmount: number) {
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;
}
}
getNextLevel(): lvlPeriod {
if(this.currentLvl == this.lvlPeriods.length) {
return lvlPeriods[this.currentLvl - 1];
}
return lvlPeriods[this.currentLvl];
}
}

View File

@ -50,6 +50,7 @@ export class MessagingService {
)
.subscribe({
next: () => {
this.messageService.clear();
this.messageService.add({
severity: 'custom',
summary: 'Спасибо за подписку!',
@ -57,6 +58,7 @@ export class MessagingService {
},
error: (err) => {
console.error('Error: ', err);
this.messageService.clear();
this.messageService.add({
severity: 'error',
summary: 'Произошла ошибка, попробуйте позже',
@ -65,11 +67,20 @@ export class MessagingService {
});
}
checkRequestPermission() {
return Notification.permission !== 'granted' ? false : true;
}
requestPermission() {
if (this.checkRequestPermission()) {
return;
}
try {
this.angularFireMessaging.requestToken.subscribe({
next: (token) => {
this.updateToken(token);
this.receiveMessage();
},
error: (e) => console.error(e),
});

View File

@ -1,12 +1,10 @@
import { Injectable } from '@angular/core';
import {environment} from "../../environments/environment";
import {HttpClient, HttpHeaders} from "@angular/common/http";
import {CookiesService} from "./cookies.service";
import {Observable, of, switchMap} from "rxjs";
import {JsonRpcBody} from "./jsonrpc.service";
import {DeliveryType, AcceptedOrder, Product} from "../interface/data";
import {ActivatedRoute} from "@angular/router";
import {Order} from "../models/order";
import { environment } from "../../environments/environment";
import { HttpClient, HttpHeaders } from "@angular/common/http";
import { CookiesService } from "./cookies.service";
import { Observable, of, switchMap } from "rxjs";
import { JsonRpcBody } from "./jsonrpc.service";
import { DeliveryType, AcceptedOrder, Product } from "../interface/data";
export enum Method {
@ -23,38 +21,30 @@ export class WpJsonService {
constructor(
private http: HttpClient,
private cookiesService: CookiesService,
private route: ActivatedRoute,
) { }
getDeliveryTypes(): Observable<DeliveryType[]>{
getDeliveryTypes(): Observable<DeliveryType[]> {
return this._request('orders/delivery-types', 'GET');
}
createOrder(order: any){
createOrder(order: any) {
return this._request('orders', 'POST', order);
}
getOrders(): Observable<AcceptedOrder[]>{
getOrders(): Observable<AcceptedOrder[]> {
return this._request('orders', 'GET', null, true);
}
getProductById(id: number): Observable<Product>{
getProductById(id: number): Observable<Product> {
return this._request(`products/${id}`, 'GET');
}
getCustomerInfo(systemId: string, token: string, url: string): Observable<any> {
return this._request(`customer_info/${systemId}/${token}/`, 'GET', null, false, url).pipe(
switchMap((response) => {
// TODO: typescript compile optional chaining ('response?.customer_info?.errorCode') without check ('response.customer_info.errorCode')
if (response && response.customer_info && response.customer_info.errorCode !== 'Customer_CustomerNotFound') {
return of(response)
} else {
return this.newCustomer(systemId, token, url).pipe(
switchMap(() => this.getCustomerInfo(systemId, token, url))
)
return this._request(`customer_info/${systemId}/${token}/`, 'GET', null, false, url);
}
})
)
getLastPurchase(systemId: string, token: string): Observable<any> {
return this._request(`last_trans/${systemId}/${token}/`, 'GET', null, false, environment.icardProxy);
}
newCustomer(systemId: string, token: string, url: string): Observable<any> {
@ -96,6 +86,6 @@ export class WpJsonService {
url = baseUrl
}
return this.http
.request( method, url + path + urlToken, options);
.request(method, url + path + urlToken, options);
}
}

24
angular/src/app/utils.ts Normal file
View File

@ -0,0 +1,24 @@
import { environment } from "src/environments/environment.prod";
export enum DeviceType {
ios,
android,
}
export function getTypeDevice(): DeviceType {
const userAgent = window.navigator.userAgent.toLowerCase();
const ios = /iphone|ipod|ipad/.test(userAgent);
return ios ? DeviceType.ios : DeviceType.android;
}
export async function pwaInstalled(): Promise<boolean> {
if ("getInstalledRelatedApps" in navigator) {
const apps = await (window.navigator as any).getInstalledRelatedApps();
for (const app of apps) {
if (app.url == environment.manifestUrl) {
return true;
}
}
}
return false;
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 291 KiB

View File

@ -0,0 +1,232 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg
width="750"
height="563.15234"
viewBox="0 0 198.43748 149.00072"
version="1.1"
id="svg5668"
inkscape:version="1.2 (dc2aedaf03, 2022-05-15)"
sodipodi:docname="applewallet.svg"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:xlink="http://www.w3.org/1999/xlink"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:dc="http://purl.org/dc/elements/1.1/">
<defs
id="defs5662">
<linearGradient
inkscape:collect="always"
id="linearGradient4120">
<stop
style="stop-color:#cbc9be;stop-opacity:1"
offset="0"
id="stop4116" />
<stop
style="stop-color:#c8c5bb;stop-opacity:0"
offset="1"
id="stop4118" />
</linearGradient>
<clipPath
clipPathUnits="userSpaceOnUse"
id="clipPath3753">
<path
sodipodi:nodetypes="sscsscsss"
inkscape:connector-curvature="0"
id="path3755"
d="m -31.594972,54.69743 c -10.576039,0 -13.725777,6.338221 -13.725777,14.211535 V 85.50908 c 0,-7.873315 3.149738,-14.211536 13.725777,-14.211536 H 122.86692 c 10.57602,0 13.72577,6.338221 13.72577,14.211536 V 68.908965 c 0,-7.873314 -3.14975,-14.211535 -13.72577,-14.211535 z"
style="opacity:1;fill:#4095ca;fill-opacity:1;stroke:none;stroke-width:18;stroke-linecap:square;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:0.607477" />
</clipPath>
<filter
inkscape:collect="always"
style="color-interpolation-filters:sRGB"
id="filter3829"
x="-0.047008887"
width="1.0940178"
y="-0.27754272"
height="1.5550854">
<feGaussianBlur
inkscape:collect="always"
stdDeviation="3.5631452"
id="feGaussianBlur3831" />
</filter>
<clipPath
clipPathUnits="userSpaceOnUse"
id="clipPath3929">
<path
style="opacity:1;fill:#f16d5e;fill-opacity:1;stroke:none;stroke-width:18;stroke-linecap:square;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:0.607477"
d="m 83.818361,394.03231 c -39.972431,0 -51.876954,23.95549 -51.876954,53.71292 v 7.60641 l 176.408203,0.32617 c 47.12943,0.0873 66.42745,19.0478 86.06641,45.57812 5.00155,6.75662 11.08589,15.9143 17.02733,21.86135 17.74131,17.75789 41.00574,26.62878 64.27148,26.62889 23.26576,1.1e-4 46.53019,-8.87097 64.2715,-26.62889 5.94144,-5.94701 12.02578,-15.10473 17.02735,-21.86135 19.63895,-26.53032 38.93696,-45.49096 86.0664,-45.57812 l 176.40819,-0.32617 v -7.60641 c 0,-29.75743 -11.90453,-53.71292 -51.87692,-53.71292 z"
id="path3931"
inkscape:connector-curvature="0"
sodipodi:nodetypes="sscssssssscsss" />
</clipPath>
<filter
inkscape:collect="always"
style="color-interpolation-filters:sRGB"
id="filter4089"
x="-0.053576618"
width="1.1071532"
y="-0.071352741"
height="1.1427055">
<feGaussianBlur
inkscape:collect="always"
stdDeviation="16.742692"
id="feGaussianBlur4091" />
</filter>
<linearGradient
inkscape:collect="always"
xlink:href="#linearGradient4120"
id="linearGradient4122"
x1="-53.582775"
y1="120.72184"
x2="-53.582775"
y2="114.41766"
gradientUnits="userSpaceOnUse" />
<linearGradient
inkscape:collect="always"
xlink:href="#linearGradient4120"
id="linearGradient4131"
gradientUnits="userSpaceOnUse"
x1="-53.582775"
y1="120.72184"
x2="-53.582775"
y2="114.41766"
gradientTransform="translate(190.17547)" />
</defs>
<sodipodi:namedview
id="base"
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1.0"
inkscape:pageopacity="0.0"
inkscape:pageshadow="2"
inkscape:zoom="0.35"
inkscape:cx="-135.71429"
inkscape:cy="572.85714"
inkscape:document-units="mm"
inkscape:current-layer="g4145"
showgrid="false"
inkscape:snap-smooth-nodes="true"
inkscape:snap-object-midpoints="true"
showguides="true"
fit-margin-top="0"
fit-margin-left="0"
fit-margin-right="0"
fit-margin-bottom="0"
inkscape:window-width="1620"
inkscape:window-height="1010"
inkscape:window-x="-6"
inkscape:window-y="-6"
inkscape:window-maximized="1"
inkscape:guide-bbox="true"
units="px"
inkscape:object-paths="true"
inkscape:showpageshadow="2"
inkscape:pagecheckerboard="true"
inkscape:deskcolor="#d1d1d1" />
<metadata
id="metadata5665">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
</cc:Work>
</rdf:RDF>
</metadata>
<g
inkscape:label="Layer 1"
inkscape:groupmode="layer"
id="layer1"
transform="translate(53.582777,-46.342901)">
<g
id="g5740"
transform="matrix(1,0,0,1.0056496,24.568462,4.937821)"
style="fill:#ff0000;fill-opacity:0;stroke-width:0.997187">
<path
inkscape:connector-curvature="0"
id="rect5713"
d="m 10.108037,0.24338852 c -36.91084,0 -63.87995,22.39413848 -63.87995,64.79580448 V 198.5445 c 0,42.40171 26.96911,64.79582 63.87995,64.79582 H 146.93146 c 36.91084,0 63.87996,-22.39411 63.87996,-64.79582 V 65.039193 c 0,-42.401691 -26.96912,-64.79580448 -63.87996,-64.79580448 z"
style="opacity:1;fill:#ff0000;fill-opacity:0;stroke:none;stroke-width:2.09409;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:0.383178" />
</g>
<g
id="g4145">
<path
inkscape:connector-curvature="0"
id="path4095"
transform="matrix(0.26458333,0,0,0.26458333,-53.771913,0.24338852)"
d="m 77.738281,174.23438 c -50.500035,-2e-5 -77.02343725,30.81653 -77.02343725,77.02343 v 409.10547 c 0,46.20688 26.52340225,77.02344 77.02343725,77.02344 H 673.69141 c 50.50004,0 77.02343,-30.81656 77.02343,-77.02344 V 251.25781 c 0,-46.2069 -26.52339,-77.02344 -77.02343,-77.02343 z"
style="opacity:1;fill:#d9d6cc;fill-opacity:1;stroke:none;stroke-width:155.717;stroke-linecap:square;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
<path
sodipodi:nodetypes="sscssssssscsss"
inkscape:connector-curvature="0"
id="rect3638"
d="m -31.594972,54.697449 c -10.576039,0 -13.725777,6.338221 -13.725777,14.211536 v 51.812855 l 46.6746696,0.0863 c 12.4696614,0.0231 17.5755964,5.03973 22.7717374,12.05921 1.323329,1.78769 2.933142,4.21066 4.50515,5.78415 4.694053,4.69844 10.849433,7.04553 17.005162,7.04556 6.15573,3e-5 12.31111,-2.34711 17.005165,-7.04556 1.572006,-1.57348 3.181821,-3.99646 4.505151,-5.78415 5.196141,-7.01948 10.302073,-12.03615 22.771737,-12.05921 l 46.674667,-0.0863 V 68.908985 c 0,-7.873315 -3.14974,-14.211536 -13.72577,-14.211536 z"
style="opacity:1;fill:#f16d5e;fill-opacity:1;stroke:none;stroke-width:18;stroke-linecap:square;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:0.607477" />
<path
transform="matrix(0.26458333,0,0,0.26458333,-53.771913,0.24338852)"
sodipodi:nodetypes="sscsscsss"
inkscape:connector-curvature="0"
id="path3676"
d="m 83.818359,205.81055 c -39.97243,0 -51.876953,23.95548 -51.876953,53.71289 v 62.74059 c 0,-29.75741 11.904523,-53.71289 51.876953,-53.71289 H 667.61133 c 39.97239,0 51.87695,23.95548 51.87695,53.71289 v -62.74059 c 0,-29.75741 -11.90456,-53.71289 -51.87695,-53.71289 z"
style="opacity:1;fill:#3295c9;fill-opacity:1;stroke:none;stroke-width:68.0315;stroke-linecap:square;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:0.607477" />
<path
clip-path="url(#clipPath3753)"
sodipodi:nodetypes="sscsscsss"
inkscape:connector-curvature="0"
id="path3685"
d="m -31.594972,71.297543 c -10.576039,0 -13.725777,6.338221 -13.725777,14.211536 v 16.600111 c 0,-7.873312 3.149738,-14.211532 13.725777,-14.211532 H 122.86692 c 10.57602,0 13.72577,6.33822 13.72577,14.211532 V 85.509079 c 0,-7.873315 -3.14975,-14.211536 -13.72577,-14.211536 z"
style="opacity:1;fill:#000000;fill-opacity:0.317757;stroke:none;stroke-width:18;stroke-linecap:square;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:0.607477;filter:url(#filter3829)" />
<path
style="opacity:1;fill:#fcad00;fill-opacity:1;stroke:none;stroke-width:18;stroke-linecap:square;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:0.607477"
d="m -31.594972,71.297544 c -10.576039,0 -13.725777,6.338221 -13.725777,14.211535 v 16.600111 c 0,-7.873311 3.149738,-14.211532 13.725777,-14.211532 H 122.86692 c 10.57602,0 13.72577,6.338221 13.72577,14.211532 V 85.509079 c 0,-7.873314 -3.14975,-14.211535 -13.72577,-14.211535 z"
id="path3833"
inkscape:connector-curvature="0"
sodipodi:nodetypes="sscsscsss" />
<path
transform="translate(4.6064453e-7,16.600115)"
style="opacity:1;fill:#000000;fill-opacity:0.313725;stroke:none;stroke-width:18;stroke-linecap:square;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:0.607477;filter:url(#filter3829)"
d="m -31.594972,71.297543 c -10.576039,0 -13.725777,6.338221 -13.725777,14.211536 v 16.600111 c 0,-7.873312 3.149738,-14.211532 13.725777,-14.211532 H 122.86692 c 10.57602,0 13.72577,6.33822 13.72577,14.211532 V 85.509079 c 0,-7.873315 -3.14975,-14.211536 -13.72577,-14.211536 z"
id="path3835"
inkscape:connector-curvature="0"
sodipodi:nodetypes="sscsscsss"
clip-path="url(#clipPath3753)" />
<path
sodipodi:nodetypes="sscsscsss"
inkscape:connector-curvature="0"
id="path3837"
d="m -31.594972,87.897659 c -10.576039,0 -13.725777,6.338221 -13.725777,14.211531 v 16.60012 c 0,-7.87332 3.149738,-14.21154 13.725777,-14.21154 H 122.86692 c 10.57602,0 13.72577,6.33822 13.72577,14.21154 v -16.60012 c 0,-7.87331 -3.14975,-14.211531 -13.72577,-14.211531 z"
style="opacity:1;fill:#50be3d;fill-opacity:1;stroke:none;stroke-width:18;stroke-linecap:square;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:0.607477" />
<path
clip-path="url(#clipPath3753)"
sodipodi:nodetypes="sscsscsss"
inkscape:connector-curvature="0"
id="path3839"
d="m -31.594972,71.297543 c -10.576039,0 -13.725777,6.338221 -13.725777,14.211536 v 16.600111 c 0,-7.873312 3.149738,-14.211532 13.725777,-14.211532 H 122.86692 c 10.57602,0 13.72577,6.33822 13.72577,14.211532 V 85.509079 c 0,-7.873315 -3.14975,-14.211536 -13.72577,-14.211536 z"
style="opacity:1;fill:#000000;fill-opacity:0.313725;stroke:none;stroke-width:18;stroke-linecap:square;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:0.607477;filter:url(#filter3829)"
transform="translate(9.2128905e-7,33.20023)" />
<path
clip-path="url(#clipPath3929)"
style="opacity:1;fill:#f26d5f;fill-opacity:0.29803923;stroke:none;stroke-width:155.717;stroke-linecap:square;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;filter:url(#filter4089)"
d="m 77.738281,174.23438 c -50.500036,-2e-5 -77.02343725,30.81653 -77.02343725,77.02343 v 409.10547 c 0,46.20688 26.52340125,77.02344 77.02343725,77.02344 H 673.69141 c 50.50004,0 77.02343,-30.81656 77.02343,-77.02344 V 251.25781 c 0,-46.2069 -26.52339,-77.02344 -77.02343,-77.02343 z m 6.080078,31.57617 H 667.61133 c 39.97239,0 51.87695,23.95548 51.87695,53.71289 v 195.82812 l -176.4082,0.32617 c -47.12944,0.0872 -66.42745,19.04781 -86.06641,45.57813 -5.00156,6.75662 -11.0859,15.91432 -17.02734,21.86133 -17.74131,17.75792 -41.00574,26.62902 -64.27149,26.6289 -23.26574,-1.1e-4 -46.53018,-8.87102 -64.27148,-26.6289 -5.94145,-5.94705 -12.02579,-15.10471 -17.02734,-21.86133 -19.63896,-26.53032 -38.93698,-45.49082 -86.06641,-45.57813 L 31.941406,455.35156 V 259.52344 c 0,-29.75741 11.904523,-53.71289 51.876953,-53.71289 z"
id="path3848"
inkscape:connector-curvature="0"
transform="matrix(0.26458333,0,0,0.26458333,-53.771913,0.24338852)" />
<path
inkscape:connector-curvature="0"
id="rect4114"
d="m -53.582775,108.55904 h 8.262028 v 12.1628 h -8.262028 z"
style="opacity:1;fill:url(#linearGradient4122);fill-opacity:1;stroke:none;stroke-width:18;stroke-linecap:square;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:0.607477" />
<path
style="opacity:1;fill:url(#linearGradient4131);fill-opacity:1;stroke:none;stroke-width:18;stroke-linecap:square;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:0.607477"
d="m 136.59269,108.55904 h 8.26203 v 12.1628 h -8.26203 z"
id="path4129"
inkscape:connector-curvature="0" />
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 13 KiB

View File

@ -8,19 +8,18 @@ export const environment = {
hasBonusProgram: true,
systemId: 'tsQ2cu59Xz9qgGTm3z',
defaultUrl: 'https://club.coffee-like.com',
manifestUrl: 'https://club.coffee-like.com/manifest.webmanifest',
firebase: {
apiKey: "AIzaSyCnKvln5itnrBj62POCPHxshAN_Vmd0zds",
authDomain: "fashionlogicanotification.firebaseapp.com",
projectId: "fashionlogicanotification",
storageBucket: "fashionlogicanotification.appspot.com",
messagingSenderId: "99855572145",
appId: "1:99855572145:web:7548c189d61b3bcc92d690",
measurementId: "G-RQF97ZK7R1"
apiKey: "AIzaSyDTb_xuMz2vDx8xGs34AJiltraKVlwmrtY",
authDomain: "coffee-like-77bfe.firebaseapp.com",
projectId: "coffee-like-77bfe",
storageBucket: "coffee-like-77bfe.appspot.com",
messagingSenderId: "1094726277369",
appId: "1:1094726277369:web:8af560662da7700e7a2a28"
},
version: packageJson.version,
appleWalletEndpoint: 'https://apple-push-notifications.it-retail.tech/apns/api',
appleWalletEndpoint: 'https://apple-wallet-iiko.it-retail.tech/apns/api',
icardProxy: 'https://club.coffee-like.com/api/icard-proxy/',
appleWalletSecret: 'Token F5mbzEERAznGKVbB6l',
clientName: 'coffeeLike'
clientName: 'coffeelike'
}

View File

@ -8,18 +8,18 @@ export const environment = {
hasBonusProgram: true,
systemId: 'tsQ2cu59Xz9qgGTm3z',
defaultUrl: 'http://192.168.0.179:4200',
manifestUrl: 'https://club.coffee-like.com/manifest.webmanifest',
firebase: {
apiKey: 'AIzaSyCnKvln5itnrBj62POCPHxshAN_Vmd0zds',
authDomain: 'fashionlogicanotification.firebaseapp.com',
projectId: 'fashionlogicanotification',
storageBucket: 'fashionlogicanotification.appspot.com',
messagingSenderId: '99855572145',
appId: '1:99855572145:web:7548c189d61b3bcc92d690',
measurementId: 'G-RQF97ZK7R1',
apiKey: "AIzaSyDTb_xuMz2vDx8xGs34AJiltraKVlwmrtY",
authDomain: "coffee-like-77bfe.firebaseapp.com",
projectId: "coffee-like-77bfe",
storageBucket: "coffee-like-77bfe.appspot.com",
messagingSenderId: "1094726277369",
appId: "1:1094726277369:web:8af560662da7700e7a2a28"
},
version: packageJson.version,
appleWalletEndpoint: 'http://192.168.0.179:4200/apns/api',
icardProxy: 'http://192.168.0.14:4200/icard-proxy/',
appleWalletEndpoint: 'https://apple-wallet-iiko.it-retail.tech/apns/api',
icardProxy: 'https://club.coffee-like.com/api/icard-proxy/',
appleWalletSecret: 'Token F5mbzEERAznGKVbB6l',
clientName: 'coffeeLike'
clientName: 'coffeelike'
};

View File

@ -2,13 +2,12 @@ importScripts('https://www.gstatic.com/firebasejs/3.6.9/firebase-app.js');
importScripts('https://www.gstatic.com/firebasejs/3.6.9/firebase-messaging.js');
firebase.initializeApp({
apiKey: "AIzaSyCnKvln5itnrBj62POCPHxshAN_Vmd0zds",
authDomain: "fashionlogicanotification.firebaseapp.com",
projectId: "fashionlogicanotification",
storageBucket: "fashionlogicanotification.appspot.com",
messagingSenderId: "99855572145",
appId: "1:99855572145:web:7548c189d61b3bcc92d690",
measurementId: "G-RQF97ZK7R1"
apiKey: "AIzaSyDTb_xuMz2vDx8xGs34AJiltraKVlwmrtY",
authDomain: "coffee-like-77bfe.firebaseapp.com",
projectId: "coffee-like-77bfe",
storageBucket: "coffee-like-77bfe.appspot.com",
messagingSenderId: "1094726277369",
appId: "1:1094726277369:web:8af560662da7700e7a2a28"
});
const messaging = firebase.messaging();

View File

@ -50,5 +50,9 @@
"purpose": "maskable any"
}
],
"gcm_sender_id": "99855572145"
"gcm_sender_id": "99855572145",
"related_applications": [{
"platform": "webapp",
"url": "https://club.coffee-like.com/manifest.webmanifest"
}]
}

View File

@ -1,5 +1,89 @@
@use '@angular/material' as mat;
@include mat.core();
$primary: (
50 : #f0e4ec,
100 : #d9bcd0,
200 : #bf90b0,
300 : #a56390,
400 : #924179,
500 : #7f2061,
600 : #771c59,
700 : #6c184f,
800 : #621345,
900 : #4f0b33,
A100 : #ff85c7,
A200 : #ff52b0,
A400 : #ff1f99,
A700 : #ff068d,
contrast: (
50 : #000000,
100 : #000000,
200 : #000000,
300 : #ffffff,
400 : #ffffff,
500 : #ffffff,
600 : #ffffff,
700 : #ffffff,
800 : #ffffff,
900 : #ffffff,
A100 : #000000,
A200 : #000000,
A400 : #ffffff,
A700 : #ffffff,
)
);
$accent: (
50 : #ffffff,
100 : #ffffff,
200 : #ffffff,
300 : #ffffff,
400 : #ffffff,
500 : #ffffff,
600 : #ffffff,
700 : #ffffff,
800 : #ffffff,
900 : #ffffff,
A100 : #ffffff,
A200 : #ffffff,
A400 : #ffffff,
A700 : #ffffff,
contrast: (
50 : #000000,
100 : #000000,
200 : #000000,
300 : #000000,
400 : #000000,
500 : #000000,
600 : #000000,
700 : #000000,
800 : #000000,
900 : #000000,
A100 : #000000,
A200 : #000000,
A400 : #000000,
A700 : #000000,
)
);
$primary-palette: mat.define-palette($primary);
$accent-palette: mat.define-palette($accent);
$theme: mat.define-light-theme((
color: (
primary: $primary-palette,
accent: $accent-palette,
),
typography: mat.define-typography-config(),
density: 0,
));
@include mat.all-component-themes($theme);
// Сброс стилей
html,body,div,span,applet,object,iframe,h1,h2,h3,h4,h5,h6,p,blockquote,pre,a,abbr,acronym,address,big,cite,code,del,dfn,em,img,ins,kbd,q,s,samp,small,strike,strong,sub,sup,tt,var,b,u,i,center,dl,dt,dd,ol,ul,li,fieldset,form,label,legend,table,caption,tbody,tfoot,thead,tr,th,td,article,aside,canvas,details,embed,figure,figcaption,footer,header,hgroup,menu,nav,output,ruby,section,summary,time,mark,audio,video{margin:0;padding:0;border:0;font-size:100%;font:inherit;vertical-align:middle}
html,body,div,span,applet,object,iframe,h1,h2,h3,h4,h5,h6,p,blockquote,pre,a,abbr,acronym,address,big,cite,code,del,dfn,em,img,ins,kbd,q,s,samp,small,strike,strong,sub,sup,tt,var,b,u,i,center,dl,dt,dd,ol,ul,li,fieldset,form,label,legend,table,caption,tbody,tfoot,thead,tr,th,td,article,aside,canvas,details,embed,figure,figcaption,footer,header,hgroup,menu,nav,output,ruby,section,summary,time,mark,audio,video{margin:0;padding:0;border:0;font-size:100%;font:inherit;}
article,aside,details,figcaption,figure,footer,header,hgroup,menu,nav,section{display:block}
html{height:100%;}
body{line-height:1}
@ -27,7 +111,7 @@ body {
}
:root {
--main-color: #00b26b;
--main-color: #7F2061;
--main-border-radius: 35px;
--background-color: #ffffff;
@ -39,6 +123,32 @@ body {
--button-text-color: #ffffff;
--button-text-color_disabled: #cccccc;
--main-color_hover: rgba(127, 32, 97, 0.3);
}
.mdc-button__label {
color: var(--button-text-color) !important;
}
.mat-mdc-outlined-button:not(:disabled) {
border-color: var(--button-text-color) !important;
}
.mat-h1,
.mat-headline-5,
.mat-typography .mat-h1,
.mat-typography .mat-headline-5,
.mat-typography h1 {
font-family: Montserrat;
}
.mat-h2,
.mat-headline-6,
.mat-typography .mat-h2,
.mat-typography .mat-headline-6,
.mat-typography h2 {
font-family: Montserrat, sans-serif;
}
hr {
@ -46,64 +156,6 @@ hr {
border-top: 1px solid #BDBDBD;
}
.mat-bottom-sheet-container {
box-shadow: 0px 8px 10px -5px rgba(255, 255, 255, 0.2), 0px 16px 24px 2px rgba(255, 255, 255, 0.14), 0px 6px 30px 5px rgba(255, 255, 255, 0.12);
background: var(--background-color);
color: var(--text-color);
}
.mat-form-field-appearance-outline .mat-form-field-outline {
color: var(--button-color_disabled) !important;
}
.mat-form-field-appearance-outline .mat-form-field-outline {
color: var(--button-color) !important;
}
.mat-form-field-wrapper {
padding: 0;
}
.mat-progress-spinner circle, .mat-spinner circle {
stroke: var(--button-color);
}
.mat-form-field-outline-start, .mat-form-field-outline-end {
border-radius: 0 !important;
}
.mat-form-field-outline-start {
border-left: none !important;
}
.mat-form-field-outline-end {
border-right: none !important;
}
.mat-focused .mat-form-field-outline > div {
border-color: var(--button-color);
}
.mat-form-field-label {
color: var(--text-color_1) !important;
}
.mat-focused .mat-form-field-label {
color: var(--text-color) !important;
}
.mat-form-field-invalid .mat-form-field-outline > div {
border-color: red;
}
.country-list-button {
color: var(--text-color) !important;
}
.mat-menu-panel {
background: var(--background-color);
border-radius: 0;
}
.country-selector {
opacity: 1 !important;
display: none !important;
@ -117,7 +169,6 @@ hr {
color: var(--text-color);
}
qr-code canvas {
transition: all 0.3s ease 0s;
}
@ -175,5 +226,21 @@ input::-webkit-date-and-time-value {
text-align: left;
}
.p-toast-message-custom {
background-color: var(--background-color);
border: solid var(--main-color);
border-width: 0 0 0 6px;
color: var(--text-color);
.p-toast-icon-close {
color: var(--main-color);
}
}
.p-toast.p-component.p-toast-top-center {
max-width: 96vw;
}
html, body { height: 100%; }
body { margin: 0; }

View File

@ -7,20 +7,8 @@ import {
platformBrowserDynamicTesting
} from '@angular/platform-browser-dynamic/testing';
declare const require: {
context(path: string, deep?: boolean, filter?: RegExp): {
<T>(id: string): T;
keys(): string[];
};
};
// First, initialize the Angular testing environment.
getTestBed().initTestEnvironment(
BrowserDynamicTestingModule,
platformBrowserDynamicTesting(),
);
// Then we find all the tests.
const context = require.context('./', true, /\.spec\.ts$/);
// And load the modules.
context.keys().forEach(context);

View File

@ -16,7 +16,7 @@
"experimentalDecorators": true,
"moduleResolution": "node",
"importHelpers": true,
"target": "es2020",
"target": "ES2022",
"module": "es2020",
"lib": [
"es2020",
@ -24,6 +24,7 @@
],
"resolveJsonModule": true,
"allowSyntheticDefaultImports": true,
"useDefineForClassFields": false
},
"angularCompilerOptions": {
"enableI18nLegacyMessageIdFormat": false,